タイトルの通り。Ruby on Railsを勉強していてこの使い方知ってるとだいぶ幅が広がるなというのがあったので、メモ。
今回は、define_methodとdefine_singleton_methodです。キーワードはメソッドの動的生成。
1.define_method
define_method (Module) - Rubyリファレンス
メソッドを定義することができる。
同じ処理を何度もしたい!けどいちいち一つずつ定義するのは面倒だ!なんてときに使います。繰り返し似たようなメソッドを作るときには売ってつけです。
たとえば、次のようなCurrencyモデルがあったとします。
Currencyモデル
id | INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL |
code | varchar(255) |
appelation | varchar(255) |
currenciesテーブルのデータ
id | code | appellation |
1 | JPY | 円 |
2 | USD | ドル |
3 | TWD | 台湾ドル |
そして、例えば、一つデータをとってくる。
[2] pry(main)> currency = Currency.first Currency Load (0.4ms) SELECT "currencies".* FROM "currencies" ORDER BY "currencies"."id" ASC LIMIT 1 => #<Currency id: 1, code: "JPY", appelation: "円">
ここで、とってきたデータがJPYかどうかを調べたいというとき。Currencyモデルにjpy?というメソッドを作る。
class Currency < ActiveRecord::Base def jpy? code == 'JPY' end end
こんな感じでjpy?というメソッドを追加します。
さて、同様にusd?とかtwd?とかいうメソッドも追加したい。だけれども、今後通貨が増えてくるし、いちいちメソッドを定義するのは面倒だ!という時に、一気にメソッドを定義してくれるのがdefine_method。
例えば、CODESという通貨コードをもった配列を用意して、くるくるまわす。
class Currency < ActiveRecord::Base CODES = ['JPY', 'USD', 'TWD'] CODES.each do |currency_code| define_method("#{currency_code.downcase}?".to_sym) { code == currency_code } end # 上のブロック内のdefine_methodによって以下が定義されたのと同じ。 #def jpy? # code == 'JPY' #end #def usd? # code == 'USD' #end #def twd? # code == 'TWD' #end end
結果がこちら。きちんとメソッドが定義されていて、期待通りの動きになっていることが分かる。
[1] pry(main)> currency = Currency.find(2) Currency Load (0.8ms) SELECT "currencies".* FROM "currencies" WHERE "currencies"."id" = ? LIMIT 1 [["id", 2]] => #<Currency id: 2, code: "USD", appelation: "ドル"> [2] pry(main)> currency.jpy? => false [3] pry(main)> currency.usd? => true [4] pry(main)> currency.twd? => false
2.define_singleton_method
define_singleton_method (Object) - Rubyリファレンス
こちらも基本的には、define_methodと同じです。ただし、特異メソッドを作ってくれます。Railsだと、例えば、モデルにクラスメソッドを動的に作成したいときに威力を発揮します。
今回の例ですと
Currency.twd
とやると、台湾ドルのデータをとってこれるようにしたいという場合を考えます。
単純にクラスメソッドを定義してやればいいです。
class Currency < ActiveRecord::Base def self.twd where(code: 'TWD').first end end
ただ、これも同様に日本円やUSドルも同様に作りたいですよね。define_methodと同じようにすればいいです。define_singleton_methodをつかうと、クラスメソッドをはやすことができます。
class Currency < ActiveRecord::Base CODES = ['JPY', 'USD', 'TWD'] CODES.each do |currency_code| define_singleton_method("#{currency_code.downcase}".to_sym) do where(code: currency_code).first end end # 上のブロック内のdefine_singleton_methodによって以下が定義されたのと同じ。 #def self.jpy # where(code: 'JPY').first #end #def self.usd # where(code: 'USD').first #end #def self.twd # where(code: 'TWD').first #end end
rails consoleで動作を確認してみます。
[1] pry(main)> Currency.jpy Currency Load (0.2ms) SELECT "currencies".* FROM "currencies" WHERE "currencies"."code" = 'JPY' ORDER BY "currencies"."id" ASC LIMIT 1 => #<Currency id: 1, code: "JPY", appelation: "円"> [2] pry(main)> Currency.usd Currency Load (0.5ms) SELECT "currencies".* FROM "currencies" WHERE "currencies"."code" = 'USD' ORDER BY "currencies"."id" ASC LIMIT 1 => #<Currency id: 2, code: "USD", appelation: "ドル"> [3] pry(main)> Currency.twd Currency Load (0.4ms) SELECT "currencies".* FROM "currencies" WHERE "currencies"."code" = 'TWD' ORDER BY "currencies"."id" ASC LIMIT 1 => #<Currency id: 3, code: "TWD", appelation: "台湾ドル">