rake / rails .save! not updating database

2020-07-06 09:27发布

问题:

I am trying to save changes to my database trough a rake task.

In my rake task I do something like:

namespace :parts do
  desc "Update Parts table, swap names in title"
  task :swap => :environment do
    Part.swap
  end
end

In my Part class I do

def self.swap
  Part.all.each do |part|
    if (part.title =~ REGEX) == 0
      part.title.gsub! REGEX, '\2 \1'
      puts part.title
      part.save!
    end
  end
end

However, this does not save the part. The save! does return true. the puts part.title does return the value I want.

If I call

Part.update(part.id, title: part.title)

The database updates properly. Why is this? Am I doing something wrong in my loop? I am working with Rails 3.1.3, Rake 0.9.2.2 and MySQL2 0.3.7

回答1:

It's because the way ActiveRecord detects that attributes are changed is through the setter. Therefore, if you use gsub! on an attribute, ActiveRecord doesn't know it needs to update the database.

You'll probably have to do this:

part.title = part.title.gsub REGEX, '\2 \1'

Update from comment

Also, if you try to assign title to another variable and then gsub! it won't work either because it's the same object (code from my project, variable names different).

ruby-1.9.3-p0 :020 > t = p.name
 => "test" 
ruby-1.9.3-p0 :023 > t.object_id
 => 70197586207500 
ruby-1.9.3-p0 :024 > p.name.object_id
 => 70197586207500 
ruby-1.9.3-p0 :025 > t.gsub! /test/, 'not a test'
 => "not a test" 
ruby-1.9.3-p0 :037 > p.name = t
 => "not a test" 
ruby-1.9.3-p0 :026 > p.save
   (37.9ms)  BEGIN
** NO CHANGES HERE **
   (23.9ms)  COMMIT
 => true 

You have to .dup the string before modifying it.

ruby-1.9.3-p0 :043 > t = p.name.dup
 => "test" 
ruby-1.9.3-p0 :044 > t.gsub! /test/, 'not a test'
 => "not a test" 
ruby-1.9.3-p0 :045 > p.name = t
 => "not a test" 
ruby-1.9.3-p0 :046 > p.save
   (21.5ms)  BEGIN
   (20.8ms)  UPDATE "projects" SET "name" = 'not a test', "updated_at" = '2012-01-02 07:17:22.892032' WHERE "projects"."id" = 108
   (21.5ms)  COMMIT
 => true