ruby on rails - best way to validate ownership with HMABTM -
i have 3 models this:
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) errors.add(:other_item_ids, 'other items must belong same user 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) errors.add(:item_ids, 'items must belong same user other item') end end end
and 2 controllers this:
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
the problem activerecord saves items on #assignline
, while call #save!
correctly raises activerecord::recordinvalid
association still persisted.
i want user able link items each other owned.
excellent question! i've got answer ;)
(tldr) ditch has_and_belongs_to_many
, create model on top of join table, put validation logic in join model, , use has_many :through
.
to demonstrate, let's consider relationship between artist , song, both belonging user. join model , has_many :through
we'll have these model classes:
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
this clean validation logic, add in 1 place:
class artistsong #... validate :ownership private def ownership unless artist.user_id == song.user_id errors[:base] << 'artist , song must belong same user' end end end
then in controller:
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
note: looks rails doing save!
on join model. means if validation fails (just on artistsong
, not artist
) raise exception instead of return false. should ever happen malicious users, no worries.
i use habtm. having model join table gives more flexibility. example, add position field , like:
class artist #... has_many :artist_songs, order: -> {'position asc'} #... end
Comments
Post a Comment