多対多の関連付け(modelの書き方が正しいか)

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,304

innjera

score 124

ユーザー(出品者)がサービス(商品)を出品し、別のユーザー(購入者)がそれを購入できるというケースを想定した場合、以下の考え方で正しいでしょうか?誤りがあればご指摘頂けますと幸甚です。

前提

・ユーザー(出品者)とサービス (商品): 1対多の関連付け
・サービス(商品)とユーザー(購入者) : 多対多の関連付け *1つのサービス(商品)に複数のユーザー(購入者)が購入できる前提。
・ユーザーは出品者にも購入者にもなれる : 出品者と購入者は同じmodel

モデル(正しいでしょうか?)

・上記前提を元にすると、必要となるmodelUserProductEntry(productとそれを買うuser(購入者))を多対多の関連付けで使用する中間テーブル)の3つ

・すなわち、以下のイメージ
user (1)<-> (多)product(1) <-> (多)entry(多) <->(1) user

各モデルのテーブル(migration file) *単純化してます

User

#User
class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :email, null: false  # メールアドレス
      t.string :family_name  # 姓
      t.string :given_name   # 名
      t.string :address  # 住所
      t.text :user_profile   # プロフィール
      t.string :hashed_password   # パスワード

      t.timestamps
    end
  end
end

#Product
class CreateProducts < ActiveRecord::Migration[5.0]
  def change
    create_table :products do |t|
      t.references :user   #外部キー
      t.integer :price   #価格
      t.string :title   #タイトル
      t.datetime :posted_at #商品投稿日
      t.binary :data #写真
      t.timestamps null: false
    end
  end
end

#Entry
class CreateEntries < ActiveRecord::Migration[5.0]
  def change
    create_table :entries do |t|
      t.references :product, null: false
      t.references :user, null: false
      t.timestamps
    end
  end
end

各モデルの書き方

#Userモデル
class User < ApplicationRecord
  has_many :products, dependent: :destroy
  has_many :applied_products, through: :entries, dependent: :destroy, source: :product
  has_many :entries, dependent: :destroy
end

#Productモデル
class Product < ApplicationRecord
  belongs_to :user
  has_many :entries, dependent: :destroy
  has_many :applicants, through: :entries, source: :user
end

#Entryモデル
class Entry < ApplicationRecord
belongs_to :applied_products, class_name: "Product", foreign_key: "product_id"
belongs_to :applicants, class_name: "User", foreign_key: "user_id"
end
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • IPU

    2017/01/18 12:14

    modelの紐付けの前に、どんなテーブルで作る予定かを書かないと伝わりづらいと思います。

    キャンセル

  • innjera

    2017/01/18 12:34

    情報かけており申し訳ありません。今しがた編集致しました。宜しくお願い致します。

    キャンセル

回答 2

+2

同じ種類の「商品」が複数あった場合、それらの商品の集まりを"1つの物"としてとらえる考え方と、個々の商品をそれぞれ"1つの物"としてとらえる考え方がありますが、個々の商品を"1つの物"として扱う方が考えやすいです。
というのは、複数の商品をひとまとめで考えると、購入者ごとの購入数の合計が商品数を超えないように常に状態を管理する必要があるので、複雑になるからです。

個々の品物をそれぞれ"1つの物"とするなら、
・ユーザー(出品者)と商品 : 1対多の関連付け (ある品物を出品するのは一人だけ、但し一人が複数の品物を出品できる)
・商品とユーザー(購入者) : 多対1の関連付け (ある品物を購入するのは一人だけ、但し一人が複数の品物を購入できる)
になります。

ある人は出品者にも購入者にもなれる。
しかし売買に関する役割(role)が異なりますから、出品者と購入者のモデルは別々にしたほうが素直だと思います。
そうすれば1つの商品に関した関係が、下記のように単純なものになるからです。
(購入者が居ない状態では、buyerが存在しない関係になります)

seller(出品者) - item(個別の商品) - buyer(購入者)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/17 17:06

    コメント有難う御座います!
    初歩的な質問で恐縮ですが、「出品者と購入者のモデルは別々にしたほうが素直」という点が、良く理解できませんでした。
    Aというユーザーが、出品もするし購入もする場合に、モデルを分けてしまうと、(例えばですが)ユーザー情報(氏名やパスワード等)をもつデーターベースを作成する為に、①seller modelのmigrationファイルと②buyer modelのmigrationの2つを作成(ほぼ同一のcolumnを持つmodel/data baseを2つ作る)ことにならないでしょうか?

    キャンセル

checkベストアンサー

0

・上記前提を元にすると、必要となるmodelはUserとProductとEntry(productとそれを買うuser(購入者))を多対多の関連付けで使用する中間テーブル)の3つ

やりたい事に対して、この構成の作り方は、正しいと思います。
1番シンプルですね。

ただし、テーブルを見ると、Entryのカラムが足りないですね。
このままだと、ユーザと商品の紐付けはわかりますが、ユーザの立場(出品or購入)がわかりません。
Entryテーブルに、出品or購入がわかるようなflag用のカラムを増やしてあげると、やりたい事が出来る構成になるはずです。

本題のmodelの紐付け部分ですが、

・すなわち、以下のイメージ 
user (1)<-> (多)product(1) <-> (多)entry(多) <->(1) user

ここも少し違いますね。
書き方を合わせて書くと、
product(1) <-> (多)entry(多) <->(1) user
だけですね。

では、これをどうコード化するかと言うと、

# app/models/user.rb
class User < ActiveRecord::Base
  has_many :entries
  has_many :products, through: :entries
end

# app/models/entry.rb
class Entry < ActiveRecord::Base
  belongs_to :user
  belongs_to :product
end

# app/models/product.rb
class Product < ActiveRecord::Base
  has_many :entries
  has_many :users, through: :entries
end


こうですね。
以下のサイトがほとんど同じことをやっているので、参考に読んでみてください。
Rails4で多対多のリレーションをモデルに実装する

あと、余談ですが、dependent: :destroyは今回のケースでは使わない方が良いと思いますよ。
ユーザを削除したりしても、関連する商品を消されては困るはずなので。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/01/19 07:45

    種々アドバイス有難うございます!よく理解できました。

    キャンセル

  • 2017/01/19 08:43

    いただいたアドバイスを踏まえ、最終的にこうしてみました。
    #User model
    has_many :entries_of_adviser, :class_name => 'Entry', :foreign_key => 'adviser_id'
    has_many :entries_of_applicant, :class_name => 'Entry', :foreign_key => 'applicant_id'
    has_many :products_of_adviser, :through => :entries_of_adviser, :source => 'product'
    has_many :products_of_applicant, :through => :entries_of_applicant, :source => 'product'

    #Product モデル
    has_many :advisers, :through => :entries
    has_many :applicants, :through => :entries
    has_many :entries

    #Entryモデル
    belongs_to :adviser, :class_name => 'User'
    belongs_to :applicant, :class_name => 'User'
    belongs_to :product


    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.22%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる