Say I am displaying all of the users their posts as shown below.
<% #users do |user| %>
<div><%= user.name %> : </div>
<% user.posts.each do |post| %>
<div><%= post.title %></div>
<% end %>
<% end %>
Is it better to load the posts (specifically for database speed) beforehand in the controller?
#users = User.includes(:posts)
or is it better to just fetch the users and load the post in the view?
#users = User.all
When you iterate through a collection and access a child assocations this creates what is called a N+1 query issue where each record will create a separate database query.
If a single request ends up using 100 database queries your server will grind to a halt in no time.
<% #users do |user| %>
<div><%= user.name %> : </div>
<!-- This line will create a N+1 query -->
<% user.posts.each do |post| %>
<div><%= post.title %></div>
<% end %>
<% end %>
So yes - preloading associations is necessary to build applications that scale (or even work). ActiveRecord provides several methods such as .includes, .preload, .eager_load, .join and .left_outer_joins (Rails 5) that each give different results and are good for different use cases.
Related
I need a little advice about the join and includes methods.
I display a list of groups in the index view. Each has a modal associated, and, in this modal, I would like to display the requests associated to this group. Normally, I'd use #requests = group.requests, but would like to use join for sending just one request to my database.
Since I'm in the index view, I don't have a #group in my action.
controller:
def index
#groups = current_user.groups
end
view (index):
<% #groups.each do |g| %>
<MODAL>
<% #requests = g.requests %>
<% #requests.each do |r| %>
<%= r.date %>
<% end %>
</MODAL>
<% end %>
I guess I can also use join and include for #groups, but there is already one SQL request, so I'm good with it.
In your controller, add includes like this to preload requests and avoid n+1 queries.
def index
#groups = current_user.groups.includes(:requests)
end
View is fine, but you can also write as:-
<% #groups.each do |g| %>
<MODAL>
<% g.requests.each do |r| %>
<%= r.date %>
<% end %>
</MODAL>
<% end %>
I'm new to rails and I'm trying to build a view that will list the parents and related children
Ex:
Passport has many Visas
I want to list information about the passport and the visas that the passport has.
So I have
<% #passport_list.each do |passport| %>
# passportFields
<% passport.visas.each do |visa| %>
<%= t.text_field :visa_type %>
<% end %>
<% end %>
I'm getting the error
undefined method `visa_type' for #Passport:0x000000091b8b28
It looks like rails is trying to find the property visa_type for passport, instead of in visa. How does the scope work within each? Can I force it to access visa_type from visa?
I think you're looking for the fields_for form helper. This will allow you to create fields for the relevant visa attributes. Replace your code sample with the following, and you should be all set.
<% #passport_list.each do |passport| %>
# passportFields
<% t.fields_for :visas do |visa_fields| %>
<%= visa_fields.text_field :visa_type %>
<% end %>
<% end %>
You can also iterate over the list as follows:
<% #passport_list.each do |passport| %>
# passportFields
<% passport.visas.each do |visa| %>
<% t.fields_for :visas do |visa_fields| %>
<%= visa_fields.text_field :visa_type %>
<% end %>
<% end %>
<% end %>
For more information on fields_for, check out the link I added above, and to customize further for your use case, check out the "One-to-many" section.
IMO you should always handle the null case of an object.
Something like this if you use rails (present? is a Rails function)...
<% if #passport_list.present? %>
<% #passport_list.each do |passport| %>
passportFields
<% passport.visas.each do |visa| %>
<%= t.text_field :visa_type %>
<%end%>
<%end%>
<% else %>
<p>Nothing to see here</p>
<% end %>
However if your #passport_list is backed by an ActiveRecord Query, you can handle this in the model/helper/controller by returning the .none query on the model. Note that this differs from an empty array because it is an ActiveRecord Scope, so you can chain AR queries onto it
# scope on AR model
def self.awesomeville
where(country_of_origin: "awesomeville")
end
# method queried in controller
#passport_list = Passport.all
if #passport_list.present?
#passport_list
else
Passport.none
end
# additional filtering in view is now possible without fear of NoMethodError
#passport_list.awesomeville
Whereas a ruby Array would raise an error as it would respond to the Array methods.
I have a User model and Post model, where User has many Posts. I need to order posts by last_date_modified. Here is code that loops over each user's posts, groups each post based on status, but it doens't order them by last_date_modified.
<% #user.posts.uniq.group_by(&:status).sort_by { |s, e| s }.each do | status, posts | %>
<% posts.each do |post| %>
<%= post.created_at %>
<%= post.title %>
<%= post.description %>
<% end %>
<% end %>
Any help is much appreciated.
But you better do this in controller not in your view
#user.posts.order("updated_at DESC").uniq.group_by(&:status)...
I have a rails table called Movies. Movies are being collected and saved from an API which means that some movies may have a release_date and some may not.
All Movies are being displayed on the home page and they are sorted by {|t| - t.release_date.strftime("%Y%m%d").to_i}
<% #movies.sort_by{|t| - t.release_date.strftime("%Y%m%d").to_i}.each do |movie| %>
<% movie.title %>
<% movie.release_date.strftime("%Y") %>
<% end %>
So this code works fine but only as long as the returned movies have a release date. If they don't have a release date assigned, it gives me the following error.
ActionView::Template::Error (undefined method `strftime' for nil:NilClass):
But im only getting this error if the movie has no release_date.
So how can i add an exception to only display films WITH a release_date, where using strftime would no longer be a problem.
I've tried
<% unless movie.release_date.blank? %>
<% #movies.sort_by{|t| - t.release_date.strftime("%Y%m%d").to_i}.each do |movie| %>
<% #movie.title %>
<% #movie.release_date.strftime("%Y") %>
<% end %>
<% end %>
But that doesn't work as it gives an undefined method for 'movie'
You should be able to use reject to reject nil release_date like follows:
<% #movies.reject{ |m| m.release_date.nil? } %>
Another problem is you are using the variable movie as instance variable #movie within your each block.
Try:
<% #movies.reject{ |m| m.release_date.nil? }.sort_by{|t| - t.release_date.strftime("%Y%m%d").to_i}.each do |movie| %>
<% movie.title %>
<% movie.release_date.strftime("%Y") %>
<% end %>
Update:
And yes, as pointed by #NicolasGarnil in his answer, it's better to do these in SQL side than in ruby side. Select only the required records and let database do the sorting. So you could update your code to be something like:
In controller:
#movies = Movie.where('release_date is not null').order('release_date desc');
Then in your view:
<% #movies.each do |movie| %>
<% movie.title %>
<% movie.release_date.strftime("%Y") %>
<% end %>
For performance reasons you should not be using ruby to sort your records. This should be done at a database level.
You should first ensure that the release_date values are persisted in an appropriate format and then just use Movie.order("release_date desc"). Records with null values will be placed at the end of the results.
The relation is that a user has many treatments and a treatment belong to user, one-to-many.
Now i want to print out all the users that have this particular treatment
Inside my treatments show view i have this double loop
<% User.all do |user| %>
<%= user.treatments.each do |t| %>
<% if (t.id).to_i == (#treatment.id).to_i %>
<%= link_to user.name, user_path(user) %><br />
<% end %>
<% end %>
<% end %>
if i change <% User.all do |user| %> to <%= User.all do |user| %> it prints out everything in my users table
can you guys spot why im not getting any users ?
i put a message in the beginning of the inner loop and it didnt display either, guess the problem is there but im not seeing it
.all returns an array. Array doesn't accept a block. Most likely, you want to do .each but forgot to write it. Try this:
<% User.all.each do |user| %>
but a better way is to not iterate all users like this, but get the correct list from the database directly.