Ruby
Rails4

いいねと思った Rails メソッド 10

More than 3 years have passed since last update.

ActiveRecord

1. create_with

create するときの条件を追加できます。

例えば、"ユーザーから「男」を探して、いなかったら「男」、「山田太郎」を追加したい" とき

User.create_with(name: 'Yamada Taro').find_or_create_by(sex: 'man')

こんな感じで find_or_create を使う際に便利です。

2. update(attributes)

内部的には、attribute をオブジェクトにセットして、save メソッドを呼んでいるだけ。
なので、これを利用すると update_or_create_by が作れます。

User.find_or_initialize_by(name: 'Yamada Taro').update(sex: 'man')

initialize しないで

User.find_or_create_by(name: 'Yamada Taro').update(sex: 'man')

とすると、INSERT した後に UPDATE するので、find_or_initialize_by した方がちょっとお得。

3. extending(*modules, &block)

ActiveRecord_Relation に対して、exetend することが出来ます。

例えば

Partner.identifiers
#=> NoMethodError: undefined method `identifiers'

module Identifiers
  def identifiers
    pluck(:identifier)
  end
end

partner = Partner.all.extending(Identifiers)
partner.identifiers
#=> ["partner1", "partner2", "partner3", "partner4"]
user = User.all.extending(Identifiers)
user.identifiers
#=> ["user1", "user2", "user3"... ]

このように、再利用することができます。

引数には、ブロックを渡すことも出来ます。

partner = Partner.all.extending do
  def identifiers
    pluck(:identifier)
  end
end

ブロックによる更なる拡張も可能です。

partner = Partner.all.extending(Identifiers) do
  def names
    pluck(:name)
  end
end

4. destroy_alldelete_all

destroy_all は条件にあったものを関連テーブルも含めて削除する。DELETE は主キーによって行うので、DELETE の手前で SELECT が発行されます。そのため、対象の DELETE は一つずつ行われます。

delete_all は関連テーブルは削除しないが、条件にあったものを bulk でまるっと消します。関連データを削除しない場合は、こっちの方が効率がいい。

5. except, only, unscope

指定した条件を外す系のメソッド達

except(*skips)

User.select(:id).except(:select)
#=> SELECT `users`.* FROM `users`

only

partner.select(:id).where(id: 1).only(:where)
#=> SELECT `partners`.* FROM `partners`  WHERE `partners`.`id` = 1

unscope(*args)

# 例えば
1. User.order('email DESC').unscope(:order)
#== User.all

2. User.order('email DESC').select('id').where(name: "John")
.unscope(:order, :select, :where)
#== User.all

3. User.where(name: "John", active: true)
.unscope(where: :name) 
#== User.where(active: true)

# こんな感じでも使える
has_many :comments, -> { unscope where: :trashed }

except も似たメソッドだが、merge で使えません。

User.order('email').merge(User.except(:order))
    == User.order('email')
User.order('email').merge(User.unscope(:order))
    == User.all

こういう感じで、chain を外す事も出来るんですね。

ActiveSupport

6. squish

String クラス
両端の空白文字を取り除き、文字列内の連続する空白文字をスペース一つに置き換える。

" foo   bar    boo     ".squish    #=> "foo bar boo"
" foo   bar\n\n\n\tboo\t\n".squish #=> "foo bar boo"

squish!

破壊的変更も!ちなみに中身は

def squish!
  gsub!(/\A[[:space:]]+/, '')
  gsub!(/[[:space:]]+\z/, '')
  gsub!(/[[:space:]]+/, ' ')
  self
end

こんなの用意されてるの知らなくて、自分で正規表現作って gsub してました。

7. remove(*patterns), remove!(*patterns)

String クラス
こちらは pattern にマッチしたものを削除する!

"foo bar test".remove(" test") #=> "foo bar"

8. all_week, all_month, all_quarter, all_year

Date, DateAndTime, Time で使える。
週、月、四半期、年の Range を返す。
中見るとこんな感じ

beginning_of_week(start_day)..end_of_week(start_day)

業務のコードでわざわざ、beginning_of_monthend_of_month 計算して、範囲指定しているところがあったので、all_month 一発でいけたんだなあと。

9. advance(options)

Date, DateAndTime, Time で使える。
key を指定して、まるっと足し算を行う
key は keys: :years, :months, :weeks, :days

today = Date.today #=> Sat, 09 May 2015
today.advance({years: 1, months: 1, weeks: 1, days: 1}) #=> Fri, 17 Jun 2016

なんかちょろっと手元で試したいときとかに使えそうだなあと。

10. with_options

Object クラス
重複しているオプションがあったら、まとめることが出来ます。

例えば

class Account < ActiveRecord::Base
  has_many :customers, dependent: :destroy
  has_many :products,  dependent: :destroy
  has_many :invoices,  dependent: :destroy
  has_many :expenses,  dependent: :destroy
end

こんな感じで、同じような条件が続く事ってよくあると思うのですが、

class Account < ActiveRecord::Base
  with_options dependent: :destroy do |assoc|
    assoc.has_many :customers
    assoc.has_many :products
    assoc.has_many :invoices
    assoc.has_many :expenses
  end
end

と書けます。
特に、レシーバーが必要ない場合は

with_options dependent: :destroy do
  has_many :customers
  has_many :products
  has_many :invoices
  has_many :expenses
end

省略可能です。

ちなみに

with_options if: :persisted?, length: {minimum: 50} do
  validates :content, if: -> { content.present? }
end

と同じオプションをブロック内でも定義した場合は

validates :content, length: {minimum: 50}, if: -> { content.present? }

とブロック内で定義した方が優先されます。

以上、なんのつながりも無くランダムに選んだ、メソッド 10 でした。