LoginSignup
360
393

More than 5 years have passed since last update.

あなたがRails触る人なら見ておきたい「体系的な」豆知識

Posted at

ここ最近複数の専門書を断片的に読む機会やら、(自分にとって)新たな技術にチャレンジする機会やらを頂いたため、最近数ヶ月で学んだ内容をせっかくなので体系的にまとめました。気づいたら大分ボリュームを増やしていましたので、必要なとき必要な箇所に行き当たってくれることを祈っています。

なお、以下の5記事を姉妹記事として同時に書いています。
文中でもリンクが出てきますのでよろしければ併せて目を通して頂けると幸いです。

■ Modelに関するあれこれ

activerecord関連

O/Rマッパー

  • modelがデータベースとのやり取りをする際に欠かせないのがO/Rマッパー
  • リレーショナルデータベースとオブジェクト指向言語の橋渡しをする
  • 例えばオブジェクトのプロパティ名をテーブルのカラム名と一致させたりする
  • Railsではデフォルトで ActiveRcord がO/Rマッパーとして使われる

yamlとは何か

  • database.yml とか設定ファイルに時々でてくる奴
  • まとめたら長すぎたので別記事にしました(下記)

YAMLとは何か? - いつもRailsの設定ファイルで出てくる奴の正体

SQL関連

whereメソッドのバリエーション

  • プレイスホルダ(?)を使用して検索条件を後置できる
  • プレイスホルダ(?)の個数分引数として値を渡してあげる必要がある
  • 具体例: Book.where('title = ? AND author = ?', <値1>, <値2>)
$ Book.where('title = ? AND author = ?', params[:title], params[:author])

Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE (title = '白鯨' AND author = 'メルヴィル')
  • ? を使用した検索条件指定は名前なしパラメータと呼ばれる
  • 名前付きパラメータを使用すると上記の例は以下のように書き換えられる
$ Book.where('title = :title AND author = :author', title: params[:title], author: params[:author])
  • 検索条件には not を付与し「●●以外のもの全て」という条件式をつくることができる
$ Book.where.not(title: "白鯨")

Book Load (0.3ms)  SELECT `books`.* FROM `books` WHERE (`books`.`title` != '白鯨')

取得するプロパティの制限

  • 多くの情報を扱うテーブルにおいて不要な列まで無条件に取り出すことは処理速度の遅延にもつながる
  • select メソッドを併せて使用してあげると取得するプロパティを制限することができる
$ Book.all.select(:id, :title)

Book Load (0.3ms)  SELECT `books`.`id`,`books`.`title` FROM `books`

#=> [#<Book:0x007fb5ea28a4b8 id: 2, title: "白鯨">,
 #<Book:0x007fb5ea28a1c0 id: 3, title: "そして">]

指定したカラムの値で配列をつくる

  • 取得してきたレコードに pluckメソッド を使用すると指定の値だけを取り出して配列にすることができる
$ Book.all.select(:id, :title).pluck(:title)

Book Load (0.3ms)  SELECT `books`.`title` FROM `books`

#=> ["白鯨", "そして"]

config関連

アプリケーションやアクティブレコードで使用するタイムゾーンを変更する

  • application.rb 内に記述する
  • デフォルトでコメントアウトされている部分の記述を以下のように直す
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'

#=>
config.time_zone = 'Tokyo'

console関連

コンソールで検証した内容を実際のデータベース等に反映させたくない時

  • 通常コンソールは rails cコマンド を用いて起動しレコードの生成・保存等の挙動を検証できる
$ rails c

Loading development environment (Rails 4.2.1)
[1] pry(main)> 
  • rails c --sandboxオプション を使用するとコンソール終了時にデータベースに対する全ての変更をロールバックする
$ rails c --sandbox

Loading development environment in sandbox (Rails 4.2.1)
Any modifications you make will be rolled back on exit
[1] pry(main)>

コード中のコメントを集めてTODO化する

  • ひっそりと存在しているコマンド rake notes
  • ルールに則りコメントを記述することでTODOを一覧化できる
  • 書き方は # [TODO/FIXME/OPTIMIZE]: hogehoge
class Users < ActiveRecord::Base
  # TODO:  set sign-in validations
  # FIXME: fix class name as singular
end
  • 出力形式は [行番号] [TODO/FIXME/OPTIMIZE] コメント
  • 特定の種類のコメントだけを出力することも可能
$ rake notes
#=>
app/models/user.rb:
  * [2] [TODO] set sign-in validations
  * [3] [FIXME] fix class name as singular

