How would I create a permalink with an id for a new model?
E.g
animal = Animal.create(name: 'cool dog') #creates animal with id of 1 and name of dog
animal.permalink => "1-cool-dog"
How do you add the proper callback so that id is inserted? before_save or after_save doesn't work
after_save :update_permalink #or before_save
def update_permalink
self.permalink = "#{id} #{name}".parameterize
end
What ends up happening is I get "cool-dog" instead of "1-cool-dog"
And I get why. It's setting an attribute without saving it on after_save. But doesn't work on before_save either because id hasn't been created on a new record.
According to http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
You should use after_commit instead of after_save
Both save and destroy come wrapped in a transaction that ensures that
whatever you do in validations or callbacks will happen under its
protected cover. So you can use validations to check for values that
the transaction depends on or you can raise exceptions in the
callbacks to rollback, including after_* callbacks.
As a consequence changes to the database are not seen outside your
connection until the operation is complete. For example, if you try to
update the index of a search engine in after_save the indexer won’t
see the updated record. The after_commit callback is the only one that
is triggered once the update is committed. See below.
As I commented above you may want to simply override the to_param method of your Animal Model like this.
def to_param
"#{id}-#{name.parameterize}"
end
This will make all of your urls automatically like the permalink you are trying to create and you can still use Animal.find(params[:id])
Perhaps you don't need to save the permalink to the database at all.
def permalink
"#{self.id} #{self.name}"
end
This approach would add a permalink to the model by concatenating the id and name each time the permalink is read.
Related
I know that before_create is called before the object gets commuted to the database and after_create gets called after.
The only time when before_create will get called and after_create while not is if the object fails to meet data base constants (unique key, etc.). Other that that I can place all the logic from after_create in before_create
Am I missing something?
In order to understand these two callbacks, firstly you need to know when these two are invoked. Below is the ActiveRecord callback ordering:
(-) save
(-) valid
(1) before_validation
(-) validate
(2) after_validation
(3) before_save
(4) before_create
(-) create
(5) after_create
(6) after_save
(7) after_commit
you can see that before_create is called after after_validation, to put it in simple context, this callback is called after your ActiveRecord has met validation. This before_create is normally used to set some extra attributes after validation.
now move on to after_create, you can see this is created after the record is stored persistently onto DB. People normally use this to do things like sending notification, logging.
And for the question, when should you use it? The answer is 'you should not use it at all'. ActiveRecord callbacks are anti-pattern and seasoned Rails developer consider it code-smell, you can achieve all of that by using Service object to wrap around. Here is one simple example:
class Car < ActiveRecord::Base
before_create :set_mileage_to_zero
after_create :send_quality_report_to_qa_team
end
can be rewritten in
# app/services/car_creation.rb
class CarCreation
attr_reader :car
def initialize(params = {})
#car = Car.new(params)
#car.mileage = 0
end
def create_car
if car.save
send_report_to_qa_team
end
end
private
def send_report_to_qa_team
end
end
If you have simple app, then callback is okay, but as your app grows, you will be scratching your head not sure what has set this or that attribute and testing will be very hard.
On second thought, I still think you should extensively use callback and experience the pain refactoring it then you'll learn to avoid it ;) goodluck
The before_create callback can be used to set attributes on the object before it is saved to the database. For example, generating a unique identifier for a record. Putting this in an after_create would require another database call.
before_create:
will be called before saving new object in db. When this method will return false it will prevent the creation by rolling back.
So when you need to do something like check something before saving which is not appropriate in validations you can use them in before_create.
For example: before creation of new Worker ask Master for permission.
before_create :notify_master
def notify_master
# notify_master via ipc and
# if response is true then return true and create this successfully
# else return false and rollback
end
Another use is as Trung Lê suggested you want to format some attribute before saving
like capitalizing name etc.
after_create:
Called after saving object in database for first time. Just when you don't want to interrupt creation and just take a note of creation or trigger something after creation this is useful.
for example: After creating new user with role mod we want to notify other mods
after_create :notify_mod, :is_mod?
def notify_mod
# send notification to all other mods
end
EDIT: for below comment
Q: What's the advantage of putting notify_mod in after_create instead of before_create?
A: Sometimes while saving the object in database it can rollback due to database side validations or due to other issues.
Now if you have written notify_mod in before create then it will be processed even if the creation is not done. No doubt it will rollback but it generates overhead. so it's time consuming
If you have placed it in after_create then notify_mod will only execute if the record is created successfully. Thus decreasing the overhead if the rollback takes places.
Another reason is that it's logical that notification must be sent after user is created not before.
What the heck is a call back method in rails? I see this term being used everywhere while I learn about controllers and models. Can someone provide examples please?
Ref ActiveRecord::Callbacks for the Callbacks w.r.to Activerecord
Callbacks are hooks into the lifecycle of an Active Record object that allow you
to trigger logic before or after an alteration of the object state. This can be
used to make sure that associated and dependent objects are deleted when destroy
is called (by overwriting before_destroy) or to massage attributes before they‘re
validated (by overwriting before_validation). As an example of the callbacks
initiated, consider the Base#save call for a new record
Take an example you have a Subscription model and you have a column signed_up_on which will contains the date at which subscription is created. For this w/o Callbacks you can do something like following in your controller.
#subscription.save
#subscription.update_attribute('signed_up_on', Date.today)
Which will perfectly fine but if suppose you have 3-4 methods in your application where subscription is get create. So to achieve it you have repeat the code in all the places which is redundant.
To avoid this you can use Callbacks and before_create Callback here. So whenever your object of subscription is get create it will assign today's date to signed_up_on
class Subscription < ActiveRecord::Base
before_create :record_signup
private
def record_signup
self.signed_up_on = Date.today
end
end
Following is the list of all the Callbacks
after_create
after_destroy
after_save
after_update
after_validation
after_validation_on_create
after_validation_on_update
before_create
before_destroy
before_save
before_update
before_validation
before_validation_on_create
before_validation_on_update
I have the following model (sort_timestamp is a datetime):
class Post < ActiveRecord::Base
[snip attr_accessible]
acts_as_nested_set
after_create :set_sort_timestamp
private
def set_sort_timestamp
self.sort_timestamp = self.created_at
end
end
I'm using https://github.com/collectiveidea/awesome_nested_set . This code doesn't set sort_timestamp. What am I doing wrong?
Unless I'm missing the point of what you're doing here, you're probably looking for before_create if you'd like it to save when the row is created. Otherwise you'll have to add self.save to the method, but that will cause extra database calls, so before_create might be the better option.
(Basically, the flow of what you were doing before was that the model would be created, saved to the database, and then the object would modify its attribute sort_timestamp to be created_at; this is after your database commit, and only performed in memory (so not persisted, unless you were persisting it in another way later in the code).
EDIT: Actually, this probably won't work because created_at probably won't be set before the record is created. A few options:
1) Add self.save to end of your method with after_create
2) Use Time.now if the times sort_timestamp and created_at don't have to be exactly the same.
or, 3) Try adding default value to migration: How to use created_at value as default in Rails
The #new_record? function determines if a record has been saved. But it is always false in the after_save hook. Is there a way to determine whether the record is a newly created record or an old one from update?
I'm hoping not to use another callback such as before_create to set a flag in the model or require another query into the db.
Any advice is appreciated.
Edit: Need to determine it in after_save hook, and for my particular use case, there is no updated_at or updated_on timestamp
I was looking to use this for an after_save callback.
A simpler solution is to use id_changed? (since it won't change on update) or even created_at_changed? if timestamp columns are present.
Update: As #mitsy points out, if this check is needed outside of callbacks then use id_previously_changed?. See docs.
No rails magic here that I know of, you'll have to do it yourself. You could clean this up using a virtual attribute...
In your model class:
def before_save
#was_a_new_record = new_record?
return true
end
def after_save
if #was_a_new_record
...
end
end
Yet another option, for those who do have an updated_at timestamp:
if created_at == updated_at
# it's a newly created record
end
There is an after_create callback which is only called if the record is a new record, after it is saved. There is also an after_update callback for use if this was an existing record which was changed and saved. The after_save callback is called in both cases, after either after_create or after_update is called.
Use after_create if you need something to happen once after a new record has been saved.
More info here: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Since the object has already been saved, you would you need to look at the previous changes. The ID should only change after a create.
# true if this is a new record
#object.previous_changes[:id].any?
There is also an instance variable #new_record_before_save. You can access that by doing the following:
# true if this is a new record
#object.instance_variable_get(:#new_record_before_save)
Both are pretty ugly, but they would allow you to know whether the object has been newly created. Hope that helps!
Rails 5.1+ way:
user = User.new
user.save!
user.saved_change_to_attribute?(:id) # => true
There is a method called previously_new_record? for exactly this use case.
user = User.new
user.new_record? # => true
user.previously_new_record? # => false
user.save
user.new_record? # => false
user.previously_new_record? # => true
Source: https://api.rubyonrails.org/v6.1.4/classes/ActiveRecord/Persistence.html#method-i-previously_new_record-3F
Looks like the proposed workaround by calling saved_change_to_id? doesn't work anymore. I'm on Rails 7.
For Rails 4 (checked on 4.2.11.1) results of changes and previous_changes methods are empty hashes {} on object creation inside after_save. So attribute_changed? methods like id_changed? won't work as expected.
But you can take advantage of this knowledge and - knowing that at least 1 attribute has to be in changes on update - check if changes is empty. Once you confirm that it's empty, you must be during object creation:
after_save do
if changes.empty?
# code appropriate for object creation goes here ...
end
end
I have a model that has an overriden to_param method returning the (unique) name of a record. This works quite fine, however with one caveat - the user can edit the name.
If I have record #1 with name="abc" and record #2 with name="xyz", then a user editing record #2 and changing name to "abc" will get an error upon saving as the validates_uniqueness_of constraint is violated. However when Rails constructs the edit.html.erb page again, it uses the unvalidated data - including to to_param which is now linking everything to record #1 ("abc"). Consequent saves thus act on record #1 instead of record #2.
What would be the recommended best practice to prevent this horrendous result? Should I reset the name value before redirecting upon an error (but what if the name was okay and the error was elsewhere) or should I change my views to manually insert the id instead of using the automatics of Rails?
Probably the easiest thing to do would be to not rely on the name attribute but instead another attribute that is hidden from the user.
eg. if you had a permalink:string column on the model you could do something like:
Class ModelName < ActiveRecord::Base
before_save :update_permalink
validates_presence_of :name
def to_param
permalink
end
private
def update_permalink
self.permalink = name.parameterize
end
end