6

I'm not exactly sure what my problem is, so this question may require some more clarification, but here's what seems to be most relevant:

I have a has_many :through and the join model has some fields that aren't foreign keys. When I build the models up and try to save I get a validation error on the non-foreign key fields from the join model.

My files look like:

Person.rb

  has_many :wedding_assignments, :dependent => :destroy
  has_many :weddings, :through=>:wedding_assignments
  accepts_nested_attributes_for :weddings
  accepts_nested_attributes_for :wedding_assignments

Wedding.rb

  has_many :wedding_assignments, :dependent => :destroy
  has_many :people, :through=>:wedding_assignments
  accepts_nested_attributes_for :people
  accepts_nested_attributes_for :wedding_assignments

WeddingAssignment.rb

  belongs_to :person
  belongs_to :wedding
  validates_presence_of :role, :person, :wedding

(role is a string)

people_controller.rb

  def new
    @person = Person.new

    1.times do
      wedding = @person.weddings.build
      1.times do
        assignment = wedding.wedding_assignments.build
        assignment.person = @person
        assignment.wedding = wedding
      end
    end
  end

  def create
    @person = Person.new(params[:person])
    @person.weddings.each do |wedding|
      wedding.wedding_assignments.each do |assignment|
        assignment.person = @person  #i don't think I should need to set person and wedding manually, but I get a validation error if I don't
        assignment.wedding = wedding
      end
    end
 end

the params that come back look like:

{"first_name"=>"", "last_name"=>"", "weddings_attributes"=>{"0"=>{"wedding_assignments_attributes"=>{"0"=>{"role"=>"Bride's Maid", "budget"=>""}}, "date"=>"", "ceremony_notes"=>""}}}

And the exact error is:

ActiveRecord::RecordInvalid in PeopleController#create
Validation failed: Role can't be blank

Which is clearly not correct, since you can see it in params[]

What am I doing wrong?

This is rails 3.0.0

SooDesuNe
  • 9,880
  • 10
  • 57
  • 91
  • 2
    I am curious that what's the use of those `1.times`? – PeterWong Oct 14 '10 at 01:40
  • I do that to make it easy if I want to change it to 2.times or more later. – SooDesuNe Oct 14 '10 at 01:49
  • You are right, doing that can even let the user to choose adding 10 weddings in one go. :D – PeterWong Oct 14 '10 at 01:54
  • Your error say error on create_with_appointment method but it's not exist on your controller output. What is this method ? – shingara Oct 14 '10 at 16:04
  • sorry that was a typo, fixed now – SooDesuNe Oct 14 '10 at 16:52
  • Am I right to presume that you are building the person, wedding and wedding_assignment all in one form? – Maran Oct 14 '10 at 17:07
  • If you're not already aware, there's a security vulnerability in rails 3.0.0 with using `accepts_nested_attributes_for` you should upgrade to rails 3.0.1 (which fixes only this bug) see the [announcement](http://groups.google.com/group/rubyonrails-security/browse_thread/thread/f9f913d328dafe0c) – Jeremy Oct 17 '10 at 01:43
  • I'm not sure why the validation error you're getting is happening, but in regards to your comment about having to set `@person` manually, it's probably because `@person` hasn't been saved to the database yet, try using `@person = Person.create(params[:person])` instead, that might help. Not sure if it's related to the validation issue though. – Jeremy Oct 17 '10 at 01:46
  • 1
    Have you tried explicitly setting the value of the `role` attribute in your `create` method? You can even check to see if this will work by temporarily removing the validation condition on the `role` attribute. – andrewle Oct 22 '10 at 00:14
  • Yes I did that, I also did it in the console, the result is the same. Thanks though! – SooDesuNe Oct 22 '10 at 00:24

2 Answers2

1

Right, this is a bit of a guess, so apologies if I wind up wasting your time here...

It looks to me like in your create method, you're creating the 'wedding' relationship (which is only a 'pretend' relationship really, has it's using :through => :wedding_assignments), and then returning this. You're then asking rails to re-create these objects in your call to Person.new. My guess is that rails is getting confused by trying to create an object at the far side of a has_many :through without the intermediate object being present.

I would be tempted to restructure this a little (untested code!):

def new
  @person = Person.new
  @wedding = Wedding.new
  @wedding_assignment = WeddingAssignment.new
end

def create
  @person = Person.new(params[:person])
  @wedding = Wedding.new(params[:person])
  @assignment = WeddingAssignment.new(params[:wedding_assignment].merge({:person => @person}))
end

I've got a feeling this'll work until the last line. I suspect to get that to work you might need to use transactions:

def create
  @person = Person.new(params[:person])
  @wedding = Wedding.new(params[:person])
  ActiveRecord::Base.transaction do
    if @person.valid? && @wedding.valid?
      [@person,@wedding].each.save!
      @assignment = WeddingAssignment.new(params[:wedding_assignment].merge({:person => @person}))
      @assignment.save!
    end
  end
end

This ought to ensure that everything is created in the right order and IDs are available at the right times etc. Unfortunately though, it's a bit more complicated than your example, and does mean that you'll struggle to support multiple weddings.

Hope this helps, and doesn't wind up being a blind alley.

Paul Russell
  • 4,727
  • 1
  • 30
  • 28
  • By far the best answer yet. Doing it this way means I wont be able to generate the form fields cleanly (i.e. as in: http://railscasts.com/episodes/196-nested-model-form-part-1). Rails should be able to manage these associations. Over the weekend I re-wrote the code to build and run starting with the join model, WeddingAssignment, instead of Person, and it works fine. So I think you are on the right track about rails 'getting confused' – SooDesuNe Oct 18 '10 at 23:35
  • I have a similar issue I'm trying to solve by this question, but I'm getting a mass-assigment security error on person. Any ideas? – ctilley79 Jun 26 '12 at 05:45
0

Try changing "Person.new" to "Person.create", maybe creating the record in the db right away will help with the associations.

coder_tim
  • 1,710
  • 1
  • 12
  • 13
  • I definitely don't want them in the database right away. What happens if the user changes their mind and navigates away from the page? Won't I have an empty record if i use 'create'? – SooDesuNe Oct 18 '10 at 23:29