$ rake notes:fixme
#=>
app/models/user.rb:
  * [3] [FIXME] fix class name as singular

migrationファイル関連

各カラムに追加設定できるオプション

  • データベースに変更を加える際にmigrationファイルにはオプションを設定することができる
  • 主なオプションとして以下の5つが挙げられる
フラグ名 オプション内容 使用可能なデータ型
limit 列の桁数 全て
default デフォルト値 全て
null null値を許可するか 全て
precision 少数以下含めた数値の全体桁 decimal型
scale 小数点以下の桁数 decimal型
  • 以下のように実行前のmigrationファイルに追記することでデータベースに反映させることができる
def change
  create_table :books do |t|
    t.string  :title,     limit: 9, null: false
    t.decimal :price,     precision: 5, scale: 0
    t.string  :publisher, default: 'HBS'

    t.timestamps
  end
end
  • 上記の指定はテーブルに対し以下の制約を与える
    • titleには9文字以下で何かしらの値が入っていなければ保存できない
    • priceは小数点以下0桁かつ全体5桁までの数字のみ保存可能
    • publisherには何も指定しなくともデフォルトで文字列'HBS'が代入される

has_and_belongs_to_many関連の中間テーブルを生成する

  • 中間テーブルは外部キー以外の列を持ってはいけないという制約がある
  • migrationファイルにおいて create_join_tableメソッド を使用すれば制約を気にせずにテーブルを生成できる
  • rails g migration CreateJoinTableTable1AndTable2 <テーブル1名> <テーブル2名> コマンドによってファイル中の記述も自動生成できる
$ rails g migration CreateJoinTableAuthorsAndBooks authors books

# invoke  active_record
# create    db/migrate/20150728175239_create_join_table_authors_and_books.rb
  • migrationファイルの中身は以下のようになっている
class CreateJoinTableAuthorsAndBooks < ActiveRecord::Migration
  def change
    create_join_table :authors, :books do |t|
      # t.index [:author_id, :book_id]
      # t.index [:book_id, :author_id]
    end
  end
end

■ Viewに関するあれこれ

viewヘルパー関連

html.erbは何の略か

  • erb = Embedded Ruby
  • html以外にもxmlやplain textにも使えるらしい
  • 使ったことはない

Rubyタグ直後の改行を無くす

  • -%> のように閉じることでデザインに影響を与えるようなタグ直後の空白・改行を打ち消せる
<%= ... -%> || <% ... -%>

コメントアウトの方法5種類

<%# 一般的なコメントアウトです %><%#= これもコメントアウトです 
     この行もコメントアウトです
     複数行に渡りコメントアウトできます %><% これはコメントではありません
  # この行がコメントになります %><% if false %>
<%= この行はコメントアウトになります %>
<% end %><%
=begin
%>
<%= この間の部分は全てコメントになります %>
<%
=end
%>

改行文字を<p>/<br>タグで置き換える

  • simple_formatメソッド によって改行文字を含むテキストをブラウザ上でも正しく表示できる
  • 以下の4つの基準に従って文章を整形する
    • 文字列全体を <p> タグで括る
    • 単一の改行文字には <br /> タグを付与
    • 連続した改行文字には </p><p> タグを付与
    • 元の改行文字の前後に追加され元の改行文字は破棄されない
# @text = 
# 本日の天気
#
# 関東地区の天気をお知らせします。
# 明日は全体的に猛暑日となる見込みです。
# 以上です。

<%= simple_format(@text) %>

# => 出力結果
<p>本日の天気</p>

<p>
関東地区の天気をお知らせします。<br />
明日は全体的に猛暑日となる見込みです。<br />
以上です。
</p>

条件に応じてリンクを生成する

  • link_to_if/unlessメソッド を使用すると条件式がtrueの場合のみリンクを生成しfalseの場合はテキストを表示する
  • link_to_unless_currentメソッド を使用するとリンク先が現在のページの場合はテキストのみを出力する
<%= link_to_if @user.present, "MY PAGE", user_path(@user) %>
<%= link_to_unless_current "HOME", root_path %>

form関連

リンク・ボタンクリック時に確認ダイアログを表示

  • link_todata-confrimオプション を指定する
  • OK を選択した場合のみ画面遷移・処理を行うようにできる
  • データの削除等、不可逆な処理に対する誤操作を極力無くすことができる
