How to determine if a record is just created or updated in after_save
Asked Answered
K

8

121

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

Kleper answered 11/2, 2011 at 23:30 Comment(2)
hmm maybe pass a param in a before_save? just thinking out loudConclusive
persisted?Motherland
S
197

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.

Sandrasandro answered 25/8, 2011 at 0:41 Comment(11)
via ActiveModel::DirtyHeyman
It's best to differentiate with an after_update and an after_create. The callbacks can share a common method that takes an argument to indicate if it's a create or update.Falgoust
This might has changed. At least in Rails 4, the after_save callback runs after the after_create or after_update callback (see guides.rubyonrails.org/active_record_callbacks.html).Fadeless
Checking neither of these fields work outside of after_save.Gargan
That's a smart way to check.Algoid
I think use after_create and before_create is a good solution.Wittman
id_changed? will be false after the record is saved (outside of the hooks at least). In that case, you can use id_previously_changed?Nb
It looks like id_changed? is being deprecated in favor of saved_change_to_id?. I saw this message in the console after using id_changed?: "DEPRECATION WARNING: The behavior of attribute_changed? inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after save returned (e.g. the opposite of what it returns now). To maintain the current behavior, use saved_change_to_attribute? instead."Zofiazoha
id_previously_changed (or saved_change_to_id) won't work (at least on Rails 5.2.0) if your record was just created with nested attributes for another record. Instead, you can use transaction_include_any_action?([:create]), which is unaffected.Darnall
@Falgoust what argument are you talking about? AR doesn't pass any arguments to the do_something callback declared as after_create :do_something or after_update :do_somethingSelvage
I like to be specific even I aware that :id shouldn't change in normal, but id_change.first.nil?Monkery
B
33

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
Bushmaster answered 12/2, 2011 at 0:58 Comment(0)
M
30

Yet another option, for those who do have an updated_at timestamp:

if created_at == updated_at
  # it's a newly created record
end
Marj answered 5/2, 2013 at 0:28 Comment(6)
Not a bad idea, but it seems that this could backfire in some situations (not necessarily bullet proof).Toussaint
Depending upon when a migration is run the created_at and updated_at records could be off. Also you always run the chance of somebody updating a record right after initially saving it which could put the time just out of sync. Its not a bad idea, just feels like a more bullet proof implementation could be added.Toussaint
Also they might be equal long after the record was initially created. If a record isn't updated for months, it's going to look like it was just created.Languishing
@Languishing Great thinking. Within the scope of the question though (an after_save callback), is that possible?Marj
@Languishing Sorry, my question was "is it possible for created_at to equal updated_at in an after_save callback at any time other than when it is first created?"Marj
@colllin: When creating a record, created_at and updated_at will be equal in an after_save callback. In all other situations, they will not be equal in the after_save callback.Languishing
F
23

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

Flanagan answered 28/11, 2012 at 21:25 Comment(2)
This might not be true actually. If you have associations in your creation, then after_create is called BEFORE the associations are created, so if you need to make sure EVERYTHING is created, then you need to use after_saveRostand
Thumbs down to this answer as this is totally wrong. In most cases after_create hasn't stored the record in the database when called.Men
S
19

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!

Simone answered 15/9, 2013 at 17:36 Comment(5)
I'm currently in byebug in an after_save callback using Rails 4 and neither of these are working to identify this new record.Flirt
I'd say that @object.previous_changes[:id].any? is quite simple and elegant. It works for me after the record gets updated (I do not call it from after_save).Fernanda
@object.id_previously_changed? is a bit less ugly.Forceful
@Flirt in after_save on Rails 4 you'd want to look at changes[:id] instead of previous_changes[:id]. This is changing in Rails5.1 however (see discussion in github.com/rails/rails/pull/25337)Fenrir
previous_changes.key?(:id) for a better understanding.Lyndell
M
15

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.

Meris answered 5/1, 2022 at 7:27 Comment(0)
P
14

Rails 5.1+ way:

user = User.new
user.save!
user.saved_change_to_attribute?(:id) # => true
Proconsul answered 30/3, 2020 at 15:14 Comment(1)
This is exactly what I was looking for. Thanks!British
F
1

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
Faux answered 13/6, 2020 at 16:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.