I have an app that has two models Category and Product
Products belong to categories.
I have created a navbar dropdown that requires an #category and #product object to be available across the entire app (the navbar is shown on every page of the application.)
What I can't work out is the best place to put these basic definitions without defining them multiple times in every page definition.
I need to define the following:
#category = Category.all
#products = #category.products.all
The navbar loop will then look something like this.
<% #category.each do |c| %>
<%= c.name %>
<% #products.each do |p| %>
<% link_to product_path(p) do %>
<%= p.name %>
<% end %>
<% end %>
<% end %>
I am a bit of a rails newbie so I am sure there are some errors in here but any help would be much appreciated!
If you need them in every single page of app, you can set them in ApplicationController's before_filter:
class ApplicationController
before_filter :get_categories
# ...
private
def get_categories
#categories = Category.includes(:products)
end
end
then, you can write in your view:
<% #categories.each do |category| %>
<%= category.name %>
<% category.products.each do |product| %>
<%= link_to p.name, p %>
<% end %>
<% end %>
I also fixed some other errors and convention incompatibilities.
The following code is incorrect.
#category = Category.all
#products = #category.products.all
This code assigns to #categories all the categories, then it attempts to fetch the products. It will not work, unless you have defined a products class method in the Category model. But I don't think so, otherwise you will just have to call Product.all.
Moreover, in the code below, you are trying to display the list of products per category, which definitely don't work with the two assignments before. According to what you are trying to achieve, you can't pre-assign the #products, because you want the products for a specific category.
Let's inline everything into the code.
<% Category.all.each do |category| %>
<%= category.name %>
<% category.products.each do |product| %>
<%= link_to product_path(product) do %>
<%= product.name %>
<% end %>
<% end %>
<% end %>
Next step is to make the code a little bit more performant, giving you need it everywhere.
<% Category.select(:id, :name).each do |category| %>
<%= category.name %>
<% category.products.select(:id, :name).each do |product| %>
<%= link_to product_path(product) do %>
<%= product.name %>
<% end %>
<% end %>
<% end %>
You could use pluck, but it will return an array and it will require a little bit more manipulation. However, it's way more performant.
<% Category.pluck(:id, :name).each do |category_id, category_name| %>
<%= category_name %>
<% Product.where(category_id: category_id).pluck(:id, :name).each do |product_id, product_name| %>
<%= link_to product_name, product_path(id: product_id) %>
<% end %>
<% end %>
It's not a good idea to chain all those methods inside a view, let's extract some code into the model.
class Category
def self.simple_listing
order(:name).pluck(:id, :name)
end
end
class Product
def self.simple_category_listing(category_id)
where(category_id: category_id).order(:name).pluck(:id, :name)
end
end
<% Category.simple_listing.each do |category_id, category_name| %>
<%= category_name %>
<% Product.simple_category_listing(category_id).each do |product_id, product_name| %>
<%= link_to product_name, product_path(id: product_id) %>
<% end %>
<% end %>
You can leave all this code into the view, or extract it into a partial. You don't even need to add a controller before filter, or make it "global". The code is self-contained, does not pollute the name space with instance variables, and it can easily be placed whenever you need it.
Related
I have a Posts Model, and a Projects Model. I want to render both of these on one index page and order them by created_at DESC. How can I do this? Thanks in advance...
Separately?
<% Post.order('created_at DESC').each do |post| %>
#do things
<% end %>
<% Project.order('created_at DESC').each do |project| %>
#do things
<% end %>
Together?
<% (Post.all + Project.all).sort_by{|item| -item.created_at}.each do |item| %>
<% if item.is_a? Post %>
<%= render 'post_partial', post: item %>
<% elsif item.is_a? Project %>
<%= render 'project_partial', project: item %>
<% end %>
<% end %>
Then create a partial for both objects, and use your attributes as needed!
In my show view I have a few attributes that aren't required to be filled in by the user. In my show view I can easily create a conditional like so:
<% if #user.occupation.present? %>
<p><%= #user.occupation %></p>
<% end %>
But if I have 3 or more of these optional attributes, creating multiple conditionals for each of them can become very tedious. I thought of doing something like this but it turns out that the method attribute is private:
<% #user.attributes.map do |attribute, name| %>
<% if #user.attributes.present? %>
<p><b><%= name %>:</b> <%= #user.attribute %></p>
<% end %>
<% end %>
For security reasons I'm not necessarily trying to change the attribute method from being private but more so just looking for a way to list attributes of my model that have been filled in.
<% #user.attributes.each do |key, value| %>
<% if value.present? %>
<p><b><%= key %>:</b> <%= value %></p>
<% end %>
<% end %>
When I read the documentation of cancan you can do something like this:
<% if can? :edit, #product %>
Now I have the following code and there it doesn't work.
<% #products.each do |product| %>
<% if can? :sort, |product| %>
MyButton
<% end %>
How can I make sure this works as well? What syntax do I need to use? I hope I don't need to do a
#product = Product.find(|product|.id)
because that would clutter the views.
Each time you should pass to can? method Product instance, which is in product local variable, so:
<% #products.each do |product| %>
<% if can? :sort, product %>
MyButton
<% end %>
<% end %>
I have a product display page which is displaying all products on website. Here I want to filter products as per their Owner. As a start, i am displaying owner names on page using each loop:
<% #products.each do |p| %>
<%= link_to p.user.profile.first_name, store_index_path %>
<% end %>
But as owner has multiple products, his name gets displayed multiple times. How to show the name only once?
In simple way you can do like this:
<% #products.map(&:user).uniq.each do |u| %>
<%= link_to u.profile.first_name, store_index_path %>
<% end %>
You can use group_by to create a hash of User => [Products]. Then, you iterate through the unique set of Users, display the information about that owner, and then display each product for that User.
<% products_by_owner = #product.group_by(&:user) %>
<% products_by_owner.keys.each do |user| %>
<%= link_to p.user.profile.first_name, store_index_path %>
<% products_by_owner[user].each do |product| %>
<%= link_to product.name, store_index_path %>
<% end %>
<% end %>
You can use group_by to create a hash with the users as keys and arrays of products as values:
# Eager loading of users will prevent multiple database hits.
# Using find:
#products = Product.find(:all, :include => :user).group_by(&:user)
# using relations:
#products = Product.where(:some_condition => 'etc').includes(:user).group_by(&:user)
In the view:
<% #products.each do |user, user_products| %>
<%= link_to p.user.profile.first_name, store_index_path %>
<% user_products.each do |product| %>
...
<% end %>
<% end %>
I have a model Category and a model Weblink. Category has_many Weblink and Weblink belongs_to Category. Now I want to show all categories in a view and within a category all weblinks belonging to that category, something link this:
<ul>
<% #categories.each do |category| %>
<%= category.category_name %>
<% #weblinks.each do |weblink| %>
<%= weblink.category_name link_to weblink.link_name, weblink.link_url %>
<% end %>
<% end %>
In the controller I have:
#categories = Category.all
#weblinks = Weblink.all
This shows every category and within every category all weblinks, instead of just the ones which belong to the specific category. How can I fix this?
Your view code should look like this
<% #categories.each do |category| %>
<%= category.name >
<% category.weblinks.each do |weblink| %>
<%= link_to weblink.name, weblink.link_url %>
<% end -%>
<% end -%>
It your controller, when querying for all the categories you should also include the weblinks model, something like this:
#categories = Category.all(:include => :weblinks)
Scope the inner loop to the outer category using the macro you get with has_many:
<% #categories.each do |category| %>
<%= category.category_name %>
<% category.weblinks.each do |weblink| %>
<%= link_to weblink.link_name, weblink.link_url %>
<% end %>
<% end %>