<%= link_to '削除', book, method: :delete, data: {confirm: '本当によろしいですか?'} %>
  • submitタグdata-confirmオプション を使用しても同様の処理を行える

form_for配下でのform_tagヘルパーの使用

  • モデルに紐付いたオブジェクトを扱うform_for内部でもモデルに紐付かない form_tagヘルパー を使用可能
  • 例えばレビューを投稿するフォームの中にタグの入力欄を並列で作る時など有効

form_forにおけるsubmitボタンの自動表示

  • form_forメソッドは扱うモデルオブジェクトによってHTTPメソッドを使い分けてくれる
  • 実はform内 submitボタン の表示文字も使い分けてくれる
  • valueを設定しない場合初期値で「Create (モデル名)」または「Update (モデル名)」が自動で入る

存在検証機能の二重化

  • コメント投稿欄に文字列が入っていなければデータを保存できないという検証機能をつける場合を考える
  • 対象の入力欄に required属性 を付与すると指定の入力欄が空の状態では警告メッセージが表示されデータを送信できなくなる
<%= f.text_filed :comment, required %>
  • 警告メッセージを非常に簡易に実装でき便利だがブラウザ側から開発者ツール等を使用すれば容易に required 指定を外し空のデータを送れてしまう
  • そのためにクライアントサイドだけでなくきちんとサーバーサイドでもバリデーションをかけなければならない
class Comment < ActiveRecord::Base
  validates :text, presence: true
end

layout関連

部分テンプレートを保存するべき場所

  • 部分テンプレートの保存先には規則が存在する
  • 規則に従うことでシンプルな記述で呼び出せるようになる
用途 保存先フォルダ
特定のコントローラでのみ共有 /views/コントローラ名
アプリケーション全体で共有 /views/application
リソースに強く関連づいた部品 /views/リソース名(複数形)
  • views/application ディレクトリに保存された部分テンプレートはパスを指定せずとも render 'review' のように呼び出せる
  • 同名の部分テンプレートが複数存在する場合にはメインテンプレートと同ディレクトリ配下の部分テンプレートが優先される

■ Controllerに関するあれこれ

controllerの挙動関連

ルーティングに対応するアクションが存在しない場合

  • アクションの定義が存在せずともエラーにはならない
  • 対応するテンプレートつまりビューファイルを直接読み込みにかかる

便利メソッド関連

sleepメソッドによる処理中の演出

  • sleepメソッド を使用すると指定秒数間処理を休止する
  • formの submit において data-disable_withオプション と併用すると処理中の演出ができる
  • 下記の場合「投稿する」ボタンを押した後3秒間ボタンが押せなくなくと同時に「処理中」の文字が表示される
class ReviewsController < ApplicationController
  def create
    sleep 3
    Review.create(review_params)
  end

  #- (中略) -#
end
<%= form_for @review do |f| %>
  <!-- (中略) -->
  <%= f.submit "投稿する", data: {disable_with: "処理中..."} %>
<% end %>

around_actionフィルタで記述をコンパクトにする

  • フィルタ発火のタイミングはアクション実行直前直後の二種類
class BooksController < ApplicationController
  before_action :start_log
  after_action  :end_log

  def create
    Book.create(book_params)
  end

  private

  def start_log
    logger.debug("[START: #{Time.now.to_s}]")
  end

  def end_log
    logger.debug("[END: #{Time.now.to_s}]")
  end
end
  • before_action + after_actionaround_action + yield で書き換えられる
class BooksController < ApplicationController
  around_action :output_log

  def create
    Book.create(book_params)
  end

  private

  def output_log
    logger.debug("[START: #{Time.now.to_s}]")
    yield #アクションを実行
    logger.debug("[END: #{Time.now.to_s}]")
  end
end

parameter関連

アクセスしたユーザーのIPアドレスを取得する

  • ユーザーのログイン認証を必要としない投票ボタンを実装する時に活躍
  • アカウント機能は作りたくないけれど極力ひとり一票までにしたいときはIPアドレスで識別しよう
  • request.remote_ip をコントローラーで使用すれば簡単にIPアドレスを取得できる
class BooksController < ApplicationController
  #--(中略)--#

  private

  def set_ip
    @ip = request.remote_ip
  end
end

status関連

条件に応じて意図的に404エラーを表示させる

  • 条件分岐内にrenderメソッドでファイル名を指定してあげればok
  • status
  • and return を付けてあげないと痛い目を見る事が多い
