I am looking to implement a simple search function while using the globalize3 gem for Ruby on Rails. Since the translations of the model are stored in a separate table, the code below doesn't work as there is no longer a :name field in the products table. How can I adjust the code below to make the search function correctly?
products_controller.rb
#products = Product.search(params[:search]).all
index.html.erb
<%= form_tag products_path, method: :get do %>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", name: nil %>
<% end %>
model
class Product < ActiveRecord::Base
translates :name
attr_accessible :name, :price, :released_at
def self.search(search)
if search
where('name LIKE ?', "%#{search}%")
else
scoped
end
end
end
You're in luck, I tackled exactly the same problem recently!
Luckly for you the answer is quite simple. You can use the class method with_translations to include translations for a given set of locales.
Here's the code:
def with_translations(*locales)
locales = translated_locales if locales.empty?
includes(:translations).with_locales(locales).with_required_attributes
end
Include it in your search method:
def self.search(search)
if search
with_translations.where('name LIKE ?', "%#{search}%")
else
with_translations
end
end
That should do it.
As an added note: you could add an optional locales parameter to the search method and pass it to with_translations to optionally narrow the search to terms in a particular language, say for example in the current locale.
Solved ...
def index
if params[:search]
#at = []
#at = Array.new
Article.translation_class.where("title LIKE ? OR description LIKE ?","%#{params[:search]}%","%#{params[:search]}%").all.each do |t|
#at.push t.article_id
end
#articles = Article.where(id: #at).recent_first.paginate(page: params[:page], per_page: 5)
else
#articles = Article.all.recent_first.paginate(page: params[:page], per_page: 5)
end
end
Related
I have something like that in my controller:
def index
#votes = Vote.all
end
private
def search
#votes = OtherVotes.all
end
I want to use search method in index action but I don't want to remove my #votes variable from index. If I use before_action, it calls method before the action so #votes doesn't change. Is it possible to call search method after my votes variable or ignore the variable without removing.
I normally go with this method when I'm looking to build a simple search:
http://railscasts.com/episodes/37-simple-search-form
Create a method in your vote.rb file:
class Vote
def self.search(search)
if search
self.where(:all, conditions: ['name LIKE ?', "%#{search}%"])
else
self.where(:all)
end
end
end
This means when you do Vote.search('term'), you'll bring up any records with a similair name. Replace name for whatever term you're searching for (i.e. title or category).
If there is no search term entered this method simply returns every instance. This means you can leave your controller looking like this:
def index
#votes = Vote.search(params[:search])
end
Finally the view for this would be something like:
<% form_tag votes_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
This will send a get request to the votes_path (the index action on your controller), with the search term parameter. If one is entered the search will return the relevant instances, and if not it will return all.
Try
class TempController < ApplicationController
after_action :search
def index
#votes = Vote.all
end
private
def search
#votes = OtherVotes.all
end
end
I am learning Ruby on Rails and have a search form set up and its working. On the pins index view I can search for pins(posts) by their title. However if I wanted to search by Username which is not in the pins table and display the results on the Pins index page how would I do this? How do I access an attribute from a different table? (Sorry for the newbie attempt at explaining my issue)
Pins controller
def index
#pins = Pin.search(params[:term])
end
Pin Model
def self.search(term)
if term
where('description LIKE ?', "%#{term}%")
else
order('id DESC')
end
end
_search.html.erb
<%= form_tag(pins_path, method: :get) do %>
<%= text_field_tag :term, params[:term] %>
<%= submit_tag 'Search', description: nil %>
<% end %>
Assuming you have set up something like
class User < ApplicationRecord
has_many :pins
# the username is stored in the attribute 'username'
end
class Pin < ApplicationRecord
belongs_to :user
end
you may do the following
# PinsController
def index
terms = params[:term]
username = params[:username]
#pins = Pin
#pins = #pins.where("description LIKE '%?%'", term) if term
#pins = #pins.includes(:user).where("users.username LIKE '%?%'", username) if username
# you may want to sort by id anyway
#pins = #pins.order('id DESC')
end
Note that I put the code straight to the controller for brevity. You may refactor this to use your search method in pin model.
# _search.html.erb
<%= form_tag(pins_path, method: :get) do %>
<%= text_field_tag :term, params[:term] %>
<%= text_field_tag :username, params[:username] %>
<%= submit_tag 'Search', description: nil %>
<% end %>
In case you want to do some more searching and filtering you may have a look at the ransack gem although I think you're going the right path in trying to figure this out yourself.
Although those railscasts episodes are from the past I think they are applicable to the current rails versions. Anyway one can get the point from them
http://railscasts.com/episodes/37-simple-search-form
http://railscasts.com/episodes/111-advanced-search-form
http://railscasts.com/episodes/240-search-sort-paginate-with-ajax
Another good resource is gorails.com (not affiliate in any way!!). I can highly recommend them as a resource for learning
Provided you have an association between User and Pin
class User
has_many :pins
end
class Pin
belongs_to :user
end
You can join :user from Pin and set conditions on the association:
Pin.joins(:user).where('users.username ? AND awesome = ?', 'Max', true)
# or the preferred method
Pin.joins(:user).where(user: { username: 'Max', awesome: true })
Note that we use users.username and not user.username when writing a SQL string you're specifying the table name - not the association.
To search for pins based on the username you could do:
def self.by_username(term)
joins(:user).where('users.username LIKE ?', "%#{term}%")
end
I'm very new to Ruby on Rails and trying to create a search function that allows the user to serach multiple parameters at the same time; from, and to. Something to keep in mind is that there will probably be even more parameters later on in the development. I've got it to work when searching for one of the fields, but not more than that.
Search view:
<%= form_tag(journeys_path, :method => "get", from: "search-form") do %>
<%= text_field_tag :search_from, params[:search_from], placeholder: "Search from", :class => 'input' %>
<%= text_field_tag :search_to, params[:search_to], placeholder: "Search to", :class => 'input' %>
<%= submit_tag "Search", :class => 'submit' %>
<% end %>
Method:
class Journey < ActiveRecord::Base
def self.search(search_from)
self.where("from_place LIKE ?", "%#{search_from}%")
end
end
Controller:
class JourneysController < ApplicationController
def index
#journeys = Journey.all
if params[:search_from]
#journeys = Journey.search(params[:search_from])
else
#journeys = Journey.all.order('created_at DESC')
end
end
def search
#journeys = Journey.search(params[:search_from])
end
end
I've tried some gems and all kind of solutions that I've found in other questions, but I'm just not good enough at RoR yet to succesfully apply them correctly without help. I would appreciate any help I can get.
Thank you!
Model:
class Journey < ActiveRecord::Base
def self.search(search_from, search_to)
self.where("from_place LIKE ? and to_place LIKE ?", "%#{search_from}%", "%#{search_to}%")
end
end
Controller:
class JourneysController < ApplicationController
def index
if params[:search_from] and params[:search_to]
#journeys = search
else
#journeys = Journey.all.order('created_at DESC')
end
end
def search
#journeys = Journey.search(params[:search_from], params[:search_to])
end
end
The best approach here is to incapsulate your search form as a separate Ruby class. Using Virtus here helps to get type coercion for free.
class SearchForm
include Virtus.model # Our virtus module
include ActiveModel::Model # To get ActiveRecord-like behaviour for free.
attribute :from, String
attribute :to, String
# Just to check if any search param present,
# you could substitute this with validations and just call valid?
def present?
attributes.values.any?{|value| value.present?}
end
```
In Rails 3 IIRC you also have to include ActiveModel::Validations to be able to validate your form input if needed.
Now, let's see how to refactor controller. We instantiate form object from params and pass that to the model query method to fetch records needed. I also moved ordering out of if clause and used symbol ordering param - cleaner IMO.
def index
#search_form = SearchForm.new(search_params)
if #search_form.valid? && #search_form.present?
#journeys = Journey.search(#search_form)
else
#journeys = Journey.all
end
#journeys = #journeys.order(created_at: :desc)
end
def search
#journeys = Journey.search(SearchForm.new(search_params)
end
private
def search_params
params.require(:search_form).permit(:from, :to)
end
Now to the view: form_for will work perfectly with our form object, as will simple_form_for
<%= form_for #search_form, url: journeys_path, method: :get do |f| %>
<%= f.text_field :from, placeholder: "Search from", class: 'input' %>
<%= f.text_field :to, placeholder: "Search to", class: 'input' %>
<%= f.submit "Search", class: 'submit' %>
<% end %>
View looks now much shorter and cleaner. Incapsulating params in object makes working with search params muuuuch easier.
Model:
class Journey < ActiveRecord::Base
def self.search(search_form)
if search_form.kind_of?(SearchForm)
journeys = all # I'm calling Journey.all here to build ActiveRecord::Relation object
if search_form.from.present?
journeys = journeys.where("from_place LIKE ?", "%#{search_form.from}%")
end
if search_form.to.present?
journeys = journeys.where("to_place LIKE ?", "%#{search_form.to}%")
end
else
raise ArgumentError, 'You should pass SearchForm instance to Journey.search method'
end
end
end
Notice how I build ActiveRecord::Relation object by calling Journeys.all and applying each search param if present. Chaining where like that would put AND in between automatically, if you need OR Rails 4 has it: Journey.or(condition).
Pros of this approach:
You are using Plain Old Ruby Classes, almost no magic here, and it works like usual Rails model in many ways. Putting search params in the object makes it a lot easier to refactor code. Virtus is the only dependency, sans Rails itself of course, and it's more for convenience and to avoid writing boring boiler-plate code.
You can easily validate input if needed (If you really want to be strict about input and show user validation error instead of silently executing stupid query with contradicting conditions and returning no results).
I am trying to generalize few methods that will be used by multiple models/views/controllers but i'm having no luck. Here is the original code that works when it is just for 1 set called Trucks.
View
<h2>Trucks</h2>
<%= form_tag trucks_path, :method => 'get' do %>
<%= hidden_field_tag :direction, params[:direction] %>
<%= hidden_field_tag :sort, params[:sort] %>
<p>
Search:
<%= text_field_tag :search %>
by
<%= select_tag :search_column, options_for_select(Truck.translated_searchable_columns(['attribute1']), params[:search_column]) %>
<%= submit_tag "Search" %>
</p>
<% end %>
<!-- Display code goes here, but im not showing since its just a table -->
Controller
def index
#trucks = Truck.search(params[:search], params[:search_column]).order(sort_column(Truck, "truck_no") + " " + sort_direction)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #trucks }
end
end
Model
class Truck < ActiveRecord::Base
attr_accessible :attribute1, :attribute2, :attribute3
def self.search(keyword, column_name)
if self.column_names.include?(column_name.to_s)
where("trucks.#{column_name} LIKE ?", "%#{keyword}%")
else
scoped
end
end
def self.searchable_columns(unwanted_columns)
self.column_names.reject{ |column| unwanted_columns.include?(column) }
end
def self.translated_searchable_columns(unwanted_columns)
columns = self.searchable_columns(unwanted_columns)
result = columns.map{ |column| [Truck.human_attribute_name(column.to_sym), column] }
result
end
end
All this works without a hitch, now I can't figure out for the life of me how to move these methods to lib and have them generalized so that lets say Trailers is able to call in the same method and pass in its information and achieve the same result. I am trying to make this code DRY as possible. Could anyone explain me what I need to do to achieve this? How does lib access the database?
The concept you're looking for is called a "concern". Rails has a convenience module for implementing concerns called ActiveSupport::Concern. Here's how you might extract your model methods:
module Searchable
extend ActiveSupport::Concern
module ClassMethods
def search(keyword, column_name)
if column_names.include?(column_name.to_s)
where("#{table_name}.#{column_name} LIKE ?", "%#{keyword}%")
else
scoped
end
end
def searchable_columns(unwanted_columns)
column_names.reject{ |column| unwanted_columns.include?(column) }
end
def translated_searchable_columns(unwanted_columns)
columns = searchable_columns(unwanted_columns)
columns.map{ |column| [human_attribute_name(column.to_sym), column] }
end
end
end
And then in your model:
class Truck < ActiveRecord::Base
include Searchable
attr_accessible :attribute1, :attribute2, :attribute3
end
As for where exactly you should store the Searchable module, it's up to you -- it just has to be someplace that's included in config.autoload_paths, just like a model or controller. Rails 4 introduced a convention that model concerns are stored in app/models/concerns, and controller concerns in app/controllers/concerns, but there is nothing special about these locations other than being autoloaded by default.
I am following Ryan Bates' railcasts: http://railscasts.com/episodes/37-simple-search-form
in solving my issue with the search results on a page with will-paginate.
Here he answers the question as to how to solve this problem. However, I've tried them and haven't had any luck. From following his second resolution, I get a NoMethod error for "search_conditions" as the result.
The Code:
projects/index.rhtml
<% form_tag projects_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
projects_controller.rb
def index
#projects = Project.search(params[:search])
end
models/project.rb
def self.search(search)
if search
find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
else
find(:all)
end
end
His Answers:
One way is to call the class method "search_conditions" and instead of having it do the find it will just return a conditions array so you could use it in the paginate method.
Course.paginate(:all, :conditions => Course.search_conditions(..))
Another is to call the method "paginated_search" and have it call "paginate" instead of "find".
Lastly you could have the search method accept a block which uses with_scope to set the find conditions. This way you could call "paginate" in that block and the conditions will automatically be applied.
Can someone explain to me how I should go about solving this? I am new to rails and maybe I am just misunderstanding what he is saying.
Railscast you following is pretty old, lots changed since then. Try to change implementation of search method like this:
def self.search(search)
if search
where 'name LIKE ?', "%#{search}%"
else
scoped
end
end
and something like this in controller:
def index
#projects = Project.search(params[:search]).paginate(:page => params[:page])
end