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