>_ Dinesh Sawant

Command Pattern for Rails Application

// PUBLISHED:
•
// DURATION: 3 min read
• DRAFT_ENTRY

Few months ago, I worked on a feature where I implemented Command pattern. I started from https://github.com/nslocum/design-patterns-in-ruby/blob/master/commands/command.rb and from there I went to implement more matured base class of Command pattern for my Rails application.

I started from this simple and straight forward implementation of Command class.

class Command
  attr_reader :description

  def initialize(description)
    @description = description
  end

  def execute
    raise NotImplementedError
  end

  def unexecute
    raise NotImplementedError
  end
end

I needed more things as I started using this. My requirements were -

  • Generic way to provide validation framework to check whether a command can be performed without any side effects.
  • If command is invalid then code should be executed to address such case.
  • Most of my commands were actually changing the state of the records in database. I wanted atomic execution of command .

Finally, I came up with this base command class.

module CommandSuite
  class Command

    # initializes command with a 
    # hash of parameters
    def initialize(h = {})
      h.each {|k,v| send("#{k}=",v)}
    end

    # Subclass should override this
    # if it returns false action will not be
    # executed and #respond_to_invalidate will be
    # called.
    def validate
    end

    # Try not to override this method as
    # this the central logic of base.
    def execute
      if validate
        ActiveRecord::Base.transaction do
          action
        end
        post_action_tasks
      else
        respond_to_invalidate
      end
    rescue => e
      handle_error(e)
    end

    # Handle error.
    def handle_error(e)
      # Log error
    end

    # Override this method.
    # this is the actual operation
    # which should be done by command class.
    # This action is wrapped in active record
    # transaction which makes it atomic.
    def action
    end

    # Logical separation of code which should
    # be executed if action is 
    # executed successfully.
    def post_action_tasks
    end

    # This method will be called if validate 
    # returns false in execute method.
    def respond_to_invalidate
    end

    # responsible for undoing action
    def undo
    end
  end
end

This is my sample command.

module CommandSuite
  class SampleChangeAppCommand < Command
    attr_accessor :app_id, :profile_id, :prev_app_id

    def validate
      return false if @app_id.blank?
      @profile = Profile.find_by_id(@profile_id)
      !@profile.blank?
    end

    def action
      @prev_app_id = @profile.app_id
      @profile.app_id = @app_id
      @profile.save!
    end

    def undo
      @profile.update(app_id: @prev_app_id)
    end

    def respond_to_invalidate
      puts "app_id not present or profile not found"
    end
  end
end

This is how it works.

For successful case.

2.2.0 :090 > command = CommandSuite::SampleChangeAppCommand.new(app_id: 1, profile_id: 1)
 => #<CommandSuite::SampleChangeAppCommand:0x00000005f6ba30 @app_id=1, @profile_id=1> 
2.2.0 :091 > command.execute
 => true

When validation fails,

2.2.0 :122 > command = CommandSuite::SampleChangeAppCommand.new(app_id: 1)
 => #<CommandSuite::SampleChangeAppCommand:0x000000070df140 @app_id=1> 
2.2.0 :123 > command.execute
app_id not present or profile not found

This setup really helped me to kick start my feature. I hope others will find this useful.

// BROADCAST_POST: