in Ruby, how write a chainable method that chains a named scope N times if given N parameters - ruby-on-rails

I have a named scope :name_not_like that omits records from a relation if the :name matches a pattern
scope :name_not_like, lambda { |n| where('name NOT ILIKE ?', n) }
I have a method that omits every tom, dick, and harry:
def self.except_tom_dick_harry
name_not_like("%tom%").name_not_like("%dick%").name_not_like("%harry%")
end
How can I write a method except_these_names() that given an array of N names will chain name_not_like() N times, so that
except_these_names(["%tom%", "%dick%", "%harry%", "%sam%", "%fred%"])
would do the same as except_tom_dick_harry() but for all the names in the array? Something like this:
def self.except_these_names(array_of_names)
array_of_names.each do |name|
# somehow 'stack' calls to name_not_like(name) ??
end
end

Something like this should work, I think. reduce fits well when you need to build-up on previous results.
def self.except_these_names(names)
names.reduce(scoped) do |criteria, name|
criteria.name_not_like("%#{name}%")
end
end
Or actually, you can do with a simple each. Just put the criteria var outside of the loop.
def self.except_these_names(array_of_names)
criteria = scoped
array_of_names.each do |name|
criteria = criteria.name_not_like(...)
end
criteria
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 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 apply boolean method to filter query results

I have some complicated boolean methods that I want to use as a filter for Database query results. I want a solution that would work for both SQL and Mongoid DB.
class Bar < ActiveRecord::Base OR include Mongoid::Document
[field :some_field]
def method1?
...
end
def method1?
...
end
This is what I would like to write :
def self.someFunc
Bar.where(some_field: some_value).filter(method1?, method2?)
end
Is there a simpler way than do do that :
def self.someFunc
results = Array.new
Bar.where(some_field: some_value).each do |result|
if result.filter1? && result.filter2?
results << result
end
end
results
end
Okay actually I've used three different techniques to achieve my goal : named_scopes, boolean functions, and array filtering
This link explains array filtering
In my code (using both named_scopes, regexp, and filtering) :
# Named scopes :
scope :current_team, ->{ where(mandate: Constants.mandate)}
# Boolean function :
def prez?
self.role.downcase =~ /.*pre[sident|z].*/
end
# Array filtering :
def self.prez
Bar.current_team.select {|admin|admin.prez?}
end.first

Rails Search with query

I want to filter jobs on the parameter passed onto the model, currently search works flawlessly without query passed into the model, but when I type query it doesn't return anything. How can I perform this query with query and criteria.
results << model.with_query(query).where(criteria). any idea would be really appreciated.
module Refinery
class SearchEngine
# How many results should we show per page
RESULTS_LIMIT = 100
# Perform search over the specified models
def self.search(query, job_region, job_division, country, job_type, page = 1)
results = []
Refinery.searchable_models.each do |model|
criteria = {:job_region => job_region,
:job_division => job_division,
:country => country,
:job_type => job_type
}.select { |key, value| value.present? }
if query.present?
results << model.with_query(query).where(criteria)
else
results << model.limit(RESULTS_LIMIT).where(criteria)
end
end
results.flatten[0..(RESULTS_LIMIT - 1)]
end
end
end
The problem here is that the method .with_query(qry) returns an Array. You want to do chain-scoping, so you must use scopes that returns ActiveRecord::Relation objects.
model.with_query(query)
# returns an Array
model.with_query(query).where(criteria)
# calling .where on an Array object => NoMethodError
model.where(criteria)
# returns an ActiveRecord::Relation
model.where(criteria).with_query(query)
# calls the query on an AR::Relation object, which is doable
Short version: Change this:
results << model.with_query(query).where(criteria)
To this:
results << model.where(criteria).with_query(query)

Rails 3 multiple parameter filtering using scopes

Trying to do a basic filter in rails 3 using the url params. I'd like to have a white list of params that can be filtered by, and return all the items that match. I've set up some scopes (with many more to come):
# in the model:
scope :budget_min, lambda {|min| where("budget > ?", min)}
scope :budget_max, lambda {|max| where("budget < ?", max)}
...but what's the best way to use some, none, or all of these scopes based on the present params[]? I've gotten this far, but it doesn't extend to multiple options. Looking for a sort of "chain if present" type operation.
#jobs = Job.all
#jobs = Job.budget_min(params[:budget_min]) if params[:budget_min]
I think you are close. Something like this won't extend to multiple options?
query = Job.scoped
query = query.budget_min(params[:budget_min]) if params[:budget_min]
query = query.budget_max(params[:budget_max]) if params[:budget_max]
#jobs = query.all
Generally, I'd prefer hand-made solutions but, for this kind of problem, a code base could become a mess very quickly. So I would go for a gem like meta_search.
One way would be to put your conditionals into the scopes:
scope :budget_max, lambda { |max| where("budget < ?", max) unless max.nil? }
That would still become rather cumbersome since you'd end up with:
Job.budget_min(params[:budget_min]).budget_max(params[:budget_max]) ...
A slightly different approach would be using something like the following inside your model (based on code from here:
class << self
def search(q)
whitelisted_params = {
:budget_max => "budget > ?",
:budget_min => "budget < ?"
}
whitelisted_params.keys.inject(scoped) do |combined_scope, param|
if q[param].nil?
combined_scope
else
combined_scope.where(whitelisted_params[param], q[param])
end
end
end
end
You can then use that method as follows and it should use the whitelisted filters if they're present in params:
MyModel.search(params)

Resources