Ruby on Rails search function solutions - ruby-on-rails

I'm new to Ruby on Rails, so I have serveral questions to my search function.
Search View:
<p>
<%= form_tag students_path, :method => 'get' do %>
<p> Advanced_search: <%= check_box_tag "advanced_search", value = "1" %> </P>
<%= select_tag(:attribute, options_for_select([['Prename',0],['Lastname',1]])) %>
<%= text_field_tag :search%>
<%= submit_tag "Search"%></p>
Controller:
def index
#stud = Student.search(params[:search], params[:advanced_search], params[:attribute])
end
Model:
def self.search(search, advanced_search, attribute)
ary = []
if advanced_search
case attribute
when '0'
ary << Array(where(Student.arel_table[:prename].matches("%#{search}%")))
when '1'
ary << Array(where(Student.arel_table[:lastname].matches("%#{search}%")))
else
raise ArgumentError, 'Something strange happened! problem with select_tag in the search function'
end
elsif search
case attribute
when '0'
ary << Array(where(prename: search))
when '1'
ary << Array(where(lastname: search))
else
raise ArgumentError, 'Something strange happened! problem with select_tag in the search function'
end
else
Student.all
end
ary
end
Index View:
<% #stud.each do |student_arr|%>
<% student_arr.each do |student| %>
<li> <%= student.prename + " " + student.lastname + " " + student._format_birthday + " DaZ: " + student.daz.to_s%>
(<%= link_to "Details", action: "detail", id: student.id %>)
(<%= link_to "Edit", action: "edit", id: student.id %>) </li>
<% end %>
<% end %>
My solution works, I don't get duplicates, but the code look really badly and I have to say "look for pre- OR lastname". May anyone can help me with a better solution.
For me it would be perfect if u are just searching for a Student and u don't have to say for what u are looking for (pre- or lastname) and u don't get duplicates. Maybe a Student name is "Peter Peter" (bad example, but it could happen^^), so I just want to get Peter once in my Student_array. Besides I would like to have the option to search for an explicit prename, so if there is Student called "Hans-Peter" and Student called "Hans" but I just want to find "Hans"...
Is there a way to realize this problems with less and more beautiful code?
Thanks for help and sorry for my bad english.. I'm doing my best :)
Bye Bye

IMO, your code need a complete refactor. Here is the base to help you refactor your search logic:
# student.rb
def self.search(searched_string)
searchable_columns = %w( prename lastname )
sql_conditions = searchable_columns.map do |column_name|
"#{column_name} ILIKE :searched_string"
# use ILIKE if you are using PostgreSQL
# use LIKE if you are using MySQL or SQLite
end.join(' OR ')
where(sql_conditions, searched_string: "%#{searched_string}%")
end
Any Student record having prename or lastname containing the string searched will be returned.
You would have to change your controller's call to the search method accordingly.

Related

Can I move this code from erb to the model?

I have 2 models Auteur and Biblio that both have_and_belongs_to_many of each other.
In the template displaying the Authors (auteur) and its books (biblio), I have this code :
<% #auteurs.each do |e| %>
<p><%= e.nom_complet %>, <i> <%= e.biblios.map{|l| l.titre }.join(', ') %></i>, <%= e.biblios.map { |l| l.lieu }.join(', ') %>, <%= e.biblios.map { |l| l.annee }.join(', ') %>
</p>
<% end %>
As each biblio has 8 fields and can belong to different types according to its form of publication and type of content, this would lead to doing a lot of map and if statements in erb.
Although this works fine, there must be a better way of doing this.
Thanks
You can move this code as instance methods in Author model
def biblios_titre
biblios.map(&:titre).join(', ')
end
def biblios_lieu
biblios.map(&:lieu).join(', ')
end
def biblios_annee
biblios.map(&:annee).join(', ')
end
And use it in views
<% #auteurs.each do |e| %>
<p><%= e.nom_complet %>,
<i><%= e.biblios_titre %></i>,
<%= e.biblios_lieu %>,
<%= e.biblios_annee %>
</p>
<% end %>
You can create a helper method in the model.
def view_biblios
self.nom_complet << "<i>" <<
self.biblios.map(&:titre).join ", " <<
"</i>, " << self.biblios.map(&:lieu).join ", " <<
", " << self.biblios.map(&:annee).join ", "
end
And in the view
<%= e.view_biblios.html_safe %>
UPDATE
Since you probably want to keep titles together, based on your comment, you may need this method instead
def view_biblios
self.nom_complet << self.biblios.map{|b|
"<i>#{b.titre}</i>, #{b.lieu}, #{b.annee}"}.join ", "
end
Thanks for all the answers. In the end I did it this way. Using each was sufficient. The code below also has a bit of css, to titles appear indented below each author (in bold).
I'll do all the cleaning up of the code and moving pieces to helpers after I get my 8 models to communicate correctly :)
<% #auteurs.each do |e| %>
<p><strong><%= e.nom_complet %></strong>
<% e.biblios.each do |l| %>
<br><span id="indent"><i><%= l.titre %></i>, <%= l.lieu %>, <%= l.annee.to_s %></span>
<% end %>
</p>
<% end %>

