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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +71 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +143 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/features/step_definitions/wherever_steps.rb +14 -0
- data/features/support/env.rb +13 -0
- data/features/wherever.feature +18 -0
- data/lib/wherever/configure.rb +20 -0
- data/lib/wherever/db_store/dataset.rb +20 -0
- data/lib/wherever/db_store/identifier.rb +10 -0
- data/lib/wherever/db_store/lookup.rb +15 -0
- data/lib/wherever/db_store/marker.rb +12 -0
- data/lib/wherever/db_store/record_matcher.rb +28 -0
- data/lib/wherever/db_store/store.rb +9 -0
- data/lib/wherever/db_store.rb +10 -0
- data/lib/wherever/wherever/accessors.rb +30 -0
- data/lib/wherever/wherever/adder.rb +71 -0
- data/lib/wherever/wherever/getter.rb +31 -0
- data/lib/wherever/wherever/lookup.rb +66 -0
- data/lib/wherever/wherever/mark.rb +11 -0
- data/lib/wherever/wherever.rb +26 -0
- data/lib/wherever.rb +10 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/wherever/adder_spec.rb +121 -0
- data/spec/wherever/custom_grouping_spec.rb +51 -0
- data/spec/wherever/getter_spec.rb +47 -0
- data/spec/wherever/key_store_spec.rb +23 -0
- data/spec/wherever/mark_spec.rb +51 -0
- data/spec/wherever/setter_spec.rb +24 -0
- data/spec/wherever/using_lookup_in_grouping_spec.rb +101 -0
- data/wherever.gemspec +98 -0
- metadata +234 -0
@@ -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,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'
|
data/spec/spec_helper.rb
ADDED
@@ -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
|