Class: Fix::Protocol::MessagePart

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/fix/protocol/message_part.rb

Overview

Basic building block for messages. Message parts can define fields, sub-parts and collections

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ MessagePart

Returns a new instance of MessagePart.



16
17
18
19
# File 'lib/fix/protocol/message_part.rb', line 16

def initialize(opts = {})
  self.name = opts[:name]
  self.class.structure.each { |node| initialize_node(node) }
end

Instance Attribute Details

#delegationsObject

Returns the value of attribute delegations.



14
15
16
# File 'lib/fix/protocol/message_part.rb', line 14

def delegations
  @delegations
end

#nameObject

Returns the value of attribute name.



14
15
16
# File 'lib/fix/protocol/message_part.rb', line 14

def name
  @name
end

#parse_failureObject

Returns the value of attribute parse_failure.



14
15
16
# File 'lib/fix/protocol/message_part.rb', line 14

def parse_failure
  @parse_failure
end

Class Method Details

.collection(name, opts = {}) ⇒ Object

Defines a collection as a part of this message part, collections typically have a counter and a repeating element

Parameters:

  • name (String)

    The collection name, this will be the name of a dynamically created accessor on the message part

  • opts (Hash) (defaults to: {})

    The required options are :counter_tag and :klass



115
116
117
118
119
120
121
122
123
# File 'lib/fix/protocol/message_part.rb', line 115

def self.collection(name, opts = {})
  structure << { node_type: :collection, name: name, counter_tag: opts[:counter_tag], klass: opts[:klass] }

  define_method(name) do
    node_for_name(name)
  end

  parent_delegate(name)
end

.field(name, opts) ⇒ Object

Defines a field as part of the structure for this class

Parameters:

  • name (String)

    The field name, this will be the base name of a set of methods created on the class instances

  • opts (Hash)

    Options hash



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/fix/protocol/message_part.rb', line 177

def self.field(name, opts)
  structure << { node_type: :field, name: name }.merge(opts)

  # Getter
  define_method(name) do
    node_for_name(name).value
  end

  # Setter
  define_method("#{name}=") do |val|
    node_for_name(name).value = val
  end

  if opts[:mapping]
    define_method("raw_#{name}") do
      node_for_name(name).raw_value
    end

    define_method("raw_#{name}=") do |val|
      node_for_name(name).raw_value = val
    end
  end

  # Delegate getter and setter from parent node
  parent_delegate(name, "#{name}=") 
end

.inherited(klass) ⇒ Object

Inherits the @structure class instance variable but allows the child to modify it without impacting the parent, this allows for message structure refinement



26
27
28
# File 'lib/fix/protocol/message_part.rb', line 26

def self.inherited(klass)
  klass.send(:instance_variable_set, :@structure, structure.dup)
end

.parent_delegate(*args) ⇒ Object

Defines the attributes that should be accessible from the parent node

Parameters:

  • args (Array<Symbol>)

    The methods that the parent should respond to and delegate to the child



104
105
106
107
# File 'lib/fix/protocol/message_part.rb', line 104

def self.parent_delegate(*args)
  @delegations ||= []
  args.each { |a| @delegations << a }
end

.parse(str) ⇒ Object

Class-level shortcut to directly parse an instance of a specific message part subclass



93
94
95
96
97
# File 'lib/fix/protocol/message_part.rb', line 93

def self.parse(str)
  instce = new
  instce.parse(str)
  instce
end

.part(name, opts = {}, &block) ⇒ Object

Defines a reusable message part as element of this particular class

Parameters:

  • name (String)

    The part name, this will be the name of a dynamically created accessor on the message part

  • opts (Hash) (defaults to: {})

    Options hash



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/fix/protocol/message_part.rb', line 131

def self.part(name, opts = {}, &block)
  options = { node_type: :part, name: name, delegate: true }.merge(opts)
  klass   = options[:klass]

  # If a block is given it overrides the +:klass+ option
  if block_given?
    names = (to_s + name.to_s.split(/\_/).map(&:capitalize).join).split('::')
    klass = names.pop
    parent_klass = (options[:node_type] == :part) ? MessagePart : UnorderedPart
    klass = names.inject(Object) { |mem, obj| mem = mem.const_get(obj) }.const_set(klass, Class.new(parent_klass))
    klass.instance_eval(&block)
    options.merge!({ klass: klass })
  elsif options[:klass]
    parent_delegate(name)
  end

  # Do we need to delegate some methods from the parent node ?
  delegations = klass.instance_variable_get(:@delegations)
  if delegations && !delegations.empty? && options[:delegate]
    def_delegators(name, *delegations)
    parent_delegate(*delegations)
  end

  structure << options.merge(opts)

  define_method(name) do
    node_for_name(name)
  end
end

.structureArray

Returns this message part class’ structure

Returns:

  • (Array)

    The message structure



209
210
211
# File 'lib/fix/protocol/message_part.rb', line 209

def self.structure
  @structure ||= []
end

.unordered(name, opts = {}, &block) ⇒ Object

Defines an unordered fields collection

Parameters:

  • name (String)

    The part name, this will be the name of a dynamically created accessor on the message part

  • opts (Hash) (defaults to: {})

    Options hash



167
168
169
# File 'lib/fix/protocol/message_part.rb', line 167

def self.unordered(name, opts = {}, &block)
  part(name, opts.merge({ node_type: :unordered }), &block)
end

Instance Method Details

#dumpString

Dumps this message part as a FIX message fragment

Returns:

  • (String)

    A FIX message fragment



35
36
37
# File 'lib/fix/protocol/message_part.rb', line 35

def dump
  nodes.map(&:dump).join
end

#errorsArray

Returns the errors for this instance

Returns:

  • (Array)

     The errors for this instance



218
219
220
# File 'lib/fix/protocol/message_part.rb', line 218

def errors
  [nodes.map(&:errors), nodes.map(&:parse_failure)].flatten.compact
end

#initialize_node(node) ⇒ Object

Initializes a node depending on its type, this is called when initializing a message part instance by the constructor. Usually one node is initialized for each structure element



61
62
63
64
65
66
67
68
69
# File 'lib/fix/protocol/message_part.rb', line 61

def initialize_node(node)
  if [:unordered, :part].include?(node[:node_type])
    nodes << node[:klass].new(node)
  elsif node[:node_type] == :field
    nodes << FP::Field.new(node)
  elsif node[:node_type] == :collection
    nodes << FP::RepeatingMessagePart.new(node)
  end
end

#node_for_name(n) ⇒ Object

Searches the immediate hierarchy by node name to return the requested node

Parameters:

  • n (String)

    The node name to look for

Returns:

  • (Object)

    The found node, if any



86
87
88
# File 'lib/fix/protocol/message_part.rb', line 86

def node_for_name(n)
  nodes.find { |node| node.name.to_s == n.to_s }
end

#nodesArray

The message part nodes, they’ll be either a FP::Field, an FP::RepeatingMessagePart or a FP::MessagePart

Returns:

  • (Array)

    The nodes for this message part



76
77
78
# File 'lib/fix/protocol/message_part.rb', line 76

def nodes
  @nodes ||= []
end

#parse(str) ⇒ String

Parses a full or partial FIX message string into the message part nodes

Returns:

  • (String)

    The string part that wasn’t consumed during the parsing



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/fix/protocol/message_part.rb', line 44

def parse(str)
  left_to_parse = str

  nodes.each do |node|
    unless parse_failure
      left_to_parse = node.parse(left_to_parse)
      self.parse_failure = node.parse_failure
    end
  end

  left_to_parse
end