ActiveRecord: when exactly is a record (model) saved to the database?

Hello, i am playing with some test application in console: creating a model, saving it, changing its id, trying to save it again, changing the id back, saving again, etc. The results are sometimes unexpected, but maybe i just do not understand how/when the records are saved.

Can anybody please tell me if calling the "save" method saves the model immediately? If not, what is the method to save it immediately?

I keep an Sqlite database browser open (a Firefox extension), and it seems that some "save" calls have no effect, but after changing some attributes, the "save" works again. I wonder if it is because of my messing around with id, or just the data base does not get updated immediately.

Thanks.

i am playing with some test application in console: creating a model, saving it, changing its id, trying to save it again, changing the id

Why are you changing it's id? You probably shouldn't be doing that...

back, saving again, etc. The results are sometimes unexpected, but maybe i just do not understand how/when the records are saved.

Can anybody please tell me if calling the "save" method saves the model immediately? If not, what is the method to save it immediately?

I keep an Sqlite database browser open (a Firefox extension), and it seems that some "save" calls have no effect, but after changing some attributes, the "save" works again. I wonder if it is because of my messing around with id, or just the data base does not get updated immediately.

Do you have any validations on the model that might be failing? save will return false if validations fail, but won't throw a fit... and obviously won't save the record.

Try save! which will throw a fit. Or ask the model for it's errors...

-philip

Hello, i am playing with some test application in console: creating a model, saving it, changing its id, trying to save it again, changing the id back, saving again, etc. The results are sometimes unexpected, but maybe i just do not understand how/when the records are saved.

Can anybody please tell me if calling the "save" method saves the model immediately?

It will, as long as validations passed and at least some attributes have changed. Changing the id could conceivably cause odd things.

Fred

Use google to search for: around_save yield

yield be inside the method around_save

Everything is before yield is before save the record

All is after yield is after save the record

def around_save

#do anything before save

yield

do anything after save

end

I do not have any validations, the model is basically empty (only attributes and associations).

save! returns true, but doesn't save anything, but saves later, after changing more attributes ...

So, i assume this could be because of changing the id. But what the method id= is for then? I may post later (tomorrow?) a minimal example.

Maybe you are changing the attribute in place thus the change is not saved ?

Read: "If an attribute is modified in-place then make use of [attribute_name]_will_change! to mark that the attribute is changing. Otherwise ActiveModel can’t track changes to in-place attributes." http://api.rubyonrails.org/classes/ActiveModel/Dirty.html

Maybe you don't see the change immediately because transaction is still going ?

Robert Pankowecki

Robert Pankowecki wrote in post #989839:

Maybe you are changing the attribute in place thus the change is not saved ?

This is possible, thanks for pointing this out, i will test.

I do not have any validations, the model is basically empty (only attributes and associations).

save! returns true, but doesn't save anything, but saves later, after changing more attributes ...

So, i assume this could be because of changing the id. But what the method id= is for then?

I've only ever used it when I wanted a new record to have a specific id.

Fred

Robert, it does not seem like i was changing attributes in-place.

Here is what is going on, i do not understand how it can be a desired behavior:

I created a test application:

$ rails new test_app $ cd test_app $ rails generate model Person name:string age:integer $ rake db:migrate

In console:

p = Person.create(:name=>"Johny", :age=>30) p.id # => 1 p.id = 2 # => 2 p.save # => true

No change in the database!

p.id # => 2 p.name = "Jim" # => "Jim" p.save # => true

No change in the database

p.name # => "Jim" p.id = 1 # => 1 p.save # => true

No change in the database

p.age = 31 # => 31 p.save # => true

In the database, the age became 31, but the name stayed "Johny"!

p.name # => "Jim" p.name.object_id # => 2155015240 p.name = "Jim" # => "Jim" p.name.object_id # => 2154887680 p.save # => true

No change in the database!

p.name = "Jimmy" # => "Jimmy" p.name.object_id # => 2154859980 p.save # => true

Finally, the name changed in the database!

Alexey.

Robert, it does not seem like i was changing attributes in-place.

Here is what is going on, i do not understand how it can be a desired behavior:

The core isssue is that changing id in activerecord is meaningless: every update statement is of the form (simplifying slightly)

update blah set ... where id = #{self.id}

So changing self.id will either update no rows or update some existing object.

The second thing coming into play is dirty attributes: activerecord only includes changed attribute in the update clause, so if it thinks that no attributes have changed since the last save it won't do anything.

Why are you changing the id of an existing record?

Fred

Frederick Cheung wrote in post #989934:

Why are you changing the id of an existing record?

Just to learn how it behaves. I also plan an application where i want to use the primary key as a foreign key for a has_one association, so there it could (possibly) make sense to change it.

If assigning anything to the primary key breaks ActiveRecord, is there a way to disable the id=() method after a record has been created?

Alexey.

Frederick Cheung wrote in post #989934:

Why are you changing the id of an existing record?

