nested attributes update triggers wrong callback in observer - ruby-on-rails

Two classes in the app/model:
class Job
accepts_nested_attributes_for :address
end
class Address
end
Then in jobs_controller, update method, I do #job.update(job_params), address params included in the job_params from the form submission.
The address can be updated correctly, however the address observer does not behave properly. Instead after_updated be called, it actually triggers after_create when address gets updated.
address_observer.rb
# cannot be triggered when address gets updated
def after_update(address)
end
# can be triggered when address gets updated
def after_create(address)
end
Cannot figure it out why, anyone could give some help on this? Thanks a lot in advance.

In your case, you need to make sure your parameters are passed in with a key address_attributes, and the correct id. If you do not include the id, a record will be created. This is why after_create is firing instead of after_update.
Here's an example (assuming a has_one relation:)
{ job: { address_attributes: { id: 1, foo: 'bar' } } }
Here is the relevant documentation: ActiveRecord::NestedAttributes::ClassMethods
You can now set or update attributes on the associated posts through an attribute hash for a member: include the key :posts_attributes with an array of hashes of post attributes as a value.
For each hash that does not have an id key a new record will be instantiated [...]

instead of observer..use callback in model...
after_commit :add_count_in_profile, on: :create
def add_count_in_profile
Rails.logger.info "---------updating images count in the profile for #
end

Related

Best way to ensure a Model attribute is consistently downcased and stripped?

