如何使用多态类模拟 has_many :through

发布于 2024-11-30 00:35:44 字数 1734 浏览 2 评论 0原文

我明白为什么 ActiveRecord 不能支持多态类上的 has_many :through 。但我想模仿它的一些功能。考虑以下情况,其中连接表关联两个多态类:

class HostPest < ActiveRecord::Base
  belongs_to :host, :polymorphic => true
  belongs_to :pest, :polymorphic => true
end
class Host < ActiveRecord::Base
  self.abstract_class = true  
  has_many :host_pests, :as => :host
end
class Pest < ActiveRecord::Base
  self.abstract_class = true  
  has_one :host_pest, :as => :pest
end
class Dog < Host ; end
class Cat < Host ; end
class Flea < Pest ; end
class Tick < Pest ; end

目标

因为我无法执行 has_many :pests, :through=>:host_pests, :as=>:host (等) ,我想模拟这四种方法:

dog.pests (returns a list of pests associated with this dog)
flea.host (return the host associated with this flea)
cat.pests << Tick.create (creates a HostPest record)
tick.host = Cat.create (creates a HostPest record)

问题 1

我已经有了前两种方法(pestshost)的工作实现,但想要知道这是否是最好的方法(具体来说,我是否忽略了 ActiveRecord 关联中的一些有帮助的内容):

class Host < ActiveRecord::Base
  def pests
    HostPest.where(:host_id => self.id, :host_type => self.class).map {|hp| hp.pest}
  end
end
class Pest < ActiveRecord::Base
  def host
    HostPest.where(:pest_id => self.id, :pest_type => self.class).first.host
  end
end

问题 2

我对如何实现 <<=< 感到困惑/code> 这里隐含的方法:

cat.pests << Tick.create  # => HostPest(:host=>cat, :pest=>tick).create
tick.host = Cat.create    # => HostPest(:host=>cat, :pest=>tick).create

有什么建议吗? (再说一遍,ActiveRecord 关联可以提供任何帮助吗?)

I understand why ActiveRecord can't support has_many :through on polymorphic classes. But I would like to emulate some of its functionality. Consider the following, where a join table associates two polymorphic classes:

class HostPest < ActiveRecord::Base
  belongs_to :host, :polymorphic => true
  belongs_to :pest, :polymorphic => true
end
class Host < ActiveRecord::Base
  self.abstract_class = true  
  has_many :host_pests, :as => :host
end
class Pest < ActiveRecord::Base
  self.abstract_class = true  
  has_one :host_pest, :as => :pest
end
class Dog < Host ; end
class Cat < Host ; end
class Flea < Pest ; end
class Tick < Pest ; end

The goal

Since I can't do has_many :pests, :through=>:host_pests, :as=>:host (etc), I'd like to emulate these four methods:

dog.pests (returns a list of pests associated with this dog)
flea.host (return the host associated with this flea)
cat.pests << Tick.create (creates a HostPest record)
tick.host = Cat.create (creates a HostPest record)

Question 1

I've got a working implementation for the first two methods (pests and host), but want to know if this is the best way (specifically, am I overlooking something in ActiveRecord associations that would help):

class Host < ActiveRecord::Base
  def pests
    HostPest.where(:host_id => self.id, :host_type => self.class).map {|hp| hp.pest}
  end
end
class Pest < ActiveRecord::Base
  def host
    HostPest.where(:pest_id => self.id, :pest_type => self.class).first.host
  end
end

Question 2

I'm stumped on how to implement the << and = methods implied here:

cat.pests << Tick.create  # => HostPest(:host=>cat, :pest=>tick).create
tick.host = Cat.create    # => HostPest(:host=>cat, :pest=>tick).create

Any suggestions? (And again, can ActiveRecord associations provide any help?)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

错爱 2024-12-07 00:35:44

Pest 类上实现 host= 方法非常简单。我们需要确保在设置新主机时清除旧主机(因为 AR 不会从中间表中清除旧值)。

class Pest < ActiveRecord::Base
  self.abstract_class = true  
  has_one :host_pest, :as => :pest

  def host=(host)
    Pest.transaction do
      host_pest.try(:destroy) # destroy the current setting if any
      create_host_pest(:host => host)
    end
  end
end

Host 类上实现 pests<< 方法有点复杂。在 Host 类上添加 pests 方法以返回害虫的聚合列表。在 pests 方法返回的对象上添加 << 方法。

class Host < ActiveRecord::Base
  self.abstract_class = true  
  has_many :host_pests, :as => :host

  # pest list accessor
  def pests
    @pests ||= begin
      host = self # variable to hold the current self. 
                  # We need it later in the block
      list = pest_list
      # declare << method on the pests list
      list.singleton_class.send(:define_method, "<<") do |pest|
        # host variable accessible in the block 
        host.host_pests.create(:pest => pest)
      end
      list
    end        
  end

