ActionHero

Move actions from methods in Rails controllers to action classes.

Motiviation

Simple Rails controllers implementing all of the REST actions get crowded enough with at least seven methods. Add any helper methods, etc and things get much worse. In addition, the spec file is even more crowded. All of these concerns jammed into a single class provide many opportunities for bugs. Why should you risk breaking your already implemented and tested index action just because you are adding a show action?

Finally, controller specs are painfully slow. When ActionHero is used to implement your actions as classes you can achieve full coverage without writing a single controller spec.

Installation

Add this line to your application's Gemfile:

gem 'action-hero'

And then execute:

$ bundle

Or install it yourself as:

$ gem install action-hero

Usage

Simplest Case

Include the ActionHero::Controller module in a controller.

# app/controllers/things_controller.rb
class ThingsController < ApplicationController
  include ActionHero::Controller
end

Define the action class.

# app/actions/things/index.rb
module Things
  class Index
    include ActionHero::Action

    def call
      expose :things, Thing.all
      respond_with @things
    end

  end
end

Use the exposed object in the view.

<%= things.each do |thing| %>
  ...
<% end %>

Exposure

Unlike controller instance variables, the instance variables set in the action class will not automatically be available in the view. This is just as well, as blindly making all instance variables in the controller available to the view is arguably a bad idea. In order to solve this problem, ActionHero provides the #expose method.

The expose method does the following:

  • Sets an instance variable in the action class with the same name as provided in the first argument to expose
  • Returns the value passed in the second parameter so that you can set a local variable
  • Stores the value in a data store held on the controller
  • Implements a helper method on the controller to access the exposed value
  • Makes the helper method available in the view

Example of usage:

# in the action class
count = expose( :count, 1 ) 
count  # => 1
@count # => 1

#in the controller
count  # => 1
@count # => nil

#in the view
count  # => 1
@count # => nil

Implicit Controller

Because controller actions can now be defined outside of the controller, defining the controller is optional. If you do not expcitily define a controller, ActionHero will fall back to the ImplicitController.

Ensure an implicit controller is defined. Without configuration, the implicit controller defaults to ImplicitController.

# app/controllers/implicit_controller.rb
class ImplicitController < ApplicationController
  include ActionHero::Controller
end

Define Several Implicit Controllers and Configure Usage

In the case you want several implicit controllers due to different use cases, such as web app controller vs API you can define several implicit controllers and configure the usage.

Define the implicit controllers.

# app/controllers/implicit_controller.rb
class ImplicitController < ApplicationController
  include ActionHero::Controller
end

# app/controllers/api/v1/implicit_controller.rb
class Api::V1::ImplicitController < ApplicationController
  include ActionHero::Controller
end

Configure the usage. The configuration uses regexs to match against the #controller_name and will use the first one matched, so order matters.

# config/initializers/action_hero.rb
ActionHero.configure do |config|
  config.implicit_controllers = [
    [/^\/api\/.*/, Api::ImplicitController],
    [/^\/.*/, ImplicitController]
  ]
end

Mix and Match

You can mix and match usage of ActionHero action classes and standard Rails controller action methods.

In order to unobtrusively tie in to the controller, ActionHero::Controller implements the #action_missing method. Thus, if you implement both an aciton class and an action method, the action method will win.

Define a controller with a show action method and define an action class for the index action.

# app/controllers/things_controller.rb
class ThingsController < ApplicationController
  include ActionHero::Controller

  def show
    ...
  end
end

# app/actions/things/index.rb
module Things
  class Index
    include ActionHero::Action

    def call
      ...
    end
  end
end

Controller Methods Available in Action Class

The following methods forward from the the action class to the controller.

  • action_name
  • env
  • flash
  • formats
  • head
  • headers
  • params
  • redirect_to
  • render
  • render_to_string
  • request
  • reset_session
  • respond_with
  • response
  • session
  • url_for
  • url_options

Logging And Implicit Controllers

When an implicit controller is used ActionHero adds a line to the normal Rails request logging in order to clarify that the implicit controller is being used in place of an explicit controller, giving the correct information of what th eexplicit controller would be inorder to clarify what is going on and aid in debugging, etc.

Started GET "/" for 127.0.0.1 at 2013-09-08 12:38:26 -0500                                                                                                    │
  [Action Hero] No explicit DashboardController defined, using ImplicitController
  Processing by ImplicitController#show as HTML