ケース
Postモデル(投稿)のbelongs_to(従属)にUserモデル(利用者)とRestaurantモデル(利用者)といった複数を指定したいが検索しても解決方が分からない。方むけ
事の発端
RailsTutorialからオリジナルのお店探索アプリへと派生していった段階で、
UserモデルとRestaurantモデルの投稿を共通のMicropostモデル(@micropost_id)にして、タイムラインフィードで投稿日時オーダーで表示したい。
今回の肝がオプションなどで柔軟性のある”has_many”側ではなく”belongs_to”側であることで
「リレーション belongs_to 複数 rails」
などの検索ワードでも解決にむかう記事がでみつからなかった為、ここに備忘録兼検索用で残します。
見つからなかったpolymorphic(ここは完全に自分の記録です)
自分で試行錯誤して行ったのは
micropostモデル内でif分岐で従属するコード
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時間はまりましたので。。。助けになれれば。