Dan Tao
Dan Tao

Reputation: 128317

How can I have a DataMapper callback happen in a transaction?

I'd like to be able to define a callback in DataMapper and have this happen in a transaction along with the accompanying update. For example:

class Comment
  include DataMapper::Resource

  has n, :updates

  property :id,   Serial
  property :text, String

  after :update do
    self.updates.create(:text => self.text)
  end
end

I think it's clear what the above code is trying to do: any time a Comment is updated, also create a corresponding Update record. Now, a possible scenario is that you could update a post, creating the update would fail—for whatever reason—and thus some history would be lost. So I would really like for this kind of operation to happen within a transaction.

Is this possible? I can think of some workarounds (e.g., defining a custom update method); but I'm curious to know if there's a "correct" way or if others can think of elegant approaches.

Upvotes: 1

Views: 296

Answers (1)

mbj
mbj

Reputation: 1042

To archive the same with an testable and extendable design I suggest a service object like this:

class CommentUpdate

  private_class_method :new

  # Run comment update within transaction
  def self.run(*args)
    Comment.transaction do
      new(*args)
    end
  end

  # Initialize object
  #
  # @param [Comment]
  #   the comment to update
  # @param [String]
  #   the text to set
  # 
  def initialize(comment, text)
    @comment, @text = comment, text
    run
  end

  # Test if update was successful
  def successful?
    @comment.saved?
  end

private

  # Run update
  def run
    @comment.text = @text
    if @comment.save
      @comment.updates.create(:text => @text)
    end
  end
end

# usage
comment = Comment.first
update = CommentUpdate.run(comment, "Some new cool text")
update.successful? # Use this to steer your control flow...

Upvotes: 7

Related Questions