I'm attempting to created a custom validation that verifies a schedule's start_date and end_date do not overlap with another schedule
class Schedule < ActiveRecord::Base
#has many scheduleTimes (fk in scheduleTime)
has_many :scheduletimes, :inverse_of => :schedule
validate :dateOverlaps?
scope :active, lambda {
where('? between start_date and end_date', Date.today)
}
def dateOverlaps?
results = ActiveRecord::Base.connection.execute("Select (start_date::DATE, end_date::DATE) OVERLAPS ('#{self.start_date}'::DATE, '#{self.end_date}'::DATE) from schedules;")
errors.add_to_base("Date ranges cannot overlap with another schedule") if results.first["overlaps"] == 't'
end
however, this causes
NoMethodError: undefined method `add_to_base'
I have tried creating a custom validator and using the private validate method to no avail. Could someone shine some light on this for me?
Try replacing this:
errors.add_to_base("Date ranges cannot overlap with another schedule")
with this:
errors.add(:base, "Date ranges cannot overlap with another schedule")
Instead of:
errors.add_to_base
try using:
errors.add
Related
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.
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 ;)
My Model:
class Student < ActiveRecord::Base
has_many :lack_knowledge_points, :through => :knowledge_point_infos,
:conditions => ['knowledge_point_infos.level <= ?',10],:source => :knowledge_point
I want that 10 be dynamic
What is my best practice?
Define a method and find_by_sql? Or can Rails do something else for me?
I am not clear how you want 'level' to be dynamic. Anyway, you could use scope with lambda or define a method in the model.
#If you want it to return an array
def lack_knowledge_points(threshold)
knowledge_point_infos.where('level <= ?', threshold).map{|info|info.knowledge_point}
end
In rails3, I make same scopes in model. for example
class Common < ActiveRecord::Base
scope :recent , order('created_at DESC')
scope :before_at , lambda{|at| where("created_at < ?" , at) }
scope :after_at , lambda{|at| where("created_at > ?" , at) }
end
I want to split common scopes to module in lib. So I try like this one.
module ScopeExtension
module Timestamps
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
scope :recent , lambda{order('created_at DESC')}
scope :before_at , lambda{|at| where("created_at < ?" , at) }
scope :after_at , lambda{|at| where("created_at > ?" , at) }
end
end
and I write this one
class Common < ActiveRecord::Base
include ScopeExtension::Timestamps
end
But Rails show this error.
undefined method `scope' for ScopeExtension::Timestamps::ClassMethods:Module
(I didn't forget auto loading library)
How can I easily reuse common scope feature in active record?
I guess this problem to relate loading sequence. But I don't have any idea to solve.
Please hint me.
I solved this calling the scope on self.included(class):
module Timestamps
def self.included(k)
k.scope :created_yesterday, k.where("created_at" => Date.yesterday.beginning_of_day..Date.yesterday.end_of_day)
k.scope :updated_yesterday, k.where("created_at" => Date.today.beginning_of_day..Date.today.end_of_day)
k.scope :created_today, k.where("created_at" => Date.today.beginning_of_day..Date.today.end_of_day)
k.scope :updated_today, k.where("created_at" => Date.today.beginning_of_day..Date.today.end_of_day)
end
end
In Rails 3 there's no difference between a declared scope and a class method that returns an ActiveRecord::Relation, so it can be more elegant to use a mixin module:
class MyClass < ActiveRecord::Base
extend ScopeExtension::Timestamps
end
module ScopeExtension
module Timestamps
def recent
order('created_at DESC')
end
def before_at(at)
where('created_at < ?' , at)
end
def after_at(at)
where('created_at > ?' , at)
end
end
end
MyClass.after_at(2.days.ago).before_at(1.hour.ago).recent
I have a tiny logical error in my code somewhere and I can't figure out exactly what the problem is. Let's start from the beginning. I have the following extension that my order class uses.
class ActiveRecord::Base
def self.has_statuses(*status_names)
validates :status,
:presence => true,
:inclusion => { :in => status_names}
status_names.each do |status_name|
scope "all_#{status_name}", where(status: status_name)
end
status_names.each do |status_name|
define_method "#{status_name}?" do
status == status_name
end
end
end
end
This works great for the queries and initial setting of "statuses".
require "#{Rails.root}/lib/active_record_extensions"
class Order < ActiveRecord::Base
has_statuses :created, :in_progress, :approved, :rejected, :shipped
after_initialize :init
attr_accessible :store_id, :user_id, :order_reference, :sales_person
private
def init
if new_record?
self.status = :created
end
end
end
Now I set a status initially and that works great. No problems at all and I can save my new order as expected. Updating the order on the other hand is not working. I get a message saying:
"Status is not included in the list"
When I check it seems that order.status == 'created' and it's trying to match against :created. I tried setting the has_statuses 'created', 'in_progress' etc but couldn't get some of the other things to work.
Anyway to automatically map between string/attribute?
from your description, looks like you're comparing a string to a symbol. Probably need to add:
define_method "#{status_name}=" do
self.status = status_name.to_sym
end
or do a #to_s on the status_names