lib/fin/models/model.rb in fin-0.1.0 vs lib/fin/models/model.rb in fin-0.1.2
- old
+ new
@@ -1,39 +1,135 @@
module Fin
- # Represents business domain model for a single item (Order, Deal, Instrument, etc...)
- # currently it is only used to extract common functionality from record wrappers,
- # down the road it may be subclassed from ActiveModel
+ # Represents business domain model for a single item (Quote, Deal, Instrument, etc...)
+ # Currently it is only used to extract common functionality from record wrappers,
+ # down the road the goal is to add ActiveModel compatibility
class Model
- def self.prop_reader *args
- args.each do |arg|
- aliases = [arg].flatten
- name = aliases.shift
- instance_eval do
- attr_reader name
- aliases.each { |ali| alias_method "#{ali}", name }
- end
+
+ include Enumerable
+
+ def self.attribute_types
+ @attribute_types ||= superclass.attribute_types.dup rescue {}
+ end
+
+ def self.model_class_id value = nil
+ if value
+ @model_class_id ||= value
+ model_classes[@model_class_id] = self
+ else
+ @model_class_id
end
end
- def self.prop_accessor *args
- args.each do |arg|
+ def self.model_classes
+ @model_classes ||= superclass.model_classes rescue {} #shared list for all subclasses
+ end
+
+ def self.property prop_hash
+ prop_hash.each do |arg, type|
aliases = [arg].flatten
name = aliases.shift
instance_eval do
- attr_accessor name
+
+ attribute_types[name.to_s] = type.to_s
+
+ define_method(name) do
+ @attributes[name]
+ end
+
+ define_method("#{name}=") do |value|
+ @attributes[name] = value
+ end
+
aliases.each do |ali|
alias_method "#{ali}", name
alias_method "#{ali}=", "#{name}="
end
end
end
+
+ # Using static calls, create class method extracting attributes from raw records
+ attribute_extractor = attribute_types.map do |name, type|
+ case type
+ when /^[ct]/ # TODO: In future, read t AsLong and convert into DateTime
+ "rec.GetValAsString('#{name}')"
+ when /^i[14]/
+ "rec.GetValAsLong('#{name}')"
+ when /^i8/
+ "rec.GetValAsString('#{name}').to_i"
+ when /^[df]/
+ "rec.GetValAsString('#{name}').to_f"
+ else
+ raise "Unrecognized attribute type: #{name} => #{type}"
+ end
+ end.join(",\n")
+
+ extractor_body = "def self.extract_attributes rec
+ [#{attribute_extractor}]
+ end"
+
+# puts "In #{self}:, #{extractor_body"
+ instance_eval extractor_body
end
- def initialize opts = {}
- opts.each { |key, value| send "#{key}=", value }
+ def self.from_record rec
+ new *extract_attributes(rec)
end
+ # Unpacks attributes into appropriate Model subclass
+ def self.from_msg msg
+ class_id = msg.first
+ model_classes[class_id].new *msg[1..-1]
+ end
+
+ # Extracts attributes from record into a serializable format (Array)
+ # Returns an Array where 1st element is a model_class_id of our Model subclass,
+ # and second element is a list of arguments to its initialize. Class method!
+ def self.to_msg rec
+ extract_attributes(rec).unshift(model_class_id)
+ end
+
+ # Converts OBJECT attributes into a serializable format (Array)
+ # Returns an Array where 1st element is a model_class_id of our Model subclass,
+ # and second element is a list of arguments to its initialize. Instance method!
+ def to_msg
+ inject([self.class.model_class_id]) { |array, (name, _)| array << send(name) }
+ end
+
+ # TODO: Builder pattern, to avoid args Array creation on each initialize?
+ def initialize *args
+ @attributes = {}
+ opts = args.last.is_a?(Hash) ? args.pop : {}
+ each_with_index { |(name, _), i| send "#{name}=", args[i] } unless args.empty?
+ opts.each { |name, value| send "#{name}=", value }
+ end
+
+ def each
+ if block_given?
+ self.class.attribute_types.each { |name, _| yield name, send(name) }
+ else
+ self.class.attribute_types.map { |name, _| [name, send(name)].to_enum }
+ end
+ end
+
+ alias each_property each
+
+ def inspect divider=','
+ map { |property, value| "#{property}=#{value}" }.join(divider)
+ end
+
+ # TODO: DRY principle: there should be one authoritative source for everything...
+ # TODO: Should such source be schema file, or Model code?
+ # TODO: Maybe, Model should just read from schema file at init time?
+
+ # All P2 records carry these properties
+ property [:replID, :repl_id] => :i8,
+ [:replRev, :repl_rev, :rev] => :i8,
+ [:replAct, :repl_act] => :i8
+
def index
object_id # TODO: @repl_id?
end
+
+ # Fin::Model itself has model_class_id 0
+ model_class_id 0
end
end