Question

I have this models relations

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

When I add some user as a trip member, like with this code:

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

I don't want to have duplicates in the trip_memberships table, but I want to follow the "Tell, don't ask" principle. I don't want to check if a given record exists.

I added this validations to the TripMembership model

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

I expected the << or concat method to return false when I try to add a record that can't be saved, like in the has many relations, but seems that with through relations it don't works that way.

trip.members << john # => ActiveRecord::RecordInvalid: Validation failed: Trip has already been taken, User has already been taken

Anyone knows an elegant way to archieve this? I think that the api should be used like this.

if @trip.members << new_member
   flash[:success] = "new member added"
else
   flash[:error] = "can't add this member to trip"
end
Was it helpful?

Solution 2

I've just had an idea.

Since I've just discovered that the standard behavior of rails is to raise an exception if the user is not a new record, and I must not change the default behavior of a well-known methods, I'll extend my association with a custom method called add_member that just alias calls << inside a begin/rescue.

has_many :members, through: :trip_memberships, source: :user, uniq: true do
  def add(*records)
    self.<< records
  rescue ActiveRecord::RecordInvalid
    false
  end
end

I have my own method that works as expected without changing the rails methods.

OTHER TIPS

You can try like this:

 if @trip.members.include?(new_member)
    flash[:error] = "Member already exists"
 else
    @trip.members << new_member
    flash[:success] = "new member added"
 end

Edit

has_many :members, through: :trip_memberships, source: :user do
  def <<(member)
    if self.include?(member)
      false
    else
      super(Array(member)-self)
    end
  end
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top