light_store 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +9 -14
- data/lib/light_store.rb +2 -0
- data/lib/light_store/class_accessor_methods.rb +51 -0
- data/lib/light_store/class_methods.rb +65 -162
- data/lib/light_store/configuration.rb +9 -0
- data/lib/light_store/version.rb +1 -1
- data/light_store.gemspec +2 -0
- data/spec/class_accessor_methods_spec.rb +79 -0
- data/spec/class_methods_spec.rb +58 -0
- metadata +49 -28
- data/spec/light_store_spec.rb +0 -299
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bb309c6ef19cbc223dae997c56c151c239eed2bc
|
4
|
+
data.tar.gz: 1602d8998fd1aa3eee4b0dc530f36d8588ee6f42
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e203288dcb0e5740f2471801116e19b840fa9b1f75d20aa38920289216611020314de90088be18800dfd2e82897fee1be1b36277db685e7f4bf8d2991f024f97
|
7
|
+
data.tar.gz: 4fd957efae0dc08162dbcb8c83da8447f1788bfb80aba877904e27884d64d2f4e62a87cee53e6b24e6b4c7436405cb5defca5d8dd199751a0a00499c096551df
|
data/README.md
CHANGED
@@ -2,15 +2,15 @@
|
|
2
2
|
|
3
3
|
A library for storing data about an object in spreadsheet-like format (array of hashes).
|
4
4
|
|
5
|
-
This library aims to provide an easy way to store data about objects. Data that is typically generated with complex queries could be stored in a flat format. This is meant to be a general purpose library, but it was created to improve performance of dynamic report generators. This comes from an idea that each 'row' of report data could be identified by the object id and a
|
5
|
+
This library aims to provide an easy way to store data about objects. Data that is typically generated with complex queries could be stored in a flat format. This is meant to be a general purpose library, but it was created to improve performance of dynamic report generators. This comes from an idea that each 'row' of report data could be identified by the object id and a date constraint relevant to the report.
|
6
6
|
|
7
7
|
Data format example: `[{account_id: 1, month: '2014-11', revenue: 987.65}, {account_id: 1, month: '2014-12', revenue: 1234.56}]`
|
8
8
|
|
9
|
-
In the above example `account_id:` is a primary
|
9
|
+
In the above example `account_id:` is a primary key and `month:` is a date constraint field.
|
10
10
|
|
11
11
|
## Installation
|
12
12
|
|
13
|
-
This gem relies on redis for storage.
|
13
|
+
This gem relies on [redis](http://redis.io/) for storage. This gem depends on [redis-rb](https://github.com/redis/redis-rb) gem. Please be sure that it's installed and working correctly.
|
14
14
|
|
15
15
|
Add this line to your application's Gemfile:
|
16
16
|
|
@@ -34,13 +34,8 @@ Define a class:
|
|
34
34
|
|
35
35
|
```ruby
|
36
36
|
class RevenueReport < LightStore::Data
|
37
|
-
|
38
|
-
|
39
|
-
set_secondary_key :month # required
|
40
|
-
|
41
|
-
set_sortable_field :month, :datetime
|
42
|
-
set_sortable_field :revenue, :float
|
43
|
-
set_sortable_field :number_of_orders, :integer
|
37
|
+
primary_field :id
|
38
|
+
date_constraint_field :month # required
|
44
39
|
end
|
45
40
|
```
|
46
41
|
|
@@ -67,16 +62,16 @@ data = RevenueReport.get_data()
|
|
67
62
|
|
68
63
|
Getting data for a given id:
|
69
64
|
```ruby
|
70
|
-
data = RevenueReport.get_data(2)
|
65
|
+
data = RevenueReport.get_data(primary_key: 2)
|
71
66
|
```
|
72
67
|
|
73
68
|
Getting data for a given id within range:
|
74
69
|
```ruby
|
75
|
-
data = RevenueReport.get_sorted_data(2, :
|
76
|
-
# or
|
77
|
-
data = RevenueReport.get_sorted_data(2, :number_of_orders, 20, 30)
|
70
|
+
data = RevenueReport.get_sorted_data(primary_key: 2, date_range: ['2012-02', '2012-04'])
|
78
71
|
```
|
79
72
|
|
73
|
+
Running `RevenueReport.add_data(data)` for the same data will not result in duplication. The dataset passed to `light_store` instance could be the same or slightly different, it doesn't matter. All records will be handled correctly. `LightStore` will generate a key that combines primary key and date constraint field. Data corresponding to that record will be either created or updated.
|
74
|
+
|
80
75
|
|
81
76
|
## Contributing
|
82
77
|
|
data/lib/light_store.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "light_store/version"
|
2
2
|
require "light_store/configuration"
|
3
3
|
require "light_store/class_methods"
|
4
|
+
require "light_store/class_accessor_methods"
|
4
5
|
|
5
6
|
module LightStore
|
6
7
|
|
@@ -15,6 +16,7 @@ module LightStore
|
|
15
16
|
class << self
|
16
17
|
def included(base)
|
17
18
|
base.extend ClassMethods
|
19
|
+
base.extend ClassAccessorMethods
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module LightStore
|
2
|
+
module ClassAccessorMethods
|
3
|
+
def base_name
|
4
|
+
"LightStore:#{self.name}"
|
5
|
+
end
|
6
|
+
|
7
|
+
def primary_field(x = nil)
|
8
|
+
@primary_field = x unless x.nil?
|
9
|
+
raise ArgumentError, 'primary_field must be set' unless @primary_field
|
10
|
+
@primary_field
|
11
|
+
end
|
12
|
+
|
13
|
+
def date_constraint_field(x = nil)
|
14
|
+
@date_constraint_field = x if x
|
15
|
+
raise ArgumentError, 'date_constraint_field must be set' unless @date_constraint_field
|
16
|
+
@date_constraint_field
|
17
|
+
end
|
18
|
+
|
19
|
+
def row=(h)
|
20
|
+
@row = h
|
21
|
+
end
|
22
|
+
|
23
|
+
def row
|
24
|
+
@row
|
25
|
+
end
|
26
|
+
|
27
|
+
def primary_key
|
28
|
+
row[primary_field]
|
29
|
+
end
|
30
|
+
|
31
|
+
def secondary_key
|
32
|
+
Time.parse(row[date_constraint_field].to_s).to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
def row_key
|
36
|
+
"#{base_name}:row:#{primary_key}:#{secondary_key}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def row_reference_key
|
40
|
+
"#{base_name}:key:#{primary_key}:#{secondary_key}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def primary_range_key(x)
|
44
|
+
"#{base_name}:primary_range:#{x}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def range_key
|
48
|
+
"#{base_name}:range"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -4,51 +4,6 @@ module LightStore
|
|
4
4
|
LightStore.configuration.redis
|
5
5
|
end
|
6
6
|
|
7
|
-
def set_namespace(x)
|
8
|
-
@namespace = x
|
9
|
-
end
|
10
|
-
|
11
|
-
def namespace
|
12
|
-
@namespace ||= 'light_store'
|
13
|
-
end
|
14
|
-
|
15
|
-
def set_prefix(x)
|
16
|
-
@prefix = x
|
17
|
-
end
|
18
|
-
|
19
|
-
def prefix
|
20
|
-
@prefix ||= self.name
|
21
|
-
end
|
22
|
-
|
23
|
-
def set_primary_key(x)
|
24
|
-
@primary_key = x
|
25
|
-
end
|
26
|
-
|
27
|
-
def primary_key
|
28
|
-
@primary_key ||= :id
|
29
|
-
end
|
30
|
-
|
31
|
-
def set_secondary_key(x)
|
32
|
-
@secondary_key = x
|
33
|
-
end
|
34
|
-
|
35
|
-
def secondary_key
|
36
|
-
raise ArgumentError, 'secondary_key must be set' unless @secondary_key
|
37
|
-
@secondary_key
|
38
|
-
end
|
39
|
-
|
40
|
-
def set_sortable_field(field_name, field_type = :integer)
|
41
|
-
allowed_field_types = [:integer, :float, :datetime]
|
42
|
-
raise ArgumentError, 'field_type must be [:integer, :float, :datetime]' unless allowed_field_types.include?(field_type)
|
43
|
-
h = {field_name: field_name, field_type: field_type}
|
44
|
-
@sortable_fields = self.sortable_fields
|
45
|
-
@sortable_fields.push(h) unless @sortable_fields.include?(h)
|
46
|
-
end
|
47
|
-
|
48
|
-
def sortable_fields
|
49
|
-
@sortable_fields ||= []
|
50
|
-
end
|
51
|
-
|
52
7
|
def marshal(h)
|
53
8
|
Marshal.dump(h)
|
54
9
|
end
|
@@ -57,149 +12,97 @@ module LightStore
|
|
57
12
|
Marshal.load(h)
|
58
13
|
end
|
59
14
|
|
60
|
-
def
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
end
|
67
|
-
|
68
|
-
def base_member_ids_key
|
69
|
-
"#{base_key}:member_ids"
|
70
|
-
end
|
71
|
-
|
72
|
-
def base_sorted_member_ids_key
|
73
|
-
"#{base_key}:sorted_member_ids"
|
74
|
-
end
|
75
|
-
|
76
|
-
def get_primary_id(h)
|
77
|
-
raise ArgumentError, 'primary_id must be set' unless h[primary_key]
|
78
|
-
h[primary_key]
|
79
|
-
end
|
80
|
-
|
81
|
-
def get_secondary_id(h)
|
82
|
-
raise ArgumentError, 'secondary_key must be set' unless h[secondary_key]
|
83
|
-
h[secondary_key]
|
84
|
-
end
|
85
|
-
|
86
|
-
def get_data_row_key(h)
|
87
|
-
"#{base_data_key}:#{get_primary_id(h)}:#{get_secondary_id(h)}"
|
88
|
-
end
|
89
|
-
|
90
|
-
def get_member_id_key(h)
|
91
|
-
"#{base_member_ids_key}:#{get_primary_id(h)}"
|
92
|
-
end
|
93
|
-
|
94
|
-
def get_sorted_member_id_key(_primary_id, _sortable_field)
|
95
|
-
"#{base_sorted_member_ids_key}:#{_primary_id}:#{_sortable_field}"
|
96
|
-
end
|
97
|
-
|
98
|
-
def get_score(value, sortable_field_type)
|
99
|
-
raise ArgumentError, "value for #{sortable_field_type} must be set" unless value
|
100
|
-
case sortable_field_type
|
101
|
-
when :integer
|
102
|
-
value.to_i
|
103
|
-
when :float
|
104
|
-
value.to_f
|
105
|
-
when :datetime
|
106
|
-
if value.is_a? Time
|
107
|
-
value.to_i
|
108
|
-
else
|
109
|
-
Time.parse(value).to_i
|
110
|
-
end
|
15
|
+
def time_to_integer(t)
|
16
|
+
case t
|
17
|
+
when Integer, String
|
18
|
+
t = Time.new(t)
|
19
|
+
when Date, Time
|
20
|
+
# do nothing
|
111
21
|
else
|
112
|
-
raise ArgumentError, "
|
22
|
+
raise ArgumentError, "#{t.inspect}: #{t.class.name} should be string,integer,date or time"
|
113
23
|
end
|
24
|
+
Time.parse(t.to_s).to_i
|
114
25
|
end
|
115
26
|
|
116
|
-
def
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
27
|
+
def add_data(data)
|
28
|
+
add_data_block = proc {
|
29
|
+
data.each do |h|
|
30
|
+
self.row = h
|
31
|
+
self.persist_row
|
32
|
+
end
|
33
|
+
}
|
34
|
+
if LightStore.configuration.pipelined?
|
35
|
+
datastore.pipelined do
|
36
|
+
add_data_block.call
|
37
|
+
end
|
38
|
+
else
|
39
|
+
add_data_block.call
|
127
40
|
end
|
128
41
|
end
|
129
42
|
|
130
|
-
def
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
datastore.zadd(sorted_member_id, score, data_row_key)
|
43
|
+
def persist_row
|
44
|
+
datastore.set(row_key, marshal(self.row))
|
45
|
+
datastore.set(row_reference_key, row_key)
|
46
|
+
_primary_range_key = primary_range_key(self.primary_key)
|
47
|
+
datastore.zadd(_primary_range_key, secondary_key, row_reference_key)
|
48
|
+
datastore.zadd(range_key, secondary_key, row_reference_key)
|
137
49
|
end
|
138
50
|
|
139
|
-
def
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
added_records_count = 0
|
147
|
-
data.each do |h|
|
148
|
-
data_row_key = get_data_row_key(h)
|
149
|
-
marshaled_h = marshal(h)
|
150
|
-
new_member_check = add_member_id(h)
|
151
|
-
if new_member_check
|
152
|
-
datastore.set(data_row_key, marshaled_h)
|
153
|
-
add_sorted_member_ids(h)
|
51
|
+
def get_data(options = {})
|
52
|
+
reference_keys = []
|
53
|
+
if options.has_key?(:primary_key)
|
54
|
+
# With primary key.
|
55
|
+
if options.has_key?(:date_range)
|
56
|
+
# With date range.
|
57
|
+
reference_keys = get_data_by_range(options[:primary_key], *options[:date_range])
|
154
58
|
else
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
59
|
+
# date_range was not passed in, getting data for the key.
|
60
|
+
reference_keys = get_all_reference_keys(options[:primary_key])
|
61
|
+
end
|
62
|
+
else
|
63
|
+
# Without primary key.
|
64
|
+
if options.has_key?(:date_range)
|
65
|
+
reference_keys = get_data_by_range(nil, *options[:date_range])
|
66
|
+
else
|
67
|
+
# date_range was not passed in, getting all data.
|
68
|
+
reference_keys = get_all_reference_keys()
|
159
69
|
end
|
160
|
-
added_records_count += 1 if new_member_check
|
161
70
|
end
|
162
|
-
|
163
|
-
|
71
|
+
return [] if reference_keys.empty?
|
72
|
+
get_data_by_reference_keys(reference_keys)
|
164
73
|
end
|
165
74
|
|
166
|
-
def
|
167
|
-
|
168
|
-
|
169
|
-
marshaled_data
|
170
|
-
unmarshaled_data = marshaled_data.collect{ |d| unmarshal(d) }
|
75
|
+
def get_data_by_reference_keys(reference_keys)
|
76
|
+
row_keys = datastore.mget(reference_keys)
|
77
|
+
marshaled_data = datastore.mget(row_keys)
|
78
|
+
marshaled_data.collect{ |d| unmarshal(d) }
|
171
79
|
end
|
172
80
|
|
173
|
-
def
|
174
|
-
|
175
|
-
|
176
|
-
raise ArgumentError, "sortable field '#{_sortable_field}' not declared" unless grouped_sortable_fields.has_key?(_sortable_field)
|
177
|
-
field_type = grouped_sortable_fields[_sortable_field].first[:field_type]
|
178
|
-
min = get_score(min, field_type) unless min == '-inf'
|
179
|
-
max = get_score(max, field_type) unless max == '+inf'
|
180
|
-
sorted_member_id = get_sorted_member_id_key(_primary_key, _sortable_field)
|
181
|
-
|
182
|
-
data_keys = datastore.zrangebyscore(sorted_member_id, min, max, options)
|
183
|
-
marshaled_data = datastore.mget(data_keys)
|
184
|
-
unmarshaled_data = marshaled_data.collect{ |d| unmarshal(d) }
|
81
|
+
def get_all_reference_keys(primary_key = nil)
|
82
|
+
key_substring = "#{base_name}:key:#{primary_key}*"
|
83
|
+
datastore.keys(key_substring)
|
185
84
|
end
|
186
85
|
|
187
|
-
def
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
86
|
+
def get_data_by_range(_primary_key = nil, start_date, stop_date)
|
87
|
+
min = time_to_integer(start_date)
|
88
|
+
max = time_to_integer(stop_date)
|
89
|
+
if _primary_key.nil?
|
90
|
+
# range_key
|
91
|
+
return datastore.zrangebyscore(range_key, min, max)
|
92
|
+
else
|
93
|
+
# primary_range_key
|
94
|
+
_primary_range_key = primary_range_key(_primary_key)
|
95
|
+
return datastore.zrangebyscore(_primary_range_key, min, max)
|
96
|
+
end
|
192
97
|
end
|
193
98
|
|
194
99
|
def clear_all
|
195
|
-
all_keys = datastore.keys("#{
|
100
|
+
all_keys = datastore.keys("#{base_name}*")
|
196
101
|
clear(all_keys)
|
197
102
|
end
|
198
103
|
|
199
|
-
# make private.
|
200
104
|
def clear(keys)
|
201
105
|
deleted_count = datastore.del(keys) unless keys.empty?
|
202
106
|
end
|
203
|
-
|
204
107
|
end
|
205
108
|
end
|
@@ -2,6 +2,7 @@ module LightStore
|
|
2
2
|
class Configuration
|
3
3
|
def initialize
|
4
4
|
self.redis = nil
|
5
|
+
self.pipelined = true
|
5
6
|
end
|
6
7
|
|
7
8
|
def redis
|
@@ -11,5 +12,13 @@ module LightStore
|
|
11
12
|
def redis=(r)
|
12
13
|
@redis = r
|
13
14
|
end
|
15
|
+
|
16
|
+
def pipelined?
|
17
|
+
@pipelined
|
18
|
+
end
|
19
|
+
|
20
|
+
def pipelined=(b)
|
21
|
+
@pipelined = b
|
22
|
+
end
|
14
23
|
end
|
15
24
|
end
|
data/lib/light_store/version.rb
CHANGED
data/light_store.gemspec
CHANGED
@@ -21,6 +21,8 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
22
|
spec.add_development_dependency "rake"
|
23
23
|
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "faker"
|
25
|
+
spec.add_development_dependency "mock_redis"
|
24
26
|
|
25
27
|
spec.add_dependency "redis"
|
26
28
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'light_store'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler'
|
4
|
+
Bundler.setup(:default, :test)
|
5
|
+
Bundler.require(:default, :test)
|
6
|
+
|
7
|
+
require 'rspec'
|
8
|
+
require 'redis'
|
9
|
+
require 'logger'
|
10
|
+
describe LightStore::Data do
|
11
|
+
before(:each) do
|
12
|
+
LightStore.configure do |config|
|
13
|
+
config.redis = Redis.new
|
14
|
+
config.pipelined = false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
context 'for class accessor methods' do
|
18
|
+
before(:each) do
|
19
|
+
class TestReport < LightStore::Data
|
20
|
+
primary_field :some_field_name
|
21
|
+
date_constraint_field :some_date_field
|
22
|
+
end
|
23
|
+
class DefaultTestReport < LightStore::Data
|
24
|
+
end
|
25
|
+
end
|
26
|
+
context 'for base_name' do
|
27
|
+
it 'sets base_name' do
|
28
|
+
TestReport.base_name.should == 'LightStore:TestReport'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
context 'for primary_field' do
|
32
|
+
it 'returns primary_field' do
|
33
|
+
TestReport.primary_field.should_not be_nil
|
34
|
+
end
|
35
|
+
it 'errors without primary_field' do
|
36
|
+
expect{DefaultTestReport.primary_field}
|
37
|
+
.to raise_error(ArgumentError, 'primary_field must be set')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
context 'for date_constraint_field' do
|
41
|
+
it 'returns date_constraint_field' do
|
42
|
+
TestReport.date_constraint_field.should_not be_nil
|
43
|
+
end
|
44
|
+
it 'errors without date_constraint_field' do
|
45
|
+
expect{DefaultTestReport.date_constraint_field}
|
46
|
+
.to raise_error(ArgumentError, 'date_constraint_field must be set')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
context 'for row' do
|
50
|
+
let(:date) { '2012-05-15' }
|
51
|
+
let(:key) { 1 }
|
52
|
+
let(:h) {{some_field_name: 1, some_date_field: date}}
|
53
|
+
before(:each) do
|
54
|
+
TestReport.row = h
|
55
|
+
end
|
56
|
+
it 'sets and gets row' do
|
57
|
+
TestReport.row.should == h
|
58
|
+
end
|
59
|
+
it 'gets primary_key' do
|
60
|
+
TestReport.primary_key.should == key
|
61
|
+
end
|
62
|
+
it 'gets secondary_key' do
|
63
|
+
TestReport.secondary_key.should == Time.parse(date).to_i
|
64
|
+
end
|
65
|
+
it 'has row_key' do
|
66
|
+
TestReport.row_key.should match(":row:#{TestReport.primary_key}:#{TestReport.secondary_key}")
|
67
|
+
end
|
68
|
+
it 'has row_reference_key' do
|
69
|
+
TestReport.row_reference_key.should match(":key:#{TestReport.primary_key}:#{TestReport.secondary_key}")
|
70
|
+
end
|
71
|
+
it 'has primary_range_key' do
|
72
|
+
TestReport.primary_range_key(TestReport.primary_key).should match(":primary_range:#{TestReport.primary_key}")
|
73
|
+
end
|
74
|
+
it 'has range_key' do
|
75
|
+
TestReport.range_key.should match(":range")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'light_store'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler'
|
4
|
+
Bundler.setup(:default, :test)
|
5
|
+
Bundler.require(:default, :test)
|
6
|
+
|
7
|
+
require 'rspec'
|
8
|
+
require 'redis'
|
9
|
+
require 'logger'
|
10
|
+
describe LightStore::Data do
|
11
|
+
before(:each) do
|
12
|
+
LightStore.configure do |config|
|
13
|
+
config.redis = Redis.new
|
14
|
+
config.pipelined = false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
context 'for class methods' do
|
18
|
+
let(:dataset1) {
|
19
|
+
[
|
20
|
+
{id: 2, s_id: 20, name_1: 'John', name_2: 'Krakis', super_date: Time.new(2002)},
|
21
|
+
{id: 2, s_id: 25, name_1: 'Jake', name_2: 'Harken', super_date: Time.new(2003)},
|
22
|
+
{id: 3, s_id: 30, name_1: 'Jake', name_2: 'Marken', super_date: Time.new(2004)},
|
23
|
+
{id: 3, s_id: 35, name_1: 'Jake', name_2: 'Barken', super_date: Time.new(2005)},
|
24
|
+
{id: 3, s_id: 40, name_1: 'Jake', name_2: 'Farken', super_date: Time.new(2006)},
|
25
|
+
{id: 3, s_id: 45, name_1: 'Jake', name_2: 'Larken', super_date: Time.new(2007)},
|
26
|
+
{id: 3, s_id: 50, name_1: 'Jake', name_2: 'Tarken', super_date: Time.new(2008)},
|
27
|
+
]
|
28
|
+
}
|
29
|
+
before(:each) do
|
30
|
+
class SomeReport < LightStore::Data
|
31
|
+
primary_field :id
|
32
|
+
date_constraint_field :super_date
|
33
|
+
end
|
34
|
+
SomeReport.add_data(dataset1)
|
35
|
+
end
|
36
|
+
after(:each) do
|
37
|
+
SomeReport.clear_all
|
38
|
+
end
|
39
|
+
# make these tests testier
|
40
|
+
describe '#add_data' do
|
41
|
+
it 'sets data' do
|
42
|
+
SomeReport.add_data(dataset1)
|
43
|
+
end
|
44
|
+
it 'gets data no args' do
|
45
|
+
SomeReport.get_data()
|
46
|
+
end
|
47
|
+
it 'gets data with primary key' do
|
48
|
+
SomeReport.get_data(primary_key: 2)
|
49
|
+
end
|
50
|
+
it 'gets data with date range' do
|
51
|
+
SomeReport.get_data(date_range: [2000, '2013'])
|
52
|
+
end
|
53
|
+
it 'gets data with primary key and date range' do
|
54
|
+
SomeReport.get_data(primary_key: 1, date_range: [Time.new(2001), Time.new(2013)])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
metadata
CHANGED
@@ -1,78 +1,97 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: light_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.2
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Michael Kompanets
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2014-
|
11
|
+
date: 2014-04-29 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: bundler
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- - ~>
|
17
|
+
- - "~>"
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '1.3'
|
22
20
|
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- - ~>
|
24
|
+
- - "~>"
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '1.3'
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: rake
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
|
-
- -
|
31
|
+
- - ">="
|
36
32
|
- !ruby/object:Gem::Version
|
37
33
|
version: '0'
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
|
-
- -
|
38
|
+
- - ">="
|
44
39
|
- !ruby/object:Gem::Version
|
45
40
|
version: '0'
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: rspec
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
|
-
- -
|
45
|
+
- - ">="
|
52
46
|
- !ruby/object:Gem::Version
|
53
47
|
version: '0'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
|
-
- -
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: faker
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mock_redis
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
60
81
|
- !ruby/object:Gem::Version
|
61
82
|
version: '0'
|
62
83
|
- !ruby/object:Gem::Dependency
|
63
84
|
name: redis
|
64
85
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
86
|
requirements:
|
67
|
-
- -
|
87
|
+
- - ">="
|
68
88
|
- !ruby/object:Gem::Version
|
69
89
|
version: '0'
|
70
90
|
type: :runtime
|
71
91
|
prerelease: false
|
72
92
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
93
|
requirements:
|
75
|
-
- -
|
94
|
+
- - ">="
|
76
95
|
- !ruby/object:Gem::Version
|
77
96
|
version: '0'
|
78
97
|
description: A library for storing data about an object in spreadsheet-like format
|
@@ -83,41 +102,42 @@ executables: []
|
|
83
102
|
extensions: []
|
84
103
|
extra_rdoc_files: []
|
85
104
|
files:
|
86
|
-
- .gitignore
|
105
|
+
- ".gitignore"
|
87
106
|
- Gemfile
|
88
107
|
- LICENSE.txt
|
89
108
|
- README.md
|
90
109
|
- Rakefile
|
91
110
|
- lib/light_store.rb
|
111
|
+
- lib/light_store/class_accessor_methods.rb
|
92
112
|
- lib/light_store/class_methods.rb
|
93
113
|
- lib/light_store/configuration.rb
|
94
114
|
- lib/light_store/version.rb
|
95
115
|
- light_store.gemspec
|
96
|
-
- spec/
|
116
|
+
- spec/class_accessor_methods_spec.rb
|
117
|
+
- spec/class_methods_spec.rb
|
97
118
|
homepage: https://github.com/mkompanets/light_store
|
98
119
|
licenses:
|
99
120
|
- MIT
|
121
|
+
metadata: {}
|
100
122
|
post_install_message:
|
101
123
|
rdoc_options: []
|
102
124
|
require_paths:
|
103
125
|
- lib
|
104
126
|
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
127
|
requirements:
|
107
|
-
- -
|
128
|
+
- - ">="
|
108
129
|
- !ruby/object:Gem::Version
|
109
130
|
version: '0'
|
110
131
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
-
none: false
|
112
132
|
requirements:
|
113
|
-
- -
|
133
|
+
- - ">="
|
114
134
|
- !ruby/object:Gem::Version
|
115
135
|
version: '0'
|
116
136
|
requirements: []
|
117
137
|
rubyforge_project:
|
118
|
-
rubygems_version:
|
138
|
+
rubygems_version: 2.2.2
|
119
139
|
signing_key:
|
120
|
-
specification_version:
|
140
|
+
specification_version: 4
|
121
141
|
summary: This library aims to provide an easy way to store report data about objects. Data
|
122
142
|
that is typically generated with complex queries and methods could be stored in
|
123
143
|
a flat and accessible format. This is meant to be a general purpose library, but
|
@@ -125,5 +145,6 @@ summary: This library aims to provide an easy way to store report data about obj
|
|
125
145
|
from an idea that each 'row' of report data could be identified by the object id
|
126
146
|
and a secondary id relevant to the report.
|
127
147
|
test_files:
|
128
|
-
- spec/
|
148
|
+
- spec/class_accessor_methods_spec.rb
|
149
|
+
- spec/class_methods_spec.rb
|
129
150
|
has_rdoc:
|
data/spec/light_store_spec.rb
DELETED
@@ -1,299 +0,0 @@
|
|
1
|
-
require 'light_store'
|
2
|
-
require 'rubygems'
|
3
|
-
require 'bundler'
|
4
|
-
Bundler.setup(:default, :test)
|
5
|
-
Bundler.require(:default, :test)
|
6
|
-
|
7
|
-
require 'rspec'
|
8
|
-
require 'redis'
|
9
|
-
require 'logger'
|
10
|
-
describe LightStore::Data do
|
11
|
-
before(:each) do
|
12
|
-
LightStore.configure do |config|
|
13
|
-
config.redis = Redis.new
|
14
|
-
end
|
15
|
-
end
|
16
|
-
context 'for class initialization' do
|
17
|
-
before(:each) do
|
18
|
-
class DefaultValuesClass < LightStore::Data
|
19
|
-
end
|
20
|
-
class SetValuesClass < LightStore::Data
|
21
|
-
set_namespace 'some_namespace'
|
22
|
-
set_prefix 'some_prefix'
|
23
|
-
set_primary_key 'some_id'
|
24
|
-
set_secondary_key 'some_secondary_key'
|
25
|
-
set_sortable_field :f1
|
26
|
-
set_sortable_field :f2, :integer
|
27
|
-
set_sortable_field :f3, :float
|
28
|
-
set_sortable_field :f4, :datetime
|
29
|
-
end
|
30
|
-
SetValuesClass.clear_all
|
31
|
-
end
|
32
|
-
describe 'DefaultValuesClass' do
|
33
|
-
let(:default_values_instance) { DefaultValuesClass.new }
|
34
|
-
it 'datastore type is Redis' do
|
35
|
-
DefaultValuesClass.datastore.class.name.should == 'Redis'
|
36
|
-
end
|
37
|
-
it 'sets class level namespace to light_store' do
|
38
|
-
DefaultValuesClass.namespace.should == 'light_store'
|
39
|
-
end
|
40
|
-
it 'sets class level prefix to class name' do
|
41
|
-
DefaultValuesClass.prefix.should == 'DefaultValuesClass'
|
42
|
-
end
|
43
|
-
it 'sets instance level namespace to light_store' do
|
44
|
-
default_values_instance.namespace.should == 'light_store'
|
45
|
-
end
|
46
|
-
it 'sets class level primary_key to :id by default' do
|
47
|
-
DefaultValuesClass.primary_key.should == :id
|
48
|
-
end
|
49
|
-
it 'sets instance level primary_key to :id by default' do
|
50
|
-
default_values_instance.primary_key.should == :id
|
51
|
-
end
|
52
|
-
it 'raises ArgumentError if secondary_key not set' do
|
53
|
-
expect{DefaultValuesClass.secondary_key}.to raise_error(ArgumentError)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
describe 'SetValuesClass' do
|
57
|
-
let(:set_values_instance) { SetValuesClass.new }
|
58
|
-
let(:sample_h1) {
|
59
|
-
{
|
60
|
-
'some_id' => 1,
|
61
|
-
'some_secondary_key' => 's2',
|
62
|
-
f1: '1',
|
63
|
-
f2: '2',
|
64
|
-
f3: '3.2',
|
65
|
-
f4: Time.now,
|
66
|
-
}
|
67
|
-
}
|
68
|
-
context 'for class methods' do
|
69
|
-
it 'sets class namespace to some_namespace' do
|
70
|
-
SetValuesClass.namespace.should == 'some_namespace'
|
71
|
-
end
|
72
|
-
it 'sets class level prefix to some_prefix' do
|
73
|
-
SetValuesClass.prefix.should == 'some_prefix'
|
74
|
-
end
|
75
|
-
it 'sets class primary_key to some_id' do
|
76
|
-
SetValuesClass.primary_key.should == 'some_id'
|
77
|
-
end
|
78
|
-
it 'sets class secondary_key to some_secondary_key' do
|
79
|
-
SetValuesClass.secondary_key.should == 'some_secondary_key'
|
80
|
-
end
|
81
|
-
end
|
82
|
-
context 'for key setting methods' do
|
83
|
-
describe '#base_key' do
|
84
|
-
it 'sets key to #{namespace}:#{prefix}' do
|
85
|
-
k = "#{SetValuesClass.namespace}:#{SetValuesClass.prefix}"
|
86
|
-
SetValuesClass.base_key.should == k
|
87
|
-
end
|
88
|
-
end
|
89
|
-
describe '#base_member_ids_key' do
|
90
|
-
it 'sets key to #{base_key}:member_ids' do
|
91
|
-
k = "#{SetValuesClass.base_key}:member_ids"
|
92
|
-
SetValuesClass.base_member_ids_key.should == k
|
93
|
-
end
|
94
|
-
end
|
95
|
-
describe '#base_sorted_member_ids_key' do
|
96
|
-
it 'sets key to "#{base_key}:sorted_member_ids"' do
|
97
|
-
k = "#{SetValuesClass.base_key}:sorted_member_ids"
|
98
|
-
SetValuesClass.base_sorted_member_ids_key.should == k
|
99
|
-
end
|
100
|
-
end
|
101
|
-
describe '#base_data_key' do
|
102
|
-
it 'sets key to #{namespace}:#{prefix}:data' do
|
103
|
-
k = "#{SetValuesClass.namespace}:#{SetValuesClass.prefix}:data"
|
104
|
-
SetValuesClass.base_data_key.should == k
|
105
|
-
end
|
106
|
-
end
|
107
|
-
describe '#get_primary_id' do
|
108
|
-
it 'gets primary key' do
|
109
|
-
k = sample_h1['some_id']
|
110
|
-
SetValuesClass.get_primary_id(sample_h1).should == k
|
111
|
-
end
|
112
|
-
it 'raises ArgumentError if not set' do
|
113
|
-
expect{SetValuesClass.get_primary_id({})}.to raise_error(ArgumentError)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
describe '#get_secondary_id' do
|
117
|
-
it 'gets secondary_key key' do
|
118
|
-
k = sample_h1['some_secondary_key']
|
119
|
-
SetValuesClass.get_secondary_id(sample_h1).should == k
|
120
|
-
end
|
121
|
-
it 'raises ArgumentError if not set' do
|
122
|
-
expect{SetValuesClass.get_secondary_id({})}.to raise_error(ArgumentError)
|
123
|
-
end
|
124
|
-
end
|
125
|
-
describe '#sortable_fields' do
|
126
|
-
it 'field_type defaults to integer' do
|
127
|
-
SetValuesClass.sortable_fields.should include({field_name: :f1, field_type: :integer})
|
128
|
-
end
|
129
|
-
end
|
130
|
-
describe '#get_data_row_key' do
|
131
|
-
it 'gets data row key' do
|
132
|
-
d = SetValuesClass.get_data_row_key(sample_h1)
|
133
|
-
base_key = SetValuesClass.base_data_key
|
134
|
-
primary_id = SetValuesClass.get_primary_id(sample_h1)
|
135
|
-
secondary_id = SetValuesClass.get_secondary_id(sample_h1)
|
136
|
-
d.should == "#{base_key}:#{primary_id}:#{secondary_id}"
|
137
|
-
end
|
138
|
-
end
|
139
|
-
describe '#get_member_id_key' do
|
140
|
-
it 'gets member id key' do
|
141
|
-
d = SetValuesClass.get_member_id_key(sample_h1)
|
142
|
-
base_key = SetValuesClass.base_member_ids_key
|
143
|
-
primary_id = SetValuesClass.get_primary_id(sample_h1)
|
144
|
-
d.should == "#{base_key}:#{primary_id}"
|
145
|
-
end
|
146
|
-
end
|
147
|
-
describe '#get_sorted_member_id_key' do
|
148
|
-
it 'gets sorted member id key' do
|
149
|
-
field_name = SetValuesClass.sortable_fields.first[:field_name]
|
150
|
-
primary_id = SetValuesClass.get_primary_id(sample_h1)
|
151
|
-
d = SetValuesClass.get_sorted_member_id_key(sample_h1, field_name)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
describe '#get_score' do
|
155
|
-
it 'gets the score for integer type' do
|
156
|
-
v = 1
|
157
|
-
SetValuesClass.get_score(v, :integer).should be_a Integer
|
158
|
-
end
|
159
|
-
it 'gets the score for float type' do
|
160
|
-
v = 1
|
161
|
-
SetValuesClass.get_score(v, :float).should be_a Float
|
162
|
-
end
|
163
|
-
it 'gets the score for datetime type' do
|
164
|
-
v = Time.now.to_s
|
165
|
-
SetValuesClass.get_score(v, :datetime).should be_a Integer
|
166
|
-
end
|
167
|
-
it 'raises an error for nil value' do
|
168
|
-
v = nil
|
169
|
-
expect{SetValuesClass.get_score(v, :unknown)}.to raise_error(ArgumentError)
|
170
|
-
end
|
171
|
-
it 'raises an error for unexpected type' do
|
172
|
-
v = Time.now.to_s
|
173
|
-
expect{SetValuesClass.get_score(v, :unknown)}.to raise_error(ArgumentError)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
describe '#add_member_id' do
|
177
|
-
it 'adds member id to set' do
|
178
|
-
SetValuesClass.add_member_id(sample_h1).should be_true
|
179
|
-
row_id = SetValuesClass.get_data_row_key(sample_h1)
|
180
|
-
SetValuesClass.get_member_ids.should include(row_id)
|
181
|
-
end
|
182
|
-
end
|
183
|
-
describe '#add_sorted_member_ids' do
|
184
|
-
it 'adds sorted member ids to set' do
|
185
|
-
SetValuesClass.add_sorted_member_ids(sample_h1)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
describe '#add_sorted_member_id' do
|
189
|
-
it 'adds sorted member id to set' do
|
190
|
-
field_name = SetValuesClass.sortable_fields.first[:field_name]
|
191
|
-
field_type = SetValuesClass.sortable_fields.first[:field_type]
|
192
|
-
SetValuesClass.add_sorted_member_id(sample_h1, field_name, field_type)
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
|
-
context 'for instance methods' do
|
197
|
-
it 'sets instance namespace to some_namespace' do
|
198
|
-
set_values_instance.namespace.should == 'some_namespace'
|
199
|
-
end
|
200
|
-
it 'sets instance primary_key to some_id' do
|
201
|
-
set_values_instance.primary_key.should == 'some_id'
|
202
|
-
end
|
203
|
-
it 'sets instance secondary_key to some_secondary_key' do
|
204
|
-
set_values_instance.secondary_key.should == 'some_secondary_key'
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
context 'for class methods' do
|
211
|
-
let(:dataset1) {
|
212
|
-
[
|
213
|
-
{id: 2, s_id: 20, name_1: 'John', name_2: 'Krakis', super_date: Time.new(2002)},
|
214
|
-
{id: 2, s_id: 25, name_1: 'Jake', name_2: 'Harken', super_date: Time.new(2003)},
|
215
|
-
{id: 3, s_id: 30, name_1: 'Jake', name_2: 'Marken', super_date: Time.new(2004)},
|
216
|
-
{id: 3, s_id: 35, name_1: 'Jake', name_2: 'Barken', super_date: Time.new(2005)},
|
217
|
-
{id: 3, s_id: 40, name_1: 'Jake', name_2: 'Farken', super_date: Time.new(2006)},
|
218
|
-
{id: 3, s_id: 45, name_1: 'Jake', name_2: 'Larken', super_date: Time.new(2007)},
|
219
|
-
{id: 3, s_id: 50, name_1: 'Jake', name_2: 'Tarken', super_date: Time.new(2008)},
|
220
|
-
]
|
221
|
-
}
|
222
|
-
before(:each) do
|
223
|
-
class SomeData < LightStore::Data
|
224
|
-
set_prefix 'some_data'
|
225
|
-
set_secondary_key :s_id
|
226
|
-
set_sortable_field :super_date, :datetime
|
227
|
-
end
|
228
|
-
SomeData.clear_all
|
229
|
-
end
|
230
|
-
|
231
|
-
describe '#add_data' do
|
232
|
-
it 'sets data' do
|
233
|
-
new_rows = SomeData.add_data(dataset1)
|
234
|
-
end
|
235
|
-
it 'does not duplicate data' do
|
236
|
-
SomeData.add_data(dataset1)
|
237
|
-
new_rows = SomeData.add_data(dataset1)
|
238
|
-
new_rows.should == 0
|
239
|
-
end
|
240
|
-
end
|
241
|
-
describe '#get_data' do
|
242
|
-
it 'gets data' do
|
243
|
-
new_rows = SomeData.add_data(dataset1)
|
244
|
-
data = SomeData.get_data
|
245
|
-
data.size.should == new_rows
|
246
|
-
end
|
247
|
-
it 'gets data in proper format' do
|
248
|
-
SomeData.add_data(dataset1)
|
249
|
-
data = SomeData.get_data
|
250
|
-
data.should be_a Array
|
251
|
-
data.first.should be_a Hash
|
252
|
-
end
|
253
|
-
it 'gets data correctly by id' do
|
254
|
-
SomeData.add_data(dataset1)
|
255
|
-
|
256
|
-
# Two rows for id: 2
|
257
|
-
data = SomeData.get_data(2)
|
258
|
-
data.size.should == 2
|
259
|
-
|
260
|
-
# Five rows for id: 3
|
261
|
-
data = SomeData.get_data(3)
|
262
|
-
data.size.should == 5
|
263
|
-
end
|
264
|
-
end
|
265
|
-
describe '#get_sorted_data' do
|
266
|
-
it 'get sorted data' do
|
267
|
-
SomeData.add_data(dataset1)
|
268
|
-
data = SomeData.get_sorted_data(3, :super_date, Time.new(2003), Time.new(2007))
|
269
|
-
data.size.should == 4
|
270
|
-
end
|
271
|
-
it 'get sorted data includes correctly sorted values' do
|
272
|
-
SomeData.add_data(dataset1)
|
273
|
-
data = SomeData.get_sorted_data(3, :super_date, Time.new(2003), Time.new(2007))
|
274
|
-
values_within_range = true
|
275
|
-
data.each do |h|
|
276
|
-
sorted_value = h[:super_date]
|
277
|
-
values_between_range = false unless sorted_value >= Time.new(2003) && sorted_value <= Time.new(2007)
|
278
|
-
end
|
279
|
-
values_within_range.should be_true
|
280
|
-
end
|
281
|
-
end
|
282
|
-
describe '#clear_all_data' do
|
283
|
-
it 'clears all data' do
|
284
|
-
SomeData.add_data(dataset1)
|
285
|
-
SomeData.clear_all_data
|
286
|
-
data = SomeData.get_data
|
287
|
-
data.size.should == 0
|
288
|
-
end
|
289
|
-
end
|
290
|
-
describe '#clear_all' do
|
291
|
-
it 'clears all data' do
|
292
|
-
SomeData.add_data(dataset1)
|
293
|
-
SomeData.clear_all
|
294
|
-
all_keys = SomeData.datastore.keys("#{SomeData.base_key}*")
|
295
|
-
all_keys.size.should == 0
|
296
|
-
end
|
297
|
-
end
|
298
|
-
end
|
299
|
-
end
|