Sharing methods between the named areas

I have a bunch of named fields and there is a method inside one of them that I would like to split between other namespaces. I did this using define_method and lambda. However, there is another duplicate code, and I am wondering if there is a better approach?

Here is a simplified example of what I have. Suppose I have a project table and each project has many users.

As part of the user model, I have ...

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 

Then I would use it as ...

 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
source share
4 answers

Here is a completely different solution, which is probably more in the spirit of what asked the question.

named_scope accepts a block, which can be any Proc. Therefore, if you create lambda / Proc that defines the filter_by_name method, you can pass it as the last argument to 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) 

This will do what you are looking for. If you still find this too repetitive, you can combine it with the methods in the mrjake2 solution to identify several named areas at once. Something like that:

 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 
+2
source

Named regions can be chained, so you make it more complicated for yourself than you need.

Below, when defined in a custom model, you will get what you want:

 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 

Then the following fragments will be executed:

 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" 

I see that you are using detect , perhaps in order to avoid excessive hits in the database. But your examples do not use it properly. Detect returns only the first element in the list for which the block returns true. In the above example, some_users will only have one entry, the first user called either β€œPete” or β€œAlan”. If you want to get all users with the name "Pete" or "Alan", then you want to select . And if you want select , you better use a named scope.

Named regions during evaluation return a special object containing the components necessary for constructing an SQL statement to generate results, while a chain of other named regions has not yet performed the statement. Until you try to access the methods in the result set, for example, call each or map.

+2
source

I would probably use a bit of metaprogramming:

 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 

Thus, if you want to add more search engines in the future, you can simply add the method name and conditions for the hash of the param methods.

+1
source

You can also do this with a second named 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]} 

Then you can do @project.users.active.filter_by_name('Francis') .

If you really need to do this with Enumerable # detect, I would define a filter_by_name method in a module that can then expand named areas:

 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 

This adds the filter_by_name method to the class returned by all three namespaces.

0
source

Source: https://habr.com/ru/post/1303135/


All Articles