class BooksController < ApplicationController
  def show
    @book = Book.find(params[:id])
    if @book.status == '1'
      render file: Rails.root.join('public/404.html'), status: :not_found and return
    end
    render 'top/show'
  end
end

404以外のステータスコード

  • logを見るときにもステータスコードを知っていると読解量が違ってくる
  • SEOの観点からも302なのか301なのか正しいステータスコードを付与することは大切
シンボル コード 意味
:ok 200 成功
:created 201 リソースの生成に成功
:moved_permanently 301 リソースが恒久的に移動した
:found 302 リソースが一時的に移動した
:see_other 303 リソースが別の場所にある
:unauthorized 401 認証を要求
:forbidden 403 アクセスが禁止されている
:not_found 404 リソースが存在しない
:method_not_allowed 405 HTTPメソッドが許可されていない
:internal_server_error 500 サーバーエラー

helper関連

現在のコントローラーに対応したヘルパーファイルのみ読み込む

  • デフォルトではどのヘルパーファイルにヘルパーメソッドを記述してもアプリケーション全体で使用できてしまう
  • アプリケーションが肥大化した場合名前の競合が意図せず起きてしまうことも考えられる
  • 現在のコントローラー名に対応したヘルパーファイルだけを読み込む設定は config/application.rb に記述する
class Application < Rails::Application
  # Do not share helper methods
  config.action_controller.include_all_helpers = false
end
  • こうすることで、現在 UsersController に関連した動作を行っている場合は users_helper.rb が読み込まれる

認証関連

ダイジェスト認証の設定

  • コントローラーに記述するだけで簡単に実装できるセキュリティには二種類ある
  • ベーシック認証とそれよりもセキュリティ強度の増した ダイジェスト認証 の2つ
  • before_action を利用し簡単に実装可能かつ記述も簡潔なダイジェスト認証の実装を紹介
  • サイトにアクセスする際に共通ユーザー名と共通パスワードの入力が必須となる
class ApplicationController < ActionController::Base
  before_action :digest_auth

  private

  def digest_auth
    members = { '<ユーザー名>' => '<パスワード>' }
    authenticate_or_request_with_http_digest('<アプリケーション名>') do |name|
      members[name]
    end
  end
end

flashメッセージ関連

redirect_toのオプションとしてフラッシュを設定する

  • Bootstrapのalertのスタイルに当たるように任意のキーで送信できるようにする
  • 長くなったので別記事にまとめました(下記)

redirect_to使った時にフラッシュメッセージを表示させる

■ Routingに関するあれこれ

ルーティング表示関連

検索機能付きルーティング一覧を表示する

  • 最も使われるルーティング表示方法は rake routesコマンド
  • 大規模なアプリケーションではルーティングが肥大化し検索機能を使いたい時も
  • そんな時は http://localhost:[ポート番号]/rails/info/routes にアクセスすると良い
  • リアルタイム検索機能付きのルーティング一覧を表示してくれる

(.:format)の記述をルーティングから外す

  • resourcesを利用してルーティングを設定すると勝手に (.:format) のオプションが付与される
  • 大変親切なのだがjsonとかxmlとか今回使わねーしという場合外したくなる時も
  • format: false オプションを指定し て外してあげよう
routes.rb
resources :users, format: false, only: [:index]

#=> users GET /users users#index

routes.rbに記述すべき順番

  • routes.rb に記載した順番がそのまま優先順位になる
  • そのため汎用性の高いルーティングほど下に書くべきである
  • 例えば rootパス の設定は非常に汎用性が高いと言えるので多くの場合最下部に記述する

アクション追加関連

resourcesの7つのアクション以外にアクションを定義する

  • resourcesメソッドで生成される7つのアクションでは補いきれない場合がある
  • その時は collectionブロック または memberブロック を使用してアクションを追加できる
  • collection は複数のオブジェクトを扱うアクションに(ex:index)
  • member は単一のオブジェクトを扱うアクションに(ex:edit)
routes.rb
resources :books do
  collection do
    get :newest
  end
  member do
    get :archive
  end
end

#=> $ rake routes
# newest_books GET /books/newest      books#newest
# archive_book GET /books/:id/archive books#archive
  • ブロック配下のアクションがひとつである場合には onパラメータ を使用してシンプルに記述することも可能
routes.rb
resources :books do
  get :newest,  on: :collection
  get :archive, on: :member
end

■ フロントエンドに関するあれこれ

読み込み関連

