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
Related
I have a model:
class A < ActiveRecord::Base
has_many :B
end
And I want to reset or update A's B association, but only save it later:
a = A.find(...)
# a.bs == [B<...>, B<...>]
a.bs = []
#or
a.bs = [B.new, B.new]
# do some validation stuff on `a` and `a.bs`
So there might be some case where I will call a.save later or maybe not. In the case I don't call a.save I would like that a.bs stay to its original value, but as soon as I call a.bs = [], the old associations is destroyed and now A.find(...).bs == []. Is there any simple way to set a record association without persisting it in the database right away? I looked at Rails source and didn't find anything that could help me there.
Thanks!
Edit:
I should add that this is for an existing application and there are some architecture constraint that doesn't allow us to use the the regular ActiveRecord updating and validation tools. The way it works we have a set of Updater class that take params and assign the checkout object the value from params. There are then a set of Validater class that validate the checkout object for each given params. Fianlly, if everything is good, we save the model.
In this case, I'm looking to update the association in an Updater, validate them in the Validator and finally, persist it if everything check out.
In summary, this would look like:
def update
apply_updaters(object, params)
# do some stuff with the updated object
if(validate(object))
object.save(validate: false)
end
Since there are a lot of stuff going on between appy_updaters and object.save, Transaction are not really an option. This is why I'm really looking to update the association without persisting right away, just like we would do with any other attribute.
So far, the closest solution I've got to is rewriting the association cache (target). This look something like:
# In the updater
A.bs.target.clear
params[:bs].each{|b| A.bs.build(b)}
# A.bs now contains the parameters object without doing any update in the database
When come the time to save, we need to persist cache:
new_object = A.bs.target
A.bs(true).replace(new_object)
This work, but this feel kind of hack-ish and can easily break or have some undesired side-effect. An alternative I'm thinking about is to add a method A#new_bs= that cache the assigned object and A#bs that return the cached object if available.
Good question.
I can advice to use attributes assignment instead of collection manipulation. All validations will be performed as regular - after save or another 'persistent' method. You can write your own method (in model or in separated validator) which will validate collection.
You can delete and add elements to collection through attributes - deletion is performed by additional attribute _destroy which may be 'true' or 'false' (http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html), addition - through setting up parent model to accept attributes.
As example set up model A:
class A < ActiveRecord::Base
has_many :b
accepts_nested_attributes_for :b, :allow_destroy => true
validates_associated :b # to validate each element
validate :b_is_correct # to validate whole collection
def b_is_correct
self.bs.each { |b| ... } # validate collection
end
end
In controller use plain attributes for model updating (e.g update!(a_aparams)). These methods will behave like flat attribute updating. And don't forget to permit attributes for nested collection.
class AController < ApplicationController
def update
#a = A.find(...)
#a.update(a_attributes) # triggers validation, if error occurs - no changes will be persisted and a.errors will be populated
end
def a_attributes
params.require(:a).permit([:attr_of_a, :b_attributes => [:attr_of_b, :_destroy]])
end
end
On form we used gem nested_form (https://github.com/ryanb/nested_form), I recommend it. But on server side this approach uses attribute _destroy as mentioned before.
I finally found out about the mark_for_destruction method. My final solution therefor look like:
a.bs.each(&:mark_for_destruction)
params[:bs].each{|b| a.bs.build(b)}
And then I can filter out the marked_for_destruction? entry in the following processing and validation.
Thanks #AlkH that made me look into how accepts_nested_attributes_for was working and handling delayed destruction of association.
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.
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.
I got a field in MYSQL called "date_added". I want this to have the value Time.now.to_i as its default value (in other words, the time the record was added). I know a similar column is added for free in ActiveRecord, but ignoring that... how can I add this default value?
:default => ???
You can use before_create:
class SomeModel < ActiveRecord::Base
before_create :set_date_added
protected
def set_date_added
self.date_added = Time.now.to_i
end
end
This gets called right before an object is saved for the first time, exactly the conditions you want.
Alternatively, you can use a hack to rename the default activerecord created_at to date_added by following the answers here: Renaming the created_at, updated_at columns of ActiveRecord/Rails
See http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html for more information on callbacks.
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