class AttrChain

Attaches configurable behaviour to accessor methods

class Foo
   attr_chain :name, :require
   attr_chain :email, -> {""}
   attr_chain :birth_day, :immutable, :valid => lambda { |i| (1870..Time.now.year+1).include?(i) }, :require => true
   attr_chain :children, :convert => lambda {|s| s.to_i}
end

Sets up public methods variable_name and variable_name= which both can be used to access the fields. Giving any parameters for the method makes it a “set” operation and giving no parameters makes it a “get” operation. “Set” stores the value and returns self, so set calls can be chained. “Get” returns the stored value.

foo.email("test@email.com").name("test")
foo.email => "test@email.com"
foo.name => "test"

Parameters can be given in short and long format. Short format works by identifying parameter types, long format works by given the name and value as hash parameters:

Advantages for using attr_chain

Warnings about attr_chain

@see InstanceVariableAccessor @see Object.attr_chain @see Module#attr_chain

Constants

HashAccess
InstanceVariableAccess

Public Class Methods

new(clazz, variable_name, attr_configs) click to toggle source

Parses parameters with parse_short_syntax and set_parameters and configures class methods

  • each attr_chain definition uses one instance of AttrChain which holds the configuration for the definition

  • Object::define_method is used to add two methods to target class and when called both of these methods call attr_chain with their parameters

@see Object.attr_chain @see Module#attr_chain

# File lib/util/attr_chain.rb, line 65
def initialize(clazz, variable_name, attr_configs)
  @variable_name = variable_name
  @accessor = InstanceVariableAccess
  set_parameters(variable_name, parse_short_syntax(variable_name, attr_configs))
  me = self
  attr_call = lambda { |*args| me.attr_chain(self, args) }
  [variable_name, "#{variable_name}="].each do |method_name|
    if clazz.method_defined?(method_name)
      clazz.send(:undef_method, method_name)
    end
    clazz.send(:define_method, method_name, attr_call)
  end
end

Public Instance Methods

attr_chain(object, args) click to toggle source

Handles incoming methods for “get” and “set”

  • called by methods defined to class

  • configuration is stored as instance variables, the class knows which variable is being handled

  • method call parameters come as list of parameters

# File lib/util/attr_chain.rb, line 152
def attr_chain(object, args)
  if args.empty?
    if !@accessor.defined?(object, @variable_name)
      if defined? @default
        @accessor.set(object, @variable_name, object.instance_exec(&@default))
      elsif defined? @require
        if @require.kind_of?(String)
          raise "'#{@variable_name}' has not been set: #{@require}"
        else
          raise "'#{@variable_name}' has not been set"
        end
      end
    end
    @accessor.get(object, @variable_name)
  else
    if defined?(@immutable) && @accessor.defined?(object, @variable_name)
      raise "'#{@variable_name}' has been set once already"
    end
    value_to_set = if args.size == 1
                     args.first
                   else
                     args
                   end
    if defined? @convert
      value_to_set = object.instance_exec(value_to_set, &@convert)
    end
    if defined?(@valid_items) || defined?(@valid_procs)
      is_valid = false
      if defined?(@valid_items) && @valid_items.include?(value_to_set)
        is_valid = true
      end
      if is_valid == false && defined?(@valid_procs)
        @valid_procs.each do |valid_proc|
          if is_valid=object.instance_exec(value_to_set, &valid_proc)
            break
          end
        end
      end
      if is_valid == false
        raise "invalid value for '#{@variable_name}'"
      end
    end
    @accessor.set(object, @variable_name, value_to_set)
    object
  end
end
parse_short_syntax(variable_name, attr_configs) click to toggle source

Converts short syntax entries in attr_configs to long syntax

  • warns about not supported values and already defined values

# File lib/util/attr_chain.rb, line 81
def parse_short_syntax(variable_name, attr_configs)
  params = {}
  attr_configs.each do |attr_config|
    key_values = if [:require, :immutable].include?(attr_config)
                   [[attr_config, true]]
                 elsif attr_config.kind_of?(Proc)
                   [[:default, attr_config]]
                 elsif attr_config.kind_of?(Array)
                   [[:valid, attr_config]]
                 elsif attr_config.kind_of?(Hash)
                   all = []
                   attr_config.each_pair do |pair|
                     all << pair
                   end
                   all
                 else
                   raise "attr_chain :#{variable_name} unsupported parameter: '#{attr_config.inspect}'"
                 end
    key_values.each do |key, value|
      if params.include?(key)
        raise "attr_chain :#{variable_name}, :#{key} was already defined to '#{params[key]}' (new value: '#{value}')"
      end
      params[key]=value
    end
  end
  params
end
set_parameters(variable_name, params) click to toggle source

Parses long syntax values and sets configuration for this field

# File lib/util/attr_chain.rb, line 110
def set_parameters(variable_name, params)
  params.each_pair do |key, value|
    case key
      when :require
        @require = value
      when :default
        if !value.kind_of?(Proc)
          raise "attr_chain :#{variable_name}, :default needs to be a Proc, not '#{value.inspect}'"
        end
        @default = value
      when :immutable
        @immutable = value
      when :valid
        if !value.kind_of?(Array)
          value = [value]
        end
        value.each do |valid|
          if valid.kind_of?(Proc)
            @valid_procs ||= []
            @valid_procs << valid
          else
            @valid_items ||= {}
            @valid_items[valid]=valid
          end
        end
      when :convert
        if !value.kind_of?(Proc)
          raise "attr_chain :#{variable_name}, :convert needs to be a Proc, not '#{value.inspect}'"
        end
        @convert = value
      when :accessor
        @accessor = value
      else
        raise "attr_chain :#{variable_name} unsupported parameter: '#{key.inspect}'"
    end
  end
end