I'd like to know what the best way to ensure a user supplied parameter is downcased and stripped in all situations.
I would like to achieve the following:
Guarantee that the attribute will not be saved to the DB unless stripped/downcased
Queries against the db should always downcase/strip the attribute
Validations are run against a downcased/stripped version of user supplied params
Models return downcase/stripped attribute (which shouldn't be a problem given item #1)
you need to write a before_save callback method, within which you downcase and strip the attributes set by the user.
For eg:
class User < ActiveRecord::Base
before_save :format_values
def format_values
self.name = self.name.downcase
end
end
EDIT
I had missed your 3rd point about validations. So if you need to also run validations on these values. You'd need to use the before_validation callback instead.
class User < ActiveRecord::Base
before_validation :format_values
def format_values
self.name = self.name.strip.downcase if name
end
end
Updated the answer based on the comment.
No need to get fancy with callbacks (don't use callbacks, anyway). Just override the setter for your attribute.
class MyModel
def some_attribute=(value)
value = value.strip.downcase if value
write_attribute(:some_attribute, value)
end
end
You would do that in a before_validation callback:
# in your model
before_validation :normalize_attribute
private
def normalize_attribute
# change `attribute` to your actual attribute's name
self.attribute = attribute.strip.downcase if attribute
end
Or you could do that with a custom setter:
# change `attribute` to your actual attribute's name
def attribute=(value)
write_attribute(:attribute, value.strip.downcase) if value
end
The first option will sanitize the attribute's value every time the object is saved, even if the value has not changed. This might be helpful if you introduce this sanitize method when records in the database already exist, because this allow to sanitize all existing record with just one line of code in the Rails console: Model.find_each(&:save). The second option will only sanitize values when they are set. This is a bit more performant.
I both cases I suggest to check for if attribute otherwise you might call strip.downcase on nil values what would lead to an exception.

Rails model with array field, modify push method

I've been trying to find an answer to my question but so far no luck.
I have a model with an array field and I'd like method calls to happen when something gets pushed into the array.
class Shop::Order
include Mongoid::Document
include Mongoid::Timestamps
embeds_many :items,class_name: 'Shop::OrderItem', inverse_of: :order
accepts_nested_attributes_for :items
field :price, type: Money, default: Money.new(0)
field :untaxed_price, type: Money, default: Money.new(0)
end
So when doing order.items << Shop::OrderItem.new(...)
I'd like a method foo to be called.
EDIT: Add reason
So the reason for this is that I want to update the price and untaxed_price of an order each time an item is added to it.
Does it need to happen as soon as you push it? Or can it happen before you save the order? If you can wait until you save, you can do this:
before_validate :update_tax_info
def update_tax_info
if items_changed?
calculate_tax #whatever that may be
end
end
Throwing it in a validation would allow that callback to be called without saving. You could call #order.valid? to update the tax information.
I think monkey patching << is a bad idea. I have two ideas:
Use some kind of observer which listens on create of OrderItem and performs appropriate action
Overwrite OrderItem.create method (or even better provide abstraction):
```
class OrderItem
def add(params)
if create(params)
calculate_something
end
end
end
```
This documentation gives you the range of choices that you have to implement the desired behavior: http://mongoid.org/en/mongoid/docs/callbacks.html
To paraphrase, you have the option of using callbacks like before_save and before_update to do your calculations, or you can implement an Observer class to do this for you.
You can also use the changed method to see if the items array has changed and whether you need to update the derived fields.
Here's some example code:
class OrderObserver < Mongoid::Observer
def before_save(order)
do_something
end
end
Do remember to instantiate your observer in application.rb using:
config.mongoid.observers = :order_observer

Rails after_create callback can't access model's attributes

I can't access my model's attributes in the after_create callback... seems like I should be able to right?
controller:
#dog = Dog.new(:color => 'brown', :gender => 'male')
#dog.user_id = current_user.id
#dog.save
model:
class Dog < ActiveRecord::Base
def after_create
logger.debug "[DOG CREATED] color:#{color} gender:#{gender} user:#{user_id}"
end
end
console: (all seems well)
>>Dog.last
=>#<Dog id: 1, color: "brown", gender: "male", user_id: 1>
log: (wtf!?)
...
[DOG CREATED] color: gender:male user
...
Some of my attributes show up and others don't! oh no! Anyone know what I'm doing wrong? I've always been able to user after_create in such ways in the past.
Note: The actual variable names and values I used were different, but the methods and code are the same.
Figured out my own problem.
One of the attributes was a virtual one, in which I used self.update_attribute...oops!
def price=(amt)
self.update_attribute(:price_in_cents, (amt*100.0).to_i)
end
So for the record, update_attribute will actually create database record (and trigger after_create) if it hasn't been created yet.
Next time I'll be sure to post full code!
Try this instead:
class Dog < ActiveRecord::Base
def after_create(dog)
logger.debug "[DOG CREATED] color:#{dog.color} gender:#{dog.gender} user:#{dog.user_id}"
end
end
Try using after_save instead of after_create may be that works. no tested though.
after_create () Is called after Base.save on new objects that haven‘t been saved yet (no record exists). Note that this callback is still wrapped in the transaction around save. For example, if you invoke an external indexer at this point it won‘t see the changes in the database.
The macro style callback is probably a better idea generally than simply overriding the method. It lets you fire a few different methods at the same time in the lifecycle (if you want). I think what you need is this:
class Dog < ActiveRecord::Base
after_create :dog_logger
def dog_logger
logger.debug "[DOG CREATED] color:#{self.color} gender:#{self.gender} user:#{self.user_id}"
end
end

How to detect attribute changes from model?

I'd like to create a callback function in rails that executes after a model is saved.
I have this model, Claim that has a attribute 'status' which changes depending on the state of the claim, possible values are pending, endorsed, approved, rejected
The database has 'state' with the default value of 'pending'.
I'd like to perform certain tasks after the model is created on the first time or updated from one state to another, depending on which state it changes from.
My idea is to have a function in the model:
after_save :check_state
def check_state
# if status changed from nil to pending (created)
do this
# if status changed from pending to approved
performthistask
end
My question is how do I check for the previous value before the change within the model?
You should look at ActiveModel::Dirty module:
You should be able to perform following actions on your Claim model:
claim.status_changed? # returns true if 'status' attribute has changed
claim.status_was # returns the previous value of 'status' attribute
claim.status_change # => ['old value', 'new value'] returns the old and
# new value for 'status' attribute
claim.name = 'Bob'
claim.changed # => ["name"]
claim.changes # => {"name" => ["Bill", "Bob"]}
Oh! the joys of Rails!
you can use this
self.changed
it return an array of all columns that changed in this record
you can also use
self.changes
which returns a hash of columns that changed and before and after results as arrays
For Rails 5.1+, you should use active record attribute method: saved_change_to_attribute?
saved_change_to_attribute?(attr_name, **options)`
Did this attribute change when we last saved? This method can be
invoked as saved_change_to_name? instead of
saved_change_to_attribute?("name"). Behaves similarly to
attribute_changed?. This method is useful in after callbacks to
determine if the call to save changed a certain attribute.
Options
from When passed, this method will return false unless the original
value is equal to the given option
to When passed, this method will return false unless the value was
changed to the given value
So your model will look like this, if you want to call some method based on the change in attribute value:
class Claim < ApplicationRecord
after_save :do_this, if: Proc.new { saved_change_to_status?(from: nil, to: 'pending') }
after_save :do_that, if: Proc.new { saved_change_to_status?(from: 'pending', to: 'approved') }
def do_this
..
..
end
def do_that
..
..
end
end
And if you don't want to check for value change in callback, you can do the following::
class Claim < ApplicationRecord
after_save: :do_this, if: saved_change_to_status?
def do_this
..
..
end
end
I recommend you have a look at one of the available state machine plugins:
acts_as_state_machine
alter_ego
Either one will let you setup states and transitions between states. Very useful and easy way of handling your requirements.
I've seen the question rise in many places, so I wrote a tiny rubygem for it, to make the code a little nicer (and avoid a million if/else statements everywhere): https://github.com/ronna-s/on_change.
I hope that helps.
You will be much better off using a well tested solution such as the state_machine gem.

How to set some field in model as readonly when a condition is met?

I have models like this:
class Person
has_many :phones
...
end
class Phone
belongs_to :person
end
I want to forbid changing phones associated to person when some condition is met. Forbidden field is set to disabled in html form. When I added a custom validation to check it, it caused save error even when phone doesn't change. I think it is because a hash with attributes is passed to
#person.update_attributes(params[:person])
and there is some data with phone number (because form include fields for phone). How to update only attributes that changed? Or how to create validation that ignore saves when a field isn't changing? Or maybe I'm doing something wrong?
You might be able to use the
changed # => []
changed? # => true|false
changes # => {}
methods that are provided.
The changed method will return an array of changed attributes which you might be able to do an include?(...) against to build the functionality you are looking for.
Maybe something like
validate :check_for_changes
def check_for_changes
errors.add_to_base("Field is not changed") unless changed.include?("field")
end
def validate
errors.add :phone_number, "can't be updated" if phone_number_changed?
end
-- don't know if this works with associations though
Other way would be to override update_attributes, find values that haven't changed and remove them from params hash and finally call original update_attributes.
Why don't you use before_create, before_save callbacks in model to restrict create/update/save/delete or virtually any such operation. I think hooking up observers to decide whether you want to restrict the create or allow; would be a good approach. Following is a short example.
class Person < ActiveRecord::Base
#These callbacks are run every time a save/create is done.
before_save :ensure_my_condition_is_met
before_create :some_other_condition_check
protected
def some_other_condition_check
#checks here
end
def ensure_my_condition_is_met
# checks here
end
end
More information for callbacks can be obtained here:
http://guides.rubyonrails.org/activerecord_validations_callbacks.html#callbacks-overview
Hope it helps.

Resources