Duplicate results from Rails search (Railscast #111) - ruby-on-rails

I'm getting a weird result when doing a search I've implimented from Railscast #111 (Advanced Search).
The search results are coming through OK from the entries that I have, but when the result is more than 1 (Iv'e only tested for two results) - it doubles the results. So when I expect two results, I get 4 (rendering out in a table).
The weird part is that when I expect 1 result, it only renders the 1 result.
I have a feeling it has something to do with my 'Search.rb' file which gives the search parameters. Can someone else shed some light on why this would be giving duplicate results?
class Search < ActiveRecord::Base
def entries
#entries ||= find_entries
end
private
def find_entries
entries = Entry.order(:firstname)
entries = entries.where("firstname like ?", "%#{firstname}%") if firstname.present?
entries = entries.where("lastname like ?", "%#{lastname}%") if lastname.present?
entries
end
end
I am searching by either firstname or lastname, but the entries have more fields in them.

Try
def find_entries
entries = Entry.order(:firstname)
if !firstname.blank?
entries = entries.where("firstname like ?", "%#{firstname}%")
if !lastname.blank?
entries = entries.where("lastname like ?", "%#{lastname}%")
end
else
if !lastname.blank?
entries = entries.where("lastname like ?", "%#{lastname}%")
end
end
entries
end

Related

building a simple search form in Rails?

I'm trying to build a simple search form in Ruby on Rails, my form is simple enough basically you select fields from a series of options and then all the events matching the fields are shown. The problem comes when I leave any field blank.
Here is the code responsible for filtering the parameters
Event.joins(:eventdates).joins(:categories).where
("eventdates.start_date = ? AND city = ? AND categories.name = ?",
params[:event][:date], params[:event][:city], params[:event][:category]).all
From what I get it's that it looks for events with any empty field, but since all of them have them not empty, it wont match unless all 3 are filled, another problem arises when I try to say, look events inside a range or array of dates, I'm clueless on how to pass multiple days into the search.
I'm pretty new to making search forms in general, so I don't even know if this is the best approach, also I'm trying to keep the searches without the need of a secialized model.
Below is probably what you are looking for. (Note: If all fields all blank, it shows all data in the events table linkable with eventdates and categories.)
events = Event.joins(:eventdates).joins(:categories)
if params[:event]
# includes below where condition to query only if params[:event][:date] has a value
events = events.where("eventdates.start_date = ?", params[:event][:date]) if params[:event][:date].present?
# includes below where condition to query only if params[:event][:city] has a value
events = events.where("city = ?", params[:event][:city]) if params[:event][:city].present?
# includes below where condition to query only if params[:event][:city] has a value
events = events.where("categories.name = ?", params[:event][:category]) if params[:event][:category].present?
end
To search using multiple days:
# params[:event][:dates] is expected to be array of dates.
# Below query gets converted into an 'IN' operation in SQL, something like "where eventdates.start_date IN ['date1', 'date2']"
events = events.where("eventdates.start_date = ?", params[:event][:dates]) if params[:event][:dates].present?
It will be more easy and optimised . If you use concern for filter data.
Make one concern in Model.
filterable.rb
module Filterable
extend ActiveSupport::Concern
module ClassMethods
def filter(filtering_params)
results = self.where(nil)
filtering_params.each do |key, value|
if column_type(key) == :date || column_type(key) ==
:datetime
results = results.where("DATE(#{column(key)}) = ?",
Date.strptime(value, "%m/%d/%Y")) if
value.present?
else
results = results.where("#{column(key)} Like ? ", "%#{value}%") if
value.present?
end
end
results
end
def resource_name
self.table_name
end
def column(key)
return key if key.split(".").count > 1
return "#{resource_name}.#{key}"
end
def column_type(key)
self.columns_hash[key].type
end
end
end
Include this concern in model file that you want to filter.
Model.rb
include Filterable
In your controller Add this methods
def search
#resources = Model.filter(class_search_params)
render 'index'
end
def class_search_params
params.slice(:id,:name) #Your field names
end
So, It is global solution. You dont need to use query for filter. just add this concern in your model file.
That's it.

Ignore uppercase, downcase and accent in search

I have a search system with filter here. This system work like a charm but I have some problem with downcase / uppercase and accent.
For example if I search "marée" I have result but if I search "MAREE" or "Marée" or "maree". I don't have result.
I want to fix that. How I can fix this ? Thank you.
my controller
def resultnohome
if params[:query].blank?
redirect_to action: :index and return
else
#campings = Camping.searchi(params[:query], params[:handicap], params[:animaux], params[:television], params[:plage], params[:etang], params[:lac])
if params[:query] == "aube"
#pub = Camping.find_by_id(1)
else
end
end
end
My model
def self.searchi(query, handicap, animaux, television, plage, etang, lac)
return scoped unless query.present?
result = left_outer_joins(:caracteristiquetests, :situations).where('nomdep LIKE ? OR name LIKE ? OR nomregion LIKE ? OR commune LIKE?', "%#{query}%", "%#{query}%", "%#{query}%", "%#{query}%")
result = result.where('handicap LIKE ?', "%#{handicap}%") if handicap
result = result.where('animaux LIKE ?', "%#{animaux}%") if animaux
result = result.where('television LIKE ?', "%#{television}%") if television
result = result.where('plage LIKE ?', "%#{plage}%") if plage
result = result.where('etang LIKE ?', "%#{etang}%") if etang
result = result.where('lac LIKE ?', "%#{lac}%") if lac
return result
end
If you insist on using SQLite then you don't have many good options. The most common suggestion is to have extra columns in your database, that are normalized values so if your plage column contains "Marée" then you also have a column plage_ascii that contains "maree"
you need to create the additional columns with migrations, then you would have a before_save action in your model...
before_save :create_normalized_strings
def create_normalized_strings
self.handicap_ascii = handicap.downcase.mb_chars.normalize(:kd).gsub(/[^x00-\x7F]/n, '').to_s
self.animaux_ascii = animaux.downcase.mb_chars.normalize(:kd).gsub(/[^x00-\x7F]/n, '').to_s
# etc etc
end
Then in your search do...
if handicap
test_handicap = handicap.downcase.mb_chars.normalize(:kd).gsub(/[^x00-\x7F]/n, '').to_s
result = result.where('handicap_ascii LIKE ?', "%#{handicap}%")
end
It's not great, as it basically forces you to duplicate data in your database into extra columns. If you can consider more sophisticated databases other than SQLite then you'd be better off... personally I wouldn't ever use SQLite in a production environment.

Ignore parameters that are null in active record Rails 4

I created a simple web form where users can enter some search criteria to look for venues e.g. a price range. When a user clicks "find" I use active record to query the database. This all works very well if all fields are filled in. Problems occur when one or more fields are left open and therefore have a value of null.
How can I work around this in my controller? Should I first check whether a value is null and create a query based on that? I can imagine I end up with many different queries and a lot of code. There must be a quicker way to achieve this?
Controller:
def search
#venues = Venue.where("price >= ? AND price <= ? AND romance = ? AND firstdate = ?", params[:minPrice], params[:maxPrice], params[:romance], params[:firstdate])
end
You may want to filter out all of the blank parameters that were sent with the request.
Here is a quick and DRY solution for filtering out blank values, triggers only one query of the database, and builds the where clause with Rails' ActiveRecord ORM.
This approach safeguards against SQL-injection, as pointed out by #DanBrooking. Rails 4.0+ provides "strong parameters." You should use the feature.
class VenuesController < ActiveRecord::Base
def search
# Pass a hash to your query
#venues = Venue.where(search_params)
end
private
def search_params
params.
# Optionally, whitelist your search parameters with permit
permit(:min_price, :max_price, :romance, :first_date).
# Delete any passed params that are nil or empty string
delete_if {|key, value| value.blank? }
end
end
I would recommend to make method in Venue
def self.find_by_price(min_price, max_price)
if min_price && max_price
where("price between ? and ?", min_price, max_price)
else
all
end
end
def self.find_by_romance(romance)
if romance
where("romance = ?", romance)
else
all
end
end
def self.find_by_firstdate(firstdate)
if firstdate
where("firstdate = ?", firstdate)
else
all
end
end
And use it in your controller
Venue
.find_by_price(params[:minPrice], params[:maxPrice])
.find_by_romance(params[:romance])
.find_by_firstdate(params[:firstdate])
Another solution to this problem, and I think a more elegant one, is using scopes with conditions.
You could do something like
class Venue < ActiveRecord::Base
scope :romance, ->(genre) { where("romance = ?", genre) if genre.present? }
end
You can then chain those, which would work as an AND if there is no argument present, then it is not part of the chain.
http://guides.rubyonrails.org/active_record_querying.html#scopes
Try below code, it will ignore parameters those are not present
conditions = []
conditions << "price >= '#{params[:minPrice]}'" if params[:minPrice].present?
conditions << "price <= '#{params[:maxPrice]}'" if params[:maxPrice].present?
conditions << "romance = '#{params[:romance]}'" if params[:romance].present?
conditions << "firstdate = '#{params[:firstdate]}'" if params[:firstdate].present?
#venues = Venue.where(conditions.join(" AND "))

Rails: outputting data where record has more votes

Right now I have this
def index
#trips = Trip.all
end
And I'm outputting data like so:
- #trips.order('created_at desc').first(4).each do |trip|
- trip.trip_images.first(1).each do |image|
= trip.title_name.titleize
However, I have a votable table (from acts_as_votable gem) associated to trips. I was wondering if I can only output trips where trips have a certain amount of votes?
I can get the votes like this:
- #trips.order('created_at desc').first(4).each do |trip|
= trip.get_likes.size #this is where I can get the likes
- trip.trip_images.first(1).each do |image|
= trip.title_name.titleize
EDIT
If I do this instead:
def index
#votes = ActsAsVotable::Vote.where(votable_type: 'Trip').group(:votable_id).count
#trips = Trip.where(#votes)
end
#votes gives me something like this:
{195=>1, 106=>1, 120=>1, 227=>1, 247=>1, 264=>1, 410=>1}
How do I get it where trip will only get the ids?
EDIT 2
I think I figured it out...
def index
#votes = ActsAsVotable::Vote.where(votable_type: 'Trip').group(:votable_id).count
#trips = Trip.where(id: #votes.keys)
end
I got some kind of output. Is there a better way?
Yesterday I answered similar question.
This is how you could get the id(s) of trip with certain amount of votes (you can use =, >, <= and so on):
trip_ids = ActsAsVotable::Vote
.where(votable_type: 'Trip')
.group(:votable_id)
.having('count(votable_id) > 1') #any number of votes
.pluck(:votable_id)
.uniq
Trip.where(id: trip_ids)
Have you considered making this a method in your Trip model?
Something like,
def popular_trip_images
Trip.select(:trip_images).where("likes > ?", 200)
end
Then use it something like,
...
trip.popular_trip_images.each do |image|
...
Edit:
However, I have a votable table (from acts_as_votable gem) associated to trips. I was wondering if I can only output trips where trips have a certain amount of votes?
Sorry, missed this part. The gem has a find_liked_items method but don't see offhand how to set something like liked > 400 etc.
I've been trying to work through the comments, but right now, I've gotten this code to work:
def index
#votes = ActsAsVotable::Vote.where(votable_type: 'Trip').group(:votable_id).count
#votes = #votes.select {|k,v| v > 1}
#trips = Trip.where(id: #votes.keys)
end
If someone else can come up with a better solution! I'll mark as correct.

Rails handle multiple Params in search query

I have the following model and I want to pass multiple params in "with_query", don't know how to achieve it. currently you can see it takes only "query" param. how can I filter it with country and job_type. any help would be really appreciated.
search Model
def self.search(query, country, job_type, page = 1)
results = []
Refinery.searchable_models.each do |model|
results << model.limit(RESULTS_LIMIT).with_query(query)
end if query.present?
results.flatten[0..(RESULTS_LIMIT - 1)]
end
Controller
def show
#results = Refinery::SearchEngine.search(params[:query], params[:country], params[:job_type], params[:page])
present(#page = Refinery::Page.find_by_link_url("/search"))
end
I would try changing the line that builds your results in the search model to:
results << model.limit(RESULTS_LIMIT).with_query(query).where(country: country, job_type: job_type)

Resources