在activerecord中使用父/子自我加入

时间:2015-05-07 04:33:22

标签: ruby-on-rails ruby activerecord

我正在尝试让一个父/子has_many / belongs_to关系在rails中工作。这似乎比更典型的模型更棘手。我是Rails的新手,请原谅我,如果这看起来像一个非常简单的问题。 这是我的模特:

class Category < ActiveRecord::Base
  has_many   :subcategories,   class_name: "Category", foreign_key: "parent_id", dependent: :destroy
  belongs_to :parent,          class_name: "Category", foreign_key: "parent_id"
end

这就是创建对象的正确方法:

parent = Category.new(name: 'animal')
child = parent.subcategories.build(name: 'cat')

parent.subcategories #this works fine
child.parent #this returns nil

我发现作品的唯一方法是添加child.parent = parent,但这似乎不是正确的做法。

编辑:

这是我正在使用的rspec测试和输出

require 'rails_helper'

RSpec.describe Category, type: :model do
  context "Category" do
    before(:all) do
      @parent = Category.new(name: 'animal')
      @child = @parent.subcategories.build(name: 'cat')
    end

    it "should equal subcategory" do
      expect(@parent.subcategories.first).to eq(@child)
    end
    it "should reference to parent" do
      expect(@child.parent).to eq(@parent)
    end
  end
end

结果

21:44:56 - INFO - Running: spec/models/category_spec.rb
.F

Failures:

  1) Category Category should reference to parent
     Failure/Error: expect(@child.parent).to eq(@parent)

       expected: #<Category id: nil, name: "animal", parent_id: nil, created_at: nil, updated_at: nil>
            got: nil

       (compared using ==)
     # ./spec/models/category_spec.rb:14:in `block (3 levels) in <top (required)>'

Finished in 0.02808 seconds (files took 3.48 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/models/category_spec.rb:13 # Category Category should reference to parent

4 个答案:

答案 0 :(得分:3)

我认为问题是您的父母尚未保存。改变这个:

@parent = Category.new(name: 'animal')

以下内容:

@parent = Category.create(name: 'animal')

答案 1 :(得分:3)

因此,经过一些(好一堆)搜索ActiveRecord的时间之后,我就找到了。

假设:

class create_category_table < ActiveRecord::Migration
  def change
    create_table :categories do |t|
      t.integer :parent_id
    end
  end
end 

class Category < ActiveRecord::Base
  has_many :sub_categories, class: Category, foreign_key: :parent_id
  belongs_to :parent, class: Category
end

@parent = Category.new
@child  = @parent.sub_categories.build

基本上@child.parent返回nil的原因是因为当新记录通过@parent.sub_categories方法添加到#build时,活动记录无法知道has_many关联的反向关系是。这是因为我们给他们起名字,&#34; sub_categories&#34;和&#34; parent&#34;,不能通过类名中的活动记录派生。活动记录允许您添加:class:foreign_key选项,以阐明在实例化相关对象时要使用的类以及在查找记录时要查找的foreign_key。但它特别不会为关系创建反向反射(在#build中添加记录时关联会查找该反转),因为:foreign_key位于INVALID_AUTOMATIC_INVERSE_OPTIONS数组中。

除此之外还有更多内容,但是从这次探索中,我发现了一个非常简单的解决方案,那就是明确声明反射的反名称。

在关联下方添加此代码,您可以查找@parent.sub_categories@child.parent 没有 持久保存数据库中的任何记录

reflections["sub_categories"].options[:inverse_of] = :parent
reflections["parent"].options[:inverse_of] = :sub_categories

或(我真的希望我没有在这上面浪费很多时间),只需在关联选项中添加:inverse_of选项:

class Category < ActiveRecord::Base
  has_many :sub_categories, class: Category, foreign_key: :parent_id, inverse_of: :parent
  belongs_to :parent, class: Category, inverse_of: :sub_categories
end

Aaaaaaaaaaaaannd发现了所有这些之后,这里有明确解释的文档:(http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

...

答案 2 :(得分:1)

belongs_to关系不需要foreign_key属性。请参阅:http://guides.rubyonrails.org/association_basics.html#self-joins

答案 3 :(得分:1)

明显的buildActiveRecord association方法。因此它最终会在构建期间保持关联。但是new对象并不知道关联。所以它反映了nil

你的加入是完美的。