This project is archived and is in readonly mode.
save (ActiveRecord::Persistence) can return true even when the model is not saved, and seems to break the principle of "least surprise"
-
Alexey Muranov
I am new to Rails, i am only starting my first Rails application, so please feel free to express your opinions about this problem, or how it should be dealt with. I know that i am supposed to work on it myself if i want it to be fixed, but i am not ready for that, i am just interested in knowing if others consider this a bug too.
-
Alexey Muranov
I want however to state what behavior would look to me natural and "least surprising", or how i would have changed this part of
ActiveRecord::Persistenceif there were no need to keep compatibility with already existing applications:- implement
insertandupdatemethods to simply execute SQL and returntrueorfalseaccordingly, - switch
@persistedtofalsewhen the record is destroyed or deleted or whenid=()method is called, - remove
destroyed?method and @destroyed instance variable, - implement
saveso that it would only returntrueif the model/record has been saved.
- implement
-
Alexey Muranov
In fact, i think that if in item (3) above
destroyed?is not removed but only deprecated, than the suggested changes should not break most of applications... -
Neeraj Singh
- Importance changed from to Low
Following code is causing problem
p = Person.create(:name=>'Bill') Person.find(p.id).destroy p.persisted? # => true p.destroyed? # => false p.name = "John" # => "John" p.save # => trueHowever if you try following code then you will get "TypeError: can't modify frozen hash".
p = Person.create(:name=>'Bill') p2 = Person.find(p.id) p2.destroy p2.persisted? p2.destroyed? p2.name = "John" p2.saveIn the first example author is manipulating record 'P' which is still not frozen.
I guess a good fix for this problem would be to make sure that p2 is same as p. I turned on the identity mapping and still p2.object_id != p.object_id .
I did not follow very closely how identity mapping is implemented. I guess it is time to see how it is done. I'm sure there is a reason why
p1 = User.create p2 = User.find(p1.id) p3 = User.last puts p1.object_id == p2.object_id #=> false puts p1.object_id == p3.object_id #=> false puts p2.object_id == p3.object_id #=> false -
Neeraj Singh
It seems miloops has a fix for Identity mapping. https://github.com/rails/rails/pull/251/files
-
Alexey Muranov
The main issue here that i consider a bug is that
savereturnstruedespite not saving. -
Neeraj Singh
- State changed from new to resolved
Fixed in https://github.com/rails/rails/commit/b35617235d43bdb32016a623044e7... .
Make sure that in console first you do
ActiveRecord::IdentityMap.enabled = true -
Alexey Muranov
This does not resolve the issue that
savereturnstruewithout saving in the above examples, or if the record has been deleted in the background by another application. -
Neeraj Singh
Before hitting save you are doing
p.name = "John"Now that line is throwing exception since the object is frozen. So you don't even get to save method.
-
Alexey Muranov
Ok, i see your point. However, if the record is deleted in the background by another process,
saveis going to returntruewithout saving anything. Is this a desired behavior? -
Neeraj Singh
- State changed from resolved to open
@Alexey. Now I see your point.
class User < ActiveRecord::Base def self.lab u1 = User.create User.delete_all u1.name = 'foo' puts u1.save #=> true u2 = User.create User.delete_all u2.name='foo1' puts u2.save! #=> true end endI am marking this ticket as open so that more people can look at it. I will look into this.
-
Alexey Muranov
- Tag set to activerecord rails3
-
Wicked Tribe
There really seems to be two issues here. The first would be how active record instance behaves on a save when it knows the record has been deleted. That I believe would be the easiest to solve. The second is how or should an active record instance attempt to determine if its record has been deleted without its knowledge.
The complexity on the second issue revolves around when and if the database should be checked to determine if a record still exists. If a response from the database during an update can be used to determine that a record is no longer there, the instance can then execute the code that would solve the first issue. If not, then things get ugly.
Additionally there is the issue of what should happen when no attributes have been changed, a second matching instance has deleted the record, and then the first has been saved. Rails will desire to skip an actual call to the database as this would normally be an performance improvement by not having the db update the record with identical values. Having rails make an extra call in this situation just to see if the record still exists would work against that optimization.
For now I would suggest looking to solve the first problem as the issues with the second are non trivial.