【问题标题】:Rails 5, has_many relationship with custom name test failureRails 5,has_many 与自定义名称测试失败的关系
【发布时间】:2017-09-22 13:10:06
【问题描述】:

我在使用 Rails 5.1 中的自定义名称时遇到了 has_many 关系问题

我有一个包含用户、目录中的项目和收藏夹的域模型。一个用户可以拥有许多最喜欢的项目。

收藏夹也可以有其他项目。

我将其作为修改现有应用的学习练习,因此我已通过专用迁移添加每个关系:

class AddUserToFavourite < ActiveRecord::Migration[5.1]
  def change
    add_reference :favourites, :user, foreign_key: true
  end
end

class AddItemToFavourite < ActiveRecord::Migration[5.1]
  def change
    add_reference :favourites, :item, foreign_key: true
  end
end

class AddAdditionalItemsToFavourite < ActiveRecord::Migration[5.1]
  def change
    add_reference :favourites, :additional_item, foreign_key: { to_table: :items }
  end
end

生成的架构是:

ActiveRecord::Schema.define(version: 20170921111049) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "favourites", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.bigint "user_id"
    t.bigint "item_id"
    t.bigint "additional_item_id"
    t.index ["additional_item_id"], name: "index_favourites_on_additional_item_id"
    t.index ["item_id"], name: "index_favourites_on_item_id"
    t.index ["user_id"], name: "index_favourites_on_user_id"
  end

  create_table "items", force: :cascade do |t|
    t.string "description"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "users", force: :cascade do |t|
    t.string "name"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  add_foreign_key "favourites", "items"
  add_foreign_key "favourites", "items", column: "additional_item_id"
  add_foreign_key "favourites", "users"
end

这是我的模型的样子:

# user.rb
class User < ApplicationRecord
  has_many :favourites
end

# item.rb
class Item < ApplicationRecord
  belongs_to :additional_item, class_name: 'Favourite', optional: true
end

# favourite.rb
class Favourite < ApplicationRecord
  belongs_to :user
  belongs_to :item

  has_many :additional_items, class_name: 'Item', foreign_key: 'additional_item_id'
end

这是我对Favourite 的规范:

# favourite_spec.rb
require 'rails_helper'

RSpec.describe Favourite, type: :model do
  it { should belong_to :user }
  it { should belong_to :item }
  it { should have_many(:additional_items).with_foreign_key('additional_item_id') }

  it 'can be created with no additional items' do
    expect(Favourite.create!(name: 'Name', user: User.create!, item: Item.create!).additional_items).to be_empty
  end
end

如果我运行测试,我会收到此错误:

ActiveRecord::StatementInvalid:
       PG::UndefinedColumn: ERROR:  column items.additional_item_id does not exist
       LINE 1: SELECT  1 AS one FROM "items" WHERE "items"."additional_item...
                                                   ^
       : SELECT  1 AS one FROM "items" WHERE "items"."additional_item_id" = $1 LIMIT $2

我了解错误的含义。表 items 没有 additional_item_id 列。您也可以在架构中看到它。

我不明白为什么它应该在那里?收藏夹表中的外键还不够吗?

我在编写迁移或模型中的关系时做错了什么?

我对 Rails 和 DB 理论和设计非常生疏,因此请随时指出我犯的任何愚蠢错误。谢谢:)

【问题讨论】:

    标签: ruby-on-rails activerecord ruby-on-rails-5 ruby-on-rails-5.1


    【解决方案1】:

    我想通了。

    问题在于我如何尝试对收藏夹与其附加项目之间的关系进行建模。 我必须更新一下我的数据库知识...

    收藏夹可以在称为additional_items 的关系中包含许多项目这一事实并不意味着has_many 是在定义模型时描述这种关系的正确方式。

    特别是,has_many 定义了one-to-many relationship 的“一”侧。

    我的附加项目关系改为many-to-many。是的,一个收藏夹可以有许多附加项目,但同时一个项目可以是许多收藏夹的附加项目集的一部分。

    Rails 提供了描述多对多关系的方法,has_many :throughhas_and_belongs_to_many

    就我而言,has_and_belogns_to_many 是最合适的选择,正如 Rails guide on choosing between the two options 中所建议的那样。

    我当时做的是:

    1. 使用迁移还原提交,添加从favouriteitems 的引用、相关架构更新以及Favourite 模型中的has_many 定义。 p>

    2.删除数据库,bundle exec rake db:drop

    3. 生成一个新的迁移以添加一个连接表来表示多对多关联:

    class CreateFavouritesAdditionalItemsJoinTable < ActiveRecord::Migration[5.1]
      def change
        create_join_table :favourites, :items, table_name: 'additional_items_favourites'
      end
    end
    

    4. 更新 Favourite 模型的规范:

    require 'rails_helper'require 'rails_helper'
    
    RSpec.describe Favourite, type: :model do
      it { should belong_to :user }
      it { should belong_to :item }
      it { should have_and_belong_to_many(:additional_items) }
    
      # These tests here are just for me to ensure I configured things properly.
      # In a real app you wouldn't need to do this because there would be other
      # tests that implicitly act on how the association is setup.
      it 'can be created without additional items' do
        expect(Favourite.create!(user: User.create!, item: Item.create!).additional_items).to be_empty
      end
    
      it 'can be updated adding additional items' do
        f = Favourite.create!(user: User.create!, item: Item.create!)
        item1 = Item.create!(description: 'foo')
        item2 = Item.create!(description: 'bar')
        f.additional_items << [item1, item2]
    
        expect(f.additional_items.length).to eq 2
        expect(f.additional_items).to include item1
        expect(f.additional_items).to include item2
      end
    end
    

    5.Faovurite中实现实际关联:

    class Favourite < ApplicationRecord
      belongs_to :user
      belongs_to :item
    
      has_and_belongs_to_many :additional_items,
        join_table: 'additional_items_favourites',
        association_foreign_key: 'item_id',
        class_name: 'Item'
    end
    

    请注意,由于关联的自定义名称,必须指定关联模型的 join_tableclass_name 的名称,否则 ActiveRecord 会从 :additional_items 推断它们,无法找到这样的表和类。

    我很高兴地说,没有必要分别指定 foreign_keyassociation_foreign_keyfavourite_iditem_id,因为它们可以从类名中推断出来。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-27
      相关资源
      最近更新 更多