119

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

Freedom_Ben
  • 11,247
  • 10
  • 69
  • 89
Aaron Qian
  • 4,477
  • 2
  • 24
  • 27

8 Answers8

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.

Zubin
  • 9,422
  • 7
  • 48
  • 52
  • 3
    via [ActiveModel::Dirty](http://api.rubyonrails.org/classes/ActiveModel/Dirty.html) – chaserx Jul 03 '12 at 21:16
  • 9
    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. – matthuhiggins Oct 09 '13 at 23:44
  • 2
    This might has changed. At least in Rails 4, the after_save callback runs after the after_create or after_update callback (see http://guides.rubyonrails.org/active_record_callbacks.html). – Mark Jul 16 '14 at 14:16
  • 3
    Checking neither of these fields work outside of `after_save`. – fatuhoku Jul 20 '16 at 14:19
  • That's a smart way to check. – Rajesh Paul Sep 02 '16 at 10:45
  • I think use `after_create` and `before_create` is a good solution. – timlentse Nov 05 '16 at 11:33
  • 4
    `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?` – mltsy Nov 10 '17 at 16:48
  • 2
    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." – theblang Sep 17 '18 at 22:51
  • 1
    `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. – Pelle Nov 16 '18 at 23:38
  • @matthuhiggins 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_something` – Hirurg103 Jul 27 '19 at 13:49
  • I like to be specific even I aware that `:id` shouldn't change in normal, but `id_change.first.nil?` – x'ES Jun 15 '20 at 04:30
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
Jeff Paquette
  • 7,089
  • 2
  • 31
  • 40
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
colllin
  • 9,442
  • 9
  • 49
  • 65
  • Not a bad idea, but it seems that this could backfire in some situations (not necessarily bullet proof). – Ash Blue Jul 30 '13 at 18:39
  • 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. – Ash Blue Aug 02 '13 at 20:30
  • 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_. – bschaeffer Dec 11 '14 at 19:56
  • @bschaeffer Great thinking. Within the scope of the question though (an `after_save` callback), is that possible? – colllin Dec 12 '14 at 01:44
  • 1
    @bschaeffer 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?" – colllin Dec 14 '14 at 06:56
  • 1
    @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. – bschaeffer Dec 15 '14 at 18:26
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

user664833
  • 18,397
  • 19
  • 91
  • 140
sharpone
  • 332
  • 3
  • 4
  • 4
    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_save – Niels Kristian Nov 23 '16 at 15:21
  • 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. – svelandiag Apr 20 '21 at 22:14
18

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!

Tom Rossi
  • 11,604
  • 5
  • 65
  • 96
  • I'm currently in byebug in an `after_save` callback using Rails 4 and neither of these are working to identify this new record. – MCB Mar 17 '15 at 20:44
  • 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`). – thekingoftruth Jul 15 '16 at 18:05
  • 1
    `@object.id_previously_changed?` is a bit less ugly. – aNoble Jan 08 '17 at 15:30
  • @MCB 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 https://github.com/rails/rails/pull/25337) – gmcnaughton Aug 03 '17 at 17:04
  • 1
    `previous_changes.key?(:id)` for a better understanding. – Sebastián Palma Feb 06 '20 at 19:43
14

Rails 5.1+ way:

user = User.new
user.save!
user.saved_change_to_attribute?(:id) # => true
Jacka
  • 2,270
  • 4
  • 27
  • 34
14

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.

Tobias
  • 4,523
  • 2
  • 20
  • 40
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