Rails 3.1高级Has_many和belongs_to模型连接

时间:2023-02-14 00:18:46

I have two tables. Items, and Vendors. Items are sold by Vendors. So Item belongs_to :vendor and Vendor has_many :items. That works fine.

我有两个表。项目和供应商。项目由供应商出售。所以Item belongs_to:vendor and vendor has_many:items。工作的很好。

However, Items are not always manufactured by the Vendors that sell them, but sometimes they are. So I have a new column in my Item table called "manufacturer_id". Rather than generate a new model called Manufacturer that duplicates Vendor identically, I tried to do a complex has_many and belongs_to to define manufacturer.

然而,产品并不总是由销售它们的供应商制造的,但有时确实是这样。因此,我在我的项目表中有一个新的列,名为“manufacturer_id”。我尝试使用复杂的has_many和belongs_to来定义制造商,而不是生成一个名为Manufacturer的新模型,它与供应商完全相同。

See here:

在这里看到的:

class Item < ActiveRecord::Base
  belongs_to :vendor
  belongs_to :manufacturer, :class_name => "Vendor", :foreign_key => "manufacturer_id"
end

class Vendor < ActiveRecord::Base
  has_many :items
  has_many :manufactured_items, :class_name => "Item", :foreign_key => "manufacturer_id"
end

Populating manufacturer_id in the items table works as expected on Create commands:

在items表中填充manufacturer_id在Create命令上的工作与预期一样:

Item.create(:manufacturer => Vendor.find_by_abbrev("INV"))

And I can even get the manufacturer as the operation

我甚至可以让制造商做手术。

item.manufacturer

which returns:

返回:

<Vendor:0x007ff06684e398>

HOWEVER:

然而:

item.manufacturer.name

fails completely with a hard exeption and I get the error:

完全失败,有一个硬感受,我得到错误:

