Active Record query when some conditions are absent - ruby-on-rails

I have the following hash conditions for retrieving products from the database:
#products = Product.where(category: #category, color: params[:color], brand_id: params[:brand], size: params[:size]).
Is there a way to get the query result when one or more of the conditions are not supplied, for example
if params[:color] is absent, how can i retrieve the products with the other conditions?, or how can i restructure the conditions to achieve this?

I'm going to base myself on techvineet's answer (editing was becoming messy).
Try this:
# This line throws away the :action and :controller arguments
params.except!(:action, :controller) # notice the ! now
params.delete_if {|k,v| v.nil?} # throw out any empty args
params[:category] = #category
#products = Product.where(params)

You could also use something like this:-
params.except(:action, :controller)
params[:category] = #category
#products = Product.where(params)

You can try Periscope gem
Or Ransack

You can build your query using conditions like this:
product_scope = Product.where(:category => #category)
product_scope = product_scope.where(:color => params[:color]) if params[:color]
# (...)
#products = product_scope.all
That way, only the valid and present conditions are being added to the scope. Or you could build a hash with your options in a similar way and add only the keys with valid params, then call Product.where(conditions_hash).all

Related

Problem with selecting elements with the same params

What i do wrong? I want to return every products which pass condition:
def show
products = Product.select{|x| x[:category_id] == params[:id]}
render json: products
end
When i write
def show
products = Product.select{|x| x[:category_id] == 1}
render json: products
end
it works why the first example doesn't work?
I am pretty sure that there is mismatch in data type.
1=='1' #will be always false
1==1 #will be true
'1'=='1' #will be true as well
And also check for nil value from params[:id]
Please make sure to change as follows
def show
products = Product.select{|x| x.category_id == params[:id].to_i}
render json: products
end
OR
The best solution as suggested by #Eyeslandic is to use .where as it will not check for mismatch in data type. And also you don't have to take care of nil value from params[:id].
You should really be using a where to stop sql from loading all your products.
#products = Product.where('category_is = ?', params[:id])
The being said, if you are sticking to rails restful conventions, the fact you have a param called :id that is the category_id suggests you are on the category controller. So maybe consider changing your logic to:
#category = Category.includes(:products).find(params[:id])
you can then access products via
#category.products
or if your not interested in the category too much maybe
#products = Category.includes(:products).find(params[:id])&.products

Rails 3: Search method returns all models instead of specified

What I'm trying to do: I have a model "Recipe" in which I defined a method "search" that takes an array of strings from checkboxes (I call them tags), and a single string. The idea is to search the db for recipes that has anything in it's 'name' or 'instructions' that contains the string, AND also has any of the tags matching it's 'tags' property.
Problem: The search method return all the recipes in my db, and doesn't seem to work at all at finding by the specific parameters.
The action method in the controller:
def index
#recipes = Recipe.search(params[:search], params[:tag])
if !#recipes
#recipes = Recipe.all
end
respond_to do |format|
format.html
format.json { render json: #recipe }
end
end
The search method in my model:
def self.search(search, tags)
conditions = ""
search.present? do
# Condition 1: recipe.name OR instruction same as search?
conditions = "name LIKE ? OR instructions LIKE ?, '%#{search[0].strip}%', '%#{search[0].strip}%'"
# Condition 2: if tags included, any matching?
if !tags.empty?
tags.each do |tag|
conditions += "'AND tags LIKE ?', '%#{tag}%'"
end
end
end
# Hämtar och returnerar alla recipes där codition 1 och/eller 2 stämmer.
Recipe.find(:all, :conditions => [conditions]) unless conditions.length < 1
end
Any ideas why it return all records?
if you are using rails 3, then it is easy to chain find conditions
def self.search(string, tags)
klass = scoped
if string.present?
klass = klass.where('name LIKE ? OR instructions LIKE ?', "%#{string}%", "%#{string}%")
end
if tags.present?
tags.each do |tag|
klass = klass.where('tags LIKE ?', "%#{tag}%")
end
end
klass
end
When you do
search.present? do
...
end
The contents of that block are ignored - it's perfectly legal to pass a block to a function that doesn't expect one, however the block won't get called unless the functions decides to. As a result, none of your condition building code is executed. You probably meant
if search.present?
...
end
As jvnill points out, it is in general much nicer (and safer) to manipulate scopes than to build up SQL fragments by hand

How to chain optional Mongoid criteria in separate statements?

I'm trying to chain criteria based on optional rails
parameters.
I want to be able to simultaneously filter based on selected tags as
well as searching.
Here is the current code that works in all situations:
if params[:tag] and params[:search]
#notes = Note.tagged_with_criteria(params[:tag]).full_text_search(params[:search])
elsif params[:tag]
#notes = Note.tagged_with_criteria(params[:tag])
elsif params[:search]
#notes = Note.full_text_search(params[:search])
end
I tried simply using
#notes = Note.tagged_with_criteria(params[:tag]).full_text_search(params[:search])
without the if statement, but then if only one of the params was
given, then all notes are returned.
Each of the methods (tagged_with_criteria and full_text_search) are
returning Note.criteria if their parameter is nil / empty.
Is there a simpler, more elegant way to chain Mongoid criteria like this?
I'd rather keep tacking on criteria one-by-one instead of having to do
the weird "if params[...] and params[...]" thing..
UPDATE: here are the current methods:
def tagged_with_criteria(_tags)
_tags = [_tags] unless _tags.is_a? Array
if _tags.empty?
criteria
else
criteria.in(:tags => _tags)
end
end
def self.full_text_search(query)
if query
begin
regex = /#{query}/
# supplied string is valid regex (without the forward slashes) - use it as such
criteria.where(:content => regex)
rescue
# not a valid regexp -treat as literal search string
criteria.where(:content => (/#{Regexp.escape(query)}/))
end
else
# show all notes if there's no search parameter
criteria
end
end
In a situation like that, I would modify the scopes to do nothing when passed in blank values.
I think what might be happening is you are getting empty strings from the params hash, which is causing your code to think that something was entered. Try the scopes with these edits.
def tagged_with_criteria(_tags)
_tags = Array.wrap(_tags).reject(&:blank?)
if _tags.empty?
criteria
else
criteria.in(:tags => _tags)
end
end
def self.full_text_search(query)
if query.present?
begin
regex = /#{query}/
# supplied string is valid regex (without the forward slashes) - use it as such
criteria.where(:content => regex)
rescue
# not a valid regexp -treat as literal search string
criteria.where(:content => (/#{Regexp.escape(query)}/))
end
else
# show all notes if there's no search parameter
criteria
end
end

DRYing Search Logic in Rails

I am using search logic to filter results on company listing page. The user is able to specify any number of parameters using a variety of named URLs. For example:
/location/mexico
/sector/technology
/sector/financial/location/argentina
Results in the following respectively:
params[:location] == 'mexico'
params[:sector] == 'technology'
params[:sector] == 'financial' and params[:location] == 'argentina'
I am now trying to cleanup or 'DRY' my model code. Currently I have:
def self.search(params)
...
if params[:location]
results = results.location_permalink_equals params[:location] if results
results = Company.location_permalink_equals params[:location] unless results
end
if params[:sector]
results = results.location_permalink_equals params[:sector] if results
results = Company.location_permalink_equals params[:sector] unless results
end
...
end
I don't like repeating the searchs. Any suggestions? Thanks.
This is how I would write it:
[params[:location], params[:sector]].reject(&:nil?).each do |q|
results = (results ? results : Company).location_permalink_equals q
end
There's plenty of other ways, just an idea. Has the benefit of making it easy to add say params[:street] or something.
I don't think you can really DRY that up much when sticking to SearchLogic... I'd suggest to refine your routes to directly emit *_permalink as parameter names and do something like this:
Company.all :conditions => params.slice(:location_permalink, :sector_permalink)
or
Company.find :all, :conditions => params.slice(:location_permalink, :sector_permalink)
Documentation link: http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Hash/Slice.html

Optional mix of filter parameters in a search the Rails way

I've got a simple list page with a couple of search filters status which is a simple enumeration and a test query which I want to compare against both the title and description field of my model.
In my controller, I want to do something like this:
def index
conditions = {}
conditions[:status] = params[:status] if params[:status] and !params[:status].empty?
conditions[???] = ["(descr = ? or title = ?)", params[:q], params[:q]] if params[:q] and !params[:q].empty?
#items = Item.find(:all, :conditions => conditions)
end
Unfortunately, it doesn't look like I can mix the two types of conditions (the hash and the paramatized version). Is there a "Rails Way" of doing this or do I simply have to do something awful like this:
has_status = params[:status] and !params[:status].empty?
has_text = params[:q] and !params[:q].empty?
if has_status and !has_text
# build paramatized condition with just the status
elsif has_text and !has_status
# build paramatized condition with just the text query
elsif has_text and has_status
# build paramatized condition with both
else
# build paramatized condition with neither
end
I'm migrating from Hibernate and Criteria so forgive me if I'm not thinking of this correctly...
Environment: Rails 2.3.4
You can mix hash and array conditions using scopes:
hash_conditions = {}
# build hash_conditions
items_scope = Item.scoped(:conditions => hash_conditions)
unless params[:q].blank?
items_scope = items_scope.scoped(:conditions => ["(descr = ? or title = ?)", params[:q], params[:q]])
end
...
items = items_scope.all
So you can mix and match any types of conditions, and the query will be executed only when you do items_scope.all
a=[],b=[]
unless params[:status].blank?
a << "status = ?"
b << params[:status]
end
unless params[:q].blank?
a << "(descr = ? or title = ?)"
b << params[:q] << params[:q]
end
#items = Item.all( :conditions => [a.join(" AND "), b] )
A better search on my part turned up something called "named scopes" which looks like is exactly what I'm looking for. I'm about to see if it will work with the will_paginate gem....
Reference:
http://edgerails.info/articles/what-s-new-in-edge-rails/2010/02/23/the-skinny-on-scopes-formerly-named-scope/

Resources