Ensure multiple steps work/saving multiple models via a service I just thought of something new re: saving multiple objects in Rails (though Rails is not necessary).

Objectives:


Create an author and a tomato, but both of those must be valid.
If invalid creation, then return an error object.
Not raise an ActiveRecord if I can (slow). (Is this something that I am even supposed to think about or am I overkilling this?)
Make it easy to add steps/add steps in front or in the back.


That is just something I thought of. I just made creating authors and tomatoes methods that are sequentially called and checked if they are valid? or not.

I'm not sure what design pattern this is because the alternative is multiple if statements. If there is a formal gem or better way then I'd appreciate suggestions:

class AuthorAndTomatoSave
  def call
    steps.map do |step|
      result = step.call

      # If error, return
      return result unless result.valid?
    end

    # hypothetical case where we want to return the author. 
    # (this is also why create_author is memoized)
    create_author
  end

  private

  def steps
    [ proc { create_author }, proc { create_tomato } ]
  end

  def create_author
    @created_author ||= Author.create(author_params)
  end

  def create_tomato
    Tomoto.create(tomato_params)
  end
end
A couple of issues I see here: 


What happens if an Author is saved, and then Tomato creation fails? Should the author record remain in the database? (In most cases the answer would be no)
How does a caller differentiate between a successful call and a failure to save an Author? In both cases, call returns an Author object. Would they have to always call author.valid? to check?



  Not raise an ActiveRecord if I can (slow). (Is this something that I am even supposed to think about or am I overkilling this?)



This seems like a micro-optimization to me, I'd use whatever makes code easier to read.


The generic pattern to handle such cases is to use a transaction. Transactions aren't specific to Rails, they are a feature that most databases offer. ActiveRecord supports transactions out of the box.

In your case, this would be reduced to 

def create_author_and_tomato!
  ActiveRecord::Transaction.do
    Tomato.create!(tomato_params)
    Author.create!(author_params)
  end
end


If any of those steps fail, the transaction will be rolled back (i.e. all databases changes reversed) before an exception is raised. Also, the Author object is returned if everything succeeds. 

If you need to perform a different action if saving one record fails, catch the ActiveRecord::RecordInvalid exception.

begin
  author = create_author_and_tomato!

  # Use `author` for whatever
rescue ActiveRecord::RecordInvalid =&gt; e
  # `e.record` can be used to access the invalid object
end