Rails 7 Advanced Search Chain Where - ruby-on-rails

I'd like to setup an advanced search of a Rails resource (i.e Product) where I can perform both positive and negative searches. For example:
Product is a phone
Product is not made by Apple
Product was made in the last 16 months
I can pass multiple parameters to a page but is there a way to chain queries?
#results = Product.where("lower(type) LIKE ?", "%#{search_term.downcase}%").where(....
I'd like to use a combination of where and where.not:
def search
word1 = params[:word_1]
word2 = params[:word_2]
if word1.starts_with?('not')
chain1 = where.not("lower(tags) LIKE ?", "%#{word1.downcase}%")
else
chain1 = where("lower(tags) LIKE ?", "%#{word1.downcase}%")
end
if word2.starts_with?('not')
chain2 = where.not("lower(tags) LIKE ?", "%#{word2.downcase}%")
else
chain2 = where("lower(tags) LIKE ?", "%#{word2.downcase}%")
end
#products = Product.chain1.chain2
end
but I get the following error:
undefined method where' for #ProductsController:0x0000000000ac58`

You can chain where like this
Product.
where(type: "phone").
where.not(factory: "Apple").
where(manufactered_at: 16.months.ago..)
Also rails 7 introduces invert_where (it inverts all condtions before it) so you can
Product.
where(factory: "Apple").invert_where.
where(type: "phone").
where(manufactered_at: 16.months.ago..)
You can use scopes
class Product < ApplicationRecord
scope :phone, -> where(type: "phone")
scope :apple, -> where(factory: "Apple")
scope :manufacatured_last, ->(period) { where(manufactered_at: period.ago..) }
end
Product.apple.invert_where.phone.manufacatured_last(16.months)

Related

Rails Searchkick with scopes in controller

I'm making a search page where I have a couple of filters on the side and I'm trying to integrate them with Searchkick to query products.
These are my scopes I'm using for the products
models/product.rb
scope :in_price_range, ->(range) { where("price <= ?", range.first) }
scope :in_ratings_range, -> (range) { where("average_rating >= ?", range.first) }
def self.with_all_categories(category_ids)
select(:id).distinct.
joins(:categories).
where("categories.id" => category_ids)
end
This is where I'm actually calling the scopes
controllers/search_controller.rb
#results = Product.search(#query)
#results = #results.with_all_categories(params[:category_ids]) if params[:category_ids].present?
#results = #results.in_price_range(params[:price]) if params[:price].present?
#results = #results.in_ratings_range(params[:rating]) if params[:rating].present?
After running it, I get an error saying the searchkick model doesn't have any methods with the name of my scope.
undefined method `with_all_categories' for #Searchkick::Results:0x00007f4521074c30>
How do I use scopes with my search query?
You can apply scopes to Searchkick results with:
Product.search "milk", scope_results: ->(r) { in_price_range(params[:price]) }
See "Run additional scopes on results" in the readme.
However, if you apply ActiveRecord where filters, it will throw off pagination. For pagination to work correctly, you need to use Searchkick's where option:
Product.search(query, where: {price_range: 10..20})
The error (unknown to me at the time of writing this answer) might be because you defined with_all_categories as a class method on Product, but in your controller you call it on #results which must be an ActiveRecord::Relation.
Turning it into a scope should fix the issue:
Change this:
def self.with_all_categories(category_ids)
select(:id).distinct.
joins(:categories).
where("categories.id" => category_ids)
end
to:
scope :with_all_categories, -> (category_ids) { select(:id).distinct.joins(:categories).where("categories.id" => category_ids) }

How can I get all records that return True in a model function in a rails app?

I have the following model:
class AuthorizedDriver < ActiveRecord::Base
belongs_to :car
def authorized?
!self.authorized_until.nil? && self.authorized_until.to_date >= Time.current.to_date
end
end
I would like to be able to do:
def show_authorized_drivers
#car = Car.find(params[:id])
#authorized_drivers = #car.authorized_drivers.where(authorized?: true)
end
I know I can do this with a specific field, but I would like to use the authorized? function (or another function at a later time) above.
Any guidance on this would be much appreciated, thanks!
I am using Rails 4.1.4 and Ruby 2.1.2.
You can do it like so:
#authorized_drivers = #car.authorized_drivers.to_a.select(&:authorized?)
Note that this fetches all the authorized_drivers for that car, then filters them by calling the #authorized? method.
Also note that
.select(&:authorized?)
is shortcut notation for
.select {|it| it.authorized? }
Add the following scope to your Driver model:
scope :authorized, -> { where('authorized_until >= ?', Time.current) }
Then you can query authorized drivers for a car like this:
#authorized_drivers = #car.authorized_drivers.authorized
Update (to answer your comment): You have two options if you need to add another criteria.
You could combine both conditions into one scope:
scope :authorized, -> {
where('authorized_until >= ?', Time.current).where(status: 'Active')
}
Or you could add another scope:
scope :active, -> { where(status: 'Active') }
and just chain the scopes:
#authorized_drivers = #car.authorized_drivers.authorized.active

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)

Rails3 How can I use :params in named scope?

I'm trying to display a list of milestones for a particular order. (Orders have many milestones.)
In my orders model, I have this:
scope :open, lambda {
joins("join milestones on milestones.order_id = orders.id").
where("order_id = ? AND milestone_status = ?", :params[:order_id], true).
group("orders.id")
}
The problem I'm having is getting the current order ID to work - :params[:order_id] is clearly wrong.
In my routes I have this:
resources :orders do
resources :milestones
end
And my url is as follows:
http://127.0.0.1/orders/2/milestones
How is this possible? I have tested the scope by replacing with an order ID manually.
-- EDIT --
As per advice below, I've put the following in my milestones controller:
#orders = Order.open( params[:order_id] )
And in my view, I have this:
<% #orders.each do |open| %>
But I get an error:
wrong number of arguments (1 for 0)
The full stacktrace is here: http://pastie.org/2442518
Define it like this:
scope :open, lambda { |order_id|
joins("join milestones on milestones.order_id = orders.id").
where("order_id = ? AND milestone_status = ?", order_id, true).
group("orders.id")
}
And call it on your controller like this:
def index
#orders = Order.open( params[:order_id] )
end

How to specify a Rails 3 scope.limit - with an offset?

So I have some posts, and would like to show n..m most recent entries in the sidebar (these numbers being set in a config)
I can get the latest n records easily enough
class Post < ActiveRecord::Base
default_scope :order => "created_at DESC"
scope :published, lambda { where("blog_entries.created_at <= ?", Time.zone.now) }
scope :latest, lambda { |n| published.limit(n) }
end
#posts = Post.latest(6)
But what I'd like is
#posts = Post.published.limit(6, 12)
but this gives wrong number of arguments, so is there any way in AR? Right now I'm playing with will_paginate, but it seems hacky to use it for this.
Ok, so the answer is, I think:
#posts = Post.published.limit(6).offset(5)
It will retrieve 6 posts, starting from the sixth.
edit2: About the limit([6, 12]), I find that strange:
attr_accessor :limit_value
def limit(value)
relation = clone
relation.limit_value = value
relation
end
def build_arel
...
arel.take(connection.sanitize_limit(#limit_value)) if #limit_value
...
end
def sanitize_limit(limit)
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
limit
elsif limit.to_s =~ /,/
Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
else
Integer(limit)
end
end
So I don't really see how it works with an array. But I obviously missed something. Do you see what?
For rails 5 (not sure for rails 4). offset(x).limit(y) works correctly. limit(y).offset(x) still behaves as described in other answers.

Resources