How to define the scope? - ruby-on-rails

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

Related

Rails: how to use scope inside has_many block in Model

In Rails 4 I am using:
class Ticket < ActiveRecord::Base
has_many :request_attendances, dependent: :destroy
has_many :attending_request_attendances, -> {
where("data->>'rsvp_completed' = 'true'")
.where("data->>'is_coming' = 'true'")
}, class_name: 'RequestAttendance'
end
In my Tickets model
And
class RequestAttendance < ActiveRecord::Base
belongs_to :tickets, inverse_of: :request_attendances
scope :is_coming, -> { where("data->>'is_coming' = 'true'")}
scope :rsvp_completed, -> { where("data->>'rsvp_completed' = 'true'")}
end
In my RequestAttendance model
I would like to do something like this
has_many :attending_request_attendances, -> {
:is_coming
:rsvp_completed
}, class_name: 'RequestAttendance'
To reuse the scope I have created in my RequestAttendance model.
Is something like this possible, at the moment it does not work, giving me the following error:
undefined method `except' for :rsvp_completed:Symbol
If I add a where to the has_many block like this:
has_many :attending_request_attendances, -> {
:is_coming
:rsvp_completed
where("data->>'rsvp_completed' = 'true'")
}, class_name: 'RequestAttendance'
It does not error, however it also does not use the scope clauses either.
You can chain scopes together inside an association like this:
has_many :attending_request_attendances, -> {
is_coming.rsvp_completed
}, class_name: 'RequestAttendance'
You have added the below code in RequestAttendance model
scope :is_coming, -> { where("data->>'is_coming' = 'true'")}
scope :rsvp_completed, -> { where("data->>'rsvp_completed' = 'true'")}
if you use the below code in Tickets Model
class Tickets < ActiveRecord::Base
has_many :RequestAttendance
end
scopes are available to has_many associations so it will fetch all the records with is_coming' = 'true' and "data->>'rsvp_completed' = 'true'"
You can fetch it using object tickets.requestAttendance.is_coming.rsvp_completed
Is it your expectation or If I misunderstood pls explain

rails scope without associations

I have a model called "activos", I need to show only the records that are not associated with another model called "relactivo".
I've been trying this in the model: scope :ts, -> { includes(:relactivo).where(relactivo: { activo: nil}) }
this is my model "activos"
class Activo < ActiveRecord::Base
self.primary_key = "IdActivos"
scope :ts, -> { includes(:relactivo).where(relactivo: { activo: nil}) }
has_one :relactivo, class_name: "Relactivo", foreign_key: "Activo"
end
and my model "relactivo"
class Relactivo < ActiveRecord::Base
self.primary_key = "IdRow"
belongs_to :activo, class_name:"Activo", foreign_key: "Activo"
end
Try doing this for your scope:
class Activo < ActiveRecord::Base
self.primary_key = "IdActivos"
scope :ts, -> { joins('LEFT OUTER JOIN relactivos ON relactivos.IdActivos = activos.IdActivos WHERE relactivos.IdActivos IS null'))) }
has_one :relactivo, class_name: "Relactivo", foreign_key: "IdActivos"
end
See if this works, The custom primary/foreign key makes it a little strange but try this.

Scope with 2 different conditions

I would like to filter stories on my index based on 2 different conditions where one is for the Current Country and the other one is for All Countries. Is it possible to create a scope where it could fetch stories for both this condition ?
All Countries is boolean field where in my Story table. The logic is if the Story is created for all countries the field, all_countries = 1
Featured Item model, is where the stories could be featured on the index page if the writer would like to do so.
This is how my model looks like for now with the scopes
class Country < ActiveRecord::Base
has_many :stories
end
class Story < ActiveRecord::Base
belongs_to :countries
has_many :featured_items, dependent: :destroy
scope :by_country, lambda { |id| where(:country_id => id)}
scope :for_all_countries, where(:all_countries => true)
end
class FeaturedItem < ActiveRecord::Base
belongs_to :story
scope :by_country, -> (country) {joins(:story).where('`stories`.country_id = ?', Country.find(country) )}
scope :for_all_countries, -> { joins(:story).where('`stories`.all_countries = ?',true) }
end
p/s the scope for all countries on the featured Items also returns an error.
You can do this sort of thing:
scope :by_country, -> (country) { country == :all ? where(:all_countries => true) : where(:country_id => country) }
You may need to add a little more logic to handle bad params.
And for the join table, you can join and filter on the stories.
class FeaturedItem < ActiveRecord::Base
scope :by_country, -> (country) { (country == :all ? where( :stories => { :all_countries => true } ) : where( :stories => { :country_id => country } ) ).joins(:story) }
end
Your scope syntax is currently wrong, as is your pluralization of the belongs_to association.
You'll need to use the following (#swards answer is right, this is just an addition):
#app/models/story.rb
class Story < ActiveRecord::Base
belongs_to :country
scope :countries, ->(ids = :all) { ids == :all ? where(all_countries: true) : find(ids) }
end
This will allow you to call Story.countries to return all countries, and Story.countries(1,2,4,5) to return individual ones.
filter stories on my index based on 2 different conditions where one is for the Current Country and the other one is for All Countries.
Have you considered using the following in your Country model:
#stories = #country ? #country.stories : Country.stories
#app/models/country.rb
class Country < ActiveRecord::Base
has_many :stories
scope :stories, -> { joins(:stories).where(story: {all_countries: true}) }
end

Rails querying many-to-many

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")

Trouble translating SQL query to Arel

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

Resources