Rails - Retrieve data from multiple tables - ruby-on-rails

I have read so much about Rails in the past week in the process of trying to learn it. The problem is when I need something I find it difficult to recall where I read it! ha ha
I have two models: Guest and Booking where Guests has_many Bookings. Therefore the Booking model has a guest_id field included.
I would like to retrieve all the booking data and the guest data and display in a view as one object. I know I have seen a simple solution to this (in the Rails doc I think) that does just this.
At the moment I'm doing the following in my BookingsController:
#bookings = Booking.all
and in my associated view I have:
<% #bookings.each do |booking| %>
<p><%= booking.id %></p>
<p><%= booking.email %></p> //this does not work as email is in guests table!!!
<% end %>
but how can I get the linked Guest table included too?

If your booking belongs to guest
class Booking < ActiveRecord::Base
belongs_to :guest
...
end
you can first include guest to avoid n+1 queries
#bookings = Booking.includes(:guest).all
and then in view, traverse the association
<% #bookings.each do |booking| %>
<p><%= booking.id %></p>
<p><%= booking.guest.email %></p>
<% end %>

In your bookings controller:
#bookings = Booking.includes(:guest).all

Related

Displaying has_many, :through association records

I am working on an application a deep association. A Story belongs to a Planning Level, the Planning Level belongs to one or many Programs.
class Program < ActiveRecord::Base
has_and_belongs_to_many :planning_levels
has_many :stories, :through => :planning_levels
end
class PlanningLevelsPrograms < ActiveRecord::Base
end
class PlanningLevel < ActiveRecord::Base
has_and_belongs_to_many :programs
has_many :stories
end
class Story < ActiveRecord::Base
belongs_to :planning_level
end
Within the Program show page I'd like to display the Program, each Planning Level and aggregate Story count for each Planning Level.
I'm not sure how to access the Story model from the Program show page.
The following works great for displaying each Planning Level belonging to the Program.
<% #program.planning_levels.each do |p| %>
<p><%= p.name %></p>
<% end %>
...but I have no idea how to make the following work for each displayed Planning Level. How do I access the Story model from with the Program? Is there something needed in the Program controller that I'm missing. Thanks in advance!
#program.planning_level.stories.count(:id)
Each Planning Level:
<% #program.planning_levels.each do |planning_level| %>
<p><%= planning_level.name %></p>
# Aggregate Story count for each planning_level
<p><%= planning_level.stories.count %></p>
<% end %>
Aggregate Story count for #program (if you want):
#program.stories.count
Hope this can help you.
In your view, you can simply use the model associations by name to do this. Try this code as a starting point for your display needs:
<% #program.planning_levels.each do |planning_level| %>
<p><%= planning_level.name %> with <%= planning_level.stories.count %> stories</p>
<% planning_level.stories.each do |story| %>
<p><%= story.name %></p>
<% end %>
<% end %>
You can output any details that you choose for the stories loop. You can add styling to get the presentation that you need.
For instance, you might consider formatting this as a nested list, like so:
<% #program.planning_levels.each do |planning_level| %>
<ul>
<li>Planning Level: <%= planning_level.name %> with <%= planning_level.stories.count %> stories
<ul>
<% planning_level.stories.each do |story| %>
<li>Story: <%= story.name %></li>
<% end %>
</ul>
</li>
</ul>
<% end %>
Adding CSS class and id attributes would give you the ability to add styling to the elements to give your UI some flair.

Rails views based on HABTM Category

