Forum: Ruby on Rails HABTM Reading Rows From Two Tables

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
newbie (Guest)
on 2006-12-24 18:26
(Received via mailing list)
I'm a newbie and loving rails but I'm stuck.

All I want to do is to be able to show in my "list.rhtml" view the
information between two tables who both have a HABTM relationship. So
this is what I have as follows.

--------------------------
TABLES
--------------------------
# Projects
  create_table "projects", :force => true do |t|
    t.column "title", :string
    t.column "description", :text
  end

# Users
  create_table "users", :force => true do |t|
    t.column "first_name", :string
    t.column "last_name", :string
end

# Projects_Users
  create_table "projects_users", :force => true do |t|
    t.column "project_id", :integer, :default => 0, :null => false
    t.column "user_id", :integer, :default => 0, :null => false
  end

--------------------------
MODELS
--------------------------
class Project < ActiveRecord::Base
has_and_belongs_to_many :users
end

class User < ActiveRecord::Base
has_and_belongs_to_many :projects
end

--------------------------
CONTROLLER
--------------------------
# Projects_Controller
# I have the generic scaffold running my list view

  def list
    @project_pages, @projects = paginate :projects, :per_page => 10
  end

# list.rhtml
# I just want to be able to pull the projects and users that belong to
the project into a view.
#  Some projects may have 3 or 4 users.

# so I have this
<% for project in @projects %>
<%= project.title %><br>
<%= project.users %><br>
<% end %>

# Which outputs this when you run it...

Project 1
#
Project 2
##
Project 3
#
Project 4

# Which from the database is somewhat accurate. I have two users in
"project 2" but for some reason ROR won't let me access the information
about them so it only shows them as "##", but those are users. Here is
my DB table below.

# projects_users - DB
project_id	user_id
1			1
2			2
2			3
3			1


# What I want to achieve is something like this .
#
<% for project in @projects %>
<%= project.title %><br>
<%= project.users.first_name %><br>
<% end %>

**When I try to run this code above it gives me an error saying
undefined method `first_name' for User:Class

The syntax looks somewhat correct, but I'm not doing it right.

Any help on my set-up I would appreciate it.
Mike Deck (Guest)
on 2006-12-24 20:50
(Received via mailing list)
I'm pretty new to Rails as well, but I think you need another for loop
to iterate over all the users.  The experssion project.users refers to
a collection of users which doesn't have a first name.  I would do
something like this:

<% for project in @projects %>
<%= project.title %><br>
  <% for user in project.users %>
    <%= user.first_name %>&nbsp<br>
  <% end %>
<% end %>

Hope that helps.
newbie (Guest)
on 2006-12-24 21:14
(Received via mailing list)
Hey Mike,

That totally worked.

Some additional thought...
You know for some reason I figured when you do a HABTM relationship
that an array would have both tables contained and you could access
them using just 1 loop. As I outlined in my attempted solution below:

<% for project in @projects %>
<%= project.title %><br>
<%= project.users.first_name %><br>
<% end %>

Your soultion works but it does require 2 loops. I just wonder do you
really need to use 2 loops. I'm trying to follow the whole DRY
principle and it seems too repeatative and it should be 1 loop.

Just a thought.

I'll just leave this posted to see if anyone else see other ways to do
it.
askegg (Guest)
on 2006-12-24 22:06
(Received via mailing list)
Rather than trying to flatten the array Rails lets the user decide how
to handle it.  This is a wise decision as many values may be
dynamically looked up during render.

For example, say you wanted to display the name of the who created the
user:

<% for project in @projects %>
<%= project.title %><br>
<%= project.user.first_name %><br>
<%= project.user.added_by.user.first_name %><br>
<% end %>

Since the relationship can be extremely complex and circular, there is
no way to determine what the user wants beforehand and it would in
inefficient to try.
newbie (Guest)
on 2006-12-25 00:24
(Received via mailing list)
Hey,

Re: Your example
So you have one loop pulling in information from two tables?
Are you suggesting this is how it should be done or should it be like 2
loops as shown in the above entry?

Or are you suggesting the data is too complex between the two tables
and you really can't pull it in at once because you get code like this.
<%= project.user.added_by.user.first_name %><br>

any feedback would be appriecated
askegg (Guest)
on 2006-12-26 00:00
(Received via mailing list)
Yes - this is one loop pulling information rom multiple tables (the
actual number depends on the underlying schema).

Unfortunately, here is no single answer to your question.  Two loops
should be used when it is unclear how many (sub) entries there may be.
One loop is fine if you *know* there will only be one entry.  It all
comes down to how your data is modeled.

If you pull all the possible information in a single table (like a
spreadsheet) the results could be enormous because the framework needs
to map all the possible combinations and collate them.  If you have
*any* circular references the resulting table will be infinite.
Follow?

In you original example, one loop if fine if every project has one
user, which is almost certainly not the case.
newbie (Guest)
on 2006-12-26 00:16
(Received via mailing list)
Now that you further explained it, I understand a bit more. Thanks for
the extra feedback.
Mike Deck (Guest)
on 2006-12-26 20:58
(Received via mailing list)
I don't think this violates DRY at all.  Those two loops are doing two
different things, one displays projects and one displays users.  But I
can see how this is a little cluttered.  If you really want to get rid
of the second for loop you could use a partial template.

I'm not actually running the code I'm giving you here so there might be
a couple bugs, but this is the basic idea.

First of all lets change the way you're displaying the project list to
make it a little less trivial.  Perhaps you want the user's names to be
in a list under each project.  So the original template code looks like
this:

<% for project in @projects %>
<h2><%= project.title %></h2><br />
<ul>
<% for user in project.users %>
  <li><%= user.first_name %></li>
<% end %>
</ul>
<br />
<% end %>

Then you abstract the function of displaying a single user into another
rhtml file called _user.rhtml (the underscore prefix means it's a
partial template.)  _user.rhtml would look something like this:

<li><%= user.first_name %></li>

Then you can change the original template to use the new partial
template.  One of the nice features of using a partial is that it
allows you to pass in a collection to the render method which does the
iteration automatically for you.  The new project list template would
look like this:

<% for project in @projects %>
<h2><%= project.title %></h2><br />
<ul>
  <%= render(:partial => "user", :collection => project.users)%>
</ul>
<br />
<% end %>

Is that simpler than the previous example?  I don't know.  You've used
the render method to take the nested loop out of your template, but
you've also added another file to project.  To me the extra for loop is
still readable and I think it's overall less complex than adding
another file to maintain.  If however you had multiple places in your
app where you were displaying lists of users this could be valuable
because then it really would be helping you adhere to the DRY principle.
newbie (Guest)
on 2006-12-26 22:10
(Received via mailing list)
Hey

Re: Partials
That is a very interesting technique for using partials. It does look
like a lot of work but I do have other areas where I need to bring up
that list.

So many new concepts to learn in ROR but this one is good.

I think I'll need to get the basics up and running and then go through
the code and start recfactoring it so it's clean.

thanks for the extra feedback
This topic is locked and can not be replied to.