Aggregated attribute changed but _was is changed too. So nothing is update in database.

(Frenchy, sorry =) )

Hi everyone, i've got some troubles with aggregated attribute.

I created a point and path class which receive postgresql point and
path type data. Path object contains an array of point object.

I'm currently working on a "hunting" module in web browser based game,
and i need a crontab executed every n minutes to move the character on
the map. I've created a function which remove the first point from the
path and then save:

  def one_step_forward!
    path_will_change!
    path.points.shift
  end

But look at this rails console tests:

ruby-1.9.3-rc1 :001 > @hunting = Hunting.find(1)
  Hunting Load (7.6ms) SELECT "huntings".* FROM "huntings" WHERE
"huntings"."id" = $1 LIMIT 1 [["id", 1]]
=> #<Hunting id: 1, path: "((7,6),(5,4))", created_at: "2011-10-02
16:02:11", updated_at: "2011-10-02 18:08:45">

ruby-1.9.3-rc1 :006 > @hunting.path
=> #<Path:0xaabba1c @points=[#<Point:0xaabb968 @x=7, @y=6>, #<Point:
0xaabb8c8 @x=5, @y=4>]>

ruby-1.9.3-rc1 :002 > @hunting.one_step_forward!
=> #<Point:0xaebc058 @x=7, @y=6>

ruby-1.9.3-rc1 :003 > @hunting.changed
=> ["path"]

