after_destroy callback claims object still exists - ruby-on-rails

I have an after_destroy callback that I'd expect to return nil but instead still has a value.
class WeighIn < ActiveRecord::Base
belongs_to :check_in
after_destroy :add_employee_weightloss
def add_employee_weightloss
p self.check_in.weigh_in.present? # returns true
end
end
specs:
it "employee weightloss" do
ci = CheckIn.create()
wi = WeighIn.create(check_in_id: ci.id)
wi.destroy
expect(wi.reload).to eq(nil) # returns wi instead of nil
end

You should use destroyed? (or exists?, or persisted?) instead, cause present? just checks if the object is present, which is the correct behaviour after destruction (destroy itself returns deleted object).
def add_employee_weightloss
p check_in.weigh_in.destroyed?
end
Also you should not use the following:
expect(wi.reload).to eq(nil)
cause if wi was destroyed you are going to get ActiveRecord::RecordNotFound exception instead of nil. You can try the following:
it "employee weightloss" do
wi = WeighIn.create(check_in: CheckIn.create)
wi.destroy
expect(wi.destroyed?).to eq(true)
end

Related

How to add errors before updating attributes?

I'm trying to handle the situation where the user has entered info incorrectly, so I have a path that follows roughly:
class Thing < AR
before_validation :byebug_hook
def byebug_hook
byebug
end
end
thing = Thing.find x
thing.errors.add(:foo, "bad foo")
# Check byebug here, and errors added
if thing.update_attributes(params)
DelayedJobThatDoesntLikeFoo.perform
else
flash.now.errors = #...
end
byebug for byebug_hook> errors.messages #=> {}
Originally I thought that maybe the model was running its own validations and overwriting the ones I added, but as you can see even when I add the before hook the errors are missing, and I'm not sure what's causing it
ACTUAL SOLUTION
So, #SteveTurczyn was right that the errors needed to happen in a certain place, in this case a service object called in my controller
The change I made was
class Thing < AR
validate :includes_builder_added_errors
def builder_added_errors
#builder_added_errors ||= Hash.new { |hash, key| hash[key] = [] }
end
def includes_builder_added_errors
builder_added_errors.each {|k, v| errors.set(k, v) }
end
end
and in the builder object
thing = Thing.find x
# to my thinking this mirrors the `errors.add` syntax better
thing.builder_added_errors[:foo].push("bad foo") if unshown_code_does_stuff?
if thing.update_attributes(params)
DelayedJobThatDoesntLikeFoo.perform
else
flash.now.errors = #...
end
update_attributes will validate the model... this includes clearing all existing errors and then running any before_validation callbacks. Which is why there are never any errors at the pont of before_validation
If you want to add an error condition to the "normal" validation errors you would be better served to do it as a custom validation method in the model.
class Thing < ActiveRecord::Base
validate :add_foo_error
def add_foo_error
errors.add(:foo, "bad foo")
end
end
If you want some validations to occur only in certain controllers or conditions, you can do that by setting an attr_accessor value on the model, and setting a value before you run validations directly (:valid?) or indirectly (:update, :save).
class Thing < ActiveRecord::Base
attr_accessor :check_foo
validate :add_foo_error
def add_foo_error
errors.add(:foo, "bad foo") if check_foo
end
end
In the controller...
thing = Thing.find x
thing.check_foo = true
if thing.update_attributes(params)
DelayedJobThatDoesntLikeFoo.perform
else
flash.now.errors = #...
end

Rails 4 – How to rescue from NoMethodError and continue trying method?