So I have this app where I'm using a HABTM association to determine "User Skills"; When a new user is created (via the new user view) the user can declare his/her skills via a group of HABTM Checkboxes available on that view with the form...
What I want to do is to have a view where I have Links based on the different skills, for example: "policemen", "doctors", "musicians" etc. And these links should point to other views where I can show to the visitor a list of only the users that belong to the specific category they clicked on.
My users/skills models (association part) look like this:
#User Model
class User < ActiveRecord::Base
has_and_belongs_to_many :skills
#Skill Model
class Skill < ActiveRecord::Base
has_and_belongs_to_many :users
And (if it's helpful) my HABTM checkboxes look like this:
<p> What Skills do you have?
<% for skill in Skill.find(:all) %>
<div>
<%= check_box_tag "user[skill_ids][]", skill.id, #user.skills.include?(skill) %>
<%= skill.name %>
</div>
<% end %>
</p>
Let's say the skills we have are: "policeman, doctor, musician" for example... How can I create links in a view wich point to the group of users that have X skill and then with what code could I render some views that display lists with only the users that belong to X skill category?
I bet the solution is really simple... But I'm missing something obvious, maybe. Could you point me in the right direction?
Thanks!
In config/routes.rb:
resources :skills
Generate a SkillsController with rails g controller skills and put there:
def index
#skills = Skill.all
end
def show
#skill = Skill.find(params[:id])
end
Then your views:
#app/views/skills/index.html.erb
<ul>
<% #skills.each do |skill| %>
<li><%= link_to skill.name, skill_path(skill) %></li>
<% end %>
</ul>
and
#app/views/skills/show.html.erb
<h1>Users who have the <%= #skill.name %> skill</h1>
<ul>
<% #skill.users.each do |user| %>
<li><%= user.full_name %></li>
<% end %>
</ul>
First of all dont use the has_and_belongs_to_many. Here is a link to RoR Guides showing how you are supposed to do the has_many :through assosiation. http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
Secondly if you want to show your user that have 'x' skill it just the skills#show action.
def show
#skill = Skill.find params[:id]
#users = #skill.users
end
And on your links to view this would be something like skill_path(skill)

Check for join in View

I have something like
<% #users.each do |u| %>
...
<% #interests.each do |i| %>
<% if i joins u in interests_users %> <-- That is not working that way! = Problem
...
<% i.id %>
...
<% end %>
<% end %>
...
<% end %>
I need to output each interest-id that joins users in interests_users for every user. But i cannot use a sql query for each user, because that would be too much for the server, so i query the whole interests table and want to filter it in the view.
Anyone got a simple solution for that?
You're thinking about this too view-centric - this is a data issue, so the model(s) should take care of it.
I'm guessing you have an n-m relationship between users and interests, with the interests-users join table in the middle. The models should define that relationship something like this:
class User < ActiveRecord::Base
has_and_belongs_to_many :interests
end
class Interest ...
has_and_belongs_to_many :users
end
This is with has_and_belongs_to_many (HABTM). The models then already take care of "selecting" which interests are "on" each user, simply query them with user_instance.interests, i.e. do
<% #users.each do |u| %>
...
<% u.interests.each do |i| %>
If that generates a query for interests for each user, you can eager load the data when getting the users:
#users = User.includes(:interests).all
Edit:
Oh yeah and if you want to list all interests and mark those a user has associated, something like this should work:
#interests.each do |interest|
if u.interests.include?(interest) ...

Rails: Cutting Down Code

I know that I might have too much logic in my view, so I'm wondering how I can include it in my controller:
Controller:
def purchasers
#deal = Deal.find(params[:id])
#pl = #deal.purchases
end
View:
<% title "List Of Purchases" %>
Total Purchases: <%= #pl.count %><BR><BR>
<%
#pl.each do |p|
u = User.find(p.user_id)
%>
<%= u.email %><BR>
<%
end
%>
I'd suggest that you remove the call to User.find inside the view code.
It looks like you're looking up the user from the user_id stored in the purchase. Why not in the model use:
class Purchase < ActiveRecord::Base
belongs_to :user
...
end
And then in the view code:
<% #pl.each do |purchase| %>
<%= purchase.user.email %><BR>
<% end %>
Hope this helps.
It looks like you might not have set up your associations correctly in your Purchases and Users models. Instead of doing u = User.find(p.user_id) you should be able to write p.user.email, assuming that each Purchase belongs_to :user.
if your Purchase model belongs to User model, you don't need to find User with User.find.
if not, belong your Purchase model to User model then
<% #pl.each do |p| %>
<%= p.user.email %>
<% end %>
Its also worth noting the following can be improved to make use of Rails' skills when it comoes to caching collections:
<%= #pl.count %>
to
<%= #pl.size %>
The size method will return the number of purchases but won't load the objects into memory again as they have already been looked up in the controller.

Eliminating current_user activity from records being returned through a complex association

I have built a Ruby on Rails application (Rails 2.3.9) that allows users to track workouts. After a workout is created other users can comment on that workout. I am now working on the Dashboard index view to display recent activity.
In this particular section I am trying to display comments from all on workouts that current_user has commented on. I am successfully pulling those comments, ordering them, and limiting the output through the below code.
I am now trying to exclude comments from current user. Here is the code:
/views/dashboard/index.html.erb
<% unless current_user.comment_stream.blank? %>
<h3>Recent Comments from WODs you commented on</h3>
<% current_user.comment_stream[0,10].each do |comment| %>
<p>
Comment from <%= link_to (comment.user.username), comment.user %>
<%= time_ago_in_words(comment.created_at) %> ago on Workout:
<%= link_to (comment.workout.title), comment.workout %>
</p>
<% end %>
<% end %>
User.rb
def workouts_on_which_i_commented
comments.map{|x|x.workout}.uniq
end
def comment_stream
workouts_on_which_i_commented.map do |w|
w.comments
end.flatten.sort{|x,y| y.created_at <=> x.created_at}
end
Example of Problem:
Here is an example of what happens with this code:
User A creates a workout and User B comments on it. Then User C and User D also comment on User A's workout. In User B's dashboard view, I want him to see comments from User C and User D in the activity stream...but I don't want him to see his own comments.
I could simply use a <% unless comment.user_id == current_user.id %> but that messes up the number of records being displayed as those are fished prior to the exclusion line.
In comment_stream, you can add filter out the comments you posted
def comment_stream
workouts_on_which_i_commented.map do |w|
w.comments
end.flatten.reject{|c| c.user_id == current_user.id}.sort{|x,y| y.created_at <=> x.created_at}
end

Resources