早く知っておけば良かったrailsの技
はじめに
自分が rails をさわり始めたときはバージョン1からバージョン2に変わるあたりだったのですが、バージョン2が出た年を振り返るとなんと2007年でした。
月日の流れが速い事に驚く中、早く知ってたら良かったのになぁって事をつらつらとまとめてみました。
最近 rails さわり始めてみたよ!って方の参考になれば良いなと思います。
今回は便利な gem とかではなく、素のrailsで出来ることを挙げています。
ちなみにバージョンは以下の環境です。
About your application's environment
Ruby version 2.1.3-p242 (x86_64-darwin14.0)
RubyGems version 2.2.2
Rack version 1.5
Rails version 4.1.8
app 以下のディレクトリ構成は追加しても良いんだよ
rails new したときの app
以下のディレクトリ構成は以下のようになっています。
- app/
|+ assets/
|+ controllers/
|+ helpers/
|+ mailers/
|+ models/
|+ views/
最初はこの通りに作らなきゃいけないのかなって思っていたのですが、最初から用意されている枠に合わない物は追加しても大丈夫です。
例えば自作のバリデーターをまとめる validators
とか、バックグラウンドで動くワーカーが入ってる workers
とかですね。
- app/
|+ assets/
|+ controllers/
|+ decorators/
|+ helpers/
|+ jobs/
|+ mailers/
|+ models/
|+ queries/
|+ uploaders/
|+ validators/
|+ views/
|+ workers/
それでも、app
以下じゃ無いよなーって思うような、汎用的なユーティリティとかは lib
以下に配置することもあります。
ちょっと気をつけるのがロードパス。
app
以下の一階層分は勝手に呼んでくれるのですが、例えば app/workers/concerns
なんてディレクトリを作ったとすると、そこまでは読んでくれません。
なので config/application.rb
でロードするように設定しましょう。
下の例では式展開を使っているので %w
では無く、大文字の %W
であることにも注意です。
深い階層まで一気に追加する場合は、「lib 以下もロードパスに追加」で使っている方法も使えますよ。
require File.expand_path('../boot', __FILE__)
require 'rails/all'
Bundler.require(*Rails.groups)
module SampleApp
class Application < Rails::Application
# app 以下の独自ディレクトリも読み込む
config.autoload_paths += %W(#{config.root}/app/jobs/concerns #{config.root}/app/workers/concerns)
# lib 以下もロードパスに追加
config.autoload_paths += Dir["#{config.root}/lib/**/"]
# 中略
end
end
ネストしたリソースの routes は shallow を検討しよう
ここでは、説明のためにサンプルとなるアプリケーションを作ります。
ユーザー User
は複数のノート Note
を持っているアプリケーションをイメージしてください。
./bin/rails g scaffold user name:string
./bin/rails g scaffold note user:references title:string body:text
さて、リソースがネストしているので、config/routes.rb
にもそのように記述します。
Rails.application.routes.draw do
resources :users do
resources :notes
end
end
そうするとルーティングは以下のようになります。
Prefix Verb URI Pattern Controller#Action
user_notes GET /users/:user_id/notes(.:format) notes#index
POST /users/:user_id/notes(.:format) notes#create
new_user_note GET /users/:user_id/notes/new(.:format) notes#new
edit_user_note GET /users/:user_id/notes/:id/edit(.:format) notes#edit
user_note GET /users/:user_id/notes/:id(.:format) notes#show
PATCH /users/:user_id/notes/:id(.:format) notes#update
PUT /users/:user_id/notes/:id(.:format) notes#update
DELETE /users/:user_id/notes/:id(.:format) notes#destroy
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
これでルーティングもネストしたリソースに沿った物になりました。
ただ、特定のノートを参照しようとしたとき、ノートIDさえ分かれば対象は一意に決まるはずですが、その場合でもユーザーIDが必要になってしまいます。
そこで、shallow: true
を指定すると、、、
Rails.application.routes.draw do
resources :users, shallow: true do
resources :notes
end
end
Prefix Verb URI Pattern Controller#Action
user_notes GET /users/:user_id/notes(.:format) notes#index
POST /users/:user_id/notes(.:format) notes#create
new_user_note GET /users/:user_id/notes/new(.:format) notes#new
edit_note GET /notes/:id/edit(.:format) notes#edit
note GET /notes/:id(.:format) notes#show
PATCH /notes/:id(.:format) notes#update
PUT /notes/:id(.:format) notes#update
DELETE /notes/:id(.:format) notes#destroy
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
これで notes#show
の時に冗長だったユーザーID指定が不要になりました。
ちょっと注意なのが form_for
に渡すパスです。例えば、notes#create
の時は /users/:user_id/notes
で、notes#update
の時は /notes/:id
とユーザーIDの有無が異なります。
これだと form_for(@note) do
のままだと notes#new
の時に undefined method notes_path
なんて言われちゃいます。
(shallow
オプションを指定しない場合は form_for([@user, @note])
のように指定すれば良い)
そこで、form_for
は以下のように設定します。
form_for(@note, url: polymorphic_path([@user, @note])) do |f|
ちなみにコントローラーの new
, edit
はこんな感じ
# GET /notes/new
def new
@user = User.find params[:user_id]
@note = @user.notes.build
end
# GET /notes/1/edit
def edit
end
こうすることで、
新規作成時には action="/users/1/notes"
、編集時には action="/notes/1"
とちゃんと指定できます。
nilかも?って時には ||, && とか presense とか try とか便利だよ
例えば、User
クラスには名前 name
という変数があるとき、name
が nil
だったら値を設定したいなんて時は、
user.name = 'anonymous' unless user.name
こうせずに
user.name ||= 'anonymous'
こんな感じで書くことが出来るんですね。
ruby だとこれを「nilガード」なんて呼ばれています。
分かりやすく式を展開すれば下の意味と同じです。
user.name = (user.name || 'anonymous')
もちろん使いどころは nilガードだけじゃありません。
User
クラスにニックネーム nickname
もあったとして、「ニックネームが設定されていればそちらを、されていない(nilなら)名前を出したい」なんて時はこうすれば良いわけです。
user.nickname || user.name
同じようなユースケースで presence
が使える場合もあります。
presence
は present?
メソッドが真なら self
を、偽なら nil
を返すメソッドです。
present?
は nil
, false
, 空の配列, 空のハッシュ, 空の文字列, 特定文字列のみ文字列を判定してくれるメソッドです。
ここでの特定文字列とは 正規表現 /\A[[:space:]]*\z/
で表される物です。
user.name
で nil
だけじゃ無くて空白文字も判定したいんだって時に
user.name.present? ? user.name : 'anonymous'
presence
を使うと
user.name.presence || 'anonymous'
と書けるわけですね。
次に、少し例を変えて User
は Note
を has_many
で持っているとしましょう。
こんな感じです。
class User < ActiveRecord::Base
has_many :notes
end
class Note < ActiveRecord::Base
belongs_to :user
end
さて、Note
からユーザー名を表示したいとして、でも必ずしも対応するユーザーが存在するか分からないとき、
note.user.name if note.user
とか
note.user && note.user.name
みたいに書くことも出来るのですが、こんな時には try
が使えます。
try
は引数でメソッド名を渡して実行するもので、ただし対象がnil
の時には実行されずに nil
を返してくれる物です。
実装はシンプルで、Object
と NilClass
に try
が用意されているんですね。
class Object
def try(*a, &b)
if a.empty? && block_given?
yield self
else
public_send(*a, &b) if respond_to?(a.first)
end
end
end
class NilClass
def try(*args)
nil
end
end
try
だとメソッド名の打ち間違いなども nil
で返ってきますが、存在しないメソッドの場合は NoMethodError
を返してくれる try!
も用意されています。
delegate も使ってみよう
上の例では note.user.name
とメソッドの呼び出しをしていますが、オブジェクト指向プログラミングの界隈だと、「デメテルの法則」に反しているとも言われます。
それじゃあ、といって
class Note < ActiveRecord::Base
belongs_to :user
def user_name
user.try :name
end
end
ノートからユーザー名を取得するメソッドを定義しても良いのですが、数が多くなると大変になっちゃいますね。
そんなときには delegate
が使えます。
class Note < ActiveRecord::Base
belongs_to :user
delegate :name, to: :user, prefix: :author, allow_nil: true
end
こうすると note.author_name
が呼び出せるようになります。
delegate
の使い方は、委譲するメソッド名、移譲先(to
)、さらにはメソッドのプレフィックス(prefix
)、そしてnilを許可するかどうか(allow_nil
)です。
prefix: false
なら note.name
と呼び出すことになりますし、prefix: true
なら note.user_name
となります。
allow_nil: true
としておけば note.user
が nil
の場合でもエラーにならないので便利ですね。
さらに、移譲先は関連以外でも定数、クラス変数、インスタンス変数でも大丈夫で、多段の delegate をすることも可能です。
class Foo
CONSTANT
@@class_val
def initialize
@instance_val
end
delegate :foo, to: :CONSTANT
delegate :bar, to: :@@class_val
delegate :baz, to: :@instance_val
end
ちなみにプレーンなruby にも委譲の為の Forwardable
モジュールが用意されており、使い方も似ています。
Forwardable
の方はメソッドのリネームが出来る所が長所ですが、ActiveSupport
の delegate
に用意されている prefix
と allow_nil
が使い勝手が良いので、rails だと ActiveSupport
の delegate
をよく使います。
クラスマクロは自分で作れる
クラスマクロって例えば attr_accessor
みたいなやつですね。
class User
attr_accessor :name
end
これは用意されている物を使うだけで無く、自分で作ることも出来るんです。
「マクロとか難しそう」という訳では無く、その実態はただのクラスメソッドなので怖くないよ。
class User
attr_accessor :name
def self.suffix attr, value
define_method("#{attr}_with_suffix") {
name = instance_variable_get "@#{attr}"
"#{name} #{value}" if name.present?
}
alias_method_chain attr, :suffix
end
end
class Boy < User
suffix :name, 'くん'
end
class Girl < User
suffix :name, 'さん'
end
オマケで alias_method_chain
の例も入れてみたよ。
クラスマクロは def self.suffix attr, value
の部分で、この定義によって継承したクラスでクラスマクロが使えるようになっている訳です。
> girl = Girl.new
=> #<Girl:0x007fb9314610f8>
> girl.name = 'はなこ'
=> "はなこ"
> girl.name_with_suffix
=> "はなこ さん"
> girl.name
=> "はなこ さん"
> girl.name_without_suffix
=> "はなこ"
実行してみた結果が上の様子です。>
が入力、=>
が出力です。
クラスマクロによって、使うと "#{attr}_with_suffix"
というメソッドが作られるようになっています。
つまり Boy, Girl クラスで suffix :name, 'さん'
と使ったことによって、name_with_suffix
メソッドがマクロによって定義されているのです。
さらにマクロによって定義したメソッドの内容は、「名前が設定されていれば、後ろにマクロで指定した文字列を追加する」という物だったので、girl.name_with_suffix
によって「はなこ さん」と返ってきている訳です。
この例だと実は girl.name
と呼び出すだけで girl.name_with_suffix
と同じ結果を得ることが出来ます。
これは、alias_method_chain
によって、元々の name
メソッドが name_without_suffix
として待避され、name_with_suffix
として定義したメソッドが name
としても呼び出せるようになっているからなのです。
クラスマクロを定義する場所は、今回のように継承元クラスや、ActiveSupport::Concern
でモジュール化するなど、いろいろな方法があります。
super do で操作を差し込む
さて、下のようなクラスを作ってみます。
class User
attr_accessor :age
def initialize
@age = 0
end
def birthday
puts 'happy birthday!'
@age += 1
end
end
User#birthday
メソッドは、「happy birthday!」とメッセージを表示し、年齢に1を加えてその値を返す物です。
class Boy < User
def birthday
super
puts 'some process...'
end
end
Userクラスを継承したBoyクラスにおいて、birthday
メソッドをオーバーライドしようとしたとき、super
によって継承元のメソッドを呼び出すことが可能です。
しかし、「メッセージの表示」と「年齢の加算」の間に処理を挟みたい時にはどうしましょう。
そのために super
を使わずにオーバーライドしてしまうと、メッセージの定義が重複するなどしてメンテナンス性が下がってしまいます。
そんなときには継承元クラスに処理を差し込めるポイントを作っておきましょう。
class User
attr_accessor :age
def initialize
@age = 0
end
def birthday
puts 'happy birthday!'
yield if block_given?
@age += 1
end
end
class Boy < User
def birthday
super do
puts 'some process...'
end
end
end
Userクラスに yield if block_given?
を用意したので、継承したクラスにおいて birthday
メソッドをオーバーライドしたとき、ブロックによる super
を呼び出したときに、そこに処理を差し込むことが可能になります。
ログイン処理などによく用いられる plataformatec/devise では、例えば Devise::SessionsController
等において、「ログインしたときにそのユーザーに何か処理したいな」ってケースのために処理差し込みのポイントを用意してくれていたりします。
class Devise::SessionsController < DeviseController
# POST /resource/sign_in
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, location: after_sign_in_path_for(resource)
end
end
active model は使えるやつ
rails アプリを作成していると、DBには永続化しないんだけど、ActiveRecordと同じような使い勝手のモデルが欲しいなーと思うことがあります。
例えば検索用のフォームなどですね。
view で form_for 使いたいし、validate も同じように定義したいからです。
そんなときには ActiveModel
の出番です。
class UserSearchForm
include ActiveModel::Model
attr_accessor :name, :age
validates :name, presence: true
end
使い方もとても簡単。include ActiveModel::Model
するだけです。
これで validates
なども使えるようになっています。
おわりに
便利そうな Rails Tips をまとめてみました。誰かのお役に立てば幸いです。