View loop based on model column data [Rails] - ruby-on-rails

This is probably very simple to do, but I am having trouble wrapping my head around it. I have a model called "document" and a field within the model called: category.
Category can consist of 3 options:
Meeting Notes
Sales
Engineering
In my view I have: [index.html.erb]
<% #documents.each do |document| %>
<div class="table-div">
<div class="col-sm-12 col-md-12 div-table-label">
<strong><%= document.category %></strong>
</div>
<div class="col-sm-12 col-md-12">
<%= link_to document.title, document %>
</div>
</div>
<% end %>
In my controller, just the default:
def index
#documents = Document.all
end
What I want to do is loop through each category and grab all documents tied to that category to group them together.
Right now, it just prints each document with its category heading in the order it is created, no logic to it at all.
Thanks.

Try
def index
#documents = Document.where(true).order(:category).order(:title)
end
so that the ordering is done on the database which can be faster especially if you have a index on the category column and to order the documents by title
And change the view to render the header when ever a new category is encountered
<% category = "" %>
<% #documents.each do |document| %>
<div class="table-div">
<% if category != document.category %>
<div class="col-sm-12 col-md-12 div-table-label">
<strong><%= document.category %></strong>
</div>
<% category = document.category %>
<% end %>
<div class="col-sm-12 col-md-12">
<%= link_to document.title, document %>
</div>
</div>
<% end %>

What I want to do is loop through each category and grab all documents
tied to that category to group them together.
I'd start with an easy way to find documents by category name. For this, a scope is great:
class Document
# ...
scope :for_category, -> category_name { where(category: category_name) }
# ...
end
Then you could loop through the category names and find all documents for that category, such as:
ALL_CATEGORIES = ["Meeting Notes", "Sales", "Engineering"]
All_CATEGORIES.each do |category|
documents = Document.for_category(category)
# Do something with the documents for this category...
end
Of course, it's not really ideal to put this much logic into the view, but expanding on this is a bit beyond the scope of this question. If curious, look into Avdi Grimm's Exhibit Pattern.

You could always create a static method in the Document class that does the following:
class Document
...
def self.all_by_category
docs_by_category = {}
all.each do |document|
if docs_by_category.has_key? document.category
docs_by_category[document.category] << document
else
docs_by_category[document.category] = [document]
end
end
docs_by_category
end
You could then look through the returned hash and print out the documents that are a part of each category like so:
def index
#you may want to change the name because it's misleading to leave it as #documents
#documents_by_category = Document.all_by_cateogry # returns hash
end
and then
<% #documents_by_category.each do |category_name, documents|
documents.each do |document| %>
YOUR CODE HERE
<% end %>
<% end %>
If the number of documents are high, however, this method may become inefficient.
It might be better to create another database table for categories, and then use a Category model to query each category and get their associated documents.

do you mean something like:
#categories.each do |cat|
# output cat.name or something
cat.documents.each do |doc|
# doc.name or whatever
end
end
you'd obviously need to get the #categories in your controller
def index
#categories = Category.all
end
alternatively you could be getting the documents grouped by their category
def index
#documents = Document.all.group_by(&:category)
end
see here

Related

Rails retrieving all records in show controller

