Rails 3: Polymorphic conditioned query - ruby-on-rails-3

I've got the following models:
class Notification < ActiveRecord::Base
belongs_to :notificatable, polymorphic: true
end
class BounceEmailNotification < ActiveRecord::Bas
has_one :notification, :as => :notificatable, :dependent => :destroy
end
class UserNotifierEmailNotification < ActiveRecord::Base
has_one :notification, :as => :notificatable, :dependent => :destroy
end
As you can see, a notification can be of type "bounce email notification" or "user notifier email notification". The BounceEmailNotification model has a event string attribute. What if I want retrieve all user notifier email notifications and all bounce email notifications which have a specific event value, ordered by created_at?
Something like this (using squeel):
(Notification.joins{ notificatable(BounceEmailNotification) }.where('bounce_email_notifications.event' => 'error') + Notification.joins { notificatable(UserNotifierEmailNotification) }).sort_by { |n| n.created_at }
will work, but I don't want to use Ruby to order the notifications. What can I do? Thanks

Try this.
Notification.joins('left outer join bounce_email_notifications on notifications.notificatable_id = bounce_email_notifications.id and notificatable_type = "BounceEmailNotification"').
where("bounce_email_notifications.event = ? or notificatable_type = ?",'error',UserNotifierEmailNotification.to_s).
order('notifications.created_at')
This is using active record 3.2 but I guess it should work in rails 3.
I have not used squeel cant comment much on how to use it with squeel but this can give you a brief idea.
In squeel I came up with something like this ( NOT TESTED) created by using squeel docs
Notification.notificatable{notable(BounceEmailNotification).outer}. where{
{ ('bounce_email_notifications.event'=>'error') | (:notificatable_type=>UserNotifierEmailNotification.to_s) }
Conceptual steps
left join on the bounce_email_notifications to get all bounce_email_notifications and and non bounce_email_notifications notification in result
check if the bounce_email_notifications.event = event
or
notificatable_type = 'UserNotifierEmailNotification' for all UserNotifierEmailNotification records
sort the records by notifications.created at
Hope this helps.

Related

Find cards with today date when user receive mails in RoR

Every day I need to send letters to users with today's tasks.
For do this I need to find all users who are allowed to send letters, and among these users to find all cards that have a deadline today. The result is three array elements with a nil value. How is this better done and right?
#users = User.all {|a| a.receive_emails true}
#user_cards = []
#users.each_with_index do |user, index|
#user_cards[index] = user.cards.where(start_date: Date.today).find_each do |card|
#user_cards[index] = card
end
end
My user model:
class Card < ApplicationRecord
belongs_to :user
# also has t.date "start_date"
end
My card model:
class User < ApplicationRecord
has_many :cards, dependent: :destroy
# also has t.boolean "receive_emails", default: false
end
Something like #cards_to_send = Card.joins(:users).where("users.receive_emails = true").where(start_date: Date.today)
Have a look at https://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-the-joined-tables for the docs on how to query on a joined table.
You could do this with a SQL join like this
User.joins(:cards).where(receive_emails: true, cards: { start_date: Date.today })
https://guides.rubyonrails.org/active_record_querying.html#joining-tables

How to convert this query to ActiveRecord logic?

I'm working in rails. My model is like this:
class Topic < ActiveRecord::Base
has_many :topics, dependent: :delete_all
belongs_to :parent, foreign_key: 'topic_id', class_name: 'Topic'
has_many :contents
validates :name, uniqueness: true, presence: true
end
So I have a topic that can have many "sub-topics". Every sub-topic can have many sub-topics, indefinitely. I'm trying to make a method that returns me all "leaf" topics. A leaf topic is a topic with no sub-topics.
def self.leafs
where(???)
end
I can't formulate this in active record logic, so actually I use this query:
Topic.find_by_sql("SELECT * FROM topics WHERE id NOT IN (SELECT t.topic_id FROM topics t WHERE topic_id IS NOT NULL)")
How can I write this in an active record way?
Try this:
child_ids_with_topic_id = where.not(topic_id: nil).pluck(:topic_id)
where.not(id: child_ids_with_topic_id)
def self.leafs
topics.where("topic_id IS NOT NULL")
end
ActiveRecord 4.0 and above adds where.not so you can do this:
scope :leafs, -> topics.where.not(topic_id: nil)
scope :without_topics, includes(:topics).where(:topics => { :id => nil })
Although i am not sure but i have tried using this Rails 3 finding parents which have no child
scope :leafs, joins("left join topics as sub_topics sub_topics.topic_id = topics.id").where("topics.topic_id is null")

Rails 3: has_many through query

I have these three Active Record models:
class Event < ActiveRecord::Base
has_many :event_categories, inverse_of: :event
has_many :categories, through: :event_categories
end
class EventCategory < ActiveRecord::Base
belongs_to :event
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :event_categories
has_many :events, through: :event_categories
end
I think the relations are good.
If I want to know what Events have a Category, for example id=5.. I do:
Category.find(5).events
But, if I want to know all Events for more than one category, for example:
Category.where(:id => [3,5]).events
It isn't working. Any ideas?
Please note, when you do has_many :events in a model, Active Record defines a method of name events for that class.
When you do Category.find(5).events, you get events associated with one object (i.e. Category.find(5)) , however Category.where(:id => [3,5]) returns an array of Category objects, so you can't use events function on an array, Only way to get events for all search results is iterate over them and access them individually, something like following:
all_events = Category.where(:id => [3,5]).inject([]) {|res,cat| res << cat.events}
Above code will do one query per iteration, to avoid this, we can include events, in the first query itself, like following, which will provide result in only one query:
all_events = Category.includes(:events).where(:id => [3,5]).inject([]) {|res,cat| res << cat.events}

Use a scope by default on a Rails has_many relationship

Let's say I have the following classes
class SolarSystem < ActiveRecord::Base
has_many :planets
end
class Planet < ActiveRecord::Base
scope :life_supporting, where('distance_from_sun > ?', 5).order('diameter ASC')
end
Planet has a scope life_supporting and SolarSystem has_many :planets. I would like to define my has_many relationship so that when I ask a solar_system for all associated planets, the life_supporting scope is automatically applied. Essentially, I would like solar_system.planets == solar_system.planets.life_supporting.
Requirements
I do not want to change scope :life_supporting in Planet to
default_scope where('distance_from_sun > ?', 5).order('diameter ASC')
I'd also like to prevent duplication by not having to add to SolarSystem
has_many :planets, :conditions => ['distance_from_sun > ?', 5], :order => 'diameter ASC'
Goal
I'd like to have something like
has_many :planets, :with_scope => :life_supporting
Edit: Work Arounds
As #phoet said, it may not be possible to achieve a default scope using ActiveRecord. However, I have found two potential work arounds. Both prevent duplication. The first one, while long, maintains obvious readability and transparency, and the second one is a helper type method who's output is explicit.
class SolarSystem < ActiveRecord::Base
has_many :planets, :conditions => Planet.life_supporting.where_values,
:order => Planet.life_supporting.order_values
end
class Planet < ActiveRecord::Base
scope :life_supporting, where('distance_from_sun > ?', 5).order('diameter ASC')
end
Another solution which is a lot cleaner is to simply add the following method to SolarSystem
def life_supporting_planets
planets.life_supporting
end
and to use solar_system.life_supporting_planets wherever you'd use solar_system.planets.
Neither answers the question so I just put them here as work arounds should anyone else encounter this situation.
In Rails 4, Associations have an optional scope parameter that accepts a lambda that is applied to the Relation (cf. the doc for ActiveRecord::Associations::ClassMethods)
class SolarSystem < ActiveRecord::Base
has_many :planets, -> { life_supporting }
end
class Planet < ActiveRecord::Base
scope :life_supporting, -> { where('distance_from_sun > ?', 5).order('diameter ASC') }
end
In Rails 3, the where_values workaround can sometimes be improved by using where_values_hash that handles better scopes where conditions are defined by multiple where or by a hash (not the case here).
has_many :planets, conditions: Planet.life_supporting.where_values_hash
In Rails 5, the following code works fine...
class Order
scope :paid, -> { where status: %w[paid refunded] }
end
class Store
has_many :paid_orders, -> { paid }, class_name: 'Order'
end
i just had a deep dive into ActiveRecord and it does not look like if this can be achieved with the current implementation of has_many. you can pass a block to :conditions but this is limited to returning a hash of conditions, not any kind of arel stuff.
a really simple and transparent way to achieve what you want (what i think you are trying to do) is to apply the scope at runtime:
# foo.rb
def bars
super.baz
end
this is far from what you are asking for, but it might just work ;)

Help doing a dynamic sort?

I have a notifications table which contains different types of notifications for different events.
Inside the table is a notifications_type:string column that contains the type of notification, i.e. "foo" or "bar" or "oof"
I want the user to be able to select what notifications they want to display, so there are checkboxes below the result that correspond to prefs_display_foo:boolean, prefs_display_bar:boolean in the User model.
What is an elegant way for me to set the :conditions in the find to properly display the sorted results? Also, currently I have it as a method in the User model, but how would I do it as a has_many :notifications, :conditions => .....
You could try this
leave the has_many without any conditions and then create a named_scope to handle the preferences, this code is untested
class User
has_many :notifications
def notification_preferences
list = []
list << 'Foo' if prefs_display_foo
list << 'Bar' if prefs_display_bar
# etc...
return list
end
end
class Notification
belongs_to :user
named_scope :preferred,
lambda { |preferences| {
:conditions => ["notifications_type IN (?)", preferences]
}
}
end
# ...
user = User.find(1)
notifications = user.notifications.preferred(user.notification_preferences)