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

Returns a new instance of HasManyAssociation.



44
45
46
47
# File 'lib/ach/component/has_many_association.rb', line 44

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.



41
42
43
# File 'lib/ach/component/has_many_association.rb', line 41

def name
  @name
end

Instance Method Details

#build(str) ⇒ Object

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



77
78
79
80
# File 'lib/ach/component/has_many_association.rb', line 77

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

#containerObject

Returns 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



103
104
105
# File 'lib/ach/component/has_many_association.rb', line 103

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

#container_for_associatedObject

Returns array for associated object to be pushed in. 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'>]}

Raises:



127
128
129
130
131
132
133
# File 'lib/ach/component/has_many_association.rb', line 127

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, &block) ⇒ Object

Creates associated object using common to ACH controls pattern, and pushes it to 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


89
90
91
92
93
94
95
96
97
98
# File 'lib/ach/component/has_many_association.rb', line 89

def create(*args, &block)
  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(&block) if block
    container_for_associated << component
  end
end

#delegation_methodsObject

Returns 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



71
72
73
# File 'lib/ach/component/has_many_association.rb', line 71

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

#for(owner) ⇒ Object

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



52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/ach/component/has_many_association.rb', line 52

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