使用HMABTM验证所有权的最佳方法

时间:2013-07-18 09:07:55

标签: ruby-on-rails validation activerecord has-and-belongs-to-many

我有三个这样的模型:

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

两个控制器是这样的:

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

现在的问题是,ActiveRecord已将项目保存在#assignline上,而对#save!的调用正确引发ActiveRecord::RecordInvalid该关联仍然存在。

我希望用户只能将他拥有的物品链接到彼此。

1 个答案:

答案 0 :(得分:4)

很棒的问题!我也有一个很好的答案;)

TLDR )Ditch has_and_belongs_to_many,在连接表顶部创建模型,将验证逻辑放入连接模型,并使用has_many :through

为了演示,让我们考虑艺术家和歌曲之间的关系,两者都属于用户。使用连接模型和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

这将显着清理您的验证逻辑,只需将其添加到一个地方:

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