Scope in where clause - ruby-on-rails

You can see the last lines of these two models use the same code:
class Position < ActiveRecord::Base
scope :last_day,
where('positions.created_at > ? AND positions.hidden = ?',
DateTime.now.in_time_zone("Sofia").yesterday.beginning_of_day + 10.hours, false)
end
class Subscriber < ActiveRecord::Base
scope :notify_today,
joins(:tags => :positions).
where('positions.created_at > subscribers.created_at
AND positions.created_at > ? AND positions.hidden = ?',
DateTime.now.in_time_zone("Sofia").yesterday.beginning_of_day + 10.hours, false)
end
Is it possible to reuse the 'last_day' scope somehow in the second model?

where() and joins() both return ActiveRecord::Relation objects, and they have a method called merge() which can merge other ActiveRecord::Relation objects. Therefore, you can simply do this
scope :notify_today, joins(:tags => :positions).merge(Position.last_day)
Also, & is an alias to merge(), so you should also be able to do this
scope :notify_today, joins(:tags => :positions) & Position.last_day

Related

How to do a query with a two-value range and a scope, with Ruby on Rails?

I'm trying to get the results between two values in Rails. I am using scope in model. My model is like the following:
# Model ad.rb
scope :min_price, -> (number) { "ads.price = #{number}%" }
scope :max_price, -> (number) { "ads.price = #{number}%" }
And this is my controller
# Controller ads_controller.rb
def index
#ads = Ad.where((min_price(params[:min_price]))..(max_price(params[:max_price]))).order("created_at DESC") if params[:min_price].present? if params[:max_price].present?
end
This does not work. What's the right way?
You should combine them when using them together, otherwise you will have one overwrite the other (depending on how you call them in the query).
class Ad
scope :min_max_price ->(min,max) { where('price > ? AND price < ?', min, max) }
end
Or to do it separately:
class Ad
scope :min_price, ->(min) { where('price > ?', min) }
scope :max_price, ->(max) { where('price < ?', max) }
end
class AdController < ApplicationController
def index
#ads = Ad.min_price(params[:min]).max_price(params[:max])
end
end
Scopes are designed to be chained, not included in the args to a where list. In particular, when you call where ActiveRecord returns a relation object that corresponds to the query. When you chain off of it with where, limit, order, etc, it creates a new Relation object that is the combination of the chained methods.
When you finally access an object of the collection with something like all or each, that's when the actual query is constructed and sent on to the database.

Dynamic search form for multiple columns of the same model in Rails 4

