Searching multiple fields with Ransack - ruby-on-rails

I'm using Ransack to allow advanced searching on my users. Currently, the users have first_name, middle_name, and last_name columns. Here is my code:
.field
= f.attribute_fields do |a|
= a.attribute_select
...
How can I have a custom one called 'Name', that lets the user search through all three of the columns mentioned above?
Note
I would still like to keep the remaining attributes as options as well, such as email, phone number, etc. Please keep this in mind when determining an answer.

I would suggest to provide a dedicated column for this search. While that might create redundant data, it is way easier to search than doing some SQL magic on the existing columns.
You can easily automate the setting of this field:
before_save :set_full_name
def set_full_name
self.full_name = [first_name, middle_name, last_name].reject(&:blank?).join(" ")
end
Then you can use the normal ransack methods to search this field.

Use this to search multiple fields:
= f.text_field(: first_name_or_middle_name_or_last_name_cont)
This will generate a query like this:
where first_name like '%q%' or middle_name like '%q%' or last_name like'%q%'
when you fill in a q as search parameter

Another approach is to search every attribute of the model, excluding only the fields that you don't want to search. To do this, you could create a custom helper method that builds the label and search field name expected by Ransack's search method. The following method (located in a helper) returns a concatenation of all attributes that you wish to search in a way that Ransack expects:
def most_attributes_cont
most_attributes = []
attributes_to_exclude = [
"id",
"created_at",
"updated_at"
]
ModelName.column_names.each do |column_name|
most_attributes << column_name unless column_name.in?(attributes_to_exclude)
end
most_attributes.join("_or_") + "_cont"
end
Then, just add the method call to your search form:
<%= search_form_for #q do |f| %>
<%= f.label most_attributes_cont %>
<%= f.search_field most_attributes_cont %>
<%= f.submit %>
<% end %>

Related

Ruby on Rails Joining Two Strings in Dropdown Text

