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
Related
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
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
Quick question here. Given the following example many-to-many relationship, how would I query the Physician table for appointments they have today?
class Physician < ActiveRecord::Base
has_many :appointments
has_many :patients, through: :appointments
end
class Appointment < ActiveRecord::Base
belongs_to :physician
belongs_to :patient
end
class Patient < ActiveRecord::Base
has_many :appointments
has_many :physicians, through: :appointments
end
On the Physician model I have the following:
scope :for, -> (name) { find_by_name(name: name) }
# I need a hand here, the join I assumed would work didn't seem to filter properly.
# scope :appointments_today, -> { joins(:appointment).where("appointments.appointment_date = ?", Date.today) }
scope :appointments_today, -> { ??? }
I'd like to chain queries on the controller as such:
data = Physician.for("test").appointments_today
Do you want a list of Physician records that have an appointment scheduled for today, or do you want a list of Appointment records that are for a specific physician and are scheduled for today?
Physicians that have an appointment today:
Physician.
joins(:appointments).
where(
name: "test",
appointments: {
appointment_date: (Date.today.beginning_of_day..Date.today.end_of_day)
}
)
Appointments for a physician that are today:
Appointment.
joins(:physician).
where(
appointment_date: (Date.today.beginning_of_day..Date.today.end_of_day),
physicians: { name: "test" }
)
As scopes, you can do physicians that have an appointment today:
class Physician < ActiveRecord::Base
scope :named, -> (name) { where(name: name) }
scope :with_appointments_on, -> (date) { joins(:appointments).where(appointments: { appointment_date: (date.beginning_of_day..date.end_of_day) })}
end
Physician.named("test").with_appointments_on(Date.today)
Or appointments for a physician that are today:
class Appointment < ActiveRecord::Base
scope :on_date, -> (date) { where(appointment_date: (date.beginning_of_day..date.end_of_day)) }
end
Physician.find_by_name("test").appointments.on_date(Date.today)
Try to do this
scope :for, -> (name) { where(name: name) }
Next, you have to add plural into appointments
scope :appointments_today, -> { joins(:appointments).where("appointments.appointment_date = ?", Date.today) }
I hope this help you.
If you are trying to fetch a list of appointments, the logic should go in the Appointment model:
class Appointment < ActiveRecord::Base
belongs_to :physician
belongs_to :patient
scope :for_today, -> { where('appointments.appointment_date >= ? AND appointments.appointment_date < ?', Time.zone.now.beginning_of_day, Time.zone.now.end_of_day) }
scope :for_physician, -> (name) { joins(:physician).where(physicians: {name: name}) }
end
And the you can find the appointments by:
data = Appointment.for_today.for_physician("test")
I have a class which is generating queries for a db:
They should concatenated, so I do not repeat my code for a query (DRY).
I thought I could do something like this:
ruby
class Blah < ActiveRecord::Base
def no_scope
{scope: false}
end
def for_user(user_id)
{user_id: user_id}
end
end
Now my query
Blah.no_scope.for_user(1)
RESULT SHOULD BE A HASH:
{user_id: 1, scope: false}
Did you try to achieve this?
class Blah < ActiveRecord::Base
scope :no_scope, -> { where(scope: false) }
scope :for_user, -> (id) { where(user_id: id) }
end
Blah.no_scope.for_user(1) # where(scope: false, user_id: 1)
You just need to create scopes.
class Blah < ActiveRecord::Base
scope :no_scope, -> {all}
scope :for_user, -> (user_id) {where(user_id: user_id)}
end
Blah.no_scope.for_user(1) # where(user_id: 1)
See also scopes
To give the result as a real Hash instead of a Activerecord relation, you do it like this
class Blah < ActiveRecord::Base
scope :no_scope, ->{where(scope: false)}
scope :for_user, ->(id){where(user_id: id)} # no space in ->(id)
end
# give results as an array of hashes
Blah.no_scope.for_user(1).map { |e| {scope: e.scope, user_id: e.user_id} }
gives
[{"scope"=>false, "user_id"=>1}]
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