Делегирование динамического поиска - PullRequest
0 голосов
/ 16 марта 2012

У меня есть несколько моделей, которые представляют различные виды очков, которые пользователь может заработать:

class User
  has_many :totals
end

class Total
  belongs_to :user
  belongs_to :total_type
end

class TotalType
  has_many :totals
end

И есть именованные типы, такие как points и goals.Я добавил method_missing к Total:

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

Теперь самая сложная часть.Я хочу иметь возможность вызывать те же самые методы points и goals непосредственно для пользователя.Если я использую delegate, тогда мне нужно заранее знать название методов:

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

Но, конечно, я бы предпочел, чтобы это также было динамичным.Я попытался создать method_missing для Пользователя, но когда я пытаюсь send что-то totals, я получаю 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 без необходимости жесткого кодирования этих имен динамического поиска?Любая помощь будет оценена, спасибо!

Ответы [ 2 ]

1 голос
/ 17 марта 2012

Да, лучше всего использовать named_scope (или метод класса).

Например, если это тотал.рб:

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 может выглядеть так:

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

То есть, когда вызывается User.first.points, вызывается первый пользователь # method_missing, который видит user.totals.respond_to? :точки. Сначала это не так, поэтому вызывается Total :: response_to_missing, который определяет, есть ли тип total_type, называемый 'points'. Есть ... поэтому он вызывается, а затем вызывается Total :: method_missing, который возвращает вам сумму баллов для этого пользователя.

Конечно, вы можете легко кешировать результаты, чтобы ненужные запросы SQL не выполнялись каждый раз, когда user.points вызывается в методах method_missing и response_to_missing, но это просто усложняется, а издержки метода не стоят этого. Менее динамичное решение, которое я предлагал ранее, - это ваша лучшая ставка на то, как ваши модели выложены сейчас.

Надеюсь, это поможет,

1020 * Люк *

0 голосов
/ 17 марта 2012

Если вас интересует динамика, именованная область видимости, такая как 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')
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...