application.js/.cssの役割

  • ファイル内に記述された マニフェスト と呼ばれるリストを元にアセットを読み込む
  • 読み込む動作自体は Sprockets というgemが担っている
  • application.js に記述してある標準ライブラリは turbolinks というgemの中に配置されている
application.js
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require_tree .
application.css
/*
 *= require_tree .
 *= require_self
 */

application.html.erbの役割

  • 用意したJavaScriptやStyleSheetをviewで読み込む際には個々のファイル名は記述しない
  • application.html.erb にマニフェストのファイル名を指定する
application.html.erb
<!DOCTYPE html>
<html lang="ja">
  <head>
    <!-- 中略 -->
    <%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
    <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  </head>

アセットパイプライン関連

アセットパイプラインのしくみ

  • Sprocketsによって提供されるJavaScriptやStyleSheetをまとめて管理する仕組み
  • 大きく4つの段階が存在する
  1. コンパイル

    オリジナルソースである複数の
    .js.coffee形式で存在するファイルたちを.js形式に
    .css.scss形式で存在するファイルたちを.css形式に
    それぞれ変換する。
    (※実行速度が遅延するためproduction環境ではスキップ)

  2. 統合

    コンパイル済みである複数の
    .jsファイルたちをapplication.js
    .cssファイルたちをapplication.css
    それぞれ統合する。
    (※デバッグしやすいためdevelopment環境ではここで出力)

  3. 圧縮

    統合されたapplication.jsapplication.cssから
    コメント・改行・空白等取り除き圧縮する。

  4. ダイジェスト付与

    圧縮されたapplication.cssapplication.jsと個別の画像たちに
    ハッシュ値というそのファイル固有の文字列を付与する。
    これによりアセットに編集作業を加えアップデートするごとに
    全く別のファイルだと認識され予期せぬキャッシュの適用を防ぐ。

  • コンパイルの対象となるCoffeeScriptの正体に関しては以下のまとめを参照されたい

JavaScriptを卒業してCoffeeScriptで飛躍しよう - 基本などまとめ

アセットプリコンパイル

  • アセットをアプリケーションで使う際には コンパイル して形式を統一する必要がある
  • しかしproduction環境では処理速度に対する影響を避けるためコンパイル処理を自動で行わない
  • そのため事前に手動でコンパイルを実行しなければならない
  • 事前に手動でコンパイルを行っておくことを アセットプリコンパイル と呼ぶ
  • ターミナルから以下のコマンドで実行できる
$ rake assets:precompile

※WEBrick環境では /config/environments/production.rb 中の config.serve_static_assets = false をコメントアウトして静的アセットファイルの処理を放棄する

Turbolinks関連

  • ページ遷移に際してページ全体ではなくタイトルと本文のみを置き換える仕組み
  • このことによりページの読み込みを高速化させている
  • 内部的にAjax通信(非同期通信)を発生させている
  • デフォルトで有効にされているため無効にする場合はgemfileに記載の gem 'turbolinks' をコメントアウトし $ bundle する

Ajax関連

  • HTMLだけで完結する内容でもなかったので別記事にまとめたものを参照されたい

モモンガでも使えるAjax(えいじゃっくす) − 投稿したコメントをリロードせずに表示しよう

■ その他Railsに関するあれこれ

Mailer関連

  • 例えば記事が更新された際に読者全員にお知らせメールを自動配信するような機能を簡単に実装できる。
  • 導入からTipsまで若干体系的な話になり別記事にまとめたため下記を参照されたい。

Railsでメール自動配信機能をつくるまでの道程

Gemfile関連

数字の意味

  • gem名の隣には様々数字が書いてある場合があるがそれには意味がある
  • 各々の数字はgemのバージョンを指定している
  • 記号と意味は下表のとおり
指定例 概要
2.4.3 特定のバージョンで固定
>= 2.1.2 特定のバージョン以上のものが必要
~> 3.1 3.x は良いが 4.x は不可

バージョンの意味

  • gemに限らず何かしらのバージョンは最大で数字4つを点で区切って表す事が多い
  • 「3.1.4.2」というバージョンがあったとする
  • 左から「メジャーバージョン.マイナーバージョン.リビジョン.ビルド」と呼ばれる
  • 各バージョンの変更が持つべき意味は下表のとおり。上に行くほど変化の角度が高い。
バージョン名 概要
メジャーバージョン 抜本から変更を加える
マイナーバージョン 大幅な仕様変更や機能追加を行う
リビジョン 仕様変更や機能追加を行う
ビルド 修正パッチを追加する
360
393
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
360
393