Martin
Martin

Reputation: 11336

Using find_or_create_by to create an object that includes a nested object

I imagine a query like this.

Movie.find_or_create_by_title(title: 'foo').photos.find_or_create_by_name(name: 'bar')

The given query will create the Photo object but will not consider its parent Movie.

 => #<Photo id: 3, movie_id: nil …

Is there any way I can pass the movie to it?

Update: The reason I try to "save both at once" is because I have a validation in place that requires a Movie to have at least one photo. See: https://stackoverflow.com/a/12962317/471313

Upvotes: 5

Views: 2968

Answers (2)

adimitri
adimitri

Reputation: 1296

I'm using the updated Rails 3.2 syntax for find_or_create_by since it will be deprecated in Rails 4.0. The important thing is to have the accepts_nested_attributes_for in your Movie model like so:

class Movie < ActiveRecord::Base
  has_many :photos
  accepts_nested_attributes_for :photos
end

This allows you to specify a key in your model attributes with the form <relation-name>_attributes, in your case photo_attributes.

@movie = Movie.where(:title => 'foo').first_or_create :director => 'Steven Speilberg',
                                                      :photos_attributes => [{
                                                        :caption => "Thrilling!"
                                                      }]
@movie.save

After this, you just save the parent model, and that will automatically save the child models, again in your case photos. It is necessary to save the parent first because the child needs to know what id to put into the child record. So after @movie is saved, it will then place it's id in the movie_id field on the photos record. It can't save the child before the parent because then it doesn't know what id to use.

If you're using a Rails version before 3.2, it will look something like this:

@movie = Movie.find_or_create_by_title "W00t!", :director => 'Steven Speilberg',
                                                :photos_attributes => [{
                                                  :caption => "Thrilling!"
                                                }]

Upvotes: 4

varatis
varatis

Reputation: 14740

movie = Movie.where(title: 'foo').first
if movie.nil?
  movie = Movie.new(title: 'foo')
  movie.photos.build(name: 'bar')
  movie.save
else
  movie.photos.create(name: 'bar')
end

Upvotes: 0

Related Questions