我有三个这样的模型:
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
该关联仍然存在。
我希望用户只能将他拥有的物品链接到彼此。
答案 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