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.
on 2006-12-24 17:26
on 2006-12-24 19:50
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 %> <br> <% end %> <% end %> Hope that helps.
on 2006-12-24 20:14
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.
on 2006-12-24 21:06
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.
on 2006-12-24 23:24
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
on 2006-12-25 23:00
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.
on 2006-12-25 23:16
Now that you further explained it, I understand a bit more. Thanks for the extra feedback.
on 2006-12-26 19:58
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.
on 2006-12-26 21:10
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