Callback conflict - ruby-on-rails

Both of my callback methods have update_attributes in them. So it looks like when calculate_rating runs it also calls modify_rating. I only want calculate_rating to run for creating a new record and modify_rating to run only when editing and updating a record through a form.
after_create :calculate_rating
before_update :modify_rating
def calculate_rating
end
def modify_rating
end

From the fine manual for update_attributes:
Updates the attributes of the model from the passed-in hash and saves the record [...]
So when you call update_attributes, it will try to save the object and that means that update_attributes is not appropriate for either of the callbacks you're using; update_attributes is meant to be used by controllers for mass assignment and the like.
You could replace the update_attributes call with simple assignments:
def calculate_rating
self.attr1 = 11
self.attr2 = 23
#...
end

Related

ActiveRecord before_update callback understanding

Do I understand correctly that if I execute
model = Model.create(some_attr: "attr_value")
model.update(some_attr: "new_attr_value")
and for Model I have
before_update :before_update_callback
def before_update_callback
some_attr
end
that callback will return "new_attr_value", since the internal ruby object (variable "model") changed before the callback was called.
Yes. Thats exactly what will happen.
# Updates the attributes of the model from the passed-in hash and saves the
# record, all wrapped in a transaction. If the object is invalid, the saving
# will fail and false will be returned.
def update(attributes)
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
with_transaction_returning_status do
assign_attributes(attributes)
save
end
end
See:
https://api.rubyonrails.org/v6.1.4/classes/ActiveRecord/Persistence.html#method-i-update
https://api.rubyonrails.org/classes/ActiveModel/AttributeAssignment.html

Generating a slug after Active Record object creation in rails

I'm trying to generate a unique slug for an object after the object is created using the after_commit callback.
after_commit :create_slug, on: :create
def create_slug
self.slug = generate_slug
self.save
end
When I try to save the object I get a "stack level too deep" error. I'm assuming because I'm saving the object and it's called the after_commit callback again and again.
What's the best way to generate and save the unique slug in this situation?
I recommend using the after_validation callback on create rather than the after_commit. You will be calling multiple transactions, which is not the intention of this callback. What I would do is this:
after_validation(on: :create) do
self.slug = generate_slug
end
Also make sure there are no save actions going on inside the generate_slug. That method should simply be returning a value to insert into the slug attribute.
Use a method that does not trigger callbacks like: update_column
def create_slug
update_column('slug ', generate_slug)
end

How to call a method when creating new ActiveRecord object

I'm trying to integrate an algorithm I wrote in pure Ruby into a Rails app. The main class of my Ruby project could be a resource.
When it was initialized in Ruby I immediately called a function in the initialize method:
def initialize(keyword)
#keyword = keyword
#sources = get_titles_of_sources
end
In Rails, when I create new objects I usually don't have or at least see a initialize method.
#user = User.new(attribute1: value1, attribute2: value2)
But this style doesn't allow me to automatically call a method when creating a new object.
Given what you said in comments, I feel like you should avoid the callbacks (btw you should always avoid callbacks if you can...).
Anyway, I suggest you to create a build method like:
class User
def self.build(props = {})
new(props).tap do |user|
#your code on initialize
end
end
end
I usually tend to move build methods into builders. But it makes you create an additional class
Use after_initialize callback.
Lastly an after_find and after_initialize callback is triggered for
each object that is found and instantiated by a finder, with
after_initialize being triggered after new objects are instantiated as
well.
Active Record Callbacks
Sounds like a use for ActiveRecord Callbacks (http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html).

Order of params stops *_attributes= from working

So I've implemented a hack and I want to know what the "proper" way is to do it.
The issue is that I have an *_attributes=() method that uses an instance variable. The reason this is a problem is that at the time the method is called, that instance variable hasn't been set. Here is the method in question:
def proposed_times_attributes=(attributes)
attributes.each do |key,value|
value[:timezone] = timezone
end
assign_nested_attributes_for_collection_association(:proposed_times, attributes)
end
The timezone is in the params hash after proposed_times_attributes. Therefore my hack is to delete it from params, then add it back, thus moving it to the end of the line.
def create
p = params[:consultation]
a = p.delete(:proposed_times_attributes)
p[:proposed_times_attributes] = a
#consultation = current_user.advised_consultations.new(p)
...
end
What is the proper way that I should be doing this?
new() calls load() where the loop is that goes through each key/value pair.
Thankfully I'm using Ruby 1.9.2 which keeps the order, but it would be nice to know how to do this so that it wouldn't depend on this fact.
If the next operation after new will always be a save operation, you can store the attributes in an accessor, and use a before_validation callback to operate on them as you wish.
class Consultations < ActiveRecord::Base
attr_accessor :proposed_times_attributes
before_validation :assign_proposed_times
def assign_proposed_times
proposed_times_attributes.each do |key,value|
value[:timezone] = timezone
end
assign_nested_attributes_for_collection_association(:proposed_times, attributes)
end
end
Now in your controller you simply have:
def create
#consultation = current_user.advised_consultations.new(params[:consultation])
...
end
If you wish to do other operations before calling save, then pulling out the param as you did in your example, then passing it to an appropriate method after calling new would be the way to go.

Ruby on Rails: "after_create" and validations

I have a record that needs to be validated before doing some action. Am I required to use a "valid?" method if I'm doing it with after_create?
For example, I have in my User model:
def after_create
if valid?
...
end
end
I thought it wasn't necessary to put in the valid method, but my application is telling me otherwise. Any idea?
You do not need the if valid? declaration there because after_create gets called after the record has already been validated (and created).
What do you mean your application is telling you otherwise?
Also, for the callback methods, you should use something like:
after_create :call_my_method
private
def call_my_method
# Do cool stuff
end

Resources