2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rails ポリモーフィックアソシエーション(belongs_to で複数のモデルを紐づけしたい)

2
Posted at

ケース

Postモデル(投稿)のbelongs_to(従属)にUserモデル(利用者)とRestaurantモデル(利用者)といった複数を指定したいが検索しても解決方が分からない。方むけ

事の発端

RailsTutorialからオリジナルのお店探索アプリへと派生していった段階で、
UserモデルとRestaurantモデルの投稿を共通のMicropostモデル(@micropost_id)にして、タイムラインフィードで投稿日時オーダーで表示したい。

今回の肝がオプションなどで柔軟性のある”has_many”側ではなく”belongs_to”側であることで
「リレーション belongs_to 複数 rails」
などの検索ワードでも解決にむかう記事がでみつからなかった為、ここに備忘録兼検索用で残します。

見つからなかったpolymorphic(ここは完全に自分の記録です)

自分で試行錯誤して行ったのは
micropostモデル内でif分岐で従属するコード

micopostモデル

  scope :user?, -> {where user_id: nil}
  scope :restaurant?, -> {where restaurant_id: nil}
  #写真に関するpresense validation かけるか悩み中
  has_many :favorites , dependent: :destroy
  has_many :comments , dependent: :destroy
  has_many :likes , dependent: :destroy
  if user?
    belongs_to :user 
    p "とおってへn"
  elsif restaurant?
    belongs_to :restaurant 
  end


  validates :user_id ,presence: true , if: ":restaurant_id.nil?"
  validates :restaurant_id , presence: true , if: ":user_id.nil?"

調べていく中でしらなかったScopeをしったのでメモ
https://www.sejuku.net/blog/26994
↑参考

[5] pry(main)> r1=Restaurant.first
  Restaurant Load (0.2ms)  SELECT  "restaurants".* FROM "restaurants" ORDER BY "restaurants"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Restaurant:0x00000006f8c888
 id: 1,
 restaurant_name: "やきにくや",
 restaurant_number: "kiwya",
 email: "kiwami@gmai.com",
 phone: "",
 password_digest: "$2a$10$73MDO6LUUwUGn4VGFbP84ODyhz3v/21T5.vdvfS4a7WhAJ/KUQcoy",
 remember_digest: nil,
 admin: false,
 activation_digest: nil,
 activated: false,
 activated_at: nil,
 created_at: Thu, 15 Feb 2018 10:39:20 UTC +00:00,
 updated_at: Thu, 15 Feb 2018 10:39:20 UTC +00:00>
[7] pry(main)> m1=r1.microposts.build(content:"asdffdf")
=> #<Micropost:0x00000006e0f000 id: nil, content: "asdffdf", user_id: nil, created_at: nil, updated_at: nil, picture: nil, restaurant_id: 1>
[8] pry(main)> m1.save
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> false
[9] pry(main)> m1.errors.messages
=> {:user=>["must exist"]}

進めて行く中でどうしても解決しないのが
バリデーションでのif文は作動して、:○○_id にはUser,Restaurantのいずれかが埋めているがリレーション部分でif文など様々な手が上手く通らずエラーで出ている部分
Referenceでつなげた部分であると推測。
belongs_toをいじらなければと思い立った

https://blog.falconsrv.net/articles/415
↑参考(今回役に立たせることは出来なかったがすごく参考になりました)
上の記事を応用してみたけど上手くいかず。

2つの人モデルに1つの共通ポストモデル

どう調べても決定的な解決策がでないので
あきらめムードで検索かけたのがあたった
「2 user 1 post model rails 」

外国の質問サイトで似たような状況の質問者がいてAnswerからたどって導き出した
Polymorphicという単語。

http://o.inchiki.jp/obbr/149
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
↑参考

解決フロー

1、Micropostにあったforeign_keyつきの:user、:restaurant カラムを削除

class DropMicropostTable < ActiveRecord::Migration[5.1]
  def change
    remove_reference :microposts, :user, index: true
    remove_foreign_key :microposts , :user
    remove_reference :microposts, :restaurant , index: true
    remove_foreign_key :microposts , :restaurant
  end
end

2、○○_idと_typeを追加、複合インデックスも追加

class AddPolymorphicId < ActiveRecord::Migration[5.1]
  def change
    add_column :microposts , :account_id , :integer
    add_column :microposts , :account_type , :string
    
    add_index :microposts, [:account_id , :account_type]
  end
end

上の記事で○○は何でもいいとのことを記述してありました、今回は複数の人のアカウントが肝なので参照する名称としてaccountを使用

3、モデルのリレーションを整理

class User < ApplicationRecord
    has_many :likes , dependent: :destroy
    has_many :favorites , dependent: :destroy
    has_many :comments , dependent: :destroy
    has_many :micropost, as: :account , dependent: :destroy
end

class Restaurant < ApplicationRecord
    has_many :microposts, as: :account  , dependent: :destroy
end

class Micropost < ApplicationRecord
  
  #写真に関するpresense validation かけるか悩み中
  has_many :favorites , dependent: :destroy
  has_many :comments , dependent: :destroy
  has_many :likes , dependent: :destroy
  belongs_to :account , polymorphic: true 
