Ruby on Rails - How to combine display Search results in the View - ruby-on-rails

I have Search controller that searches 2 models - Posts and Categories. The search works, however I am unable to display results in the View correctly - I can't get category names to show up.
I am very confused and frustrated at this point and hope to find some help!
I'm pretty sure (99% sure) the problem is in the View somewhere, because I can get results to display through render inspect thingy.
SearchController.rb
class SearchController < ApplicationController
def index
#posts = Post.search(params[:search])
#categories = Category.search(params[:search])
# combine the results
#results = #posts + #categories
#results.uniq # may be necessary to remove duplicates
end
end
index.html.erb (views/search)
<%= render 'posts/posts', :posts => #posts %>
_posts.html.erb (view/posts)
<h1>Listing posts</h1>
<table>
<tr>
<th>Name</th>
<th>Category</th>
<th>Description</th>
<th>Flag</th>
</tr>
<% if posts %>
<% posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= post.category.name %></td>
<td><%= post.description %></td>
<td><%= post.flag %></td>
</tr>
<% end %>
<% else %>
<tr><td>No posts</td></tr>
<% end %>
</table>
I can get posts that match the search to display, but I can't display categories. How can I do this? Any help highly appreciated!! Thank you.

If you are using a search backend like sunspot solr then you would be able to combine the searches like
#search = Sunspot.search [User, Company] do
fulltext params[:search]
end
#results = #search.results
And then return the necessary values. In this example, it's showing where you can retrieve the class of the action (controller_name) may not work depending on which controller the results are returned in.
<% #results.each do |result| %>
<% case result.class.to_s %>
<% when "Company" %>
<li><%= "Company: #{result.name}" %></li>
<% when "User" %>
<li><%= "User: #{result.username}" %></li>
<% end %>
<% end %>

