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 でした。