ActiveRecord callback on column change - ruby-on-rails-3

I have the following relationship model:
class Doctor
has_many :appointmets
# has attribute 'average_rating' (float)
end
class Appointment
belongs_to :doctor
# has_attribute 'rating' integer
end
I want to set a callback to set doctor.average_rating each time an appointment gets rated, i.e. each time rating column on Appointments table gets touched.
I tried with this:
class Appointment
after_update :update_dentist_average_rating, if: :rating_changed?
# also tried with after_save
private
def update_dentist_average_rating
ratings = Appointment.where(dentist_id: dentist.id).map(&:rating)
doctor.average_rating = ratings.reduce(:+) / ratings.size if ratings.any?
doctor.save
end
end
But doesn't work, appointment.dentist.average_rating returns nil always. I can't seem to test whether the callback is running on appointment.save. What I'm missing?
Edit: this is the app flow
A user(of class Patient) saves an appointment with rating: nil and some doctor_id.
If the appointment.date is past, appointment.rating can be edited or set on a form via ''
Then I need to update appointment.doctor.average_rating

Achieved it with this callback, though I don't know if it's the best way:
after_save if: :rating_changed? do |apt|
ratings = Appointment.where(doctor_id: 10).map(&:rating)
doctor.average_rating = ratings.reduce(:+) / ratings.size if ratings.any?
doctor.save!
end

Related

How to prevent save model changes in a before_save callback

So i have to 1) not save changes but instead 2) save the audit with these changes. The second part is achieved by send_for_audit method, but if i return false in it the new Audit instance is not created as well as changes to the Article instance are not saved (there is the simplified code).
class Article < ActiveRecord::Base
before_save :send_for_audit, if: :changed?
has_many :audits, as: :auditable
private
def send_for_audit
audits.destroy_all
Audit.create!(auditable: self, data: changes)
end
end
class Audit < ActiveRecord::Base
attr_accessible :auditable, :auditable_id, :auiditable_type, :data
store :data
belongs_to :auditable, polymorphic: true
def accept
object_class = self.auditable_type.constantize
object = object_class.find_by_id(auditable_id)
data.each do |attr, values|
object.update_column(attr.to_sym, values.last)
end
self.destroy
end
end
I've tried to add an additional before_save callback thinking that the order in which they are triggered will do the trick:
before_save :send_for_audit, if: :changed?
before_save :revert_changes
def send_for_audit
audits.destroy_all
Audit.create!(auditable: self, data: changes)
#need_to_revert = true
end
def revert_changes
if #need_to_revert
#need_to_revert = nil
false
end
end
but anyway i got no Audit instance..
Any thoughts how i could achieve the desired result?
i've figured it out
i just dont use before_save, but
def audited_save!(current_user)
if current_user.superadmin
save!
else
audits.destroy_all
Audit.create!(auditable: self, data: changes)
end
end
and then i use that method in the update controller action

Rails 3 Custom Validation

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

How do I verify an association based off a column in Rails 3.2?

Say I have an assets model that contains two columns:
Assets
ownership:string
lease_id:integer # My Lease Object
So ownership can either be "OWN" or "LEASE".
Now, I only want to allow a lease_id if ownership is LEASE and if ownership happens to be LEASE then I want to require a lease object.
How can this be done in Rails 3.2.2?
You can add optional validations based on a method which returns true/false. Keep in mind that this will only enforce that a lease_id is present when ownership == "LEASE". This will not restrict a lease_id from being added in any case.
class Asset < ActiveRecord::Base
validates :lease_id, presence: { :if => :lease? }
def lease?
self.ownership == "LEASE"
end
end
If you want to restrict a lease_id altogether, you can use a callback to remove the property before the object is saved to the DB.
class Asset < ActiveRecord::Base
before_create :restrict_lease_id
def restrict_lease_id
lease_id = nil if self.ownership == "LEASE"
end
end

Where to set session default value?

guys!
Prior to asking i should mention, that i`m working without ActiveRecord or any self-hosted-database. So thats why i have to store some values in the session.
From the very begining i desided to set session value of the users city in the layout. - i supposed it would be loaded before anything else. So i`ve done something like this:
<% session[:city] ||= {:name => 'City-Name', :lat => '40', :lng => '40'}%>
But when i`m loading directly to inner page it occurs that session[:city is nil *(
How should i set the session properely, so that it wouldn`t be nil???
I had similar needs in one of the applications I worked on. It needed the users data to be loaded on sign-in and stored in the session. So, wrote a module called session_helpers.rb with the following:
module SessionHelpers
def get_value(key)
session[key.to_sym]
end
protected
def store_data(*objects)
objects.each do |object|
if object.is_a?(Hash)
object.each do |key, value|
session[key.to_sym] = value
end
end
end
end
def remove_data(*objects)
objects.each do |object|
if object.is_a?(String)
key = to_id(object)
else
key = to_id(object.class.name)
end
session[key] = nil
end
end
def update_data(key, value)
session[key.to_sym] = value
end
private
def to_id(name)
"#{name.parameterize('_').foreign_key}".to_sym
end
end
You can make any or all the methods available to views as well:
# application_controller.rb
helper_method :get_value
From the model I would retrieve a hash of the data that needs to be put up in the session about the user:
def common_data
#data = Hash.new
#data.merge!( { 'news' => self.news.count } )
...
#data
end
As I wanted to do this after sign-in I overrode the devise method to do this:
def after_sign_in_path_for(resource_or_scope)
store_data( '_count', current_user.common_data )
dashboard_path
end
This way I was able to load important data about the user on sign-in and store it in the session and retrieve whenever I wanted. Hope this helps.

Rails 3 - Update Parent Class

I have a Trans (transaction) class in my application that submits a transaction to the server. When a transaction is submitted, I also want to do an update on a parent User class to update their cached "balance." Here is relevant code:
# tran.rb
class Tran < ActiveRecord::Base
belongs_to :submitting_user, :class_name => 'User'
end
And my controller:
#trans_controller.rb
def create
#title = "Create Transaction"
# Add the transaction from the client
#tran = Tran.new(params[:tran])
# Update the current user
#tran.submitting_user_id = current_user.id
# ERROR: This line is not persisted
#tran.submitting_user.current_balance = 4;
# Save the transaction
if #tran.save
flash[:success] = 'Transaction was successfully created.'
redirect_to trans_path
I have a couple of problems:
When I update the current_balance field on the user, that balance isn't persisted on the user after the transaction is saved. I think maybe I need to use update_attributes?
I am not even sure that the code should be a part of my controller - maybe it makes more sense in the before_save of my model?
Will either of these make this transactional?
def create
title = "Create Transaction"
#tran = Tran.new(params[:tran])
#tran.submitting_user_id = current_user.id
# to make it "transactional" you should put it after #tran.save
if #tran.save
current_user.update_attribute :current_balance, 4
...
And yes - it is better to put it into after_save callback
class Tran < AR::Base
after_save :update_user_balance
private
def update_user_balance
submitting_user.update_attribute :current_balance, 4
end
end