The answer (I include all the things I changed to make it work + few files that I didn't change but that have to be there), or How to make simple search for multiple models:
SearchController.rb
class SearchController < ApplicationController
def index
#posts = Post.search(params[:search])
end
end
post.rb
class Post < ActiveRecord::Base
attr_accessible :category_id :name :description :flag
belongs_to :category
def self.search(search)
if search
find(:all, :joins => :category, :conditions => ['posts.equipment LIKE ? OR posts.description LIKE ? or categories.name like ?', "%#{search}%", "%#{search}%", "%#{search}%"])
else
find(:all)
end
end
search/index.html.erb
<%= render 'posts/posts', :posts => #posts %>
I added 2 files _post.html.erb and _category.html.erb. They are similar, this is _post.html.erb:
post: <%= post.name %>
(This might not be necessary in some cases or for some models. I can search a third model without this file in its' views. However the third model doesn't have file like _posts.html.erb either).
Finally, _posts.html.erb remains the same:
...
<% if posts %>
<% posts.each do |post| %>
<tr>
<td><%= post.name %></td>
<td><%= post.category.name %></td>
<td><%= post.description %></td>
<td><%= post.flag %></td>
</tr>
<% end %>
<% else %>
<tr><td>No posts</td></tr>
<% end %>
...
This works now. Can add new models to the search easily. The only other thing needed for the search is input field.

Related

What Controls the Mapping of the Rails Controller Method to the View Rendered?

I have a rails application which is not routing as I expected. The search method in the controller is rending show. I've cut down the code to the minimal components and I am posting them here as suggested.
Rails.application.routes.draw do
resources :backups
get 'backups/search' => 'backups#search'
resources :components
resources :backup_media
end
class Component < ActiveRecord::Base
has_many :backups
has_many :backup_media, :through => :backups
end
class BackupMedium < ActiveRecord::Base
has_many :backups
has_many :components, :through => :backups
end
class Backup < ActiveRecord::Base
belongs_to :component
belongs_to :backup_medium
# value to match either the name of the component or backup_medium
def self.search(value)
tables = "backups, components, backup_media"
joins = "backups.backup_medium_id = backup_media.id and components.id = backups.component_id"
c = find_by_sql "select * from #{tables} where components.name like '%#{value}%' and #{joins}"
b = find_by_sql "select * from #{tables} where backup_media.name like '%#{value}%' and #{joins}"
c.count > 0 ? c : b
end
end
class BackupsController < ApplicationController
def search
#backups = Backup.search(params[:search])
render 'index'
end
def index
#backups = Backup.all
end
def show
# this would normally be the code to show an individual backup
# but I'm re-using the code from index because the routing is broken
#backups = Backup.all
end
end
views/backups/_search.html.erb
<%= form_tag backups_search_path, :method => 'get' do %>
<%= label_tag(:search, "Search for:") %>
<%= text_field_tag :search, params[:search], {:placeholder => 'Component or Media' }%>
<%= submit_tag "Search", :name => nil %>
<% end %>
views/backups/index.html.erb
<h1>Listing Backups</h1>
<p id="notice"><%= notice %></p>
<%= render :partial => 'search' %>
<table>
<tr>
<th>id</th>
<th>component_id</th>
<th>backup_medium_id</th>
</tr>
<% #backups.each do |backup| %>
<tr>
<td><%= backup.id %></td>
<td><%= backup.component.name %></td>
<td><%= backup.backup_medium.name %></td>
</tr>
<% end %>
</table>
views/backups/show.html.erb is copied from index.html.erb since it is incorrectly receiving the search results
<h1>Show Backup</h1>
<p id="notice"><%= notice %></p>
<%= render :partial => 'search' %>
<table>
<tr>
<th>id</th>
<th>component_id</th>
<th>backup_medium_id</th>
</tr>
<% #backups.each do |backup| %>
<tr>
<td><%= backup.id %></td>
<td><%= backup.component.name %></td>
<td><%= backup.backup_medium.name %></td>
</tr>
<% end %>
</table>
Suggestions on improving the search method will be welcomed.
As mentioned above, after the search is executed, the show.html.erb is rendered instead of search.html.erb
For a working demo (with better code thanks to suggestions here) see
https://github.com/pamh09/rails-search-demo
You do not have a backups_search_path in your routes, therefore it is treating search in the query string as an id and thus rendering show.html.erb, so try
get 'backups/search' => 'backups#search', as: :backups_search
In debugging I found that rails consistently routed to the wrong view when it was unhappy with the return object coming from the model.

Displaying has and belongs to many associations in index

Hey I'm new to Rails and all this so bear with me, thanks!
I have two models:
class User < ApplicationRecord
has_and_belongs_to_many :sports
end
class Sport < ApplicationRecord
has_and_belongs_to_many :users
end
My users have a few different sports that they can choose each. I'm simply trying to display all users in a table, along with which sports they do. However.. the only way I've managed to get anything without an error is by using current_user as shown below. I've been looking how to do this for hours... I know it's going to be stupidly simple but I just can't figure it out or even know how to go in the right direction.
# users_controller.rb
def index
#users = User.all
#sports = current_user.sports
end
# users/index.html.erb
<% #users.each do |user| %>
<tr>
<td><%= link_to user.name, user %></td>
<td><%= link_to user.email, user %></td>
<% #sports.each do |s| %>
<td><%= s.name %></td>
<% end %>
</tr>
<% end %>
That's my current code but obviously this shows only the signed in users associations and repeats it for the other users like this:
<table>
<tr>
<th>Name</th>
<th>Sport 1:</th>
<th>2:</th>
</tr>
<tr>
<td>User 1 (current_user)</td>
<td>Football</td>
<td>Running</td>
</tr>
<tr>
<td>User 2</td>
<td>Football (User 1's Sports)</td>
<td>Running </td>
</tr>
</table>
Thanks in advance.
You can try using the following and deleting #sports = current_user.sports:
<% user.sports.each do |s| %>
<td><%= s.name %></td>
<% end %>
using user.sports while looping through each of the user will lead to N+1 queries on your database. You can change your controller method to something like
def index
#users = User.all.eager_load(:sports)
end
and then in html
<% user.sports.each do |s| %>
<td><%= s.name %></td>
<% end %>
This will load users along with left_outer_join on sports table and this will save to lot of extra queries on your database.
For Info you can refer this good blog.
Thanks

Removing conditional logic from a shared partial view or alternative solution

For a current project, I have duplicate code between views, and I'm not sure of the best route to refactor it.
I appear to be in a position where I can have duplicate code across various .html.erb files, or I could put identical code into a partial and use conditionals. I've always heard logic should stay out of views. Neither option seems ideal, and I don't currently know of alternatives.
To illustrate my question, I created a simple rails app called animals. I scaffolded for two models: one for cat and one for dog. Images display their corresponding attributes:
Displaying #cats and #dogs is pretty much the same. Cats just have a column for meows while Dogs have a column for barks, and a dog has the additional attribute column of plays_catch.
Lets say we choose to reduce the duplicate code for displaying cats and dogs by making a shared view partial:
#views/shared/_animal.html.erb
<tr>
<td><%= animal.name %></td>
<td><%= animal.age %> </td>
<% if animal.class == Cat %>
<td><%= animal.meows %> </td>
<% end %>
<% if animal.class == Dog %>
<td><%= animal.barks %> </td>
<td><%= animal.plays_catch %> </td>
<% end %>
</tr>
Then to render #cats = Cat.all:
<%= render partial: "shared/animal", collection: #cats %>
Then to render #dogs = Dog.all:
<%= render partial: "shared/animal", collection: #dogs %>
Obviously it would be overkill to do something like this for this specific example, but the real world project I'm applying it to would not be overkill.
The overall question is: how do you remove nearly identical code that iterates over collections, where the only difference is adding/removing a column of information? It just doesn't feel right to put that logic in the view itself, and leaving the duplication feels wrong.
You could use decorators and add methods that return the extra column(s):
class DogDecorator < Draper::Decorator
def extra_columns
[:barks, plays_catch]
end
end
class CatDecorator < Draper::Decorator
def extra_columns
[:meows]
end
end
...
<% animal.extra_columns.each do |column| %>
<td><%= animal.attributes[column.to_s] %>
<% end %>
...
<% #cats = CatDecorator.decorate_collection(Cat.all)
<%= render partial: "shared/animal", collection: #cats %>
You can use respond_to? to solve the problem more generically. The view logic doesn't feel so wrong when it's more generic.
<% [:meows, :barks, :plays_catch].each do |method| %>
<% if animal.respond_to?(method) %>
<td><%= animal.send(method) %> </td>
<% end %>
<% end %>
You can add a method of the same name to both Cat and Dog classes which would return the specific instance attributes names and values. I'd recommend returning two arrays (one with the names of the fields, other with the fields' values, or vice-versa) since hashes are not exactly ordered. This way you can control the order in which they'll appear in the view.
For example:
#models/cat.rb
def fields_and_attributes
fields = ["Name","Age","Meows"]
attributes = [self.name, self.age]
if self.meows
attributes.push("Yes")
else
attributes.push("No")
end
[fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end
#models/dog.rb
def fields_and_attributes
fields = ["Name","Age","Plays catch"]
attributes = [self.name, self.age]
if self.plays_catch
attributes.push("Yes")
else
attributes.push("No")
end
[fields,attributes] # make sure each attribute is positioned in the same index of its corresponding field
end
#controllers/animals_controller.rb
def display_animals
#animals = Cat.all + Dog.all # an array containing the different animals
end
#views/display_animals.html.erb
for i in (0...#animals.size)
fields_and_attributes = #animals[i].fields_and_attributes
for f in (0...fields_and_attributes[0].size)
<p><%= fields_and_attributes[0][f] %> : <%= fields_and_attributes[1][f] %></p>
end
end
Here, we first iterate over all of the animals and call the .fields_and_attributes method of that specific record; we then iterate over the results of calling that method, displaying fields and attributes in the same order as the one defined within the method and also guaranteeing that the code will display every field and every attribute regardless of the difference in the total number of fields for each different animal.
I don't know of any canonical way to accomplish this, but I would use one partial for this in the following way:
<tr>
<% animal.attributes.each do |_, value| %>
<td><%= value %></td>
<% end %>
</tr>
You can get rid of repeated attributes calls by providing in the partial a local variable with pre-obtained model attributes.
EDIT: if you only want to display some attributes.
# Declare whitelist of attributes
# (you can also declare a blacklist and just calculate the difference between two array: all_attributes - blacklist_attributes):
<% whitelist = [:name, :age, :barks] %>
<%= render partial: 'shared/animal',
collection: #dogs,
locals: {attrs: (#dogs.first.attributes.keys.map(&:to_sym) & whitelist)} %>
views/shared/_animal.html.erb:
<tr>
<% attrs.each do |attr| %>
<td><%= animal[attr] %></td>
<% end %>
</tr>
Below is my answer after reviewing posted answers. Basically:
I left the differences within each scaffold model's index page
I made shared partials for common table headers and table data
code below:
#app/views/cats/index.html.erb
<h1>Listing Cats</h1>
<table>
<thead>
<tr>
<%= render partial: "shared/cat_dog_table_headers" %>
<th>Meows</th>
</tr>
</thead>
<tbody>
<% #cats.each do |cat| %>
<tr>
<%= render partial: "shared/cat_dog_table_data", locals: {animal: cat} %>
<td><%= cat.meows %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Cat', new_cat_path %>
And for the dogs:
#app/views/dogs/index.html.erb
<h1>Listing Dogs</h1>
<table>
<thead>
<tr>
<%= render partial: "shared/cat_dog_table_headers" %>
<th>Barks</th>
<th>Plays catch</th>
</tr>
</thead>
<tbody>
<% #dogs.each do |dog| %>
<tr>
<%= render partial: "shared/cat_dog_table_data", locals: {animal: dog} %>
<td><%= dog.barks %></td>
<td><%= dog.plays_catch %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Dog', new_dog_path %>
The shared table headers for cats and dogs:
#app/views/shared/_cat_dog_table_headers
<td><%= Name %></td>
<td><%= Age %></td>
The shared table data for cats and dogs:
#app/views/shared/_cat_dog_table_data_headers
<td><%= animal.name %></td>
<td><%= animal.age %></td>

rails 4 def to_param with 2 models

I came accross something strange when trying to create friendly URLs.
I have an item model which has:
def to_param
"#{id}-#{name}".parameterize
end
This makes my item URLs contain the "ID" and "name" just fine:
www.domain.com/items/ID-name
I also have a category model (item belongs to category and user models) where I have the same def to_param as above but the category URLs stay "unfriendly" no "name" included:
domain.com/categories/ID
I have name column in category table and it has values.
I also use ancestry for the category model. Maybe has_ancestry is causing the issue?
I tried below but no luck:
def to_param
[id, name.parameterize].join("-")
end
Thanks for any advice!
Papirtiger's comment lead me to the solution.
The problem was I had links like:
<% #category.children.in_groups_of(4, false) do |childs| %>
<tr>
<% for categories in childs %>
<td>
<%= link_to "../categories/#{categories.id}" do %><%= categories.name %><% end %></td>
<% end %>
</tr>
Beginners solution. :)
Removed the ugly part and now it works:
<% #category.children.in_groups_of(4, false) do |childs| %>
<tr>
<% for categories in childs %>
<td><%= link_to categories do %><%= categories.name %><% end %> (<%= Item.where(:category_id => categories.id).count %>)</td>
<% end %>
</tr>

Rails 3 , Simple search form problem

Code in my product model (product.rb):
def self.search(search)
if search
find(:all)
else
find(:all)
end
end
Code in my search controller (search_controller.rb):
def index
#products = Product.search("Apple")
end
Code in my view (index.html.erb):
<h1>Products</h1>
<% form_tag client_search_path , :method => :get do %>
<p>
<%= search_field_tag :term, params[:term], :class=> "auto_search_complete"%>
<%= submit_tag "Search", :name => nil, :class => 'button', :id => "search_bn" %>
</p>
<% end %>
<table border="1px">
<tr>
<th>Name</th>
<th>Brand</th>
<th>Quantity available</th>
<th>Category</th>
<th>Shopcenter name</th>
<th>Shopcenter streetnumb</th>
<th>Shopcenter streetname</th>
<th>Shopcenter postal</th>
<th>Shopcenter province</th>
</tr>
<% for product in #products%>
<tr>
<td><%= product.name %></td>
<td><%= product.brand %></td>
<td><%= product.quantity_available %></td>
<td><%= product.category %></td>
<td><%= product.shopCenter_name %></td>
<td><%= product.shopCenter_streetNumb %></td>
<td><%= product.shopCenter_streetName %></td>
<td><%= product.shopCenter_postal %></td>
<td><%= product.shopCenter_province %></td>
</tr>
<% end %>
</table>
I load this all is good, but if I comment one of the line of codes in my model:
def self.search(search)
if search
#find(:all)
else
find(:all)
end
end
I expect this to work also at least for the initial render, or when I submit an empty search term, but it's not. And changing the code to of the model to:
def self.search(search)
if search
find_all_by_name(search)
else
find(:all)
end
end
Doesn't work it gives me an error that the view is working with a nil object, which is impossible because my database has entries.
Can someone explain what is going on? I have the impression that both the conditions in my model are being executed. At least that's what 2 puts statement in each case showed me.
Please advice.
I think you should set search = nil if search == "" in your controller otherwise it will always go to the first condition.
It had some compatibility issues with Rails 3.
I updated rails and ruby and it works fine now

Resources