private
  def pest_list
    # put your pest concatenation code here
  end
end

现在

cat.pests # returns a list
cat.pests << flea # appends the flea to the pest list

Implementing the host= method on the Pest class is straight forward. We need to make sure we clear the old host while setting a new host (as AR doesn't clear the old value from the intermediary table.).

class Pest < ActiveRecord::Base
  self.abstract_class = true  
  has_one :host_pest, :as => :pest

  def host=(host)
    Pest.transaction do
      host_pest.try(:destroy) # destroy the current setting if any
      create_host_pest(:host => host)
    end
  end
end

Implementing pests<< method on Host class is bit more involved. Add the pests method on the Host class to return the aggregated list of pests. Add the << method on the object returned by pests method.

class Host < ActiveRecord::Base
  self.abstract_class = true  
  has_many :host_pests, :as => :host

  # pest list accessor
  def pests
    @pests ||= begin
      host = self # variable to hold the current self. 
                  # We need it later in the block
      list = pest_list
      # declare << method on the pests list
      list.singleton_class.send(:define_method, "<<") do |pest|
        # host variable accessible in the block 
        host.host_pests.create(:pest => pest)
      end
      list
    end        
  end

private
  def pest_list
    # put your pest concatenation code here
  end
end

Now

cat.pests # returns a list
cat.pests << flea # appends the flea to the pest list
单调的奢华 2024-12-07 00:35:44

您可以通过使用 STI 和常规关联来解决您的问题:

class HostPest < ActiveRecord::Base
  belongs_to :host
  belongs_to :pest
end

将所有主机存储在名为 hosts 的表中。将名为 type 的字符串列添加到表中。

class Host < ActiveRecord::Base
  has_many :host_pests
  has_many :pests, :through => :host_pests
end

继承Host类来创建新的主机。

class Dog < Host ; end
class Cat < Host ; end

将所有害虫存储在名为 pests 的表中。将名为 type 的字符串列添加到表中。

class Pest < ActiveRecord::Base
  has_one :host_pest
  has_one :host, :through => :host_pest
end

继承Pest类来创建新的害虫。

class Flea < Pest ; end
class Tick < Pest ; end

现在,您可以运行以下命令:

dog.pests (returns a list of pests associated with this dog)
flea.host (return the host associated with this flea)
cat.pests << Tick.create (creates a HostPest record)
tick.host = Cat.create (creates a HostPest record)

注意

Rails 支持 多态类上的 has_many :through 。您需要指定 source_type 才能使其工作。

考虑标记模型:

class Tag
  has_many :tag_links
end

class TagLink
  belongs_to :tag
  belongs_to :tagger, :polymorphic => true
end

假设产品和公司可以被标记。

class Product
  has_many :tag_links, :as => :tagger
  has_many :tags, :through => :tag_links
end

class Company
  has_many :tag_links, :as => :tagger
  has_many :tags, :through => :tag_links
end

我们可以在 Tag 模型上添加关联来获取所有标记的产品,如下所示:

class Tag
  has_many :tag_links
  has_many :products, :through => :tag_links, 
                        :source => :tagger, :source_type => 'Product'
end

You can address your problem by using STI and regular association:

class HostPest < ActiveRecord::Base
  belongs_to :host
  belongs_to :pest
end

Store all the hosts in a table called hosts. Add a string column called type to the table.

class Host < ActiveRecord::Base
  has_many :host_pests
  has_many :pests, :through => :host_pests
end

Inherit the Host class to create new hosts.

class Dog < Host ; end
class Cat < Host ; end

Store all the pests in a table called pests. Add a string column called type to the table.

class Pest < ActiveRecord::Base
  has_one :host_pest
  has_one :host, :through => :host_pest
end

Inherit the Pest class to create new pests.

class Flea < Pest ; end
class Tick < Pest ; end

Now when you can run following commands:

dog.pests (returns a list of pests associated with this dog)
flea.host (return the host associated with this flea)
cat.pests << Tick.create (creates a HostPest record)
tick.host = Cat.create (creates a HostPest record)

Note

Rails supports has_many :through on polymorphic classes. You need to specify the source_type for this to work.

Consider the models for tagging:

class Tag
  has_many :tag_links
end

class TagLink
  belongs_to :tag
  belongs_to :tagger, :polymorphic => true
end

Let's say products and companies can be tagged.

class Product
  has_many :tag_links, :as => :tagger
  has_many :tags, :through => :tag_links
end

class Company
  has_many :tag_links, :as => :tagger
  has_many :tags, :through => :tag_links
end

We can add an association on Tag model to get all the tagged products as follows:

class Tag
  has_many :tag_links
  has_many :products, :through => :tag_links, 
                        :source => :tagger, :source_type => 'Product'
end
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文