Class: ACH::Component::HasManyAssociation

Inherits:
Object
  • Object
show all
Defined in:
lib/ach/component/has_many_association.rb

Overview

Objects of this class host essential functionality required to create associated object from within owner objects.

Newly instantiated HasManyAssociation object has no owner, and should be used to assign it’s copies to owners via for method. This technique has following application:

class Batch < ACH::Component
  association = HasManyAssociation.new(:entries)

  association.delegation_methods.each do |method_name|
    delegate method_name, :to => '@batches_association'
  end

  after_initialize_hooks << lambda{ instance_variable_set('@batches_association', association.for(self)) }
  # All these lines of code are macrosed by <tt>ACH::Component.has_many</tt> method
end
# Now, whenever new batch is created, it will have it's own @batches_association,
# and essential methods +batches+, +batch+, +build_batch+ delegated to it
# (accordingly, to +container+, +create+, and +build+ methods)

Defined Under Namespace

Classes: DoubleAssignmentError, NoLinkError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(plural_name, options = {}) ⇒ HasManyAssociation

Initialize the association with a plural name and options.

Parameters:

  • plural_name (String)
  • options (Hash) (defaults to: {})

Options Hash (options):

  • :linked_to (Symbol)

    plural name of records to link associated ones

  • :proc_defaults (Proc)


58
59
60
61
# File 'lib/ach/component/has_many_association.rb', line 58

def initialize(plural_name, options = {})
  @name = plural_name.to_s
  @linked_to, @proc_defaults = options.values_at(:linked_to, :proc_defaults)
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



49
50
51
# File 'lib/ach/component/has_many_association.rb', line 49

def name
  @name
end

Instance Method Details

#build(str) ⇒ Array<ACH::Record::Base>

Use klass#from_s to instantiate object from a string. Thus, klass should be descendant of ACH::Record::Base. Then pushes object to appropriate container.

Parameters:

  • str (String)

Returns:



99
100
101
102
# File 'lib/ach/component/has_many_association.rb', line 99

def build(str)
  obj = klass.from_s(str)
  container_for_associated << obj
end

#containerHash, Array

Return the main container for association. For plain (without :linked_to option), it is array. For linked associations, it is a hash, which keys are records from linking associations, and values are arrays for association’s objects.

Returns:

  • (Hash, Array)


131
132
133
# File 'lib/ach/component/has_many_association.rb', line 131

def container
  @container ||= linked? ? {} : []
end

#container_for_associatedArray

Return an array onto which the associated object may be be pushed. For plain associations, it is equivalent to container. For linked associations, uses @owner and linking association’s name to get the latest record from linking associations. If it does not exist, NoLinkError will be raised.

Example:

class Batch < ACH::Component
  has_many :entries
  has_many :addendas, :linked_to => :entries
end
batch = Batch.new
batch.entry(:amount => 100)
batch.addenda(:text => 'Foo')
batch.entry(:amount => 200)
batch.addenda(:text => 'Bar')
batch.addenda(:text => 'Baz')

batch.entries  # => [<Entry, amount=100>, <Entry, amount=200>]
batch.addendas # => {<Entry, amount=100> => [<Addenda, text='Foo'>],
               #     <Entry, amount=200> => [<Addenda, text='Bar'>, <Addenda, text='Baz'>]}

Returns:

  • (Array)

Raises:



159
160
161
162
163
164
165
# File 'lib/ach/component/has_many_association.rb', line 159

def container_for_associated
  return container unless linked?

  last_link = @owner.send(linked_to).last
  raise NoLinkError.new(linked_to.to_s.singularize, klass.name) unless last_link
  container[last_link] ||= []
end

#create(*args) ⇒ Object

Create an associated object using common to ACH controls pattern, and push it to an appropriate container. For example, for :items association, this method is aliased to item, so you will have:

item(:code => 'WEB') do
  other_code 'BEW'
  # ...
end

Parameters:

  • args (*Object)

Returns:

  • (Object)

    instance of a class under ACH namespace



115
116
117
118
119
120
121
122
123
124
# File 'lib/ach/component/has_many_association.rb', line 115

def create(*args)
  fields = args.first || {}

  defaults = proc_defaults ? @owner.instance_exec(&proc_defaults) : {}

  klass.new(@owner.fields_for(klass).merge(defaults).merge(fields)).tap do |component|
    component.instance_eval(&Proc.new) if block_given?
    container_for_associated << component
  end
end

#delegation_methodsArray<String>

Return an array of methods to be delegated by owner of the association. For example, for association named :items, it will include:

  • build_item - for instantiating Item from the string (used by parsing functionality)

  • item - for instantiating Item during common ACH File creation

  • items - that returns set of Item objects.

Returns:

  • (Array<String>)


90
91
92
# File 'lib/ach/component/has_many_association.rb', line 90

def delegation_methods
  ["build_#{singular_name}", singular_name, name]
end

#for(owner) ⇒ Object

Clone self and assign owner to clone. Also, for newly created clone association that has owner, aliases main methods so that owner may delegate to them.

Parameters:

Raises:



69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/ach/component/has_many_association.rb', line 69

def for(owner)
  raise DoubleAssignmentError.new(@name, @owner) if @owner

  clone.tap do |association|
    plural, singular = name, singular_name
    association.instance_variable_set('@owner', owner)
    association.singleton_class.class_eval do
      alias_method "build_#{singular}", :build
      alias_method singular, :create
      alias_method plural, :container
    end
  end
end