In my rails category show controller for categories I have it setup like this
def show
#categories = Category.find_by(params[:name])
end
But when I visit this controller it returns all records of products found in the category instead of single category.
Here is the code in my view controller for category
<div class="grid">
<% #categories.products.each do |product| %>
<%= link_to product_path(id: product.slug, category_name: product.category.name), class: "card" do %>
<div class="product-image">
<%= image_tag product.productpic.url if product.productpic? %>
</div>
<div class="product-text">
<h2 class="product-title"> <%= product.name %></h2>
<h3 class="product-price">£<%= product.price %></h3>
</div>
<% end %>
<% end %>
</div>
What am i doing wrong here?
First of all, for security purposes, you should never trust the params hash to retrieve records. Rails will "make the data safe" if you use a hash as your arguments. Use this code below:
def show
#category = Category.find_by(name: params[:name])
end
Second, usually on a show page, you only want to retrieve one record and therefore the variable should be named as singular. I corrected that above.
Third, it helps if you use proper indenting when posting examples. It makes it easier for us to help you.
Fourth, the line below (I changed #categories to #category) is basically saying: "Now that I have this single category, find all the products associated with it in the products table and put them into the |product| variable for iteration"
<% #category.products.each do |product| %>
I'm not sure what you want to do with the category, but if you keep this line of code, it will always show you all the products. Maybe you only want to show the most recent 3, in which case you could do something like this:
In your controller:
def show
#category = Category.find_by(name: params[:name])
#recent_products = #category.products.order(created_at: :desc).limit(3)
end
In your view:
<div class="grid">
<% #recent_products.each do |product| %>
<%= link_to product_path(id: product.slug, category_name: product.category.name), class: "card" do %>
<div class="product-image">
<%= image_tag product.productpic.url if product.productpic? %>
</div>
<div class="product-text">
<h2 class="product-title"> <%= product.name %></h2>
<h3 class="product-price">£<%= product.price %></h3>
</div>
<% end %>
<% end %>
</div>
You can do this way
in your controller you can write this code
def show
#category = Category.find_by_name(params[:name])
end
and in your view it will work
<div class="grid">
<% #category.products.each do |product|%>
// place your code what you want to display
<% end %>
</div>
I hope it would help you and still if you have any concern please let me know.

Link to previous/next record by specific attribute type

I have a Animal model which at the moment consists of Cats and Dog. I have a column called animal_type that will define what the animal is
When I view a record (show action) then it could be any animal type and I have created next and previous links to cycle through all the animal records:
def next_animal
animal = self.class.order('created_at desc').where('created_at > ?', self.created_at)
animal.first if animal
end
def previous_animal
animal = self.class.order('created_at desc').where('created_at < ?', self.created_at)
animal.last if animal
end
Controller
def show
#animal = Animal.find(params[:id])
end
View
<% if #animal.previous_animal %>
<%= link_to(#animal.previous_animal, {class: 'prev-page'}) do %>
<span class="glyphicon glyphicon-chevron-left"></span> Meet <span class="name"><%= #animal.previous_animal.name %></span>, the <%= animal_breed(#animal.previous_animal) %>
<% end %>
<% end %>
So if I am looking at a dog, what do I need to do to say only be able to cycle through the next and previous dogs, and not include any cat, and vice versa, so if I'm looking at a cat record, only cycle through other cats.
I've thought about a scope
scope :dog_type, -> { where(animal_type: 'Dog') }
but still unsure on how to implement.
You can do the following:
# model
def previous_animal
self.class.order('created_at desc').where('created_at < ?', self.created_at).where(animal_type: self.animal_type).first
end
# view
<% if previous_animal = #animal.previous_animal %> # local assignment in the if condition
<%= link_to(previous_animal, {class: 'prev-page'}) do %>
<span class="glyphicon glyphicon-chevron-left"></span> Meet <span class="name"><%= previous_animal.name %></span>, the <%= animal_breed(previous_animal) %>
<% end %>
<% end %>
The previous_animal method is simplified, calling .first on an ActiveRecord::Relation can't fail, but it can return nil.
I used the local variable assignment in the if condition because every time you call previous_animal on the record it trigger a SQL query. This local variable kind of act like a cache (won't trigger the SQL query multiple times).
def next_animal
animal = self.class.order('created_at desc').where('created_at > ? and animal_type = ?', created_at, animal_type)
animal.first if animal
end
Just add it in the where, if you use scopes then you're going to need an if statement in your previous and next.

Ruby on Rails: Grouping search results by category

I am working on a RoR WebApp. I'm trying to group results on the search page based on their taxonomy. What I want to do is to show a header for a category and list all results under that category. Something like:
CAT 1
products
CAT2
products
CAT3
.
.
I am trying using the following code:
<% if products.any? %> #products is the list of search results
<%= render :partial=> 'product_listing_feature', :locals => {:scope => scope, :scope_type => scope_type} %>
<div id="ql_product"></div>
<div class="product_rows">
<%taxons.each do |taxon|%> # taxons contains the list of unique categories in products
<div class = "product_row">
<h1><%=taxon%></h1>
<% taxonProducts = Array.new %>
<% products.each do |product| %>
<%#ptaxon = product.get_taxonomy%>
<%if #ptaxon == taxon%>
<% taxonProducts.push(product) %>
<% end %>
<% end %>
<div class ="featured_product_list">
<ul class = "featured_products">
<div class = "page">
<%= render :partial=> 'product_listing', :locals=>{:collection=> taxonProducts} %>
</div>
</ul>
</div>
</div>
<% end %>
</div>
<% end %>
Surprisingly it starts the 2nd category from a new row, but the following categories appeared jumbled up, something like
CAT1
products
CAT2
products CAT3
products
picture would give a better idea.
I am really surprised why it works only for one iteration. Could someone please help me fix this.
Thanks a lot
Way, way too much logic for a view. Just use group_by in your controller, which will give you a mapping of names to arrays of products:
products = Product.includes(:taxon).group_by { |p| p.taxon.name }

how to check whether association exists rails

I have two tables
Post
Category
I have the following code in my controller:
#category = Category.find(id)
Suppose consider category as mobile, car, bike
I need to check the existence of mobile or car or bike. So that I can disable the input element. I tried:
<% #category.each do |cat| %>
<% if cat.posts.exists %>
class = 'active'
<% else %>
class = 'inactive'
<% end %>
<div class="#{class}"><%= cat.name %></div>
<% end %>
The above code always runs the else condition.
Following are the categories:
Mobile
Car
Bike
If in my post table with column category_id has the field with value 1 then mobile should have a class active and the other two should be inactive.
I think you are looking for the any? method:
<% #category.each do |cat| %>
<div class="#{cat.posts.any? ? 'active' : 'inactive'}"><%= cat.name %></div>
<% end %>

grouping children by parent attribute

Here's what I am trying to achieve:
Group_x.name
member1.name -- member1.join_date -- etc
member2.name -- member2.join_date -- etc
...
Group_y.name
member1.name -- member1.join_date -- etc
member2.name -- member2.join_date -- etc
...
What I'm going for is really very similar to this although the implementation there doesn't work for me.
I've gotten this far in my controller:
def index
# https://stackoverflow.com/a/17835000/2128691
#user_group_ids = current_user.student_groups.map(&:id)
#students = Student.where('student_group_id IN (?)', #user_group_ids)
# https://stackoverflow.com/a/10083791/2128691
#students_by_group = #students.uniq {|s| s.student_group_id}
#title = "All students"
end
and calling the following in my view -
<% #students_by_group.all.each do |x| %>
<p>
<%= "#{x}" %>
</p>
<% end %>
gives me a list of all student objects. if i call <%= "#{x.name}" %> or <%= "#{x.created_at}" %>, etc, I get the correct information, and everything is great.
But now that I have all this information, how can I put the group.name (in my code it would be x.student_group.name) as a header for all of the students for which that group_name is true?
I think you need to use group_by on #students_by_group like this:
#students_by_group = #students_by_group.group_by { |s| s.student_group }
This would return a hash with the keys being the student group objects and the values being the students that belongs to this group, then you can do this in your view:
<% #students_by_group.each do |group, students| %>
<h3><%= group.name %></h3>
<% students.each do |x| %>
<p>
<%= "#{x}" %>
</p>
<% end %>
<% end %>
As an additional note, the group_by would fire a query for each student, so you may want to eagerly load the student group for each student like this for some performance gain:
#students = Student.where('student_group_id IN (?)', #user_group_ids).includes(:student_group)

Resources