wherever-positions 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ module DbStore
2
+ module RecordMatcher
3
+ def ==(record)
4
+ self.clean_attributes == record.clean_attributes
5
+ end
6
+
7
+ protected
8
+ def clean_attributes
9
+ att = self.attributes.clone
10
+ att.delete('_id')
11
+ att
12
+ end
13
+
14
+ def clean_attributes_a
15
+ clean_hash(self.attributes)
16
+ end
17
+
18
+ def clean_hash(values)
19
+ res = {}
20
+ values.each do |k, v|
21
+ if k == :_id
22
+ res[k.to_s] = v.is_a?(Hash) ? clean_hash(v) : v
23
+ end
24
+ end
25
+ res
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ module DbStore
2
+ class Store
3
+ include Mongoid::Document
4
+ embedded_in :marker, :class_name => 'DbStore::Marker'
5
+ embeds_many :datasets, :class_name => 'DbStore::Dataset'
6
+ embeds_many :identifiers, :class_name => 'DbStore::Identifier'
7
+ field :key, :type => Array
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module DbStore
2
+ end
3
+
4
+ require 'wherever/db_store/record_matcher'
5
+ require 'wherever/db_store/marker'
6
+ require 'wherever/db_store/store'
7
+ require 'wherever/db_store/dataset'
8
+ require 'wherever/db_store/identifier'
9
+
10
+ require 'wherever/db_store/lookup'
@@ -0,0 +1,30 @@
1
+ class Wherever
2
+ module Accessors
3
+ def get_key_store(*keys)
4
+ mark = keys.last.is_a?(Hash) ? keys.pop["marker"] : marker
5
+ collection(mark).stores.find_or_create_by(:key => keys)
6
+ end
7
+
8
+ def collection(mark=marker)
9
+ DbStore::Marker.find_by_name(mark)
10
+ end
11
+
12
+ def identifier_set
13
+ get_key_store("identifier")
14
+ end
15
+
16
+ def unique_set
17
+ get_key_store("unique")
18
+ end
19
+
20
+ def identifier_key
21
+ key = version_key
22
+ key.delete("version")
23
+ key
24
+ end
25
+
26
+ def version_key
27
+ {}.merge(@options["unique"]).merge(@options["keys"])
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,71 @@
1
+ class Wherever
2
+ module Adder
3
+ def add(values, options)
4
+ @options = options
5
+ if record = find
6
+ update(record.diff(values), record)
7
+ else
8
+ create(values)
9
+ end
10
+ end
11
+
12
+ def find
13
+ identifier_set.datasets.where(identifier_key).first
14
+ end
15
+
16
+ def create(values)
17
+ for_unique(values)
18
+ id_record = create_for_identifier(values)
19
+ config.key_groups.each do |group|
20
+ for_group(group, values, id_record)
21
+ end
22
+ end
23
+
24
+ def update(diff, record)
25
+ for_unique(diff)
26
+ id_record = update_for_identifier(diff, record)
27
+ config.key_groups.each do |group|
28
+ for_group(group, diff, id_record)
29
+ end
30
+ end
31
+
32
+ def create_for_identifier(values)
33
+ record = identifier_set.datasets.create(version_key)
34
+ update_record(record, values, record)
35
+ end
36
+
37
+ def update_for_identifier(diff, record)
38
+ record["version"] = @options["unique"]["version"]
39
+ update_record(record, diff, record)
40
+ end
41
+
42
+ def update_record(record, data, id_record, keys=config.keys)
43
+ @grouping.call(record.values, data.clone, id_record, keys)
44
+ record.save!
45
+ record
46
+ end
47
+
48
+ def for_unique(values)
49
+ unique_set.datasets.create(version_key.merge("values" => values.clone))
50
+ end
51
+
52
+ def for_group(group_keys, values, id_record)
53
+ key = {}
54
+ group_keys.each do |key_values|
55
+ k_id = "#{key_values}_id"
56
+ key.merge!(k_id => @options["keys"][k_id])
57
+ end
58
+ store = get_key_store(*group_keys)
59
+ record = store.datasets.find_or_create_by(key)
60
+
61
+ update_record(record, values, id_record, group_keys)
62
+ update_identifier(store, @options["unique"])
63
+ end
64
+
65
+ def update_identifier(store, unique)
66
+ record = store.identifiers.find_or_create_by(config._id => unique[config._id])
67
+ record["version"] = unique["version"]
68
+ record.save
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,31 @@
1
+ class Wherever
2
+ module Getter
3
+ def get(selector, mark=marker)
4
+ keys = get_keys_from(selector) + [{"marker" => mark}]
5
+ result = Hash.new(0)
6
+ if keys.first == "identifier"
7
+ get_key_store(*keys).datasets.where(selector).all.each do |record|
8
+ @grouping.call(result, record.values, result, config)
9
+ end
10
+ result
11
+ else
12
+ get_key_store(*keys).datasets.where(selector).first.try(:values) || {}
13
+ end
14
+ end
15
+
16
+ protected
17
+ def get_keys_from(selector)
18
+ check_keys(selector.keys)
19
+ keys = selector.keys.map{|key| key.gsub(/_id$/, '') }
20
+ config.key_groups.each do |group|
21
+ return group if (group & keys) == group && (keys & group) == keys
22
+ end
23
+ ["identifier"]
24
+ end
25
+
26
+ def check_keys(keys)
27
+ invalid_keys = (keys - config.keys)
28
+ raise InvalidSelector,"Unknown Selector: #{invalid_keys.join(' ')}" unless invalid_keys.empty?
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,66 @@
1
+ class Wherever
2
+ module Lookup
3
+ def create_lookup(name, keys)
4
+ lookup = DbStore::Lookup.find_or_create_by(:name => name)
5
+ lookup.keys = keys
6
+ lookup.save
7
+
8
+ self.class.class_eval do
9
+ define_method "set_#{name}" do |version, values|
10
+ lookup, record = create_lookup_record(name, version)
11
+ record.values = key_to_string(values)
12
+ record.save
13
+ recalculate if set_price_lookup(version)
14
+ end
15
+ end
16
+
17
+ self.class.class_eval do
18
+ define_method "get_#{name}" do |version, data|
19
+ lookup, record = get_lookup_record(name, version)
20
+ value_key = lookup.keys.map{|key| data[key]}.join('_')
21
+ record.values[value_key] || 0
22
+ end
23
+ end
24
+ end
25
+
26
+ protected
27
+ def key_to_string(values)
28
+ string_values = {}
29
+ values.each {|k, v| string_values[Array[k].join('_')] = v}
30
+ string_values
31
+ end
32
+
33
+ def set_price_lookup(version)
34
+ return false if collection.price == version
35
+ collection.update_attributes(:price => version)
36
+ end
37
+
38
+ def recalculate
39
+ config.key_groups.each do |group|
40
+ get_key_store(*group).datasets.delete_all
41
+ end
42
+
43
+ identifier_set.datasets.all.each do |record|
44
+ @grouping.call(record.values, nil, record, config.keys)
45
+ record.save!
46
+ config.key_groups.each do |group|
47
+ for_group(group, record.values, record)
48
+ end
49
+ end
50
+ end
51
+
52
+ def get_lookup_record(name, version)
53
+ lookup = DbStore::Lookup.where(:name => name).first
54
+ raise InvalidLookup, "Attempt to access invalid lookup: #{name}" unless lookup
55
+ return [lookup, lookup.versions.find_or_create_by(:name => version)]
56
+ end
57
+
58
+ def create_lookup_record(name, version)
59
+ lookup = DbStore::Lookup.where(:name => name).first
60
+ raise InvalidLookup, "Attempt to access invalid lookup: #{name}" unless lookup
61
+ records = lookup.versions.where(:name => version)
62
+ raise InvalidLookupSetter, "Lookup '#{version}' for '#{name}' already set" unless records.empty?
63
+ return [lookup, lookup.versions.find_or_create_by(:name => version)]
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,11 @@
1
+ class Wherever
2
+ module Mark
3
+ def mark(name)
4
+ marker = collection(name)
5
+ marker.price = collection.price
6
+ collection.stores.all.each do |store|
7
+ marker.stores.create(:key => store.key, :datasets => store.datasets.clone) unless store.key == ["unique"]
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ class InvalidSelector < StandardError; end
2
+ class InvalidLookup < StandardError; end
3
+ class InvalidLookupSetter < StandardError; end
4
+
5
+ class Wherever
6
+ include Accessors
7
+ include Adder
8
+ include Getter
9
+ include Lookup
10
+ include Mark
11
+ attr_reader :config, :marker
12
+
13
+ def initialize(options={}, &grouping)
14
+ @config = Configure.new(options)
15
+ @marker = options[:marker] || 'current'
16
+ if block_given?
17
+ @grouping = grouping
18
+ else
19
+ @grouping = lambda do |values, data, record, keys|
20
+ data.keys.each do |key|
21
+ values[key] += data[key]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
data/lib/wherever.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'mongoid'
2
+ require 'wherever/wherever/accessors'
3
+ require 'wherever/wherever/adder'
4
+ require 'wherever/wherever/getter'
5
+ require 'wherever/wherever/lookup'
6
+ require 'wherever/wherever/mark'
7
+ require 'wherever/wherever'
8
+ require 'wherever/configure'
9
+ require 'wherever/wherever/adder'
10
+ require 'wherever/db_store'
@@ -0,0 +1,21 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'wherever'
5
+ require 'ruby-debug'
6
+ require 'database_cleaner'
7
+
8
+ Wherever.new("keys" => ["fund_id"], "database" => 'wherever_test')
9
+
10
+ # Requires supporting files with custom matchers and macros, etc,
11
+ # in ./support/ and its subdirectories.
12
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
13
+
14
+ RSpec.configure do |config|
15
+ # config.treat_symbols_as_metadata_keys_with_true_values = true
16
+ config.before(:each) do
17
+ DatabaseCleaner.orm = "mongoid"
18
+ DatabaseCleaner.strategy = :truncation
19
+ DatabaseCleaner.clean
20
+ end
21
+ end
@@ -0,0 +1,121 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Wherever" do
4
+ let(:wherever) { Wherever.new("keys" => keys, "database" => 'wherever_test', "key_groups" => key_groups, "key" => "trade_id") }
5
+ let(:keys) { ["fund_id"] }
6
+ let(:key_groups) { nil }
7
+
8
+ context 'adding the first record' do
9
+ let(:options) { {"unique" => {"trade_id" => 12, "version" => 1}, "keys" => {"fund_id" => 2}} }
10
+ context 'the unique dataset' do
11
+ it 'inserts a record' do
12
+ wherever.add({"settled" => 100, "unsettled" => 0}, options)
13
+ wherever.get_key_store("unique").datasets.should ==
14
+ [DbStore::Dataset.new("trade_id" => 12, "values" => {"unsettled" => 0, "settled" => 100}, "version" => 1, "fund_id" => 2)]
15
+ end
16
+ end
17
+
18
+ context 'the id dataset' do
19
+ it 'inserts a record' do
20
+ wherever.add({"settled" => 100, "unsettled" => 0}, options)
21
+ wherever.get_key_store("identifier").datasets.should ==
22
+ [DbStore::Dataset.new("trade_id" => 12, "values" => {"unsettled" => 0, "settled" => 100}, "version" => 1, "fund_id" => 2)]
23
+ end
24
+ end
25
+
26
+ context 'the key datasets' do
27
+ context 'with a single key' do
28
+ it 'inserts a record' do
29
+ wherever.add({"settled" => 100, "unsettled" => 0}, options)
30
+ wherever.get_key_store("fund").datasets.should ==
31
+ [DbStore::Dataset.new("values" => {"unsettled" => 0, "settled" => 100}, "fund_id" => 2)]
32
+ wherever.get_key_store("fund").identifiers.should ==
33
+ [DbStore::Identifier.new("trade_id" => 12, "version" => 1)]
34
+ end
35
+ end
36
+
37
+ context 'with a multiple keys' do
38
+ let(:keys) { ["fund_id", "security_id"] }
39
+ let(:options) { {"unique" => {"trade_id" => 12, "version" => 1}, "keys" => {"fund_id" => 2, "security_id" => 4}} }
40
+
41
+ it 'inserts a record for each key combination' do
42
+ wherever.add({"settled" => 100, "unsettled" => 0}, options)
43
+
44
+ wherever.get_key_store("fund").datasets.should ==
45
+ [DbStore::Dataset.new("values" => {"unsettled" => 0, "settled" => 100}, "fund_id" => 2)]
46
+ wherever.get_key_store("fund").identifiers.should ==
47
+ [DbStore::Identifier.new("trade_id" => 12, "version" => 1)]
48
+
49
+ wherever.get_key_store("security").datasets.should ==
50
+ [DbStore::Dataset.new("values" => {"unsettled" => 0, "settled" => 100}, "security_id" => 4)]
51
+ wherever.get_key_store("security").identifiers.should ==
52
+ [DbStore::Identifier.new("trade_id" => 12, "version" => 1)]
53
+ end
54
+ end
55
+
56
+ context 'with a multiple keys and grouping configured' do
57
+ let(:keys) { ["fund_id", "security_id"] }
58
+ let(:key_groups) { ["fund", "security", ["fund", "security"]] }
59
+ let(:options) { {"unique" => {"trade_id" => 12, "version" => 1}, "keys" => {"fund_id" => 2, "security_id" => 4}} }
60
+
61
+ it 'inserts a record for each key combination' do
62
+ wherever.add({"settled" => 100, "unsettled" => 0}, options)
63
+
64
+ wherever.get_key_store("fund").datasets.should ==
65
+ [DbStore::Dataset.new("values" => {"unsettled" => 0, "settled" => 100}, "fund_id" => 2)]
66
+ wherever.get_key_store("fund").identifiers.should ==
67
+ [DbStore::Identifier.new("trade_id" => 12, "version" => 1)]
68
+
69
+ wherever.get_key_store("security").datasets.should ==
70
+ [DbStore::Dataset.new("values" => {"unsettled" => 0, "settled" => 100}, "security_id" => 4)]
71
+ wherever.get_key_store("security").identifiers.should ==
72
+ [DbStore::Identifier.new("trade_id" => 12, "version" => 1)]
73
+
74
+ wherever.get_key_store("fund", "security").datasets.should ==
75
+ [DbStore::Dataset.new("fund_id" => 2, "security_id" => 4, "values" => {"unsettled" => 0, "settled" => 100})]
76
+ wherever.get_key_store("fund", "security").identifiers.should ==
77
+ [DbStore::Identifier.new("trade_id" => 12, "version" => 1)]
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'adding subsequent records' do
84
+ let(:options_first) { {"unique" => {"trade_id" => 12, "version" => 1}, "keys" => {"fund_id" => 2}} }
85
+ let(:options_second) { {"unique" => {"trade_id" => 12, "version" => 2}, "keys" => {"fund_id" => 2}} }
86
+ before do
87
+ wherever.add({"settled" => 100, "unsettled" => 0}, options_first)
88
+ end
89
+
90
+ context 'the unique dataset' do
91
+ it 'add the change' do
92
+ wherever.add({"settled" => 110, "unsettled" => 0}, options_second)
93
+
94
+ wherever.get_key_store("unique").datasets.should ==
95
+ [DbStore::Dataset.new("fund_id" => 2, "trade_id" => 12, "version" => 1, "values" => {"unsettled" => 0, "settled" => 100}),
96
+ DbStore::Dataset.new("fund_id" => 2, "trade_id" => 12, "version" => 2, "values" => {"unsettled" => 0, "settled" => 10})]
97
+ end
98
+ end
99
+
100
+ context 'the identifier dataset' do
101
+ it 'updates the record' do
102
+ wherever.add({"settled" => 110, "unsettled" => 0}, options_second)
103
+
104
+ wherever.get_key_store("identifier").datasets.should ==
105
+ [DbStore::Dataset.new("fund_id" => 2, "trade_id" => 12, "version" => 2, "values" => {"unsettled" => 0, "settled" => 110})]
106
+ end
107
+ end
108
+
109
+ context 'the key datasets' do
110
+ context 'with a single key' do
111
+ it 'inserts a record' do
112
+ wherever.add({"settled" => 110, "unsettled" => 0}, options_second)
113
+ wherever.get_key_store("fund").datasets.should ==
114
+ [DbStore::Dataset.new("values" => {"unsettled" => 0, "settled" => 110}, "fund_id" => 2)]
115
+ wherever.get_key_store("fund").identifiers.should ==
116
+ [DbStore::Identifier.new("trade_id" => 12, "version" => 2)]
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end