Module: Decor

Defined in:
lib/decor.rb

Overview

Decor provides a simple way to define multiple representations of an object.

This is useful for when you want to retain multiple versions of your objects while providing a consistent interface between versions.

For example:

class Company < ActiveRecord::Base
  include Decor

  # the first version, customers are dependent on certain artifacts
  # like industry_id and industry
  version "v1" do
    INDUSTRIES = {1 => "Farm",
                  2 => "Software"}

    def industry
      INDUSTRIES[industry_id]
    end

    def as_json(*_)
      super(:only     => [:id, :name, :industry_id],
            :methods  => [:industry])
    end
  end

  # switch to using industry standard codes, cleans up the name
  version "v2" do
    SIC_CODES = {...}

    # use the cleaned name for the name instead
    def name
      name_cleaned
    end

    def industry
      SIC_DOES[sic_code]
    end

    def as_json(*_)
      super(:only     => [:id, :name, :sic_code],
            :methods  => [:industry])
    end
  end

end

In our API, for instance, we can then define a single endpoint for both versions:

get "/api/:version/companies/:id.json" do
  Company.find(params[:id]).for(version).to_json
end

This helps us keep our models fat and our “controllers” skinny. This also helps unit testing the versions of your API.

Further details can be found on [Github](github.com/mtodd/decor/).

Defined Under Namespace

Modules: ClassMethods Classes: Base

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(target) ⇒ Object

Decor is a mixin. Include it into your class and then use the ‘version` class methods in the class body to define your versions, then use the `for` instance method on your objects to use a specific version.

For example:

class Model
  include Decor

  version "v1" do
    # implement specifics for this version here
  end
end

model = Model.new.for("v1")


78
79
80
81
82
# File 'lib/decor.rb', line 78

def self.included(target)
  target.send(:extend, ClassMethods)
  class << target; attr_accessor :versions; end
  target.versions = {}
end

Instance Method Details

#for(version, options = {}) ⇒ Object

An object will be told to behave like the version specified.

Options provide additional values in the context of the verions.

class Model
  include Decor

  version "v1" do
    def versioned?
      true
    end
  end

  def versioned?
    false
  end
end

model = Model.new
model.          versioned?  #=> false
model.for("v1").versioned?  #=> true

An optional context can be supplied which will make external resources available for specific functions in your versions. For example:

class User
  include Decor

  version "v1" do
    def display_name
      "%s (%s)" % [name, band.display_name]
    end
  end
end

user = User.find(id).for("v1", :band => external_band)
user.display_name #=> "Dan Auerbach (The Black Keys)"

Lastly, it’s possible to pass in a ‘:module` option which will override the module already defined for the version specified (making the `version` passed in almost meaningless).

module Specialized
  # special considerations here
end

Model.new.for("v1", :module => Specialized)


201
202
203
204
205
206
# File 'lib/decor.rb', line 201

def for(version, options = {})
  version_module = self.version_module_for(version, options)
  decorator = Class.new(Base).new(self, version, options)
  decorator.send(:extend, version_module)
  decorator
end

#version_module_for(version, options = {}) ⇒ Object

Handles finding the module defined for the ‘version` specified, or overriding with the `:module` option.

See ‘for` for details.



213
214
215
216
217
# File 'lib/decor.rb', line 213

def version_module_for(version, options = {})
  return options.delete(:module)      if options.key?(:module)
  return self.class.versions[version] if self.class.versions.key?(version)
  self.class.const_get(version.upcase)
end