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?