Associations (belongs_to, has_one, and has_many) questions

Greetings,

This is something that has completely stumped my development processes on my new application. The application helps members exchange feedback on their stories (for creative writers). With that in mind, I'm trying to create associations between the different tables:

class Member < ActiveRecord::Base   has_many :stories   has_many :critiques end

class Story < ActiveRecord::Base   has_many :critiques   belongs_to :member # foreign key => member_id end

class Critique < ActiveRecord::Base   belongs_to :story # foreign key => story_id   belongs_to :member # foreign key => member_id end

I'm not entirely sure, though I'm under the impression that as long as you set up the associations correct and add the correct columns (foreign keys), Rails will use its magic. For example, if I create a new story, then the member_id column would automatically be updated with the active member's id. Unfortunately, this doesn't happen in my application. Thus, I'm either doing something wrong (very possible), or Rails doesn't do this automatically.

Is the above code correct? If so, does Rails populate the foreign key column automatically? If so... what am I doing wrong?

My next question has to deal with what you can do with these associations. Lets say I wanted to display within my index.html.erb file a list of stories (links) and for each story, a link to each of their critiques. How would the code in my controller look? And how would I structure my view code?

I've read over a lot of the API documentation, the rails way, the agile development book, and many posts here. It's just not clicking. So, any help would be very much appreciated. Thank you for your time and assistance!

~Dustin Tigner

Greetings,

This is something that has completely stumped my development processes on my new application. The application helps members exchange feedback on their stories (for creative writers). With that in mind, I'm trying to create associations between the different tables:

class Member < ActiveRecord::Base has_many :stories has_many :critiques end

class Story < ActiveRecord::Base has_many :critiques belongs_to :member # foreign key => member_id end

class Critique < ActiveRecord::Base belongs_to :story # foreign key => story_id belongs_to :member # foreign key => member_id end

I'm not entirely sure, though I'm under the impression that as long as you set up the associations correct and add the correct columns (foreign keys), Rails will use its magic. For example, if I create a new story, then the member_id column would automatically be updated with the active member's id. Unfortunately, this doesn't happen in my application. Thus, I'm either doing something wrong (very possible), or Rails doesn't do this automatically.

Is the above code correct? If so, does Rails populate the foreign key column automatically? If so... what am I doing wrong?

Depends what you mean. If you just do Story.create then member_id
won't be set. if you do current_user.stories.create then it will be set to
current_user.id

My next question has to deal with what you can do with these associations. Lets say I wanted to display within my index.html.erb file a list of stories (links) and for each story, a link to each of their critiques. How would the code in my controller look? And how would I structure my view code?

well you'd fetch some stories, and probably stick them in @stories. Then you'd just iterate over @stories, and for each stories iterate
over @stories.critiques (I use iterate in a loose sense - you could
just as easily be rendering a partial.

Fred

Thanks Frederick for the quick reply!

Unfortunately, I'm still very new to Rails and am unsure how to use your advice. My application is setup utilizing the basic scaffolding, which I've changed a lot. Thus, my create method looks like:

def create   @story = Story.new(params[:story])

  if @story.save     flash[:notice] = 'Story was successfully created.'     redirect_to manage_stories_path   else     render :action => "new"   end end

I commented everything out and tried to use: current_user.stories.create, though Rails doesn't understand what 'current_user' is. Thus, I thought perhaps you meant 'current_member' as I don't have a user class. I tried that with no success. Finally, I simply tried: member.stories.create, which also doesn't work (is not understood by Rails).

If it's not too much to ask, perhaps a working example of the create method? Right now the method looks like: (note, there is nothing checking whether or not the save was successful. I can add that back in once I understand your original suggestion).

def create   current_member.stories.create(params[:story])   redirect_to manage_stories_path end

As for my second question, here is an example of what I am currently doing within my controller to find the content:

def show   @story = Story.find(params[:id])   @critique = Critique.find(:all, :conditions => ['story_id = ?', @story.id])   @member = Member.find(:first, :conditions => ['id = ?', @story.member_id]) end

I'm used to doing everything manually (coming from PHP), though read that there is a better way to do the above through associations. I just can't seem to get anything else to work. Concerning the view, I believe I understand what you mean and will give it a go once I have the controller figured out.

I apologize for being so inexperienced here. This is one thing that I'm really struggling with. Thank you again for your time and help on this!

~Dustin Tigner

Thanks Frederick for the quick reply!

Unfortunately, I'm still very new to Rails and am unsure how to use your advice. My application is setup utilizing the basic scaffolding, which I've changed a lot. Thus, my create method looks like:

def create @story = Story.new(params[:story])

if @story.save    flash[:notice] = 'Story was successfully created.'    redirect_to manage_stories_path else    render :action => "new" end end

I commented everything out and tried to use: current_user.stories.create, though Rails doesn't understand what 'current_user' is. Thus, I thought perhaps you meant 'current_member'

I was assuming that you already had a function to return the
current_user or member. Normally whatever you use to manage logging in
to your app will provide this.

def show @story = Story.find(params[:id]) @critique = Critique.find(:all, :conditions => ['story_id = ?', @story.id]) @member = Member.find(:first, :conditions => ['id = ?', @story.member_id]) end

What you've got is the same as @story = Story.find(params[:id]) @critiques = @story.critiques @member = @story.member

I probably wouldn't bother with @critiques and @member in this case.

In your view you'd probably do something like

<%= render :partial => 'critique', :collection => @story.critiques %> You should have a _critique.html.erb file which contains the markup
for a single critique (when that partial is rendered the current
critique will be available as critique)

Fred

Hello Frederick,

Thank you very much. When I was waiting for a reply, I kept playing with the code and found a different part of the API documentation that I had missed. I was able to get the content to come up correctly (by mere chance) - very exciting. :o)