Rails 4 search function

I have this simple search function:
class SearchController < ApplicationController
def index
if params[:search].blank?
redirect_to root_path
else
#results = Post.search(params[:search])
end
end
end
I want to implement the following functionalities but I am struggling to code:
1.How to record each of the input search terms and see whether which search terms are the most searched ones. I thought of using "first_or_create" method...
2.Give I have this title: "Peter Paul Mary", how do I split them and link_to each terms to search the search function
Please advise.
1.I think you can use two-dimensional array containing results of query for each single term.
#maching_posts = Array.new
#terms = params[:search].split
#terms.each do |term|
result = Post.where(title: term)
#maching_post << result
end
Now, you have array '#maching_post' containing results of query for each single term. Row [0] contains result of first term etc.
You can use this array to generate view. Like this:
<% #terms.each_with_index do |term, index| %>
<span>Results of <%= term %></span>
<% #maching_post[index].each do |post| %>
<%= post.title %></br>
<% end %>
<% end %>
2.To get single words from string you can use split() method.
"Peter Paul Mary".split
This method returns array ["Peter", "Paul", "Mary"]
To link each term, use
link_to 'term', controller: :search, action: :index, search: 'term'
You should use this method in loop, like
<% "Peter Paul Mary".split.each do |term| %>
<%= link_to term, controller: :search, action: :index, search: term %>
<% end %>

Count the number of records in group_by clause

