3

I have a has_many through association with an attribute and some validations on the "join model". When I try to do something like @user.projects << @project and the association has already been created (thus the uniqueness validation fails), an exception is raised instead of the error being added to the validation errors.

class User 
  has_many :project_users
  has_many :projects, :through => :project_users

class Project
  has_many :project_users
  has_many :users, :through => :project_users

class ProjectUser
  belongs_to :user
  belongs_to :project


# ...
if @user.projects << @project
  redirect_to 'somewhere'
else
  render :new
end

How can I create the association like I would with the << method, but calling save instead of save! so that I can show the validation errors on my form instead of using a rescue to catch this and handle it appropriately?

coreyward
  • 77,547
  • 20
  • 137
  • 166

3 Answers3

0

I don't think you can. From the API:

collection<<(object, …) Adds one or more objects to the collection by setting their foreign keys to the collection’s primary key. Note that this operation instantly fires update sql without waiting for the save or update call on the parent object.

and

If saving fails while replacing the collection (via association=), an ActiveRecord::RecordNotSaved exception is raised and the assignment is cancelled.

A workaround might look like this:

if @user.projects.exists? @project
  @user.errors.add(:project, "is already assigned to this user") # or something to that effect
  render :new
else 
  @user.projects << @projects
  redirect_to 'somewhere'
end

That would allow you to catch the failure where the association already exists. Of course, if other validations on the association could be failing, you still need to catch the exception, so it might not be terribly helpful.

Thilo
  • 17,565
  • 5
  • 68
  • 84
  • Hmm. I saw that in the API docs, but it doesn't actually refer to what it does with the associated model. Using `:through`, it does actually trigger validations and callbacks like normal on the "join model". The bit about raising an `ActiveRecord::RecordNotSaved` error isn't relevant because it's referring to collection replacement/assignment with `association =`. – coreyward Oct 03 '11 at 02:51
0

Maybe you could try to add an validation to your project model, like:

validates :user_id, :uniqueness => {:scope => :user_id}, :on => :create

Not sure if that helps to avoid the save! method..

BvuRVKyUVlViVIc7
  • 11,641
  • 9
  • 59
  • 111
  • The `project` model doesn't have a `belongs_to :user`, thus there is no `user_id` attribute to validate against. – coreyward Oct 03 '11 at 15:00
0

Try declaring the associations as

has_many :projects, :through => :project_users, :uniq => true

Checkout out section 4.3.2.21 in http://guides.rubyonrails.org/association_basics.html.

coreyward
  • 77,547
  • 20
  • 137
  • 166
pduey
  • 3,706
  • 2
  • 23
  • 31
  • Good try, but this doesn't actually enforce uniqueness, at all. Quoting from the referenced guide section: "In the above case there are still two readings. However person.posts shows only one post because the collection loads only unique records." Tried it out anyways and it actually even loads multiple identical records, so no effect, at all. – coreyward Oct 03 '11 at 14:59