Querying through a join table Rails 4 Postgres - ruby-on-rails

I am trying to get an array of records through a join table. I want to find All of one User's Favorites that are Blue, but that are "current". User_Favorites can expire. This is what I'm trying:
User.rb
has_many :user_favorites, dependent: :destroy
has_many :favorites, through: :user_favorites
Favorite.rb
has_many :user_favorites, dependent: :destroy
has_many :users, through: :user_favorites
UserFavorite.rb
belongs_to :user
belongs_to :favorite
scope :current_as_of, -> (date) do
where('start_date <= ?',date).
where('(end_date >= ? or end_date IS NULL)', date)
end
scope :blue, -> { where('self.favorite.color = ?','blue') }
class UsersController < ApplicationController
def profile
#user = User.find(params[:id])
#blue_favorites = #user.user_favorites.current_as_of(Date.today).blue.all
end
This is the error I get:
There is an Error: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "favorite"
LINE 1: ...d_date >= '2015-10-06' or end_date IS NULL)) AND (Favorite.co...
^
: SELECT "user_favorites".* FROM "user_favorites" WHERE "user_favorites"."user_id" = $1 AND (start_date <= '2015-10-06') AND ((end_date >= '2015-10-06' or end_date IS NULL)) AND (Favorite.color = 'blue')

in regards to this:
scope :blue, -> { where('self.favorite.color = ?','blue') }
It looks like you're mixing up database and ruby syntax. The other problem too is that the query, at this point, has no idea what favorite is because it hasn't been joined yet. Try something like this instead:
scope :blue, -> { joins(:favorite).where(favorites: {color: 'blue'}) }

If I understand it correctly your :blue scope should look something like this:
scope :blue, -> { joins(:favorite).where(favorites: { color: 'blue' }) }
In the joins you have to use the association name while in the where clause you have to use the table name.

Related

In Rails 6, how do I add a condition to a left-outer-joins finder?

I’m using Rails 6.1.4.4. I have this model with a has_many
class MyObject < ApplicationRecord
has_many :products, inverse_of: :application, as: :item
How do I write a scope that does a left outer join and also adds a condition in the LEFT-OUTER-JOIN-ON clause? I have fallen back on raw sql …
scope :awaiting_funding, ->(resubmissions: false) {
joins("LEFT OUTER JOIN products on products.item_id = my_objects.id and products.product_type = 11 and products.item_type = ‘MyObject’”).where('products.id is null')
}
But I would like to convert this to a more Rails-like finder method.
Define a new has_many
class MyObject < ApplicationRecord
has_many :products, inverse_of: :application, as: :item
has_many :my_object_products, -> { where(product_type: 11, item_type: 'MyObject') }, class_name: 'Product'
Now you can define your scope
scope :awaiting_funding, ->(resubmissions: false) {
where.missing(:my_object_products)
}
This will create the query where product_type and item_type are part of the ON in the LEFT OUTER JOIN
PS: use a better name for my_object_products but you get the idea.
Does this work?
scope :awaiting_funding, ->(resubmissions: false) {
left_outer_joins(:products).where(product_type: 11, item_type: 'MyObject', products: { id: nil })
}
I will give you a much generic example of Left Outer Join
Source.
select('a.*', 'count(b.*)').
left_outer_joins(:b).
joins(:c).
where('c.body_parser = ?', true).
group('a.id').
having('count(b.id) = 0').
all
Else, You can also use includes. This will generate a LEFT OUTER JOIN query
MyObject.includes(:products).where(product_type: 11, item_type: 'MyObject', products: { id: nil })

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

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