开发者

Rails named_scope inheritance?

I'm trying to generalize some of my models by providing a common base model to inherit from that contains some mutual named_scope declarations and a filter method that activates that search for simpler querying on the controller side. This appears to be working when I run it in the console, but fails when in the controller:

# in the base model
class GenericModel < ActiveRecord::Base

  named_scope :by_name, lambda { |name|
    ( name.blank? ) ? {} : { :conditions => [ "#{self.table_n开发者_运维技巧ame}.name like ?", "%#{name}%" ] }
  }

  def filter(params)
    res = []
    res = self.by_name( (params[:name] or '') ) if params[:name]
    return res
  end

end

class MyModel < GenericModel
  set_table_name 'my_models'
end

# works in in console!
>> params = { :name => 'jimmy' }
>> MyModel.filter(params)
=> [ <#MyModel ...>, ... ]
nil

# fails in controller
@model = MyModel.filter(params)

# ActiveRecord::StatementInvalid (Mysql::Error Unknown column 'generic_models.name' in where clause...)

Apparently the parent class' named_scope is being called when in rails, but works fine in rails console. Any ideas how to mend this? thanks.


That's a bit of a train-wreck because of the way ActiveRecord is trying to interpret what you're saying. Generally the first class derived from ActiveRecord::Base is used to define what the base table name is, and sub-classes of that are defined to use Single Table Inheritance (STI) by default. You're working around this by using set_table_name but, as is often the case, while it's possible to go against the grain in Rails, things often get messy.

You should be able to do this a lot more cleanly using a mixin as suggested by Beerlington.

module ByNameExtension
  def self.extended(base)
    # This method is called when a class extends with this module

    base.send(:scope, :by_name, lambda { |name|
      name.blank? ? nil : where("#{self.table_name}.name LIKE ?", "%#{name}%")
    })
  end

  def filter(params)
    params[:name].present? ? self.by_name(params[:name]) : [ ]
  end
end

class MyModel < ActiveRecord::Base
  # Load in class-level methods from module ByNameExtension
  extend ByNameExtension
end

You should be able to keep your extensions contained to that module. If you want to clean this up even further, write an initializer that defines a method like scoped_by_name for ActiveRecord::Base that triggers this behavior:

class ActiveRecord::Base
  def scoped_by_name
    extend ByNameExtension
  end
end

Then you can tag all classes that require this:

class MyModel < ActiveRecord::Base
  scoped_by_name
end
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