views:

386

answers:

4

I've got a create action that is attempting to create a Rating and a Programme in one go:

  def create
    @rating = current_user.ratings.create(params[:rating])
    @rating.create_programme(params[:programme])
    redirect_to ratings_path
  end

In this code, a rating belongs to both a user and a programme and a user

  has_many :ratings
  has_many :programmes, :through => :ratings

and a programme

  has_many :users, :through => :ratings
  has_many :ratings

When I call the create action above in the RatingsController, the Programme is not being saved as being owned by the @rating for some reason. So if I call for instance:

rating.programme.channel

on a rating in a view, it tells me that the programme is a nil object. However, the programme has been saved fine - it's just the association that hasn't been saved. I'm sure it's a pretty basic thing here, but I can't figure it out. Can someone point me in the right direction?

thanks, a

A: 

Your code looks right. Try this. Immediately after hitting this create action go into you script/console and see whats going on. Type:

Rating.last

It should return the rating that was just created. Look for user_id and programme_id in the output and see if they are set. If they are then you have a bug elsewhere. Perhaps you are not looking at the rating you think you are or something.

Squeegy
Thanks Squuegy. tried a few different tacks here and finally got the code working below. Woudl appreciate your comments on it to let me know if it's good practice etc.
The Pied Pipes
A: 

Well you are attempting to create the parent from the child (ratings belongs to Programme, right?) . I don't think that works.

Programme.ratings.create would work.

Jeremy
So can I put both the params[:programme] and the params[:rating] into the create method call in Programme.rating.create?
The Pied Pipes
No, you'd need to create the Programme object first, then you can use the create on its child.
Jeremy
A: 

I got the create action working ok with the following. Would appreciate it if anyone could et me know whether there's a more elegant way to get this to work :)

  def create
    @programme = Programme.find_or_create_by_title(params[:programme])
    @programme.save
    @rating = current_user.ratings.create!(params[:rating].merge(:programme_id => @programme.id))
    redirect_to ratings_path
  end

andy

The Pied Pipes
I added an answer to this to the thread since I ran out of room quickly.
Squeegy
+2  A: 

In response to your question to help make the code cleaner:

def create
  @rating = Rating.new(params[:rating])
  @rating.user      = curent_user
  @rating.programme = Programme.find_or_create_by_title(params[:programme])
  @rating.save

  redirect_to ratings_path
end

First of all you call find_or_create for a title and then save it, but the record will aleady be created there so the save doesn't do anything at all. Second, while the association proxies are cool for easily creating ralated objects, they can get quite hairy in a more comlex relationship like you have here and make the code harder to read.

So instead of using the association proxies to create records, direct assignment would be better. Its easier to tell at a glance exactly what is happening and where the information is coming from, and doesn't need an ugly merge call in there. It's a little longer, but I thinks its far more easily understood at a glance.

Lastly, you probably don't need @programme as a stand alone instance variable since you would have easy access to that object from @rating.programme in your views. In most cases, it's best to pass as few instance variable as possible, especially when the objects have an easily accessible direct relationship. Its especially true in this case becuase you aren't rendering a template at all.

Squeegy
This is great. Thanks a lot for taking the time to explain this Squeegy. Big help!
The Pied Pipes