undefined method `name' for nil:NilClass

running

运行

debug item.manufacturer

gives

给了

--- !ruby/object:Vendor
attributes:
  id: 181
  name: Invitrogen
  website: http://www.invitrogen.com/
  created_at: 2012-01-08 01:39:07.486375000Z
  updated_at: 2012-01-08 01:39:07.486375000Z
  abbrev: INV

so item.manufacturer.name should return the name for that vendor object above, Vendor: 0x007ff06684e398.

因此item.manufacturer.name应该返回上面那个vendor对象的名称:0x007ff06684e398。

What am I doing wrong here?

我在这里做错了什么?

Also, once I get this working I'd like to be able to similarly call:

而且,一旦我得到这个工作,我想能够同样调用:

vendor.manufactured_items

to get all the items that have the manufacturer_id of that vendor. is there a straightforward way to do that too?

获取具有该供应商的manufacturer_id的所有项目。有没有一种简单的方法可以做到这一点?

My last ditch effort may involve having to do:

我最后的努力可能需要做:

manufacturer = Vendor.new(item.manufacturer)

制造商= Vendor.new(item.manufacturer)

But that seems totally wrong, and goes against the rails documentation here: http://guides.rubyonrails.org/association_basics.html#self-joins

但是这似乎完全错误,并且违背了这里的rails文档:http://guides.rubyonrails.org/association_basics.html#self-join

please help!

请帮助!

1 个解决方案

#1


3  

Okay, I've actually built a demo Rails 3.1 project for you and posted it on GitHub. I've included the console output in the README file to prove that calls like item.seller.name and item.manufacturer.name work, as well as round-trip calls like vendor.sold_items.first.manufacturer.name, which allow you to get the name of the manufacturer of the first sold item for a particular vendor, for example.

好的,我已经为你构建了一个演示Rails 3.1项目,并将它发布到GitHub上。我已经在README文件中包含了控制台输出,以证明像item.seller.name和item.manufacturer.name . work这样的调用,以及像vendor.sold_items.first.header .manufacturer.name这样的往返调用,例如,允许您为特定的供应商获取第一次销售的产品的制造商的名称。

I think the root of the thing, as you noted, is that a vendor and a manufacturer, for all intents and purposes, are identical. For that reason I combined them simply into the Vendor class, and setup the foreign key relationships in such a way that it should work the way I think you want it to.

我认为事情的根源,正如你所提到的,是一个供应商和一个制造商,无论出于什么目的,都是相同的。出于这个原因,我将它们简单地组合到Vendor类中,并以一种我认为应该按照您希望的方式设置外键关系。

In particular, you should pay attention to the README file, which has the console session output that I ran to show it working. You'll also want to take a look at the two model classes and how their associations are defined, as well as the spec/factories.rb file for how it sets up the fake database data (I've included those below).

特别是,您应该注意README文件,它有我运行的控制台会话输出,显示它正在工作。您还需要了解这两个模型类以及它们的关联是如何定义的,以及spec/ factory。用于设置假数据库数据的rb文件(我已经包含了下面的内容)。

In re-reading your question this morning, I'm not sure what you were doing wrong, but you can probably chalk it up to a subtle error in your associations somewhere. It's probably a case of you being really close, but not quite there. :D

在今天早上重读你的问题时,我不确定你做错了什么,但你可以把它归结为你在联想中的一个细微的错误。这可能是你离得很近的情况,但不是很近。:D

Here's some snipets from the code:

下面是代码中的一些讽刺:

app/models/item.rb

应用程序/模型/ item.rb

class Item < ActiveRecord::Base
  belongs_to :seller, :class_name => "Vendor"
  belongs_to :manufacturer, :class_name => "Vendor"
end

app/models/vendor.rb

应用程序/模型/ vendor.rb

class Vendor < ActiveRecord::Base
  has_many :sold_items, :class_name => "Item", :foreign_key => :seller_id
  has_many :manufactured_items, :class_name => "Item", :foreign_key => :manufacturer_id
end

spec/factories.rb

规范/ factories.rb

require 'populator'
require 'faker'

FactoryGirl.define do

  factory :vendor do |v|
    v.name            {Populator.words(1..3)}
    v.website         {Faker::Internet.domain_name}
    v.abbr            {(["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VWX", "YZ1"])[rand(9)]}
  end

  factory :item do |i|
    i.association :seller, :factory => :vendor
    i.association :manufacturer, :factory => :vendor
    i.name  {Populator.words(3..5)}
  end

end

lib/tasks/populator.rake

lib /任务/ populator.rake

namespace :db do  
  desc "Erase database"
  task :erase => :environment do
    puts "Erasing..."

    [Vendor, Item].each(&:delete_all)
  end

  desc "Erase and fill database"
  task :populate => [:environment, :erase] do
    require 'populator'
    require 'faker'

    puts "Populating: enjoy this random pattern generator while you wait..."

    50.times{Factory.create(:vendor)}
    Vendor.all.each do |v|
      # This line actually has a bug in it that makes all `seller_id` and `manufacturer_id`
      # columns always contain a value in the range 0..50. That means
      # `rake db:populate` will only actually work the first time, but
      # I think you get the idea of how this should work.
      10.times{Factory.create(:item, :seller_id => (rand(50) + 1), :manufacturer_id => (rand(50) + 1))}
      print (['\\', '/', '_', '|'])[rand(4)]
    end

    puts ""
  end
end

#1


3  

Okay, I've actually built a demo Rails 3.1 project for you and posted it on GitHub. I've included the console output in the README file to prove that calls like item.seller.name and item.manufacturer.name work, as well as round-trip calls like vendor.sold_items.first.manufacturer.name, which allow you to get the name of the manufacturer of the first sold item for a particular vendor, for example.

好的,我已经为你构建了一个演示Rails 3.1项目,并将它发布到GitHub上。我已经在README文件中包含了控制台输出,以证明像item.seller.name和item.manufacturer.name . work这样的调用,以及像vendor.sold_items.first.header .manufacturer.name这样的往返调用,例如,允许您为特定的供应商获取第一次销售的产品的制造商的名称。

I think the root of the thing, as you noted, is that a vendor and a manufacturer, for all intents and purposes, are identical. For that reason I combined them simply into the Vendor class, and setup the foreign key relationships in such a way that it should work the way I think you want it to.

我认为事情的根源,正如你所提到的,是一个供应商和一个制造商,无论出于什么目的,都是相同的。出于这个原因,我将它们简单地组合到Vendor类中,并以一种我认为应该按照您希望的方式设置外键关系。

In particular, you should pay attention to the README file, which has the console session output that I ran to show it working. You'll also want to take a look at the two model classes and how their associations are defined, as well as the spec/factories.rb file for how it sets up the fake database data (I've included those below).

特别是,您应该注意README文件,它有我运行的控制台会话输出,显示它正在工作。您还需要了解这两个模型类以及它们的关联是如何定义的,以及spec/ factory。用于设置假数据库数据的rb文件(我已经包含了下面的内容)。

In re-reading your question this morning, I'm not sure what you were doing wrong, but you can probably chalk it up to a subtle error in your associations somewhere. It's probably a case of you being really close, but not quite there. :D

在今天早上重读你的问题时,我不确定你做错了什么,但你可以把它归结为你在联想中的一个细微的错误。这可能是你离得很近的情况,但不是很近。:D

Here's some snipets from the code:

下面是代码中的一些讽刺:

app/models/item.rb

应用程序/模型/ item.rb

class Item < ActiveRecord::Base
  belongs_to :seller, :class_name => "Vendor"
  belongs_to :manufacturer, :class_name => "Vendor"
end

app/models/vendor.rb

应用程序/模型/ vendor.rb

class Vendor < ActiveRecord::Base
  has_many :sold_items, :class_name => "Item", :foreign_key => :seller_id
  has_many :manufactured_items, :class_name => "Item", :foreign_key => :manufacturer_id
end

spec/factories.rb

规范/ factories.rb

require 'populator'
require 'faker'

FactoryGirl.define do

  factory :vendor do |v|
    v.name            {Populator.words(1..3)}
    v.website         {Faker::Internet.domain_name}
    v.abbr            {(["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VWX", "YZ1"])[rand(9)]}
  end

  factory :item do |i|
    i.association :seller, :factory => :vendor
    i.association :manufacturer, :factory => :vendor
    i.name  {Populator.words(3..5)}
  end

end

lib/tasks/populator.rake

lib /任务/ populator.rake

namespace :db do  
  desc "Erase database"
  task :erase => :environment do
    puts "Erasing..."

    [Vendor, Item].each(&:delete_all)
  end

  desc "Erase and fill database"
  task :populate => [:environment, :erase] do
    require 'populator'
    require 'faker'

    puts "Populating: enjoy this random pattern generator while you wait..."

    50.times{Factory.create(:vendor)}
    Vendor.all.each do |v|
      # This line actually has a bug in it that makes all `seller_id` and `manufacturer_id`
      # columns always contain a value in the range 0..50. That means
      # `rake db:populate` will only actually work the first time, but
      # I think you get the idea of how this should work.
      10.times{Factory.create(:item, :seller_id => (rand(50) + 1), :manufacturer_id => (rand(50) + 1))}
      print (['\\', '/', '_', '|'])[rand(4)]
    end

    puts ""
  end
end