If I want to make the following scopes available to multiple models how do I do so without having to add them directly into each model?
scope :today, -> { where("DATE(created_at) = DATE(?)", Date.today ) }
scope :yesterday, -> { where("DATE(created_at) = DATE(?)", 1.day.ago) }
scope :last_week, -> { where("DATE(created_at) = DATE(?)", 1.week.ago) }
One of the prescribed ways is by using concerns.
You should be able to create a file like this at app/models/concerns/dateable.rb:
module Dateable
extend ActiveSupport::Concern
included do
scope :today, -> { where("DATE(created_at) = DATE(?)", Date.today ) }
scope :yesterday, -> { where("DATE(created_at) = DATE(?)", 1.day.ago) }
scope :last_week, -> { where("DATE(created_at) = DATE(?)", 1.week.ago) }
end
end
Then include it into the models that need it.
class Employee < ApplicationRecord
include Dateable
end
class Customer < ApplicationRecord
include Dateable
end
Related
Some scopes of my Unit model:
class Unit < ApplicationRecord
scope :committees, -> { where(unit_type: UnitType.committee) }
scope :departments, -> { where(unit_type: UnitType.department) }
scope :faculties, -> { where(unit_type: UnitType.faculty) }
scope :programs, -> { where(unit_type: UnitType.program) }
scope :universities, -> { where(unit_type: UnitType.university) }
end
class UnitType < ApplicationRecord
enum group: {
other: 0,
university: 1,
faculty: 2,
department: 3,
program: 4,
committee: 5
}
end
I want to create new scope with using other scopes like this:
class Unit < ApplicationRecord
...
scope :for_curriculums, -> { universities.or(faculties).or(departments) }
scope :for_group_courses, -> { faculties.or(departments) }
...
end
But in this way too many double-triple combinations are occurred.
When I use send parameter like following code, 'and' method is running instead of 'or' method.
class Unit < ApplicationRecord
...
# unit_types = ['faculties', 'departments']
def self.send_chain(unit_types)
unit_types.inject(self, :send)
end
end
How can I do, is there any possibility?
class Unit < ApplicationRecord
UnitType.groups.each do |unit_type|
scope ActiveSupport::Inflector.pluralize(unit_type), -> {
where(unit_type: unit_type)
}
end
scope :by_multiple_unit_types, ->(unit_types) {
int_unit_types = unit_types.map { |ut| UnitType.groups.index(ut) }.join(',')
where("unit_type IN (?)", int_unit_types)
}
end
I have:
price_plan.rb
class PricePlan < ActiveRecord::Base
has_many :users
scope :premium, lambda { where('price > ?', 0) }
scope :free, lambda { where('price == ?', 0) }
end
user.rb
class User < ActiveRecord::Base
belongs_to :price_plan
has_one :account
scope :free, lambda { joins(PricePlan.free) } #<--- no!
end
How to define scope for users, that use service free of charge?
This below should work, but I don't like it.
scope :free,-> where(priceplan_id: PricePlan.free.pluck(:id))
It will be
Solution 1: Use condition on relation
class User < ActiveRecord::Base
# Your current code
belongs_to :free_price_plan, -> { free }, class_name: 'PricePlan'
belongs_to :premium_price_plan, -> { premium }, class_name: 'PricePlan'
end
Solution 2: Define a scope
class User < ActiveRecord::Base
# Your current code
scope :free, -> {
joins(:price_plan).merge(PricePlan.free)
}
scope :premium, -> {
joins(:price_plan).merge(PricePlan.premium)
}
end
In apps/models/concerns/deactivable.rb
module Deactivatable
extend ActiveSupport::Concern
included do
scope :alive, -> { where(:deactivated_at => nil) }
end
def deactivate(t = Time.current)
update_attribute(:deactivated_at,t)
end
def activate
update_attribute(:deactivated_at,nil)
end
def deactivated?
deactivated_at.present?
end
end
This is being included in 2 models, app/models/activity_rules/activity_detection_rule.rb and app/models/concerns/generic_campaign.rb.
There are 2 more models which contain the same methods with different attribute name.
In redeemable.rb,
class Redeemable < ActiveRecord::Base
scope :alive, -> { where("(deactivation_date is null) and (expiry_date is null or expiry_date >= ?)",Date.today) }
def deactivate(t = Time.current)
update_attribute(:deactivation_date,t)
end
def reactivate
update_attribute(:deactivation_date,nil)
end
def deactivated?
deactivation_date.present?
end
end
and in surprise_set.rb
scope :alive, -> { where("deactivation_date is null") }
with the same 3 methods as redeemable.rb.
How to use Deactivable concern to DRY up the other two models?
You could return the attribute that indicates the time of deactivation from a class method. You can provide a default implementation in your concern and override in the class that includes the concern if you need to:
module Deactivatable
extend ActiveSupport::Concern
included do
scope :alive, -> { where(deactive_attr => nil) }
def self.deactive_attr
:deactivated_at
end
end
def deactivate(t = Time.current)
update_attribute(self.class.deactive_attr, t)
end
def activate
update_attribute(self.class.deactive_attr, nil)
end
def deactivated?
self.send(self.class.deactive_attr).present?
end
end
Then, in classes where you want to provide a different attribute you can add a class method:
include Deactivatable
def self.deactive_attr
:deactivation_date
end
You could also DRY up your alive scope a bit by allowing the class that includes the concern to define the conditions for 'aliveness'. In the concern you can define the default
scope :alive, -> { where(self.active_conditions) }
def self.active_conditions
{ self.deactive_attr => nil }
end
You can then provide a different implementation of active_conditions in the class itself:
self self.active_conditions
["(deactivation_date is null) and
(expiry_date is null or expiry_date >= ?)", Date.today]
end
I have a search page that narrows down the list of a specific class, and I want an OR condition that can grab two different conditions and add the together, for example, I have classes
model/party.rb
class Party < ActiveRecord::Base
has_many :invitations
end
mode/invitation.rb
class Invitation < ActiveRecord::Base
belongs_to :party
end
invitation has a status attribute, which will be "decline", "accept", or "unanswered"
What I want to do is grab all the parties that do not have any invitations, or any that have all of the invitations "unanswered".
I currently do
scope :not_confirmed, lambda { find_by_sql( "SELECT * FROM `parties` INNER JOIN `invitations` ON `invitations`.`party_id` = `parties`.`id` WHERE (invitations.status = 'unanswered') OR (parties.id NOT IN (SELECT DISTINCT(party_id) FROM invitations))" ) }
which works, but since it does not lazy load I can't add them in a facet like query.
I did something like
no_invitations.or(no_one_has_answered)
but it did not work.
I especially do not get the concept of using OR on AREL, could someone please help out?
edited:
For a very ugly yet functional work around until I get this down, here is what I have done
party.rb
scope :not_confirmed, lambda { joins(:invitations).where( "invitations.status NOT IN (?)", ["accepted", "declined" ] ) }
scope :with_no_invitations, lambda { includes(:invitaions).where( :invitations => { :party_id => nil } ) }
search_controller.rb
#parties = Party.all_the_shared_queries
#parties = ( #parties.not_confirmed + #parties.with_no_invitations).uniq
The query:
scope :not_confirmed, lambda { find_by_sql( "SELECT * FROM `parties` INNER JOIN `invitations` ON `invitations`.`party_id` = `parties`.`id` WHERE (invitations.status = 'unanswered') OR (parties.id NOT IN (SELECT DISTINCT(party_id) FROM invitations))" ) }
can be converted to arel with some transformation using boolean algebra too. But since it is only theoretical conversion, you have to verify it manually. So:
class Invitation < ActiveRecord::Base
belongs_to :party
scope :non_answered, -> { where(arel_table[:status].not_eq('unanswered')) }
end
class Party < ActiveRecord::Base
has_many :invitations
scope :not_confirmed, -> { not.join(:invitaions).merge(Invitation.non_answered)) }
end
Please test it and comment here.
Firstly, from the question tags, I have assumed that you are using Rails3 (had it been Rails4, there were more easy ways of doing things :))
For your requirement above (ie grab all the parties that do not have any invitations, or any that have all of the invitations "unanswered"), here is a way of doing it (using scope :unattended):
Party Model:
class Party < ActiveRecord::Base
has_many :invitations
scope :invitations_answered, -> { joins(:invitations).merge(Invitation.answered) }
scope :unattended, -> { where(arel_table[:id].not_in invitations_answered.pluck(:id)) }
end
Invitation Model:
class Invitation < ActiveRecord::Base
belongs_to :party
scope :answered, -> { where(status: ["decline", "accept"])}
end
In Rails 4, you can use where.not and simplify it further like this:
Party Model:
class Party < ActiveRecord::Base
has_many :invitations
scope :invitations_answered, -> { joins(:invitations).merge(Invitation.answered) }
scope :unattended, -> { where.not(id: invitations_answered.pluck(:id)) }
end
Invitation Model:
class Invitation < ActiveRecord::Base
belongs_to :party
scope :answered, -> { where.not(status: 'unanswered') }
end
Upgrading Rails 3.2. to Rails 4. I have the following scope:
# Rails 3.2
scope :by_post_status, lambda { |post_status| where("post_status = ?", post_status) }
scope :published, by_post_status("public")
scope :draft, by_post_status("draft")
# Rails 4.1.0
scope :by_post_status, -> (post_status) { where('post_status = ?', post_status) }
But I couldn't find out how to do the 2nd and 3rd lines. How can I create another scope from the first scope?
Very simple, just same lambda without arguments:
scope :by_post_status, -> (post_status) { where('post_status = ?', post_status) }
scope :published, -> { by_post_status("public") }
scope :draft, -> { by_post_status("draft") }
or more shorted:
%i[published draft].each do |type|
scope type, -> { by_post_status(type.to_s) }
end
From the Rails edge docs
"Rails 4.0 requires that scopes use a callable object such as a Proc or lambda:"
scope :active, where(active: true)
# becomes
scope :active, -> { where active: true }
With this in mind, you can easily rewrite you code as such:
scope :by_post_status, lambda { |post_status| where('post_status = ?', post_status) }
scope :published, lambda { by_post_status("public") }
scope :draft, lambda { by_post_status("draft") }
In the event that you have many different statuses that you wish to support and find this to be cumbersome, the following may suit you:
post_statuses = %I[public draft private published ...]
scope :by_post_status, -> (post_status) { where('post_status = ?', post_status) }
post_statuses.each {|s| scope s, -> {by_post_status(s.to_s)} }