Rails 4 chaining scope - ruby-on-rails

I would like to reuse a scope I defined, but it returns an array.
scope :currents, -> { (where('started_at < NOW()') | where('started_at' => nil)) & where('ended_at > NOW()') & where('published_at < NOW()') }
def self.findNextAuctions()
currents.order(ended_at: :asc).limit(3)
end
When I call the function findNextAuctions I get this error:
1) Error:
AuctionTest#test_should_fint_next_auctions:
NoMethodError: undefined method `order' for #<Array:0x007fae5d6e3878>
app/models/auction.rb:13:in `findNextAuctions'
test/models/auction_test.rb:14:in `block in <class:AuctionTest>'

Rails doesn't have an OR statement. You can write SQL directly though so something like
where("(started_at < NOW() or started_at = null) and ended_at > NOW() and published_at < NOW()")

Related

Rails active record where chaining losing scope

Model Food has scope expired:
Food.rb
class Food < ApplicationRecord
default_scope { where.not(status: 'DELETED') }
scope :expired, -> { where('exp_date <= ?', DateTime.now) }
belongs_to :user
end
In my controller I'm chaining where conditions to filter foods by user and status:
query_type.rb
def my_listing_connection(filter)
user = context[:current_user]
scope = Food.where(user_id: user.id)
if filter[:status] == 'ARCHIVED'
# Line 149
scope = scope.where(
Food.expired.or(Food.where(status: 'COMPLETED'))
)
else
scope = scope.where(status: filter[:status])
end
scope.order(created_at: :desc, id: :desc)
# LINE 157
scope
end
Here is the rails log:
Food Load (2.7ms) SELECT `foods`.* FROM `foods` WHERE `foods`.`status` !=
'DELETED'
AND ((exp_date <= '2020-07-02 09:58:16.435609') OR `foods`.`status` = 'COMPLETED')
↳ app/graphql/types/query_type.rb:149
Food Load (1.6ms) SELECT `foods`.* FROM `foods` WHERE `foods`.`status` != 'DELETED'
AND `foods`.`user_id` = 1 ORDER BY `foods`.`created_at` DESC, `foods`.`id` DESC
↳ app/graphql/types/query_type.rb:157
Why does active records query loses expired scope (and a condition) in line 157?
It is ignored because where doesn't expect scopes like that. But you can use merge instead. Replace
scope = scope.where(
Food.expired.or(Food.where(status: 'COMPLETED'))
)
with
scope = scope.merge(Food.expired)
.or(Food.where(status: 'COMPLETED'))
or
scope = scope.where(status: 'COMPLETED').or(Food.expired)

Creating a scope with a query from a method on Ruby on Rails

Im trying to create a scope on a Model at my project, but I want that the query filter the elements basead on a method at my Model.
This is my model:
class Talent < ApplicationRecord
scope :public_profile, -> { Talent.all.select{ |t| t.age > 13 } }
def age
now = Time.now.utc.to_date
now.year - self.birth_date.year - ((now.month > self.birth_date.month ||
(now.month == self.birth_date.month && now.day >= self.birth_date.day)) ? 0 : 1)
end
When I try to run this scope I get the error:
NoMethodError: undefined method `year' for nil:NilClass
from app/models/talent.rb:195:in `age'
from (irb):6:in `block in irb_binding'
from (irb):6
The line 195 its on my method age. So, probably when doing the select, my element is coming nil.
What Im doing wrong here?
Given:
NoMethodError: undefined method `year' for nil:NilClass
And given that you're only calling the method year twice in the age method:
def age
now = Time.now.utc.to_date
now.year - self.birth_date.year - ((now.month > self.birth_date.month ||
(now.month == self.birth_date.month && now.day >= self.birth_date.day)) ? 0 : 1)
end
And given that now is certain not to be nil:
now = Time.now.utc.to_date
It would seem that self.birth_date is returning nil. And, as the error states, nil doesn't have the year method.

Better solution for "one, other or both" cases

I was checking some code, and something similar to the following showed up:
def between_dates(date_1, date_2)
if date_1 && date_2
conditions "created_at >= date_1 AND created_at <= date_2"
elseif date_1
conditions "created_at >= date_1"
elseif date_2
conditions "created_at <= date_2"
end
end
It looked the kind of code that could be improved, but I couldn't find a more elegant solution for such a trivial and common conditional statement.
I'm looking for a better answer for this problem when we must return a value for one, other or both.
Rails lets you build a query dynamically. Here's an example using scopes and a class method. Since scopes always return an ActiveRecord::Relation object (even if the block returns nil), they are chainable:
class Event < ApplicationRecord
scope :created_before, -> (date) { where('created_at <= ?', date) if date }
scope :created_after, -> (date) { where('created_at >= ?', date) if date }
def self.created_between(date_1, date_2)
created_after(date_1).created_before(date_2)
end
end
Example usage:
Event.created_between(nil, Date.today)
# SELECT `events`.* FROM `events` WHERE (created_at <= '2018-05-15')
Event.created_between(Date.yesterday, nil)
# SELECT `events`.* FROM `events` WHERE (created_at >= '2018-05-14')
Event.created_between(Date.yesterday, Date.today)
# SELECT `events`.* FROM `events` WHERE (created_at >= '2018-05-14') AND (created_at <= '2018-05-15')
I'd use something like this:
def between_dates(date_1, date_2)
parts = []
if date_1
parts << "created_at >= date_1"
end
if date_2
parts << "created_at <= date_2"
end
full = parts.join(' AND ')
conditions(full)
end
This can be further prettified in many ways, but you get the idea.
def between_dates(date_1, date_2)
date_conditions = []
date_conditions << 'created_at >= date_1' if date_1
date_conditions << 'created_at <= date_2' if date_2
conditions date_conditions.join(' AND ') unless date_conditions.empty?
end
I am not sure if this is more elegant, but I always do reduce everything to avoid typos:
[[date_1, '>='], [date_2, '<=']].
select(&:first).
map { |date, sign| "created_at #{sign} #{date}" }.
join(' AND ')

I am trying to do a join in ruby with parameters. I am getting an error

Controller code:
#fte_new_hires = Bvadmin::EmployeeApplicant.fte_new_hires(next_startdate, next_enddate)
Model code:
class Bvadmin::EmployeeApplicant < Bvadmin::Record
self.table_name = "BVADMIN.EMPLOYEE_APPLICANTS"
self.primary_key = :applicant_id
has_many :employeeapplications
scope :fte_new_hires, -> (startdate,enddate) {EmployeeApplicant.joins (:employeeapplications).where("confirmed_eod >= ? and confirmed_eod <= ?", startdate, enddate).order('name ASC') }
end
Getting following error:
uninitialized constant Bvadmin::EmployeeApplicant::EmployeeApplicant
Your class name is 'Bvadmin::EmployeeApplicant', but you used 'EmployeeApplicant' in the scope, so it tried to find constant 'EmployeeApplicant' in this class 'Bvadmin::EmployeeApplicant', that is why you got 'uninitialized constant Bvadmin::EmployeeApplicant::EmployeeApplicant' error
joins is equivalent to self.joins which is equivalent to Bvadmin::EmployeeApplicant.joins
self in this class is Bvadmin::EmployeeApplicant, so you can just do like below,
Updated
scope :fte_new_hires, -> (startdate,enddate) {joins(:employee_applications).where("confirmed_eod >= ? and confirmed_eod <= ?", startdate, enddate).order('name ASC') }
Change your Association like below:
has_many :employee_applications, class: "Bvadmin::EmployeeApplicantion"

Rails 3 "scoped" value (scope vs class method)

I would like to know, why scoped value is different in case of scope keyword and a class method
class A < ActiveRecord::Base
scope :first_scope, -> { where( "1=1" ) } # to be used by both
scope :my_scope, -> { p "S: #{ scoped.to_sql }"; where( "2=2" ) }
def my_scope_2
p "S: #{ scoped.to_sql }";
where( "2=2" )
end
end
And testing what is it going to print out:
A.first_scope.my_scope # "S: SELECT * FROM `A`"
A.first_scope.my_scope_2 # "S: SELECT * FROM `A` WHERE (1=1)
Although they produce the same relation object in the end: SELECT * FROM A WHERE (1=1) AND (2=2), the intermediate scoped object is NOT(?) correct for scope definition
Is that expected behaviour?
rails 3.2.21; ruby 2.1.5p273

Resources