I have a table Projects each with 0 or more Categories. On my view, I want to display 0 projects until a JQuery click event associated with each category--i.e. when the user clicks "Food," I want to display all projects with category Food; when the user clicks "Photos," I want to display BOTH food and photos-related projects.
So on the jQuery click event I define an ajax call:
params = 'category_name=' + cat;
$.ajax({
url: "/projects_controller/filter_list",
data: params
})
where "cat" is the names of the Categories selected (in the format "Food Photography Journal etc")
In my projects_controller I started a filter_list method:
def filter_list
#categories = []
words = params[:category_name].split(/\W+/)
words.each { |word| #categories.push(Category.where("name = ?", word)) }
#projects = ...
end
But now I'm stuck. 1) How do I get all the projects associated with any of the categories in #categories? and 2) How do I display the #projects variable on my view? Right now I just display all like this:
<% Project.all.each do |project| %>
<tr style="display:none" class="project <% project.categories.all.each do |cat| %><%= cat.name %> <% end %>">
<td><%= project.filename %></td>
<td><a href='project/<%= project.id %>'><%= project.location %></a>
<td><% project.categories.all.each do |cat| %><%= cat.name %>, <% end %></td>
<% end %>
Your instance variables $categories, #projects are already available in the view. So in the view you can use #project rather than accessing the Class Project itself.
<% #projects.each do |project| %>
...
<% end %>
But probably you did not design your models correctly. Establish the correct relationships in your model. If a project belongs to a category, you can associate them as follows:
#models/category.rb
class Category < ActiveRecord::Base
has_many :projects
end
#models/project.rb
class Project < ActiveRecord::Base
belongs_to :category
end
#controllers/categories_controller.rb
def index
#categories = Category.all #or use your own query
end
#views/categories/index.erb
<% #categories.each do |category| %>
# here you can get all projects under this category using
# category.projects
<% end %>
Note: i'm used to HAML, so sorry if my ERB syntax is wrong
The according view(s) to a controller class have have access to the class level instance variables (i.e. #my_variable).
Simply let #projects = Project.all.each in your controller and substitute Project.all with #projects in your view.
Related
I'm new to ruby on rails and I'm building a wiki app where the navigation is to be sorted by categories. Each article, or page, can belong to a category, but a category can also be a sub-category of another category. An administrator will be able to create new categories or sub-categories calling for a dynamic approach to generating a list of categories for the menu. I'm trying to figure out how to display a list of all parent categories and all of their children and grandchildren categories where the menu would look something like this:
1. Parent1
1.a Child1
1.b Child2
2. Parent2
2.a Child1
2.a.1 Grandchild1
I currently have some nested loops in my view which kind of work, but it's not dynamic since it will only show the first two generations, and I would have to repeat the code to show more.
Model:
class Category < ApplicationRecord
has_many :sub_categories, class_name: "Category", foreign_key: "category_id"
belongs_to :category, class_name: "Category", optional: true
end
Controller:
class CategoriesController < ApplicationController
def index
#sorted_categories = Category.order(:sort_number).where("category_id IS NULL")
#sub_categories = Category.order(:sort_number).where("category_id IS NOT NULL")
end
end
View:
<% if #categories.nil? %>
<h3>There are currently no categories.</h3>
<% else %>
<ul>
<% #sorted_categories.each do |c| %>
<li><%= c.name %><%= link_to 'Move Up', categories_move_up_path(c) %> Sort:<%= c.sort_number %></li>
<% #sub_categories.each do |s| %>
<% if s.category_id == c.id %>
<ul>
<li>
<%= s.name %><%= link_to 'Move Up', categories_move_up_path(s) %> Sort:<%= s.sort_number %>
</li>
</ul>
<% end %>
<% end %>
<% end %>
</ul>
<% end %>
Any advice would be greatly appreciated, thanks!
Have a look at the acts_as_list gem, it does exactly what you want.
It will define a parent_id column, and each object will be a child of a parent, so that you can create infinite tree of categories ans sub-categories.
It also provides the methods to move objects up and down.
I have the following loop in my view to display all divisions in a given tournament. When I try to replicate the logic in the controller, then pass the variable to the view, I and "undefined methods" error. Any help with what I'm doing wrong would be appreciated.
Models
class Tournament < ApplicationRecord
has_and_belongs_to_many :divisions
end
class Division < ApplicationRecord
has_and_belongs_to_many :tournaments
end
WORKING Controller & View
Controller
def index
#tournaments = Tournament.all
end
View
<% tournament.divisions.ids.each do |tdi| %>
<%= Division.find(tdi).name %>
<% end %>
NOT WORKING Controller & View
Controller
def index
#tournaments = Tournament.all
#tournaments.divisions.ids.each do |tdi|
#divisions = Division.find(tdi).name
end
end
View
<%= #divisions %>
When I try the second (and I'm sure more correct) implementation, I get a "undefined method `divisions'" error for the following line in the index method:
#tournaments.divisions.ids.each do |tdi|
The problem is that #tournaments = Tournament.all this line returns a list of tournament objects. So, in the second line you cannot relate a list of object with any kind of association. You have to Iterate through the #tournaments and then find the divisions for each tournament.
def index
#tournaments = Tournament.all
# you can also eager load the tournamnets
# #tournaments = Tournament.includes(:divisions)
#tournaments.each do |tournament|
tournament.divisions.ids.each do |tdi|
#divisions = Division.find(tdi).name
end
end
end
What I think the error is telling you is that the method ids is not a method of divisions. Yo would have to define that method as scope in your Division model.
scope :ids, -> { pluck(:id) }
Another thing is that I don't understand why are you trying to do something like this:
<% tournament.divisions.ids.each do |tdi| %>
<%= Division.find(tdi).name %>
<% end %>
when you can just simply do this:
<% tournament.divisions.each do |tdi| %>
<%= tdi.name %>
<% end %>
First thing, in the 2nd case you are calling association on collection, You need to do this, in controller, ultimately you need all the division names, here you also have n + 1 query problem so to solve that
def index
#tournaments = Tournament.includes(:divisions)
end
In view
#tournaments.each do |tournament|
tournament.divisions.each do |division|
<%= division.name %>
<% end %>
<% end %>
Hope that helps!
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.
How can I show recent added #post and #photos in one list? For example:
post - LAlala (10.10.2011)
photos - [] [] [] [] (1.1.2011)
post - Bbbdsfbs (2.12.2010)
post - Lasdasdf2 (2.10.2009)
#posts = Post.limit(20).order('created_at desc')
#photos = Photo.limit(20).order('created_at desc')
#recent_items = (#posts + #photos).sort_by(&:created_at)
<% #recent_items.each do |item| %>
<% if item.class == "Photo" %>
<%= image_tag item.url %>
<% else %>
<%= item.title %>
<% end %>
<% end %>
Alternatively, use group_by to do it like this:
#recent_items = (#posts + #photos).group_by(&:created_at)
<% #recent_items.each do |date, items| %>
Date: <%= date %>
<% items.each do |item| %>
Show information here.
<% end %>
<% end >
I would move the view logic into a helper if I were you for DRYer code.
It is much better to do this is the database.
I just say this: polymorphism + database views.
Create a database view which contains the columns you need from both Post and Photo, including the column "type" containing a the name of the model (you need it for the polymorphism). Call this view for example "list_items". Then create a model called "ListItem". Then you can use this model like any other, paginate it and whatever you need to do.
ListItem.order("created_at > ?", Date.yesterday).page(params[:page])
And don't forget to configure the polymorphic association
However, all this is much easier to accomplish with the listable gem. Check it out!
I have a Rails 3 ActiveRecord that belongs_to two different ActiveRecords. Example
class Animal < ActiveRecord::Base
belongs_to: species
belongs_to: zoo
...
end
where the animals table contains a species_id, zoo_id, name, and description and the tables species with a scientific_name and zoo has address.
In the controller, I have a query
#animals = Animal.includes(:species, :zoo).order(:name)
and a list of columns I want displayed in the view,
#columns = ["name", "description", "species.scientific_name", "zoo.address"]
In the view, I want the creation of a HTML table to be driven by the list of column names, e.g.
<table>
<tbody>
<tr>
<% #animals.each do |animal| %>
<% %columns.each do |col| } %>
<td><%= animal[</td>
<% end %>
<% end %>
</tr>
</tbody>
</table>
this works great for animals's name and description, but does not work for species.scientific_name and zoo.address.
I know I could special case the loop and access the included classes directly like animal.species['scientific_name'], but I was hoping there would be a way to access the included classes by name. Something like animal['species']['scientific_name']
Approach 1
Monkey patch the ActiveRecord class. Refer to this answer for details about monkey patching AR class.
class ActiveRecord::Base
def read_nested(attrs)
attrs.split(".").reduce(self, &:send)
end
end
Sample nested attribute access:
animal.read_nested("zoos.address")
user.read_nested("contacts.first.credit_cards.first.name")
product.read_nested("industry.category.name")
For your use case:
Controller:
#columns = %w(name color zoo.address species.scientific_name)
View
<% #animals.each do |animal| %>
<% #columns.each do |col| } %>
<td><%= animal.read_nested(col)%></td>
<% end %>
<% end %>
Approach 2
Add select clause to select the columns and alias them.
#animals = Animal.includes(:species, :zoo).select("
animals.*,
species.scientific_name AS scientific_name,
zoos.address AS zoo_address").
order(:name)
Now in your view, you can access attributes like scientific_name, zoo_address like regular model attributes.