In my Contacts class, after a contact is created with their email address, I try to pull as much contact data from FullContact's API as possible.
I'm having this issue where if one column of data doesn't exist for a "person" in FullContact, it throws a NoMethodError and I don't get to save the rest of the data that potentially does exist to the contact, because my method stops at the error.
How can I rescue from a NoMethodError and get my method to continue running the rest of it? Like for it to just skip over the error and try the rest of the code. I've tried next and continue in my rescue code but that doesn't work.
Thanks for any help.
class Contact < ActiveRecord::Base
belongs_to :user
after_create do |contact|
contact.delay.update_fullcontact_data
end
def update_fullcontact_data
person = FullContact.person(self.email)
if person.contact_info.given_name.present?
self.name = person.contact_info.given_name
end
if person.contact_info.family_name.present?
self.last_name = person.contact_info.family_name
end
if person.demographics.location_general.present?
self.city = person.demographics.location_general
end
save!
rescue NoMethodError => exception
puts "Hit a NoMethodError"
save!
end
end
in general what may be a solution for your problem is try method (http://apidock.com/rails/Object/try). To make the story short - it returns nil instead of raising exception if method does not exist on specific object
If you just want to make sure it saves, you can use ensure do something like this:
class Contact < ActiveRecord::Base
belongs_to :user
after_create do |contact|
contact.delay.update_fullcontact_data
end
def update_fullcontact_data
person = FullContact.person(self.email)
if person.contact_info.given_name.present?
self.name = person.contact_info.given_name
end
if person.contact_info.family_name.present?
self.last_name = person.contact_info.family_name
end
if person.demographics.location_general.present?
self.city = person.demographics.location_general
end
save!
ensure
save!
end
end
More info:
http://blog.rubybestpractices.com/posts/rklemme/003-The_Universe_between_begin_and_end.html

Object can not be deleted is attribute is active - Rspec RoR

I'm trying to test if object is active and it is active then it can not be deleted. So for i have this in my plan_spec.rb:
it "can not be deleted if active" do
plan = Plan.new(active: true)
r = plan.destroy
r.should_not be_valid
end
Name of the attribute i'm trying to check is 'active' and it is boolean, so if active is true then it can't object plan can not be deleted.
Any help?
It can be achieved by using before_destroy callback which will return false if record can't be destroyed:
class Plan < ActiveRecord::Base
# ...
before_destroy :check_if_active
# ...
private
def check_if_active
!active?
end
end
With this solution, you should also rewrite your test, cause you shouldn't check validity:
it 'can not be deleted if active' do
plan = Plan.create!(active: true)
expect { plan.destroy }.to_not change { Plan.count }
end
in your Plan class, add:
before_destroy :ensure_inactive
#will abort destroy if return false
def ensure_inactive
!active?
end
Btw, your spec is wrong, its not really a validation. You should:
save the object
call destroy
ensure it has not been destroyed

Active Record Clear instance of model after_find callback

I have defined a callback after_find for checking some settings based on the retrieved instance of the model. If the settings aren't fulfilled I don't want the instance to be return from the find method. Is that possible?
an example
the controller looks like:
class UtilsController < ApplicationController
def show
#util = Util.find(params[:id])
end
end
the model:
class Util < ActiveRecord::Base
after_find :valid_util_setting
def valid_util_setting
# calculate_availability? complex calculation
# that can not be part of the sql statement or a scope
unless self.setting.calculate_availability?(User.current.session)
#if not available => clear the record for view
else
#nothing to do here
end
end
end
Instead of trying to clear the record, you could just raise an exception?
E.g.
unless self.setting.calculate_availability?(User.current.session)
raise ActiveRecord::RecordNotFound
else
...
I'm afraid you can't clear found record in this callback
Maybe you should find in scope with all your options from the beginning?
I.e. #util = Util.scoped.find(params[:id])
I found a solution
def valid_util_setting
Object.const_get(self.class.name).new().attributes.symbolize_keys!.each do |k,v|
begin
self.assign_attributes({k => v})#, :without_protection => true)
rescue ActiveModel::MassAssignmentSecurity::Error => e; end
end
end
With this I'm able to create an almost empty object

Error saving record when custom validation is in place

I have this API that saves videos, indexes them and updates them. In order to reduce the times the indexing was happening I decided to add some validation to only index videos that had changed or that where new. Before it was like so:
class Video < ActiveRecord::Base
after_save :index_me
def index_me
Resque.enqueue(IndexVideo, self.id)
end
end
The changes I made are as follows:
class Video < ActiveRecord::Base
before_save :check_new_record
after_save :index_me
def check_new_record
self.is_new = self.new_record?
end
def index_me
if self.changed? || self.is_new
Resque.enqueue(IndexVideo, self.id)
end
end
end
Without the changes everything's fine with the exception that every video gets indexed even if nothing changed. But with my changes, when a video tries to save to the database it rolls back. Any thoughts?
If I'm not wrong, when a before callback returns false, the transaction gets rolled back.
That's probably what's happening.
def check_new_record
self.is_new = self.new_record?
end
When self.new_record? returns false, it assigns false to self.is_new and then the method returns self.is_new, which is false too.
Try this instead:
def check_new_record
self.is_new = self.new_record?
true
end
For one thing, you can get rid of the hack you have to detect if the record is new in the after_save. If the record is new, the .changed? method will return true.
class Video < ActiveRecord::Base
after_save :index_me
def index_me
Resque.enqueue(IndexVideo, self.id) if self.changed?
end
end

Resources