Rails:覆盖 ActiveRecord 关联方法

37

有没有一种方法可以覆盖ActiveRecord关联提供的其中一个方法?

例如,假设我有以下典型的多态has_many:through关联:

class Story < ActiveRecord::Base
    has_many :taggings, :as => :taggable
    has_many :tags, :through => :taggings, :order => :name
end


class Tag < ActiveRecord::Base
    has_many :taggings, :dependent => :destroy
    has_many :stories, :through => :taggings, :source => :taggable, :source_type => "Story"
end

你可能知道这会给Story模型添加一整套相关的方法,例如tags、tags<<、tags=、tags.empty?等。

我该如何覆盖其中的一个方法?特别是tags<<方法。覆盖普通类方法相当容易,但是我找不到有关如何覆盖关联方法的任何信息。尝试这样做:

def tags<< *new_tags
    #do stuff
end

当调用它时会产生语法错误,所以显然并不简单。


2
你为什么要这样做?这可能会破坏其他ActiveRecord功能,而且可能有更好的方法来实现你想要的。 - Gareth
7个回答

59
你可以使用 has_many 块来扩展你的关联方法。参见注释“使用块来扩展你的关联” here
重写现有的方法也是可行的,但不确定是否是一个好主意。
  has_many :tags, :through => :taggings, :order => :name do
    def << (value)
      "overriden" #your code here
      super value
    end     
  end

当然!我忘了那个。这可能是你想要做的最好的方法。 - Daniel Beardsley
1
你会如何称呼原始方法? (我想重写build方法,添加一些默认值,然后调用原始方法) - Eran Kampf
4
我如何以这种方式覆盖关联getter呢? - lulalala
他们本可以使用RSA,这是一种更好的防止人们这样做的方法。 - yeyo
我已经尝试了Rails 5.1,但由于某些原因它不起作用。覆盖函数没有被调用。 - dowi

19

如果你想在Rails 3.2中访问模型本身,应该使用proxy_association.owner

示例:

class Author < ActiveRecord::Base
  has_many :books do
    def << (book)
      proxy_association.owner.add_book(book)
    end
  end

  def add_book (book)
    # do your thing here.
  end
end

请查看文档


这在Rails 5.1中仍然适用。 - coconup
@coconup 这在 Rails 5.1 中对我不起作用 - 覆盖的 << 方法根本没有被调用 :-( - dowi

1

我使用的方法是扩展关联。你可以在这里看到我处理“数量”属性的方式:https://gist.github.com/1399762

它基本上允许你只做

has_many : tags, :through => : taggings, extend => QuantityAssociation

如果不知道您希望通过重写这些方法来实现什么,那么很难确定是否可以做到相同的效果。


0

我认为你想要的是签名为def tags.<<(*new_tags),这应该可以工作,或者以下等效且更清晰一些的方式,如果你需要覆盖多个方法。

class << tags
  def <<(*new_tags)
    # rawr!
  end
end

我认为这两者都不会起作用。你所建议的似乎是尝试扩展tags方法返回值的特殊类。 - Daniel Beardsley
它在tags返回的对象的特殊类中定义了一个方法,这个对象很可能是一个数组。这样做的效果是向该数组添加一个新的实例方法,这正是我所理解的原始问题所询问的内容。在 Ruby 中,“extend”具有特定的含义,但这里并不是这种情况。 - x1a4
你说得对,那正是它所做的。我想我只是不明白你建议把那段代码放在哪里。无论如何,我想我的回答基本上是一样的,只是多了一些上下文。 - Daniel Beardsley

0

你需要定义tags方法来返回一个具有<<方法的对象。

你可以像这样做,但我真的不建议这样做。与其试图替换ActiveRecord使用的东西,还不如直接向你的模型添加一个执行所需操作的方法。

这本质上是运行默认的tags方法,向结果对象添加了<<方法并返回该对象。这可能会消耗一些资源,因为每次运行都会创建一个新的方法。

def tags_with_append
  collection = tags_without_append
  def collection.<< (*arguments)
    ...
  end
  collection
end
# defines the method 'tags' by aliasing 'tags_with_append'
alias_method_chain :tags, :append  

0

Rails指南关于直接覆盖添加方法的文档。

对于覆盖<<的问题,OP可能是唯一的例外情况,可以参考最佳答案。但这对于has_one=赋值方法或getter方法不起作用。


0
这可能对你的情况没有帮助,但对于其他人研究此问题可能会有用。
关联回调: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html 文档中的示例:
class Project
  has_and_belongs_to_many :developers, :after_add => :evaluate_velocity

  def evaluate_velocity(developer)
    ...
  end
end

另请参阅关联扩展:

class Account < ActiveRecord::Base
  has_many :people do
    def find_or_create_by_name(name)
      first_name, last_name = name.split(" ", 2)
      find_or_create_by_first_name_and_last_name(first_name, last_name)
    end
  end
end

person = Account.first.people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name  # => "Heinemeier Hansson"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接