ruby-1.9.3-rc1 :004 > @hunting.changes
=> {"path"=>[#<Path:0xaebbd10 @points=[#<Point:0xaebbe8c @x=5,
@y=4>]>, #<Path:0xa189da4 @points=[#<Point:0xaebbe8c @x=5, @y=4>]>]}

As you can seen, the attribute "path" is changed, but the path_was
return me the new path value! So when i'm using the save method,
nothing is saved...

I tried few things, like @path_was = path before the path_will_change!

Thank you in advance for any help.

This issue is making me crazy... Look at this:

ruby-1.9.3-rc1 :071 > @hunting = Hunting.find(1)
  Hunting Load (0.5ms) SELECT "huntings".* FROM "huntings" WHERE
"huntings"."id" = $1 LIMIT 1 [["id", 1]]
=> #<Hunting id: 1, path: "((7,6),(5,4))", created_at: "2011-10-02
16:02:11", updated_at: "2011-10-03 19:16:44">
ruby-1.9.3-rc1 :072 > @hunting.path
=> ((7,6),(5,4))
ruby-1.9.3-rc1 :073 > @hunting.changed?
=> false
ruby-1.9.3-rc1 :074 > @hunting.changes
=> {}
ruby-1.9.3-rc1 :075 > @hunting.one_step_forward
=> (7,6)
ruby-1.9.3-rc1 :076 > @hunting.path
=> ((5,4))
ruby-1.9.3-rc1 :077 > @hunting.changed?
=> true
ruby-1.9.3-rc1 :078 > @hunting.changes
=> {"path"=>[((5,4)), ((5,4))]}
ruby-1.9.3-rc1 :079 > @hunting.changed_attributes
=> {"path"=>((5,4))}
ruby-1.9.3-rc1 :080 > @hunting.save
   (0.2ms) BEGIN
   (0.5ms) UPDATE "huntings" SET "path" = '((7,6),(5,4))',
"updated_at" = '2011-10-03 19:30:15.467748' WHERE "huntings"."id" = 1
   (23.5ms) COMMIT
=> true

Ok, path_was == path, don't know why, but where the hell rails found
the '((7,6),(5,4))' used in the SQL statement?

I found a way to "fix" the issue but i don't really like it:
ruby-1.9.3-rc1 :118 > @hunting = Hunting.find(1)
  Hunting Load (0.5ms) SELECT "huntings".* FROM "huntings" WHERE
"huntings"."id" = $1 LIMIT 1 [["id", 1]]
=> #<Hunting id: 1, path: "((7,6),(5,4))", created_at: "2011-10-02
16:02:11", updated_at: "2011-10-03 19:53:11">
ruby-1.9.3-rc1 :119 > @hunting.one_step_forward
=> (7,6)
ruby-1.9.3-rc1 :120 > Hunting.update(@hunting.id, { :path =>
@hunting.path })
  Hunting Load (0.5ms) SELECT "huntings".* FROM "huntings" WHERE
"huntings"."id" = $1 LIMIT 1 [["id", 1]]
   (0.2ms) BEGIN
   (1.1ms) UPDATE "huntings" SET "path" = '((5,4))', "updated_at" =
'2011-10-03 19:53:37.810688' WHERE "huntings"."id" = 1
   (26.7ms) COMMIT
=> #<Hunting id: 1, path: "((5,4))", created_at: "2011-10-02
16:02:11", updated_at: "2011-10-03 19:53:37">

This issue is making me crazy… Look at this:

ruby-1.9.3-rc1 :071 > @hunting = Hunting.find(1)

Hunting Load (0.5ms) SELECT “huntings”.* FROM “huntings” WHERE

“huntings”.“id” = $1 LIMIT 1 [[“id”, 1]]

=> #<Hunting id: 1, path: “((7,6),(5,4))”, created_at: "2011-10-02

16:02:11", updated_at: “2011-10-03 19:16:44”>

ruby-1.9.3-rc1 :072 > @hunting.path

=> ((7,6),(5,4))

ruby-1.9.3-rc1 :073 > @hunting.changed?

=> false

ruby-1.9.3-rc1 :074 > @hunting.changes

=> {}

ruby-1.9.3-rc1 :075 > @hunting.one_step_forward

=> (7,6)

ruby-1.9.3-rc1 :076 > @hunting.path

=> ((5,4))

ruby-1.9.3-rc1 :077 > @hunting.changed?

=> true

ruby-1.9.3-rc1 :078 > @hunting.changes

=> {“path”=>[((5,4)), ((5,4))]}

Strange, I would expect here:

=> {“path”=>["((7,6),(5,4))", “((5,4))”]}

ruby-1.9.3-rc1 :079 > @hunting.changed_attributes

=> {“path”=>((5,4))}

Strange, I would expect here:

=> {“path”=>"((7,6),(5,4))"}

ruby-1.9.3-rc1 :080 > @hunting.save

(0.2ms) BEGIN

(0.5ms) UPDATE “huntings” SET “path” = ‘((7,6),(5,4))’,

“updated_at” = ‘2011-10-03 19:30:15.467748’ WHERE “huntings”.“id” = 1

(23.5ms) COMMIT

=> true

What happens, when on line :075 of your example above, you

simply say

@hunting.path ‘((5,4))’

instead of the call to Hunting#one_step_forward and replay the rest of your

example. Does it behave as expected then?

Are there any before_save hooks at play in your Hunting model?

Actually, I tried to replay your example (using a simple string assignment)

in Rails 3.1.1.rc1 and found the different results for .changes and

.changed_attributes after the assignment, mentioned above.

004:0> j = Job.find(1)

Job Load (0.5ms) SELECT “jobs”.* FROM “jobs” WHERE “jobs”.“id” = $1 LIMIT 1 [[“id”, 1]]

=> #<Job id: 1, created_at: “2011-10-03 20:56:27”, updated_at: “2011-10-03 21:02:56”, name: “foo”, path: “((7,6),(5,4))”>

005:0> j.path

=> “((7,6),(5,4))”

006:0> j.changed?

=> false

007:0> j.path = “((5,4))”

=> “((5,4))”

008:0> j.changed?

=> true

009:0> j.changes # this is different from your result and what I expected

=> {“path”=>["((7,6),(5,4))", “((5,4))”]}

010:0> j.changed_attributes # this is the original value, as documented

=> {“path”=>"((7,6),(5,4))"}

011:0> j.path_was

=> “((7,6),(5,4))”

012:0> j.path

=> “((5,4))”

013:0> j.save

(0.1ms) BEGIN

(0.4ms) UPDATE “jobs” SET “path” = ‘((5,4))’, “updated_at” = ‘2011-10-03 21:06:21.336787’ WHERE “jobs”.“id” = 1

(0.7ms) COMMIT

=> true

014:0> j = Job.find(1)

Job Load (0.4ms) SELECT “jobs”.* FROM “jobs” WHERE “jobs”.“id” = $1 LIMIT 1 [[“id”, 1]]

=> #<Job id: 1, created_at: “2011-10-03 20:56:27”, updated_at: “2011-10-03 21:06:21”, name: “foo”, path: “((5,4))”>

HTH,

Peter

This issue is making me crazy… Look at this:

ruby-1.9.3-rc1 :071 > @hunting = Hunting.find(1)

Hunting Load (0.5ms) SELECT “huntings”.* FROM “huntings” WHERE

“huntings”.“id” = $1 LIMIT 1 [[“id”, 1]]

=> #<Hunting id: 1, path: “((7,6),(5,4))”, created_at: "2011-10-02

16:02:11", updated_at: “2011-10-03 19:16:44”>

ruby-1.9.3-rc1 :072 > @hunting.path

=> ((7,6),(5,4))

ruby-1.9.3-rc1 :073 > @hunting.changed?

=> false

ruby-1.9.3-rc1 :074 > @hunting.changes

=> {}

ruby-1.9.3-rc1 :075 > @hunting.one_step_forward

=> (7,6)

ruby-1.9.3-rc1 :076 > @hunting.path

=> ((5,4))

ruby-1.9.3-rc1 :077 > @hunting.changed?

=> true

ruby-1.9.3-rc1 :078 > @hunting.changes

=> {“path”=>[((5,4)), ((5,4))]}

Strange, I would expect here:

=> {“path”=>["((7,6),(5,4))", “((5,4))”]}

ruby-1.9.3-rc1 :079 > @hunting.changed_attributes

=> {“path”=>((5,4))}

Strange, I would expect here:

=> {“path”=>"((7,6),(5,4))"}

ruby-1.9.3-rc1 :080 > @hunting.save

(0.2ms) BEGIN

(0.5ms) UPDATE “huntings” SET “path” = ‘((7,6),(5,4))’,

“updated_at” = ‘2011-10-03 19:30:15.467748’ WHERE “huntings”.“id” = 1

(23.5ms) COMMIT

=> true

What happens, when on line :075 of your example above, you

simply say

@hunting.path ‘((5,4))’

@hunting.path = ‘((5,4))’ # sorry, typo …

ruby-1.9.3-rc1 :001 > @hunting = Hunting.find(1)
  Hunting Load (7.7ms) SELECT "huntings".* FROM "huntings" WHERE
"huntings"."id" = $1 LIMIT 1 [["id", 1]]
=> #<Hunting id: 1, path: "((7,6),(5,4))", created_at: "2011-10-02
16:02:11", updated_at: "2011-10-03 20:01:45">
ruby-1.9.3-rc1 :002 > @hunting.path = Path.new("((5,4))")
=> ((5,4))
ruby-1.9.3-rc1 :003 > @hunting.path
=> ((5,4))
ruby-1.9.3-rc1 :004 > @hunting.path_was
=> "((7,6),(5,4))"
ruby-1.9.3-rc1 :005 > @hunting.changes
=> {"path"=>["((7,6),(5,4))", ((5,4))]}
ruby-1.9.3-rc1 :006 > @hunting.save
   (0.2ms) BEGIN
   (0.7ms) UPDATE "huntings" SET "path" = '((5,4))', "updated_at" =
'2011-10-04 04:53:54.121491' WHERE "huntings"."id" = 1
   (28.8ms) COMMIT
=> true

Works fine.

Actually, there is no "before_save" in my model.