Just to learn how it behaves. I also plan an application where i want to use the primary key as a foreign key for a has_one association, so there it could (possibly) make sense to change it.

I told you life would be much easier for you if you stuck to the Rails conventions :slight_smile:

Colin

Colin, i shall always listen to you better in the future. However, now i am taking this challenge personally.

Frederic, i think you are right, my problems/confusions are arising from the fact that on subsequent calls save uses UPDATE instead of INSERT. Is there a way to force it to use INSERT if the id is not used by another record?

Alexey.

After some thinking, i agree that there is no real need to ever change the primary key (the reason i want to use it in my application also as a foreign key is exactly that there will be no need to change this foreign key). If i need to copy attributes from one record to another, i can do it with attributes=(). Thanks for all the explanations.

I am posting a related question in the same thread. It is more of a philosophical question. Can anybody please give me some philosophical explanation why the following behavior of ActiveRecord is considered ok (or should i submit a bug report/feature request?):

In console:

p = Person.create(:name=>'Bill') p.destroy p.name # => "Bill" p.save # => true

Nothing is saved in the database, but what disturbs me more is that "save" returned true in such case.

A more elaborate version:

p = Person.create(:name=>'Bill') p.id # => 1 pp = Person.find(1) pp.destroyed p.persisted? => true p.destroyed? => false p.save # => true

but the database is empty. Again, what bothers me the most is the "true" returned by "save".

Alexey.

I am posting a related question in the same thread.

It is more of a philosophical question.

Can anybody please give me some philosophical explanation why the

following behavior of ActiveRecord is considered ok (or should i submit

a bug report/feature request?):

In console:

p = Person.create(:name=>‘Bill’)

p.destroy

p.name # => “Bill”

p.save # => true

Nothing is saved in the database, but what disturbs me more is that

“save” returned true in such case.

In this first example you are inializing an object instance of Person by calling the “create” method. The “create” method of class Person does a “create” to the database where as the method “new” does not. The “destroy” method you called sends a destroy to the database for the record based on that id but does not destroy the obect instance. This is because object is not a pointer to the database record, it’s an instance of the class Person. This is why you can still retrieve the name from your object instance later by calling the method “name”. The “save” method should be doing the action of create or update in the database (depending on if the “new” or “create” methods were called to inialize the object instance). It returning “true” is strange since the record in the database is neither being created or updated. That may indeed be a bug.

A more elaborate version:

p = Person.create(:name=>‘Bill’)

p.id # => 1

pp = Person.find(1)

pp.destroyed

p.persisted? => true

p.destroyed? => false

p.save # => true

but the database is empty.

Again, what bothers me the most is the “true” returned by “save”.

In this example you are inializing two seperate object instances of the class Person. One by calling the method “create” and the second by finding the first record in the database. Again, object instances are not pointers to the database record. The objects “p” and “pp” are totally seperate. Like above, activating the “destroy” method sends a destroy to the database based on the id in the object but does not destroy the calling object or any other object that happens to have the same id value. The “save” method returning true is strange since the record with the id value of 1 can’t be created again or updated.

Someone else might know more as to why “save” returns true in this case. If not, then it is most likely a bug.

B.

Try it in your SQL console of choice:

  UPDATE my_table SET field1 = 'new value' WHERE id = <non-existant-id-value>

You'll get a message "no records were updated" - that's a successful execution as far as SQL is concerned.

Although you might expect rails to check the numbers of rows modified (as it does when optimistic locking is enabled) - Select * from foo where id ='non existant' will also execute just fine but rails chooses to make Foo.find(id) raise an exception in those cases

Fred

UPDATE my_table SET field1 = 'new value' WHERE id = <non-existant-id-value>

You'll get a message "no records were updated" - that's a successful execution as far as SQL is concerned.

Although you might expect rails to check the numbers of rows modified (as it does when optimistic locking is enabled) - Select * from foo where id ='non existant' will also execute just fine but rails chooses to make Foo.find(id) raise an exception in those cases

I agree it probably breaks the principle of least surprise, so I've just had a rummage in active_record\base.rb (Rails 2.3.11)

Obviously, .find returns rows, so it's easy to raise an error if the size of the returned array is nil, but I was curious about the return value of create_or_update - so, when I try the previous example [1]:

p = Person.create(:name=>'Bill') p.destroy p.name # => "Bill" p.save # => true

It works fine as the update method returns a true value if quoted_attributes is empty (as it uses that to build the set of fields to update - no fields to update == no query to run), but if you modify the object before saving (eg, > p.name = "William"), it errors with a "TypeError: can't modify frozen hash" when trying to update the updated_at value because it's frozen the @attributes_cache collection when destroy was called...

So yes, it probably would make more sense to have .save return false or raise on saving a destroyed record - but it should raise if you try to alter a destroyed record, and it makes not much sense to save a record you've destroyed and not altered... :-/

I don't know which way to plump... any thoughts?

[1] That example is terribly contrived, and it's making it hard for me to make up my mind if this behaviour is "bad" or not. Anyone got a real-world example of where this is causing a problem?