我有这个模型关系
class User < ActiveRecord::Base
has_many :trip_memberships, dependent: :destroy
has_many :trips, through: :trip_memberships, uniq: true
end
class Trip < ActiveRecord::Base
has_many :trip_memberships, dependent: :destroy
has_many :members, through: :trip_memberships, source: :user, uniq: true
end
class TripMembership < ActiveRecord::Base
belongs_to :trip
belongs_to :user
end
当我添加一些用户作为trip成员时,如下代码所示:
trip = Trip.last
john = User.last
trip.members # => [] Empty right now
trip.members << john
trip.members # => [john] Contains John
trip.members << john
trip.members # => [john] Contains only John, but...
TripMembership.all # => [TripMembership(trip, john), TripMembership(trip, john)]
# There is 2 memberships, even the accessor methods only show one
# member because of the :uniq option
我不想在trip_memberships
表中有重复,但我想遵循"告诉,不要问"的原则。我不想检查给定的记录是否存在。
我将这个验证添加到TripMembership模型
class TripMembership < ActiveRecord::Base
belongs_to :trip
belongs_to :user
validates_presence_of :trip_id, :user_id
validates_uniqueness_of :trip_id, scope: [:user_id]
validates_uniqueness_of :user_id, scope: [:trip_id]
end
我期望<<
或concat
方法返回false时,我试图添加一个记录,不能被保存,就像在有许多关系,但似乎与through
关系,它不是这样工作的。
trip.members << john # => ActiveRecord::RecordInvalid: Validation failed: Trip has already been taken, User has already been taken
有谁知道一种优雅的存档方式吗?我认为api应该这样使用。
if @trip.members << new_member
flash[:success] = "new member added"
else
flash[:error] = "can't add this member to trip"
end
你可以这样尝试:
if @trip.members.include?(new_member)
flash[:error] = "Member already exists"
else
@trip.members << new_member
flash[:success] = "new member added"
end
编辑
has_many :members, through: :trip_memberships, source: :user do
def <<(member)
if self.include?(member)
false
else
super(Array(member)-self)
end
end
end
我有个主意。
由于我刚刚发现rails的标准行为是在用户不是新记录时引发异常,并且我不能更改已知方法的默认行为,因此我将扩展我与一个名为add_member
的自定义方法的关联,该方法只是在begin/rescue中别名调用<<
。
has_many :members, through: :trip_memberships, source: :user, uniq: true do
def add(*records)
self.<< records
rescue ActiveRecord::RecordInvalid
false
end
end
我有我自己的方法,不需要改变rails方法就能按预期工作。