3

私はこのような3つのモデルを持っています:

class User < ActiveRecord::Base
  has_many :items
  has_many :other_itmes
end

class Item < ActiveRecord::Base
  belongs_to :user
  has_and_belongs_to_many :other_items

  validate :validate_other_item_ownership
  def validate_other_item_ownership
    if
      (user_ids = OtherItem.where(id: other_item_ids).pluck(:user_id).uniq).present? &&
        (user_ids.size > 1 || user_ids.first != user_id)
    then
      errors.add(:other_item_ids, 'Other Items must belong to same user as Item')
    end
  end
end

class OtherItem < ActiveRecord::Base
  belongs_to :user
  has_and_belongs_to_many :items

  validate :validate_item_ownership
  def validate_item_ownership
    if
      (user_ids = Item.where(id: item_ids).pluck(:user_id).uniq).present? &&
        (user_ids.size > 1 || user_ids.first != user_id)
    then
      errors.add(:item_ids, 'Items must belong to same user as Other Item')
    end
  end
end

そして、次のような 2 つのコントローラー:

class ItemsController < ApplicationController
  def update
    @item = Item.find params[:id]
    @item.other_item_ids = params[:item][:other_item_ids] #assignline
    @item.save!
  end
end

class OtherItemsController < ApplicationController
  def update
    @other_item = OtherItem.find params[:id]
    @other_item.item_ids = params[:other_item][:item_ids] #assignline
    @other_item.save!
  end
end

現在の問題は、関連付けを正しく発生させる#assignline呼び出しがまだ永続化されている間に、ActiveRecord が既に項目を に保存していることです。#save!ActiveRecord::RecordInvalid

私は、ユーザーが自分が所有しているアイテムにのみリンクできるようにしたいと考えています。

4

1 に答える 1

4

素晴らしい質問です!私も良い答えを持っています;)

( TLDR )を捨てhas_and_belongs_to_manyて、結合テーブルの上にモデルを作成し、結合モデルに検証ロジックを入れて、 を使用しますhas_many :through

実証するために、Artist と Song の関係を考えてみましょう。どちらも User に属しています。結合モデルを使用すると、has_many :through次のモデル クラスが作成されます。

class Artist
  belongs_to :user
  has_many :artist_songs
  has_many :songs, through: :artist_songs
end

class Song
  belongs_to :user
  has_many :artist_songs
  has_many :artists, through: :artist_songs
end

class ArtistSong
  belongs_to :artist
  belongs_to :song
end

これにより、検証ロジックが大幅にクリーンアップされます。1 か所に追加するだけです。

class ArtistSong
  #...
  validate :ownership
private
  def ownership
    unless artist.user_id == song.user_id
      errors[:base] << 'artist and song must belong to same user'
    end
  end
end

次に、コントローラーで:

class ArtistsController
  def update
    @artist = current_user.artists.find params[:id]
    @artist.update artist_params
  end
private
  def artist_params
    params.require(:artist).permit(:name, song_ids: [])
  end
end

注: Rails はsave!結合モデルを実行しているようです。これは、検証が失敗した場合 (ArtistSongではなくArtistで)、 false を返す代わりに例外が発生することを意味します。これは悪意のあるユーザーにのみ発生するはずなので、心配する必要はありません。

HABTM はめったに使用しません。結合テーブルのモデルを持つことで、柔軟性が大幅に向上します。たとえば、位置フィールドを追加して、次のようにすることができます。

class Artist
  #...
  has_many :artist_songs, order: -> {'position asc'}
  #...
end
于 2013-11-22T22:18:04.537 に答える