I have a model course which has_many subcategories. I want to build a page that shows courses grouped by their subcategory. So far, I have
#courses = Course.personal_enrichment.order('subcategory_id').page params[:page]
#courses_facet = #courses.group_by(&:subcategory_id)
which works fine, but I need to show the actual subcategory name in the view, not the number. I've seen some other answers about this type of thing, but most of them assume the attribute you're grouping by is already human readable. Maybe I'm missing something?
When rendering the view you can just access the referenced models' attributes. Since group_by returns a hash, you could do something like this:
<% #courses_facet.each do |subcategory_id, courses| %>
<% subcategory_name = courses.first.subcategory.name rescue nil %>
<label><%= subcategory_name %></label>
<% end%>
Unless relevant subcategory models are cached this will generate N+1 queries to fetch the subcategory names. One way to avoid that is to include subcategory records to the initial resultset.
#courses.includes(:subcategories).group_by(&:subcategory_id)
Related
So in my application I have the models People and Outfits. In my show controller for people, I get the list like this:
#people = Person.where("description LIKE ?", "%#{params[:description]}%")
And in my view I show the outfits of each person like this:
<% #people.each do |person| %>
<p> Name: <%= person.name %> </p>
<% #outfits = person.outfits %>
<% #outfits.each do |outfit|
<p> Name: <%= outfit.name %> </p>
<p> Description: <%= outfit.description %> </p>
<% end %>
<% end %>
But loading the outfits for each person, as I load many people on the page, takes too long. Is there some way I can inherit the outfits of each person so I don't have to wait so long for the page to load? Or is the only way to speed this up to make an index between outfits and people? Thanks for any help
Use a join to load the associated records:
#people = Person.eager_load(:outfits)
.where("description LIKE ?", "%#{params[:description]}%")
.limit(20) # optional
Otherwise you have what is called a N+1 query issue where each iteration through #people will cause a separate database query to fetch outfits.
And yes the outfits.person_id or whatever column that creates the association should have a foreign key index. Using the belongs_to or references macro in the migration will do this by default:
create_table :outfits do |t|
t.belongs_to :person, foreign_key: true
end
Active Record Query Interface - Eager Loading Associations
Making sense of ActiveRecord joins, includes, preload, and eager_load
you should set a limit like this:
#people = Person.where("description LIKE ?", "%#{params[:description]}%").limit(20)
change the number according to your preference.
You can use .joins or .includes
If you have a table full of Person and you use a :joins => outfits to pull in all the outfit information for sorting purposes, etc it will work fine and take less time than :include, but say you want to display the Person along with the outfit name, description, etc. To get the information using :joins, it will have to make separate SQL queries for each user it fetches, whereas if you used :include this information is ready for use.
Solution
Person.includes(:outfits).where("description LIKE ?", "%#{params[:description]}%")
Assuming I have this association
User have_many posts
Post belongs_to user
User Post
----------------
id id
name title
user_id
How to list only post title and username with includes/joins ?
(list of posts [title - username])
#posts = Post.includes(:user).select('........')
don't offer this
#posts = Post.all.each {|p| p.user.username}
__________________UP_____________________
It worked for joining 2 tables.
What if I want to use it for more complex example?
check out my prev question optimize sql query rails
#Humza's answer partly worked.
it might be something like this
#posts = Post.joins(:user, :category).paginate(:page => params[:page]).order("created_at DESC")
but It doesn't display posts that don't have category
I also need to display gravatar but I think I can just use user.email as usr_email and use gravatar_for (post.usr_email) but I'll have to customize gravatar helper for this.
posts_controller.rb
def index
#posts = Post.includes(:user).includes(:comments).paginate(:page => params[:page]).order("created_at DESC")
end
index.html.erb
<%= render #posts %>
_post.html.erb
<%= gravatar_for post.user, size:20 %>
<%= link_to "#{post.title}", post_path(post) %>
<%= time_ago_in_words(post.created_at) %>
<%= post.comments.count %>
<%= post.category.name if post.category %>
Take a look at pluck.
Post.joins(:user).pluck(:title, :name)
Note that it works in this case because there's no ambiguity regarding the name column, you might want to specify the table explicitly (pluck(:title, "users.name")).
includes is used in case of eager-loading. You need joins in this case.
posts = Post.joins(:user).select("posts.title AS title, users.name AS username")
You can access the values then in the following way:
post = posts.first
post.title # will give the title of the post
post.username # will give the name of the user that this post belongs to
If you can pluck multiple columns, then the following might be more useful to you:
posts = Post.joins(:user).pluck("posts.title", "users.name")
The result will be a 2D array, with each element being an array of the form [post_title, post_username]
Post.joins(:user, :category)
but It doesn't display posts that don't have category
That's because joins uses INNER JOIN to join the tables together. If you want to everything from Post even though the particular record doesn't have its counterpart in the other table, you need to use LEFT JOIN. Unfortunately ActiveRecord doesn't have a nice way of generating it and you will need to do that manually:
Post.joins("LEFT OUTER JOIN categories ON categories.post_id = posts.id")...
See A Visual Explanation of SQL Joins for more information.
You can call array methods on a scope so:
Post.includes(:user).map { |p| [p.title, p.user.name] }
will get the posts with included user and map each post to a tuple of the post title and the user name.
That may not entirely answer your question as I think you might want to restrict the results of the query to just the required fields in which case, I think you can add a .select('title', 'users.name') to the query. (Not in a position to test at the moment)
I'm attempting to make an invoice application. Here are my models which are related to my question:
UPDATE: Model information has changed due to recent suggestions
Invoice
> id
> created_at
> sales_person_id
LineItem
> id
> invoice_id
> item_id
> qty_commit (inventory only)
> qty_sold
> price (because prices change)
> ...etc
Item
> barcode
> name
> price
> ...etc
Invoice has_many items, :through => :line_items. Ditto for Item. What I want to do is that when I create a new invoice, I'd like the form to be populated with all available Items. The only time I don't want all items to be populated is when I'm viewing the invoice (so only items which exist in the LineItems table should be retrieved). Currently - and obviously - a new Invoice has no items. How do I get them listed when there is nothing currently in the collection, and how do I populate the form? Also I'd like all products to be available when creation fails (along with what the user selected through the form).
UPDATE: I can create items through the controller via the following:
#invoice = Invoice.new
# Populate the invoice with all products so that they can be selected
Item.where("stock > ?", 0).each do |i|
#invoice.items.new(i.attributes)
end
This is of course my crude attempt at doing what I want. Visually it works out great, but as predicted my form id's and such are not playing well when I actually attempt to save the model.
LineItem(#37338684) expected, got Array(#2250012)
An example of the form:
# f is form_for
<% #invoice.items.group_by{|p| p.category}.each do |category, products| %>
<%= category.name %>
<%= f.fields_for :line_items do |line_item| %>
<% for p in products %>
<%= line_item.hidden_field :tax_included, :value => p.tax_included %>
<%= p.name %>
$<%= p.price %>
<% end %>
<% end %>
<% end %>
First of all, if you explicitly want to have a join model with additional attributes in it, you should use has_many :through instead of has_and_belongs_to_many. See the RoR Guide to the differences of the two.
Second, there is no single solution for what you want to reach. I see there two typical usages, depending on the mass of possible instances, one is better than the other:
Use radio buttons to select (and deselect) where a relation should be created or deleted. See the railscast #165 how to do part of that.
You could use select menus with a button to add a relation. See railscast #88. The added relation could be shown in a list, with a delete button nearby.
Use token fields (see railscast #258) to autocomplete multiple entries in one single text entry field.
In all the situations, you normally have to check at the end, if
a relation should be deleted
kept
or created
I hope some of the ideas may show you the right solution for your problem.
I'm attempting to make an invoice application. Here are my models which are related to my question:
UPDATE: Model information has changed due to recent suggestions
Invoice
> id
> created_at
> sales_person_id
LineItem
> id
> invoice_id
> item_id
> qty_commit (inventory only)
> qty_sold
> price (because prices change)
> ...etc
Item
> barcode
> name
> price
> ...etc
Invoice has_many items, :through => :line_items. Ditto for Item. What I want to do is that when I create a new invoice, I'd like the form to be populated with all available Items. The only time I don't want all items to be populated is when I'm viewing the invoice (so only items which exist in the LineItems table should be retrieved). Currently - and obviously - a new Invoice has no items. How do I get them listed when there is nothing currently in the collection, and how do I populate the form? Also I'd like all products to be available when creation fails (along with what the user selected through the form).
UPDATE: I can create items through the controller via the following:
#invoice = Invoice.new
# Populate the invoice with all products so that they can be selected
Item.where("stock > ?", 0).each do |i|
#invoice.items.new(i.attributes)
end
This is of course my crude attempt at doing what I want. Visually it works out great, but as predicted my form id's and such are not playing well when I actually attempt to save the model.
LineItem(#37338684) expected, got Array(#2250012)
An example of the form:
# f is form_for
<% #invoice.items.group_by{|p| p.category}.each do |category, products| %>
<%= category.name %>
<%= f.fields_for :line_items do |line_item| %>
<% for p in products %>
<%= line_item.hidden_field :tax_included, :value => p.tax_included %>
<%= p.name %>
$<%= p.price %>
<% end %>
<% end %>
<% end %>
First of all, if you explicitly want to have a join model with additional attributes in it, you should use has_many :through instead of has_and_belongs_to_many. See the RoR Guide to the differences of the two.
Second, there is no single solution for what you want to reach. I see there two typical usages, depending on the mass of possible instances, one is better than the other:
Use radio buttons to select (and deselect) where a relation should be created or deleted. See the railscast #165 how to do part of that.
You could use select menus with a button to add a relation. See railscast #88. The added relation could be shown in a list, with a delete button nearby.
Use token fields (see railscast #258) to autocomplete multiple entries in one single text entry field.
In all the situations, you normally have to check at the end, if
a relation should be deleted
kept
or created
I hope some of the ideas may show you the right solution for your problem.
I am not sure what I am doing wrong here. I find many examples that show that I am doing this right and it is really basic stuff, I know.
I am simplyfying a bit, but I have two models, 'Post' and 'Category'. I am trying to get the list of categories from the database and list them by name.
class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
# get all categories and output the names
cats = Category.all
cats.each do |cat|
cat.name
end
It instead seems to output the entire array of retrieved results. All results not even just the one I am iterating over. What gives?
Where are you putting that .each loop code? Where is the "output" code you're referring to? If you're using a loop in a view, make sure you're using
<% %>
and not
<%= %>
for the loop lines themselves. As in:
<% Category.all.each do |cat| %>
<%= cat.name %>
<% end %>
Category.all returns an array of all Category objects, which is everything the categories table contains. cats is therefore an array of all the categories. I'm not sure why you think you're only iterating over "one" of anything. To get one result, you can use find() or first:
cat = Category.first
puts cat.name
If you want all the names, you can do this:
Category.all.map(&:name)
or, a bit more efficiently, especially if there are many fields:
Category.all(:select => :name).map(&:name)