Rails 3.1高级Has_many和belongs_to模型连接

时间:2012-01-08 02:36:42

标签: ruby-on-rails ruby-on-rails-3

我有两张桌子。物品和供应商。物品由供应商出售。所以Item belongs_to:vendor和Vendor has_many:items。这很好。

然而,物品并非总是由销售它们的供应商制造,但有时它们是。所以我的Item表中有一个名为“manufacturer_id”的新列。我没有生成一个名为Manufacturer的新模型,它以相同的方式复制供应商,而是尝试使用复杂的has_many和belongs_to来定义制造商。

见这里:

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

在items表中填充manufacturer_id在Create命令上按预期工作:

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

我甚至可以将制造商作为操作

item.manufacturer

返回:

<Vendor:0x007ff06684e398>

但是:

item.manufacturer.name

完全失败了,我得到了错误:

undefined method `name' for nil:NilClass

运行

debug item.manufacturer

给出

--- !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

所以item.manufacturer.name应该返回上面那个供应商对象的名称,供应商:0x007ff06684e398。

我在这里做错了什么?

此外,一旦我开始工作,我希望能够同样打电话:

vendor.manufactured_items

获取具有该供应商的manufacturer_id的所有项目。有没有直接的方法呢?

我最后的努力可能涉及到:

manufacturer = Vendor.new(item.manufacturer)

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

请帮忙!

1 个答案:

答案 0 :(得分:3)

好的,我实际上为你和posted it on GitHub构建了一个演示Rails 3.1项目。我在README文件中包含了控制台输出,以证明item.seller.nameitem.manufacturer.name之类的通话以及vendor.sold_items.first.manufacturer.name之类的往返通话可以让您使用例如,获取特定供应商的第一个销售商品的制造商名称。

我认为事情的根源,正如你所指出的那样,vendormanufacturer,无论出于何种意图和目的,都是相同的。出于这个原因,我将它们简单地组合到Vendor类中,并以这样的方式设置外键关系,使其按照我认为你想要的方式工作。

特别是,您应该注意README文件,该文件具有我运行的控制台会话输出以显示它的工作情况。您还需要查看两个模型类及其关联的定义方式,以及spec/factories.rb文件如何设置虚假数据库数据(我已将其包含在下面)。 / p>

今天早上重新阅读你的问题时,我不确定你做错了什么,但你可能会把它归结为某个地方你的协会中的一个微妙的错误。这可能是你真的关闭的情况,但并不完全相同。 :d

以下是代码中的一些snipets:

应用/模型/ item.rb的

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

应用/模型/ 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

<强>规格/ 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 /任务/ 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