2012-03-16 12 views
0

の委任:私はユーザーが得ることができるポイントの種類を表しているいくつかのモデルを持っているダイナミックファインダー

class User 
    has_many :totals 
end 

class Total 
    belongs_to :user 
    belongs_to :total_type 
end 

class TotalType 
    has_many :totals 
end 

をそしてpointsgoalsのような種類が命名されています。私は総に素敵なmethod_missing追加:

def method_missing(method, *args, &block) 
    if TotalType.find_by_name(method) 
    joins(:total_type).where(:total_type => { :name => method }).sum(total) 
    else 
    super 
    end 
end 

をそしてそれは、私は次のようにいくつかの甘いダイナミックなものを行うことができます:今

user = User.first 
user.totals.points #=> 100 
user.totals.goals #=> 10 

難しい部分。私はユーザーの直接pointsgoalsメソッドを直接呼び出すことができるようにしたい。

class User 
    # ... 
    delegate :points, :totals, :to => :totals 
end 

しかし、もちろん、私はそれは同様にダイナミックだった好む:私はdelegateを使用する場合、私は先に時間のメソッドの名前を知っている必要があります。私は、ユーザーのmethod_missingを作成してみましたが、私はtotalssendに何かをしようとしたとき、私はNoMethodError: undefined method 'points' for #<Array:0x007fd2016e1510>ので、明らかに関連プロキシは、すでに(superは、簡単な例を維持するために取り残さ)配列に結果セットを変えた取得:

class User 
    #... 
    def method_missing(method, *args, &block) 
    totals.send(method, *args, &block) 
    end 
end 

user = User.first 
user.totals.points #=> 100 
user.points #=> NoMethodError: undefined method 'points' for #<Array:0x007fd2016e1510> 

これらの動的ファインダー名をハードコードすることなく、この通話をtotalsに転送するにはどうすればよいですか?どんな助けもありがとう、ありがとう!

+0

を何代わりに名前の範囲を使用してはどうですか? –

+0

しかし、私はスコープに 'points'や 'goals'のようなハードコードされた名前を付ける必要があります...私は本当にそれらを動的にしたい。 –

答えて

1

ええ、named_scope(またはクラスメソッド)を使用するのが最善の策です。例えば

、これはtotals.rbの場合:

class Total < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :total_type 

    def self.points 
    joins(:total_type).where(:total_types => {:name => 'points'}).sum(:total) 
    end 
end 

と、これはusers.rbです:

class User < ActiveRecord::Base 
    has_many :totals 

    def method_missing(name, *args, &blk) 
    if totals.respond_to? name 
     totals.send(name, *args, &blk) 
    else 
     super 
    end 
    end 
end 

その後、User.first.points#=> 100

あなたはこれをよりダイナミックにすることができます...しかし、コードは従うのが難しく、何かをキャッシュしなければ、多くの不必要なSQLクエリが実行されています。例えば、total.rbはこのようにすることができますUser.first.pointsが呼び出されるときに、最初のユーザ#のmethod_missingはuser.totals.respond_to場合見た、呼び出さ

class Total < ActiveRecord::Base 
    belongs_to :user 
    belongs_to :total_type 

    def self.method_missing(name, *args, &blk) 
    if TotalType.find_by_name(name.to_s) 
     joins(:total_type).where(:total_types => {:name => name.to_s}).sum(:total) 
    else 
     super 
    end 
    end 

    def self.respond_to_missing?(name, priv) 
    if TotalType.find_by_name(name.to_s) 
     true 
    else 
     super 
    end 
    end 

end 

? :ポイント。それは最初はありませんので、Total :: respond_to_missingが呼び出されます。これは、 'points'というtotal_typeがあるかどうかを調べます。これが呼び出され、Total :: method_missingが呼び出され、そのユーザーのポイントの合計が返されます。

もちろん、method_missingおよびrespond_to_missingメソッドでuser.pointsが呼び出されるたびに不必要なSQLクエリが実行されないように、結果を簡単にキャッシュできますが、複雑すぎるだけでメソッドのオーバーヘッドには値しません。私が以前に提案した動的ではないソリューションは、あなたのモデルがどのようにレイアウトされているかに最も適しています。このことができます

希望、

- ルカ

+0

しかし、Total(def self.points)でメソッドの名前をハードコードする必要がありました...コードで使用する準備ができているデータベースに新しい型が作成されるので、それらをすべて動的にします。 –

+0

そうですね、私はもっと動的なやり方で2番目の例を挙げました。 –

0

ダイナミクスは、あなたの懸念、問題を解決する可能性がありますby_total_typeのような名前のスコープがある場合。

class Total 

    scope :by_total_type, lambda { |ttype| joins(:total_type).where(:type => ttype) } 

end 

次に、あなたが言うことができる:

Total.by_total_type('point') 

それとも

user.totals.by_total_type('goal') 
+0

しかし、それは楽しいことではありません。user.totals.points(私の元のmethod_missingですでに動作しています)とuser.pointsの構文が必要です。データベースに新しいポイントタイプが追加されると、 user.goals、user.rebounds、user.assistsなど任意のメソッド名をハードコードする –

関連する問題