Your explanation above really solidified my short learning experience. I see now that I don't need all those extra instance variables, as I can do something like:

<% for critique in story.critiques -%> ... <% end -%>

I was assuming that you already had a function to return the current_user or member. Normally whatever you use to manage logging in to your app will provide this.

I see. That makes more sense now. My log in / out is based off the Agile Web Development with Rails 3rd Edition (still in beta). I'll have to play around with this and see how I can make it work.

Thank you again for all your assistance. My progress is moving smoothly forward once again. :o)

~Dustin Tigner

Greetings - again!

I've hit that wall again and hope this is a quick and easy problem to solve. When I'm within my view code, something like: story.member.username, would work just fine. However, within my controller, it doesn't seem to recognize what member or critiques are. Thus, the following code doesn't work:

What you've got is the same as @story = Story.find(params[:id]) @critiques = @story.critiques @member = @story.member

Furthermore, I would like to find the name of the member who posted feedback. I have the following code, though it doesn't work:

<% for story in @stories -%>   <% for critique in story.critiques -%>     <%= critique.member.username %>   <% end -%> <% end -%>

The member class has_many critiques and critiques belongs_to the member class. Just as I would be able to do: story.member.username, I thought I would be able to do story.critique.member.username. It seems that I am wrong. ><

Finally, I was able to get current_user.critiques.create(params[:critique]) to work. I created a new method within the application.rb and it successfully assigns my foreign key. However, it only does so for the member_id foreign key, and not the story_id. After a long while of trying to get both to be set, I surrendered and did the following:

  @critique = Critique.new(params[:critique])   @critique.story_id = @story.id   @critique.member_id = current_user

Is this 'okay' code, or should I be doing something else?

Sorry to keep bothering you guys (mainly Frederick), I just can't seem to find answers anywhere on this particular subject. I truly try not to post questions as figuring something out has the added bonus of learning more and remembering the content. If you know of any specific article, tutorial, or section in a book, peepcode, or anything that I should study up on, please let me know (I have all of them...).

Thanks for your help!

~Dustin Tigner

Hi, check the variables calls :

> What you've got is the same as > @story = Story.find(params[:id]) > @critiques = @story.critiques > @member = @story.member

Furthermore, I would like to find the name of the member who posted feedback. I have the following code, though it doesn't work:

<% for story in @stories -%>

@stories should be declared in your controller : note that it should be an Array (probably resulting from a find), otherwise the plural is not relevant.

Thanks Tongman for your reply.

@stories should be declared in your controller : note that it should be an Array (probably resulting from a find), otherwise the plural is not relevant.

I apologize for not posting my actual controller code. This is what I was using - note that my instance variable is plural as I do expect to receive multiple story objects within an array.

def index   @stories = Story.find :all   @critiques = @stories.critiques end

The above code renders an error: undefined method `critiques' for #<Array:0x231723c>, which now makes me think that if the @stories variable was not an array, this would work. That's the only difference between my view and controller. Within my view I'm working with each individual story and looping through each critique. I 'believe' this new found logic is correct, and where that answers one question, how would I go about pulling the name of the member who provided the critique?

Thanks for the replies. I hope to get down to the bottom of this so I can keep progressing. :slight_smile: Thanks again!

~Dustin Tigner