wherever-positions 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|