【问题标题】:Ruby on Rails - Which method should I override for Album.first.photos?Ruby on Rails - 我应该为 Album.first.photos 覆盖哪种方法?
【发布时间】:2010-07-22 01:09:46
【问题描述】:

我已经为ActiveRecord::Base 覆盖了find 方法,效果很好,现在我可以很好地自定义它了。

def self.find(*args)
  # my custom actions 
  super(*args)
end

然后

Album.find(1) #=> My custom result

但现在我想覆盖这个:

Album.first.photos

我不确定我应该重写什么方法来完成工作...... 我正在考虑专门针对关联查询的东西,但我不确定:(

我需要对建议动态类的 'ActiveRecord::Base' 采取行动,因此我无法为此创建方法 photos,而是创建一个可以在我尝试的所有模型中交互的方法。

非常感谢


更新

为了更好地解释我想要实现的目标:

创建一个可插入的 ruby​​ gem 用于 rails,该 gem 可以简单地从数据库中获取 ID 结构,并将其即时转换为缩短 ID,就像 bit.ly 系统一样。但这是即时完成的,只需向您的模型声明has_shortened :id,然后所有接口和查询都返回例如DH3而不是12,例如,我已经设法让这个工作了这一点需要处理关联。

关注 gem url http://github.com/ludicco/shortener 以便您可以查看它,如果您认为这是一个不错的想法,请随时提出您可能需要实施的想法。

干杯

【问题讨论】:

  • 例如,您想用它做什么?我想不出有什么好的理由……如果你写出来,也许它会提出一个好的解决方案。
  • 所以你确实有一个has_many :photos,但是你需要干预这个收藏吗?你具体需要做什么?以某种方式限制结果集?计算集合被访问的次数?您能否提供更多信息让我们一起思考,并可能提供其他替代解决方案。

标签: ruby-on-rails ruby collections associative-array


【解决方案1】:

像你用has_many 定义的关联访问器在ActiveRecord 中是相当复杂的东西。他们实际上使用反射类返回代理对象,其行为类似于模型类,但将其查找器限制为外键(可能还有其他条件)。如果您想更深入地了解它,您可能需要查看 ActiveRecord gem 中的反射.rb、associations.rb、associations/association_proxy.rb 和associations/association_collection.rb。

但是,最后,我们可以使用关联访问器,就好像它是类本身一样,ActiveRecord 会小心地将所需的附加条件传递给类查找器。

这意味着,如果您在后台调用 Album.first.photos,ActiveRecord 将调用 Photo.find(附加条件是仅返回具有 :album_id => Album.first.id 的记录)。

因此,如果您覆盖Photo.find,如果您通过Album.first.photos 使用它,它也会返回您修改后的结果。这就是 ActiveRecord 的魔力 :)

我的快速测试(使用 Rails 2.3.8 运行):

class Album < ActiveRecord::Base
  has_many :photos
end

class Photo < ActiveRecord::Base
  belongs_to :album

  def self.find (*args)
    puts "XXX running custom finder"
    super
  end
end

Rails 控制台:

> Photo.all
XXX running custom finder
Photo Load (0.4ms)   SELECT * FROM "photos" 
=> [#<Photo id: 1, album_id: 1>, … ] 

> Album.first.photos
Album Load (0.4ms)   SELECT * FROM "albums" LIMIT 1
XXX running custom finder
Photo Load (0.3ms)   SELECT * FROM "photos" WHERE ("photos".album_id = 1) 
=> [#<Photo id: 1, album_id: 1>, … ] 

【讨论】:

  • 嗨安德烈亚斯,非常感谢您的回答,我唯一仍然想知道的是如何使这个动态,为了更好地解释我正在尝试做的事情我已经用一些更新了我的问题更多细节。再次感谢
  • 这听起来更像是你遇到了一个不同的问题,即从你的 has_shortened 方法中“动态地”覆盖 find 方法。您不能简单地在类方法中执行“def self.something”。您需要混合包含新方法的模块或使用 instance_eval/class_eval 或其中一个 define_method/define_class_method 帮助器动态定义它们。但这是一个不同的故事......
【解决方案2】:

由于 Album.first 返回 Album 的实例,您应该重写 Album 类本身的 photos 方法:

class Album < ActiveRecord::Base

  # ...

  def photos(*args)
    if my_condition_is_met
      // my custom handler
    else
      super
    end
  end

  # ...

end

现在,如果您确定每个模型实例都需要这个,我宁愿将它隔离在一个模块上:

module Photo
  def photos(*args)
    if my_condition_is_met
      // my custom handler
    else
      super
    end
  end
end

然后重新打开ActiveRecord::Base 类:

class ActiveRecord::Base
  include Photo
end

通过这种方式,您可以为您的解决方案实现模块化,使其像“插件”一样发挥作用。

另外一个不相关的说明:您不必调用super(*args),因为仅调用super 将传递您收到的所有参数。

编辑:次要格式

【讨论】:

  • 感谢 kolrie 的回答,我唯一没有得到的是,如果我永远不知道哪个类与第一个类相关,我该如何制作这个插件或 gem?例如,在这种情况下,类是Photo 和一个Album has_many :photos,但如果用户得到Album has_many :pagespictures 怎么办?我想在此之前覆盖该方法,在一个超级级别上,所以只有那个方法可以用于模型可以拥有的所有关联,没有区别:) 再次感谢
【解决方案3】:

看看friendly_id插件(http://github.com/norman/friendly_idhttp://norman.github.com/friendly_id/file.Guide.html),它可以满足您的需要:为模型生成自定义ID别名。您可以定义自定义方法来生成 slug。

在模型中保留 id 字段

对于自定义#find,使用 :finder_sql 选项:

class Album < ActiveRecord::Base
  has_friendly_id :short_id

  has_many :photos, :finder_sql => 'select * from photos where album_id="#{short_id}"'
end

>> Album.last.photos
select * from photos where album_id="t54"

【讨论】:

  • 这听起来很酷 edbond,正是我需要的,非常感谢队友
【解决方案4】:

您是否有特殊原因不能使用关联扩展来执行此操作?

http://ryandaigle.com/articles/2006/12/3/extend-your-activerecord-association-methods

直接弄乱代理对象是自找麻烦。但是,如果您只想覆盖查找,则可以这样做。

【讨论】:

    猜你喜欢
    • 2012-07-08
    • 1970-01-01
    • 2016-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-17
    • 2017-06-03
    • 2012-05-14
    相关资源
    最近更新 更多