I have the following categories grouped together:
#categories = Category.all.group_by { |c| c.name }
In my view I am displaying the category names like so:
<% #categories.each do |c, v| %>
<li><%= link_to c, blog_path(:name => c) %></li>
<% end %>
Which gives this for example:
Ruby
Ruby On Rails
CSS
What I want to achieve is next to each category name have the total number of posts with that category name, so:
Ruby(2)
Ruby On Rails(10)
So I have tried:
#categories = Category.joins(:posts).all.group_by { |c| c.name }
Which results in only the categories with a post object being displayed (previously all categories would display, regardless of whether they had a post object) and in my view I tried:
<% #categories.each do |c, v| %>
<li><%= link_to c, blog_path(:name => c) %><%= c.count %></li>
<% end %>
This is outputting nothing. I'd like to find how to approach this before I confuse the matter.
It's confusing to call the grouped categories #categories because that makes it sound like a collection of Category objects, when it's actually a Hash. Using descriptive names, including in your loop, makes your code much clearer.
Try this:
#category_groups = Category.includes(:posts).all.group_by { |c| c.name }
and the view
<% #category_groups.each do |name, categories| %>
<li><%= link_to name, blog_path(:name => name) %> (<%= categories.map{|category| category.posts.size}.sum %> posts)</li>
<% end %>
I'm not sure why you're using group_by. However, you don't need a join because you don't have any condition on posts. Other examples suggest eager loading posts, but that seems overkill to initialise all the post objects in memory to just get the count of them. You'd need to do your own benchmarks though. Consider a counter_cache like another answerer suggested.
#categories = Category.all
and in the view:
<% #categories.each do |c| %>
<li><%= link_to c.name, blog_path(:name => c.name) %> (<%= c.posts.count %> posts)</li>
<% end %>
Further explanation
group_by returns a hash with the key as the unique return value from the block, and the value are all items of the original array for which the block evaluates to that key. Taking your example of Category.all (the scope is converted to an array before group_by so that's how we'll represent it here):
cats = [
#<Category name: "foo" ... >,
#<Category name: "bar" ... >,
#<Category name: "baz" ... >
]
These three categories have unique names, so using .group_by { |c| c.name } does nothing but create a pointless hash with the keys as the name, and each value as an array with one Category object like:
{
"foo" => [#<Category name: "foo" ... >],
"bar" => [#<Category name: "bar" ... >],
"baz" => [#<Category name: "baz" ... >]
}
Here's an example of where you might use group_by to some effect:
languages = ["Ada", "C++", "CLU", "Eiffel", "Lua", "Lisp",
"Perl", "Python", "Smalltalk"]
languages_grouped_by_first_letter = languages.group_by { |s| s[0] }
=> {"A"=>["Ada"], "C"=>["C++", "CLU"], "E"=>["Eiffel"], "L"=>["Lua", "Lisp"], "P"=>["Perl", "Python"], "S"=>["Smalltalk"]}
Use Following code
#categories = Category.all.group_by { |c| c.name }
Write a Hepler to find post_count
def post_count(category_ids)
Post.where(:category_id => category_ids)
end
In View:
<% #categories.each do |c, v| %>
<li><%= link_to c, blog_path(:name => c) %>(<%= post_count(v.map(&:id)) %>)</li>
<% end %>
Hope this helps! :)
What you are looking for is called counter_cache.
In our Post model,set counter_cahe => true
class Post < ActiveRecord::Base
belongs_to category,:counter_cache => true
end
Add posts_count column to categories table and do like this
#category_groups = Category.find(:all)
<% #category_groups.each do |c| %>
<li><%= link_to name, blog_path(:name => c) %>(<%= posts_count)</li>
<% end %>
Look this Railscast For implementing this.

Ruby/Rails Simple search form and white spaces

I've implemented a "simple search form" (railscasts #37) in my app, but I wonder how:
1.) I cannot display any results if the keywords field is empty, or if the index page is loaded (I've tried search != "". Maybe there is a better way)
2.) I can add a function which avoid multiple white spaces in the search. (if users wrote something like "benoit+++" or "++benoit" in the search box, it should display results)
I don't want to use thinking sphinx as I would deploy on heroku
Here is the code:
In my user model (user.rb)
def self.search(search)
if search and search != ""
find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
end
In my view (views/users/index.html.erb)
<% form_tag users_path, :method => 'get' do %>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
<% end %>
<% if #users and not #users.empty? %>
<% #users.each do |user| %>
<p><%= link_to "#{user.name}", user %></p>
.
.
.
<% end %>
<% end %>
and in my controller ( users_controller.rb)
def index
#users = User.search(params[:search])
end
Thanks for any help or ressources!!
I would change the search method:
def self.search(search)
search.blank? ? [] : all(:conditions => ['name LIKE ?', "%#{search.strip}%"])
end
something.blank? returns true if something is nil or blank.
something.strip removes all the spaces at the beginning or end of a string. If name could be made of more than 1 word and you wanted to prevent from having more than one space between the words, you could do search.strip.squeeze(" ")
Oh, I don't think you need the if #users && !#users.empty? clause in your view now.

Rails 3 Combine Two Variables

I have the following code which I need to update...
<% #users.each do |user| %>
<tr>
<td><%= link_to user.fname, user %></td>
</tr>
<% end %>
I want to learn how to update that so instead of just showing the fname, it shows fname + lname
so for a record like James Bond, it shows james bond and links it to the user in Rails.
thanks
You can join the string right in the ERB:
<%= link_to user.fname + " " + user.lname, user %>
<!-- or, slightly better: avoids problems with nil values -->
<%= link_to "#{user.fname} #{user.lname}", user %>
<!-- or, even better -->
<%= link_to [user.fname, user.lname].join(" "), user %>
Or, you can move that ugly logic into a helper method, e.g. in app/helpers/users_helper.rb:
module UsersHelper
def full_name(user)
[user.fname, user.lname].join(" ")
end
end
<%= link_to full_name(user), user %>
Or, (this is what I would do) you can put a full_name method in the model:
class User < ActiveRecord::Base
def full_name
[fname, lname].join(" ")
end
end
<%= link_to user.full_name, user %>

Resources