I have a ruby on rails site with a page called "meetings". These meetings currently work fine, but I want to change the way that one of the drop downs displays its data. Currently, I have this in my controller/view:
Controller
#meeting_times = MeetingTime.select("id, meeting_type, meeting_at").where("
(meeting_at > ?)", Time.now + 1.week).order("meeting_at")
View
<%= f.select :meeting_time_id, options_from_collection_for_select
(#meeting_times, "id", "meeting_at") %>
Currently, as you can see, the dropdown just shows the meeting time. What I want to do make the dropdown show the meeting_type + meeting_time in one string. This sounds easy, however meeting_type is an int value. So, I would need to say "If meeting_type == 0 then display 'Staff Meeting' + meeting_time", and so on. How can I accomplish this without changing the database values for meeting_type?
in MeetingTime model create method
def meeting_time_display
"#{meeting_type_names[meeting_type]} #{meeting_at}"
end
private
def meeting_type_names
##m_type_names ||= ['Staff Meeting', 'Type 2', ..., 'Type n']
end
In view call
<%= f.select :meeting_time_id, options_from_collection_for_select (#meeting_times, "id", "meeting_time_display") %>

Search/ filter method for all attributes in my index table

I'm trying to write a row for my index table that filters my objects regarding a specific value of a specific column. What I have until now is this:
pimps_controller.rb:
def index
#pimps = Pimp.search(params[:search])
end
pimp.rb:
def self.search( search)
if search
where('title LIKE ?', "%#{search}%")
else
scoped
end
end
A part of view:
<%= text_field_tag :search, params[:search] %>
That filters after the objects title only so I tried to alter it to make it functional for different search fields that can filter after different attributes. I want to pass a second parameter value if someone fires the search function to make sure it triggers for the right attributes. That's what I've tried:
pimps_controller.rb
#pimps = Pimp.search(params[:search_column],params[:search])
pimp.rb:
def self.search(search_column, search)
if search
col = "%#{search_column}"
s = "%#{search}%"
where(col 'LIKE ?', s)
else
scoped
end
end
The view:
<%= text_field_tag :search, params[:search], params[:search_column => title] %>
But it's not working. I get an error message for passing the both parameters in one search field I guess. How would you do it?
Here's a simple tutorial on how to do it:
https://we.riseup.net/rails/simple-search-tutorial
In the model, you will have to add the fields with or condition to the query.
def self.search(search)
search_condition = "%" + search + "%"
find(:all, :conditions => ['title LIKE ? OR description LIKE ?', search_condition, search_condition])
end
If you want to define the field to search in the params you can use string interpolation with simple quotes:
%q(text contains "#{search.query}")
You need 2 text fields, one for the column, one for the value:
# view
<%= text_field_tag :search_value, params[:search_value] %>
<%= text_field_tag :search_column, params[:search_column] %>
# controller
#pimps = Pimp.search(params[:search_column], params[:search_value])
# Pimp model
def self.search(search_column, search_value)
if search_value.present? && search_column.present?
column = self.column_names.include?(search_column.to_s) ? search_column : 'title'
value = "%#{search_value}%"
where("#{self.table_name}.#{column} LIKE ?", value)
else
scoped
end
end
The problem about this method is that if you don't type the exact name of the column, it will search the value in the column title. I think you should use a select_tag, listing all searchable columns of the model:
# view
<%= select_tag :search_column, options_for_select(Pimp.column_names.map { |col| [col, col] }, params[:search_column]) %>
This view code will display a select tag with the available columns of the Pimp model. You can easily limit the searchable columns by defining a class method on Pimp:
# Pimp model
def searchable_columns
self.column_names - ['id', 'created_at', 'updated_at']
end
# view
<%= select_tag :search_column, options_for_select(Pimp.searchable_columns.map { |col| [col, col] }, params[:search_column]) %>

Ransack search not working if there is 'space' in search term

I am using ransack for search in my rails 3.2 application using postgres as database.
I have a Invoice model and every invoice belongs_to a buyer. Below is my search form in index page.
views/invoices/index.html.erb
<%= search_form_for #search do |f| %>
<%= f.text_field :buyer_name_cont %>
<%= f.submit "Search"%>
<% end %>
And here is my controller code.
controllers/invoices_controller.rb
def index
#search = Invoice.search(params[:q])
#invoices=#search.result(:distinct => true).paginate(:page => params[:page], :per_page => GlobalConstants::PER_PAGE )
respond_to do |format|
format.html # index.html.erb
format.json { render json: #invoices }
end
end
Let's say a invoice is there of a buyer having name "Bat Man".
If I search "Bat", I get the invoice in results.
Again if I search "Man", I get the invoice in results.
But if I search "Bat Man", I don't get the invoice in results.
I know it might be something trivial but I am not able to resolve.
Update
When I tried the sql query formed directly in database using pgAdmin, I realized that in database there were multiple spaces in the buyer name, something like "Bat.space.space.space.Man".
Can something be done so that "Bat.space.Man" search also finds "Bat.space.space.space.Man" in results?
You could sanitize your data. For instance with regexp_replace(). Run in the database once:
UPDATE invoice
SET buyer = regexp_replace(buyer, '\s\s+', ' ', 'g')
WHERE buyer <> regexp_replace(buyer, '\s\s+', ' ', 'g');
And sanitize new inserts & updates likewise.
\s .. class shorthand for "white space" (including tab or weird spaces).
The 4th parameter 'g' is for "globally", needed to replace all instances, not just the first.
Ransack not support cont search for multi terms, I solved the requirement my customized way. the details as following:
Add scope to your model:
scope :like_search, ->(column, value) {
keywords = value.to_s.split.map{ |k| "%#{k}%" }
where(Array.new(keywords.size, "#{column} ILIKE ?").join(' AND '), *keywords)
}
in your view. instead of using f.text_field :buyer_name_cont provided by ransack, use normal field helper text_field_tag :buyer_name, params[:buyer_name]
then restrict your ransack in scope:
scope = Invoice.like_search(:name , params[:buyer_name])
#q = scope.ransack(params[:q])

Multiple identical collection_select tags in a form in Rails

I have multiple identical collection selects inside a single form. I prefer this over a multiple select list for aesthetic and UX reasons. I have to use a terrible kludge to make everything work right, and I'm wondering if there is a more elegant way to do this:
From the view:
<% 3.times do |i| %>
<%= collection_select("selected_item_" + i.to_s.to_s, :name, #items, :name, :name, { :include_blank => true }, { id: "selected_item_" + i.to_s }) %>
<% end %>
From the controller:
ItemContainer = Struct.new(:name)
3.times do |i|
param = ('selected_item_' + i.to_s).to_sym
instance_variable = '#' + param_name
if params[param] && !params[param].empty?
#selected_items << params[param][:name]
instance_variable_set(instance_variable, ItemContainer.new(params[param][:name]))
end
end
#selected_channels.each.... # do what I need to with these selections
Most of these gymnastics are needed to ensure that the item is still selected if the page is refreshed. If there were some way to force collection select to use arrays, that would be the answer, but I couldn't make that work.
If I understang right you're looking for selegt_tag method (docs: http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-select_tag)
You can write something like this
select_tag "people[]", options_from_collection_for_select(#people, "id", "name")
select_tag "people[]", options_from_collection_for_select(#people, "id", "name")
and it youd output two selects for people, which would be sent as an array on submit.
if you use the [] naming in your collection_select calls then the params will send over the data as an array
I am a bit confused as to the usage of collection_select here as it doesn't seem like you are using a model object? this example using select_tag - might be able to come up with something more appropriate to your issue if the model structures were known
# run this in the loop
# set selected_value to appropriate value if needed to pre-populate the form
<%= select_tag('name[]',
options_from_collection_for_select(#items, 'name', 'name', selected_value),
{ include_blank: true }
)
%>
in controller update/create action
# this works because the select tag 'name' is named with [] suffix
# but you have to ensure it is set to empty array if none are passed, usually only issue with checkboxes
names = params[:name] || []
names.each do |name|
puts name
end
side note: you can use string interpolation with ruby double quotes in places of + for string concatenation
<%= collection_select("selected_item_#{i}",
:name,
#items,
:name,
:name,
{ include_blank: true },
{ id: "selected_item_#{i}" }
)
%>
see also: http://apidock.com/rails/v3.2.13/ActionView/Helpers/FormOptionsHelper/options_from_collection_for_select

Cannot retrieve lookup values correctly

I've built a lookup table in my Rails application.
It is a table that stores lookup text values for drop-down pickers or possibly check boxes. I want the lookup table to have a view associated so the values can be easily edited. I also may want to share lookup values among multiple models for a single field in the future.
So far I've managed to get it to work for the selection of values, but then displaying the text value again on a show or index view has been problematic.
This is how I built the lookup table
rails g scaffold Lookup field_name lookup_text table_name note
In the edit.html.erb where there is a lookup on a field, I've got code like this, which works and allows me to pick from a list.
<div class="field">
<%= f.label :status %><br />
<%= f.collection_select :status, Lookup.find(:all,:conditions => ["table_name = 'course' and field_name = 'status'"]), :id, :lookup_text, include_blank: true,:prompt => "Status" %>
</div>
That all works fine. When I try to display it back I cannot find the correct syntax. The best I have found is this:
(in the controller)
#status = Lookup.where(:id => #course.status).pluck(:lookup_text)
(in the view)
<p>
<b>Status:</b>
<%= #status %>
</p>
I think I am getting the entire object. It displays like this:
Status: ["Active"]
My questions are:
(1) How do I display the value only?
(2) Is this the best approach?
I've had a look at these and other SO questions, but none are really what I am looking for:
Rails Polymorphic with Lookup Table
Implementing a lookup table in Rails
EDIT
OK this works, but it doesn't look like it is the correct solution. Has anyone got a better way of doing this?
#status = Lookup.where(:id => #course.status).pluck(:lookup_text)[0]
Just another way to show the value is #status = Lookup.find(#course.status).lookup_text
Why not to try use classes for different lookups:
class CourseStatus < ActiveRecord::Base
set_table_name "lookups"
default_scope where("table_name = 'course' and field_name = 'status'")
end
class Course
belongs_to :course_status
end
You then can use:
CourseStatus.all # e.g. to fill select options
Course.first.course_status.lookup_text # => "Active" or smth else
Or without classes:
class Lookup
def self._by_table_and_field(table, field)
['table_name = ? and field_name = ?', table, field]
end
scope :by_table_and_field, lambda { |table, field|
where(Lookup._by_table_and_field(table, field))
}
end
class Course
belongs_to :status, class_name: 'Lookup', conditions: Lookup._by_table_and_field('course', 'status')
end
Lookup.by_table_and_field('course', 'status').all
Course.first.status.lookup_text

Resources