end

4 ,コンソールで打込んでみた


[5] pry(main)> u1=User.first
  User Load (0.4ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User:0x000000060dad80
 id: 1,
 name: "Example User",
 email: "example@railstutorial.org",
 created_at: Sat, 17 Feb 2018 15:39:48 UTC +00:00,
 updated_at: Sat, 17 Feb 2018 15:39:48 UTC +00:00,
 password_digest: "$2a$10$./rJiyJ8baCmPxIUP3hjH.B.yK6Z8JFEcQeG/vIzVl9Tqq4PbA.GW",
 remember_digest: nil,
 admin: true,
 activation_digest: "$2a$10$Xcu3AAnrVjuY004YkGzkv.dNIg9/XKFDhSB1vY3C5z3vT5OvWUauO",
 activated: true,
 activated_at: Sat, 17 Feb 2018 15:39:48 UTC +00:00,
 reset_digest: nil,
 reset_sent_at: nil>
[8] pry(main)> m1=u1.micropost.build(content:"yeahhhhh")
=> #<Micropost:0x00000005e957c8 id: nil, content: "yeahhhhh", created_at: nil, updated_at: nil, picture: nil, account_id: 1, account_type: "User">
[9] pry(main)> m1.save
   (0.1ms)  begin transaction
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  SQL (0.5ms)  INSERT INTO "microposts" ("content", "created_at", "updated_at", "account_id", "account_type") VALUES (?, ?, ?, ?, ?)  [["content", "yeahhhhh"], ["created_at", "2018-02-17 16:03:15.240943"], ["updated_at", "2018-02-17 16:03:15.240943"], ["account_id", 1], ["account_type", "User"]]
   (10.5ms)  commit transaction
=> true
[10] pry(main)> r1=Restaurant.first
  Restaurant Load (0.2ms)  SELECT  "restaurants".* FROM "restaurants" ORDER BY "restaurants"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> nil
[11] pry(main)> r1=Restaurant.first
  Restaurant Load (0.2ms)  SELECT  "restaurants".* FROM "restaurants" ORDER BY "restaurants"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Restaurant:0x00000005b35240
 id: 1,
 restaurant_name: "やきにくや",
 restaurant_number: "kiwamiya",
 email: "kkkkk@gmail.com",
 phone: "",
 password_digest: "$2a$10$Y2RodL1/imbT5AEHMz8/4.X0xVEhfeqSofCsz8yUT7cw7kGRPp0He",
 remember_digest: nil,
 admin: false,
 activation_digest: nil,
 activated: false,
 activated_at: nil,
 created_at: Sat, 17 Feb 2018 16:04:42 UTC +00:00,
 updated_at: Sat, 17 Feb 2018 16:04:42 UTC +00:00>
[13] pry(main)> r1.microposts.build(content:"yaehhhhh")
=> #<Micropost:0x000000059b3070 id: nil, content: "yaehhhhh", created_at: nil, updated_at: nil, picture: nil, account_id: 1, account_type: "Restaurant">
[14] pry(main)> r1.save
   (0.2ms)  begin transaction
  Restaurant Load (0.3ms)  SELECT  "restaurants".* FROM "restaurants" WHERE "restaurants"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
  Restaurant Exists (0.2ms)  SELECT  1 AS one FROM "restaurants" WHERE LOWER("restaurants"."email") = LOWER(?) AND ("restaurants"."id" != ?) LIMIT ?  [["email", "kkkkk@gmail.com"], ["id", 1], ["LIMIT", 1]]
  SQL (0.4ms)  INSERT INTO "microposts" ("content", "created_at", "updated_at", "account_id", "account_type") VALUES (?, ?, ?, ?, ?)  [["content", "yaehhhhh"], ["created_at", "2018-02-17 16:05:27.475902"], ["updated_at", "2018-02-17 16:05:27.475902"], ["account_id", 1], ["account_type", "Restaurant"]]
   (12.0ms)  commit transaction
=> true
[15] pry(main)> Micropost.all
  Micropost Load (0.2ms)  SELECT "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC
=> [#<Micropost:0x0000000581dd50
  id: 2,
  content: "yaehhhhh",
  created_at: Sat, 17 Feb 2018 16:05:27 UTC +00:00,
  updated_at: Sat, 17 Feb 2018 16:05:27 UTC +00:00,
  picture: nil,
  account_id: 1,
  account_type: "Restaurant">,
 #<Micropost:0x0000000581dbe8
  id: 1,
  content: "yeahhhhh",
  created_at: Sat, 17 Feb 2018 16:03:15 UTC +00:00,
  updated_at: Sat, 17 Feb 2018 16:03:15 UTC +00:00,
  picture: nil,
  account_id: 1,
  account_type: "User">]

無事Micropost_idで一括でまとめることができ、
ずっと疑問だった○○_typeの存在性もなるほど作成したクラス名が入るということで分かりやすい。
しかし○○_idの部分が一貫して1なんですがこれは何故なんでしょうか。どこかのミスによるものなのかそれとも仕様なのか。
よければ教えて頂ければ嬉しいです

と4時間はまりましたので。。。助けになれれば。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?