HABTM Reading Rows From Two Tables


#1

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 %>

<%= project.users %>

<% 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 %>

<%= project.users.first_name %>

<% 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.


#2

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 %>

<% for user in project.users %>
<%= user.first_name %>&nbsp

<% end %>
<% end %>

Hope that helps.


#3

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 %>

<%= project.user.first_name %>

<%= project.user.added_by.user.first_name %>

<% 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.


#4

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 %>

<%= project.users.first_name %>

<% 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.


#5

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 %>

any feedback would be appriecated


#6

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.


#7

Now that you further explained it, I understand a bit more. Thanks for
the extra feedback.


#8

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 %>

<%= project.title %>


    <% for user in project.users %>
  • <%= user.first_name %>
  • <% end %>

<% 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:

  • <%= user.first_name %>
  • 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 %>

    <%= project.title %>


      <%= render(:partial => "user", :collection => project.users)%>

    <% 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.


    #9

    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