Rails model with array field, modify push method - ruby-on-rails

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

Related

In Rails can I save a record without it calling my callback?

When I do my_record.save can I pass a parameter or something to tell the record to not call it's Callback? Below is the callback I have set for my object.
class Measurable < ActiveRecord::Base
after_save :summarize_measurables_for_player
# ...
def summarize_measurables_for_player
# ...
end
end
Edit
This callback is used for when someone changes a value on measurable, then it calculates the preferred value for the Measurable_Type and then it stores that value on a column of another object. This allows me to retrieve the information much faster. I however, don't want this to be called when I import information. Because it would then summarize after each change. It would be a faster process to import all the information and then summarize all the values at once I would think.
In your case, I'd add an attr_accessor to the model and skip this method if set to true.
class Measurable < ActiveRecord::Base
after_save :summarize_measurables_for_player, :unless => :skip_summarize
attr_accessor :skip_summarize
# ...
def summarize_measurables_for_player
# ...
end
end
Then in the import, you can set :skip_summarize => true in the attributes of the imported object.

setting mandatory field automatically in Rails from Model callback, when and how?

I have this model which has a mandatory field which needs to be automatically set just before save. I'm struggling with the correct way to implement this:
build the logic in the controller before the save (and have validates rule in model)
build the logic in a before_save callback and have validates rule in model, but this seems to late in the flow? I do get validation errors this way.
build the logic in a before_save callback and don't define validation for this particular field
do it any of the ways above and don't assign a validates rule for the particular field
I was working on 2 since this seems like the correct way to implement this. Was considering the usage of before_validation, but I don't know what would happen when my other fields don't get validated... this could cause double assignment of the same value..
code for 2 which gives a basic idea of what I'm trying to achieve:
#category.rb
class Category < ActiveRecord::Base
before_create :set_position_number
def set_position_number
highest = Category.maximum(:position)
self.position = highest.to_i + 1
end
end
I'm struggling with the correct way to implement this
The most efficient way will be to use an ActiveRecord callback hook, such as you've posted:
#app/models/category.rb
class Category < ActiveRecord::Base
before_create :your_action
private
def your_action
#fires before create
end
end
but this seems to late in the flow
As mentioned in the comments, you can see the order of the callbacks (and thus their order in the flow):
Thus, if you want to populate some data before you validate, and then validate that data, you'll be best using the before_validation callback:
#app/models/category.rb
class Category < ActiveRecord::Base
before_validation :set_position_number, on: :create
validates :position, ______________
private
def set_position_number
highest = Category.maximum(:position)
self.position = highest.to_i + 1
end
end
Remember, a Rails model just populates certain attributes which are then to be either saved to the db, or validated. Rails does not care where those attributes come from; populating them before_validation is a good a source as the controller.
If you are setting a value automatically and don't take user input, you don't need validation. Write a unit test.
If the field is something like a position value, then you should indeed set it in a before_create callback.

stack level too deep after_save callback in Rails4

My model is like this,
class Slot
include Mongoid::Document
after_save :calculate_period
field :slot, type: Array
def calculate_period
if condition
do something
end
self.slot = true
save
end
end
After submit button it will show this error,
SystemStackError in SlotsController#create
stack level too deep
and also consuming more time. If i remove the save from def calculate_period then the values are not storing after_save callback.
Any solution...!!!
You should change this to before_save, that way you can change the model's attributes, and then they will be saved to the database as normal.
class Slot
include Mongoid::Document
before_save :calculate_period
def calculate_period
if condition
#do something
end
end
end
You have infinite loop - calling save in calculate_period method invokes callbacks, including your calculate_period callback. The first solution that came into my mind is to add virtual attribute and check it before calling your callback method:
class Slot
include Mongoid::Document
after_save :calculate_period, unless: :period_calculated # I'm not sure if Mongoid allows this
attr_accessor :period_calculated
def calculate_period
if condition
# do something
end
self.period_calculated = true
save
end
end

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 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