Search code examples
rubyruby-datamapper

Why can't I call self.save! inside of a Ruby DataMapper after :create callback?


I have the following minimal example DataMapper model:

# model.rb
require 'data_mapper'

class Job
  include DataMapper::Resource
  after :create, :create_id_based_data

  property :id, Serial
  property :magicNumber, String

  def create_id_based_data
    self.magicNumber =  "#{self.id}_#{Random.rand()}"
    self.save!
  end
end

DataMapper.setup(:default, 'sqlite::memory:')
DataMapper.auto_migrate!

If I run the code in irb, "magicNumber" is not saved to the database:

irb -r ./model.rb 
2.2.1 :001 > Job.all
 => [] 
2.2.1 :002 > Job.create
 => #<Job @id=1 @magicNumber="1_0.6245356525078689"> 
2.2.1 :003 > Job.all
 => [#<Job @id=1 @magicNumber=nil>]

My understanding is that DataMapper will prevent a repeated save call inside of a save callback, but shouldn't a save call be allowable inside of a create after hook? And even if a normal save call is not allowed, shouldn't the save! call bypass all callbacks and therefore be allowable?


Solution

  • Looking at this bug report, it appears the mechanism (run_once) that prevents a loop of save calls is still at play. Not clear if this is a bug or not. Using save_self gives the behavior you want, at the cost of using a semi-public API.

    # model.rb                         
    require 'data_mapper'
    
    class Job
      include DataMapper::Resource
      after :create, :create_id_based_data
    
      property :id, Serial
      property :magicNumber, String
    
      def create_id_based_data
        self.magicNumber =  "#{self.id}_#{Random.rand()}" 
        save_self(false)
      end
    end
    
    DataMapper.setup(:default, 'sqlite::memory:')
    DataMapper.auto_migrate!
    

    The result:

    irb -r ./model.rb
    2.1.0 :001 > Job.all
     => []
    2.1.0 :002 > Job.create
     => #<Job @id=1 @magicNumber="1_0.7816860975338344">
    2.1.0 :003 > Job.all
     => [#<Job @id=1 @magicNumber="1_0.7816860975338344">]
    2.1.0 :004 >