2016-08-01 6 views
0

Userというクラスがあります。これはhas_many: roles, through: user_rolesです。私はそうのようなユーザー・クラス内の関連する役割のための述語のメソッドをメタプログラミングしようとしています:クラス内のメタプログラミング述語メソッド

class User < ActiverRecord::Base 
...  
    Role.all.pluck(:name).each do |role_name| 
    define_method("#{role_name}?") do 
     roles.map(&:name).include?(role_name) 
    end 
    end 
... 
end 

Role.all.pluck(&:name)ながら、既存のロール名の配列を返すん、define_methodは呼ばれることは決してありませんし、私の仕様では、未定義のメソッドで失敗します。

... 
    subject.roles << create(:role, name: 'foo') 
    expect(subject.foo?).to be true #<= undefined method `foo?' for #<User...> 
    ... 
+0

'&'を付けずに 'pluck(:name)'にする必要があります。これはコレクションの構築物を投げ捨てて、決して呼び出されない 'define_method'につながります。 – sixty4bit

+0

@ sixty4bit oops、updated。私は '.map'を' pluck'に置き換え、 'to_proc'を取り除くことを忘れました。しかし、まだ未定義のメソッドを返す –

+1

ああ。その場合、私は答えがメタプログラミングの仕組みに根本的にあると考えています。あなたの 'Role.all ...'ロジックは 'User'クラスがロードされたとき、つまりテストが実行される前に実行されています。したがって、定義される唯一の方法は、 'User'がロードされる前にすでに存在していたロールのためのものです。 'User'がロードされた後にテストでロールを作成すると、以前に実行されたコードには何の影響もありません。うまくいけば意味があります... – sixty4bit

答えて

2

あなたが採用しているメタプログラミング技術は、クラスがロードされたときにクラス内に座っているコードが実行されるという事実を利用することを目的としています。だから、Railsは述語のメソッドを定義するために、その瞬間ロジックであなたのUserクラス、をロードするときに実行されており、方法はUserロードされているその瞬間に何Role.all戻りのために作成されています。

したがって、テストで行うように、新しいロールを作成すると、クラスがロードされてコードが実行されたときに作成された述語メソッドには影響しません。その後

$count += 1

irbオープンおよびタイプ::あなたは、ファイルを作成することによって、この動作を確認することができます

は、内部の次のコードで任意のディレクトリにcount.rbと呼ば

irb(main):001:0> $count = 0 
=> 0 
irb(main):002:0> require './count' 
=> true 
irb(main):003:0> $count 
=> 1 

ていることに注意してください$countは、ファイルがロードされたときに1だけインクリメントされました。さて、あなたがファイルrequireに戻っていたら、何も起こりません。あなたは、コードを強制することができloadを使用しての代わりにより再ロードするには必要です。

# ...continued from above 
irb(main):004:0> require './count' 
=> false 
irb(main):005:0> $count 
=> 1 
irb(main):006:0> load './foo.rb' 
=> true 
irb(main):007:0> $count 
=> 2 

は、だからあなたのテストに合格するために取得するには、あなたがして、その後、ロールを作成Userクラスのリロードを強制しなければなりませんアサーションを作る。

+0

絶対に!このテストロジックを 'Role'の単体テストに動かし、新しいロールが作成された後に' User'をインスタンス化するようにしました。 –

+0

これは答えですが、それは実用的なテストを提供しますが、私はこのアプローチを採用しません。コールドリスタート/リロードしない限り、アプリケーションに新しい役割を追加することは効果がありません。 'Role#after_commit'のコールバックを定義し、' User'クラスの述語メソッドを更新します。 – mudasobwa

+0

明確にする:私は、これがアプリケーションコードに実装されるべきアプローチであると答えようとしているわけではありませんでした。私は、なぜ* @Dimitry_Nがテストでこの問題を抱えていたのかを説明しようとしていただけで、基礎をなすメタプログラミングのコンセプトの仕組みなどを理解していることを確認していました。 – sixty4bit