I am trying to create a search form with multiple columns(all from the same model)
Now I want to set the value of all other columns as nil if the category column is set to a particular value. Something like this-
#app/models/question.rb
if (cateogry.matches => 'Exam Questions')
query = query.where("name like ? AND course like ? AND year like ?", "%#{query}%", "%#{query}%", "%#{query}%")
else
query = query.where("name like ?","%#{query}%")
end
The search form is a basic form using get method.
You could use scopes for that.
class Question < ActiveRecord::Base
...
# Scope for searching by exam
scope :by_exam, -> (name, course, year) {
match(:name, name).
match(:course).
match(:year, year)
}
# Scope forsearching by nma
scope :by_name, ->(name) { match(:name, name) }
# Helper scope for matching
scope :match, ->(key, value) { where(arel_table[key].matches("%#{value}%"} }
end
So in your controller
if (cateogry.matches => 'Exam Questions')
Question.by_exam(params[:name], params[:course], params[:year])
else
Question.by_name(params[:name])
end

ActiveRecord: How to do an 'OR' merge?

I'm trying to decouple ActiveRecord queries in a model so they are reusable in different circumstances. To keep it simple, say I have a model called Product:
class Product < ActiveRecord::Base
def self.over_stocked
where('stock_count >= ?', 20)
end
def self.expensive
where('price >= ?', 100.0)
end
end
If I wanted to create a new method to find products that have too much stock AND are expensive, I could merge the two queries:
...
def self.costly_stock
# SQL => '... WHERE stock_count >= 20 AND price >= 100.0'
over_stocked.merge(expensive)
end
However how can I use these two methods to create a new query for products that are either expensive OR are over stocked?
E.g:
...
def expensive_or_over_stocked
# SQL => '... WHERE stock_count >= 20 OR price >= 100.0'
...
end
Basically I'm looking for something like merge that uses OR rather than AND. Ideally the solution would return an ActiveRecord Relation and not an Array. Obviously I could rewrite the query with where('stock_count >= ? OR price >= ?', 20, 100.0) however that wouldn't be very DRY
I came up with the following solution. One can argue how DRY it is.
class Product < ActiveRecord::Base
scope :over_stocked, -> { where.not(stock_count: [0..19]) }
scope :expensive, -> { where.not(price: [0..99]) }
scope :costly_stock, -> { expensive.over_stocked }
scope :neither_expensive_nor_over_stocked, -> { where(stock_count: [0..19]).where(price: [0..99]) }
def self.expensive_or_over_stocked
Product.where.not(id: Product.neither_expensive_nor_over_stocked.pluck(:id))
end
end

How to get records created at the current month?

I have a candidate which has_many votes.
I am trying to get the votes of a candidate that were created in the current month?
#candidate.votes.from_this_month
scope :from_this_month, where("created_at > ? AND created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)
That gives me a PG error:
PG::Error: ERROR: column reference \"created_at\" is ambiguous
If I try
scope :from_this_month, where("vote.created_at > ? AND vote.created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)
I get the following error
PG::Error: ERROR: missing FROM-clause entry for table "vote"
Correct scope
scope :from_this_month, lambda {where("votes.created_at > ? AND votes.created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)}
This is because in rails the model names are singular(i.e Vote) and tables created are pural (e.g. votes) by convection
EDIT
This can be written simpler with lambda {where(created_at: Time.now.beginning_of_month..(Time.now.end_of_month))} and we need to use lambda due to the reason given in below comments.
Thanx BroiSatse for reminding :D
You need to enclose the where in a lamda as well.
scope :from_this_month, lambda { where("votes.created_at > ? AND votes.created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month) }
Otherwise it may appear to work and your tests will all pass, but if your app runs for more than a month you will start to get incorrect results because Time.now is evaluated when the class loads, not when the method is called.
From Rails 5 you have a much cleaner syntax. On your votes model add a scope this_month
scope :this_month, -> { where(created_at: Date.today.all_month) }
You can now query #candidates.votes.this_month
scope :this_month, -> { where(created_at: Time.zone.now.beginning_of_month..Time.zone.now.end_of_month) }
and you can call the scope:
Model.this_month
You could use an ActiveRecord Association Extension:
#app/models/Candidate.rb
Class Candidate < ActiveRecord::Base
has_many :votes do
def from_this_month
where("created_at > ? AND created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)
end
end
end
This should allow you to call #candidate.votes.from_this_month to return the required conditional data

Rails 3. how to make a nested attribute query?

I have shipment has one invoice; invoice belongs to shipment. The shipments table is the one that contains the customer_id.
I need to find all invoices...
for a particular customer and
that have customer_account_balance of 0
I have tried many different approaches but none seem to work, this last one got me error private method select or something like that...
reports_controller.rb
i = Invoice.where("customer_open_balance != 0")
s = Shipment.find_by_customer_id(#customer.id)
shipment_ids_from_invoices = i.map{|x| x.shipment_id}
#shipments = s.select{|z| shipment_ids_from_invoices.include? z.id}
Does this work?
#shipments = Shipment.joins(:invoice).where(:customer_id => #customer.id).where("customer_account_balance <> 0")
It sounds like your schema looks like this:
Shipment: (customer_id, ...)
Invoice: (customer_open_balance, shipment_id, ...)
Did you put has_one :invoice in Shipment.rb and belongs_to :shipment in Invoice.rb?
class Invoice
belongs_to :shipment
scope :with_customer, lambda { |customer_id| joins(:shipment).where(:customer_id => customer_id) }
scope :cero_balance, joins(:shipment).joins(:customer).where("customer_account_balance <> 0")
end
Then try
#for a particular customer with id 1
Invoice.with_customer 1
#that have customer_account_balance of 0
Invoice.cero_balance

Resources