Обмен методами между именованными областями - PullRequest
4 голосов
/ 04 марта 2010

У меня есть несколько именованных областей и в одной из них есть метод, которым я бы хотел поделиться с другими именованными областями. Я вроде как достиг этого, используя define_method и лямбду. Тем не менее, есть еще некоторый повторяющийся код, и мне интересно, есть ли лучший подход?

Вот упрощенный пример того, что у меня есть. Предположим, у меня есть таблица проектов, и у каждого проекта много пользователей.

В рамках модели User у меня есть ...

filter_by_name = lambda { |name| detect {|user| user.name == name} }

named_scope :active, :conditions => {:active => true} do
  define_method :filter_by_name, filter_by_name
end

named_scope :inactive, :conditions => {:active => false} do
  define_method :filter_by_name, filter_by_name
end

named_scope :have_logged_in, :conditions => {:logged_in => true} do
  define_method :filter_by_name, filter_by_name
end

Тогда я бы использовал это как ...

active_users = Project.find(1).users.active

some_users = active_users.filter_by_name ["Pete", "Alan"]
other_users = active_users.filter_by_name "Rob"

logged_in_users = Project.find(1).users.logged_in

more_users = logged_in_users.filter_by_name "John"

Ответы [ 4 ]

2 голосов
/ 04 марта 2010

Именованные области видимости могут быть объединены в цепочку, так что вы делаете это для себя сложнее, чем нужно.

Следующее определение в пользовательской модели даст вам то, что вы хотите:

class User < ActiveRecord::Base
  ...
  named_scope :filter_by_name, lambda { |name| 
     {:conditions => { :name => name}  }
  }

  named_scope :active, :conditions => {:active => true} 

  named_scope :inactive, :conditions => {:active => false} 

  named_scope :have_logged_in, :conditions => {:logged_in => true} 
end

Тогда будут работать следующие фрагменты:

active_users = Project.find(1).users.active

some_users = active_users.filter_by_name( ["Pete", "Alan"]
other_users = active_users.filter_by_name "Rob"

logged_in_users = Project.find(1).users.have_logged_in

more_users = logged_in_users.filter_by_name "John"

Я вижу, что вы используете detect, вероятно, чтобы избежать лишних попаданий в БД. Но ваши примеры не используют это должным образом. Detect возвращает только первый элемент в списке, для которого блок возвращает true. В приведенном выше примере some_users будет только одной записью, первым пользователем с именем «Пит» или «Алан». Если вы хотите, чтобы все пользователи назывались «Пит» или «Алан», тогда вам нужно select. А если вы хотите select, то лучше использовать именованную область.

Именованные области при оценке возвращают специальный объект, содержащий компоненты, необходимые для построения оператора SQL для генерации результатов, при этом цепочка других именованных областей все еще не выполняет инструкцию. Пока вы не попытаетесь получить доступ к методам в наборе результатов, таким как вызов каждого или карты.

2 голосов
/ 04 марта 2010

Вот совершенно другое решение, которое, вероятно, больше соответствует духу вопроса.

named_scope принимает блок, который может быть любым Proc. Таким образом, если вы создаете лямбда / процесс, который определяет метод filter_by_name, вы можете передать его в качестве последнего аргумента named_scope.

filter_by_name = lambda { |name| detect {|user| user.name == name} }

add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name }

named_scope(:active, :conditions => {:active => true}, &add_filter_by_name)

named_scope(:inactive, :conditions => {:active => false}, &add_filter_by_name)

named_scope(:have_logged_in, :conditions => {:logged_in => true}, &add_filter_by_name)

Это сделает то, что вы ищете. Если вы все еще думаете, что он слишком повторяющийся, вы можете объединить его с методами решения mrjake2, чтобы определить сразу несколько именованных областей. Как то так:

method_params = {
  :active => { :active => true },
  :inactive => { :active => false },
  :have_logged_in => { :logged_in => true }
}

filter_by_name = lambda { |name| detect {|user| user.name == name} }

add_filter_by_name = lambda { define_method :filter_by_name, filter_by_name }

method_params.keys.each do |method_name|
  send(:named_scope method_name, :conditions => method_params[method_name], 
    &add_filter_by_name)
end
1 голос
/ 04 марта 2010

Я бы, вероятно, использовал немного метапрограммирования:

method_params = {
  :active => { :active => true },
  :inactive => { :active => false },
  :have_logged_in => { :logged_in => true }
}

method_params.keys.each do |method_name|
  send :named_scope method_name, :conditions => method_params[method_name] do
    define_method :filter_by_name, filter_by_name
  end
end

Таким образом, если вы хотите добавить больше искателей в будущем, вы можете просто добавить имя метода и условия к хэшу method_param.

0 голосов
/ 17 августа 2010

Вы также можете сделать это со вторым именем scope.

named_scope :active, :conditions => {:active => true}
named_scope :inactive, :conditions => {:active => false}
named_scope :have_logged_in, :conditions => {:logged_in => true} 
named_scope :filter_by_name, lambda {|name| :conditions => ["first_name = ? OR last_name = ?", name, name]}

Тогда вы можете сделать @project.users.active.filter_by_name('Francis').

Если вам действительно нужно сделать это с помощью Enumerable # detect, я бы определил метод filter_by_name в модуле, который затем может расширить именованные области:

with_options(:extend => FilterUsersByName) do |fubn|
  fubn.named_scope :active, :conditions => {:active => true}
  fubn.named_scope :inactive, :conditions => {:active => false}
  fubn.named_scope :have_logged_in, :conditions => {:logged_in => true}
end

module FilterUsersByName
  def filter_by_name(name)
    detect {|user| user.name == name}
  end
end

Это добавляет метод filter_by_name к классу, возвращаемому всеми тремя именованными областями.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...