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
Related
The association is has follows
Company has_many company_commodities
CompanyCommodity belongs to Company
CompanyCommodity belongs to Commodity
Consider that company1 has an entry in the company_commodities table.
Now in the decorator file, i need to get the commodity name and id of that record.
I have implemented as follows.
company1 = Company.find(1)
arr = co.company_commodities.map(&:commodity).pluck(:name, :id)
arr.map { |a| { name: a[0], id: a[1] } }
This produces the output as
[{:name=>"Pharmaceuticals", :id=>25},
{:name=>"Medical Devices", :id=>26}]
Is there a cleaner way to do this?
class Company < ApplicationRecord
has_many :company_commodities
has_many :commodities, through: :company_commodities
end
class CompanyCommodity < ApplicationRecord
belongs_to :commodity
belongs_to :company
end
company = Company.find(1)
# this returns the object you can access by arr.first.id, arr.first.name
arr = company.commodities.select(:name, :id)
# if you want to access as hash, usage: arr.first['id'], arr.first['name']
arr = company.commodities.select(:name, :id).as_json
You can get the commodities association by using through, then you can use select to filter the attributes.
Change the associations as below
class Company
has_many :company_commodities
has_many :commodities, through: :company_commodities
end
class CompanyCommodity
belongs_to :company
belongs_to :commodity
end
And query the records as
company1 = Company.find(1)
arr = co.commodities.pluck(:name, :id)
arr.reduce([]) { |array, el| array << [[:name, :id], el].to_h}
I have :
class UserItem < ActiveRecord::Base
belongs_to :user
belongs_to :item
scope :equipped, -> { where(equipped: true) }
end
class Item < ActiveRecord::Base
has_many :user_items
has_many :users, through: :user_items
scope :armor, -> { where(type: 'Armor') }
delegate :equipped, to: :user_items
end
EDIT:
If i try
User.first.items.equipped => undefined method 'equipped' for #<ActiveRecord::Associations::CollectionProxy []>
User.first.items.armor.equipped => undefined method 'equipped' for #<ActiveRecord::AssociationRelation []>
How can I delegate to scope?
You can't easily delegate to scopes, nor would you want to since it would return objects from the targeted class (UserItem), not the child.
Instead, you can merge scopes very simply:
class UserItem < ActiveRecord::Base
scope :equipped, -> { where(equipped: true) }
end
class Item < ActiveRecord::Base
scope :equipped, -> {joins(:user_items).merge(UserItem.equipped)}
end
=> Item.equipped
=> collection of items for which they have a user_item association that is equipped
Edit: some documentation on this feature.
Using Named Scopes Across Models with ActiveRecord#Merge
http://apidock.com/rails/ActiveRecord/SpawnMethods/merge
Edit again:
If you REALLY want to return a collection of UserItems from a method you call on Item, then you can do as follows:
class Item
class << self
delegate :equipped, to: :UserItem
end
...
But this will return UserItems in a collection, not Items. Which begs the question of why delegate this at all?
If you want a collection of Items, and you want to limit those items by a collection of equipped UserItems, then use merge.
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 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.
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