0

Je ne parviens pas à avec une relation has_many avec le nom personnalisé dans Rails 5.1Rails 5, relation has_many avec échec du test de nom personnalisé

J'ai un modèle de domaine avec les utilisateurs, les éléments d'un catalogue et favoris. Un utilisateur peut avoir beaucoup d'objets favoris.

Un favori peut également contenir des éléments supplémentaires.

Je fais cela comme un exercice d'apprentissage sur la modification des applications exisiting, donc je l'ai ajouté chaque relation avec une migration dédiée:

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 

Le schéma résultant est:

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 

Cette est comment mes modèles ressemblent:

# 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 

Ceci est ma spécification pour 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 

Si je lance les tests que je reçois cette erreur:

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 

Je comprends ce que signifie l'erreur. La table items n'a pas de colonne additional_item_id. Vous pouvez également le voir dans le schéma.

Ce que je ne comprends pas c'est pourquoi devrait-il être là? La clé étrangère sur la table Favoris n'est-elle pas suffisante? Est-ce que je fais quelque chose de mal dans la façon dont j'ai écrit la migration ou les relations dans les modèles?

Je suis très rouillé avec Rails et la théorie et la conception de DB, alors n'hésitez pas à signaler toute erreur stupide que j'ai faite. Merci :)

Répondre

0

Je l'ai compris.

Le problème était de savoir comment j'essayais de modéliser la relation entre le favori et ses éléments supplémentaires. Je dû rafraîchir mes connaissances DB un peu ...

Le fait qu'un favori peut avoir de nombreux articles dans la relation APPELÉ additional_items ne signifie pas que has_many est la bonne façon de décrire cette relation lors de la définition du modèle . En particulier, has_many définit le "un" côté d'un one-to-many relationship.

Ma relation d'éléments supplémentaires est plutôt many-to-many. Oui, un favori peut avoir de nombreux éléments supplémentaires, mais en même temps, un élément peut faire partie de l'ensemble des éléments supplémentaires de nombreux favoris.

Rails permet de décrire les relations plusieurs-à-plusieurs, has_many :through et has_and_belongs_to_many.

Dans mon cas has_and_belogns_to_many était le choix le plus approprié, selon ce qui est suggéré dans le Rails guide on choosing between the two options.

Ce que je l'ai fait alors était:

1. Revert les commits avec la migration en ajoutant la référence à items de favourite, la mise à jour du schéma relatif, et la définition has_many dans le modèle Favourite.

2. Déposez la base de données, bundle exec rake db:drop

3. Générez une nouvelle migration pour ajouter une table de jointure pour représenter le nombre à plusieurs:

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

4. Mettre à jour les spécifications pour le modèle 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. Mettre en oeuvre l'association réelle 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 

Remarquez comment en raison du nom personnalisé de l'association, le nom du join_table et class_name du modèle associé a à préciser, comme ActiveRecord aurait autrement déduit de :additional_items, en ne trouvant pas une telle table et classe.

Je suis heureux de dire qu'il n'a pas été nécessaire de préciser ni le foreign_key ni le association_foreign_key, favourite_id et item_id respectivement, car ils peuvent être déduites à partir du nom de la classe.