light_store 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +87 -0
- data/Rakefile +1 -0
- data/lib/light_store.rb +36 -0
- data/lib/light_store/class_methods.rb +205 -0
- data/lib/light_store/configuration.rb +15 -0
- data/lib/light_store/version.rb +3 -0
- data/light_store.gemspec +26 -0
- data/spec/light_store_spec.rb +299 -0
- metadata +129 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Michael Kompanets
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# LightStore
|
2
|
+
|
3
|
+
A library for storing data about an object in spreadsheet-like format (array of hashes).
|
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 secondary id relevant to the report.
|
6
|
+
|
7
|
+
Data format example: `[{account_id: 1, month: '2014-11', revenue: 987.65}, {account_id: 1, month: '2014-12', revenue: 1234.56}]`
|
8
|
+
|
9
|
+
In the above example `account_id:` is a primary id and `month:` is a secondary id.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
This gem relies on redis for storage.
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'light_store'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle install
|
22
|
+
|
23
|
+
### In rails
|
24
|
+
|
25
|
+
Add an initializer:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
LightStore.configure do |config|
|
29
|
+
config.redis = Redis.new
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
Define a class:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class RevenueReport < LightStore::Data
|
37
|
+
set_prefix 'revenue_report'
|
38
|
+
set_primary_key :id # default
|
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
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
Adding data:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
data = [
|
51
|
+
{id: 1, month: '2012-01', revenue: 1234.56, number_of_orders: 10},
|
52
|
+
{id: 1, month: '2012-02', revenue: 2345.56, number_of_orders: 20},
|
53
|
+
{id: 1, month: '2012-03', revenue: 3245.56, number_of_orders: 30},
|
54
|
+
{id: 1, month: '2012-04', revenue: 2435.56, number_of_orders: 20},
|
55
|
+
{id: 2, month: '2012-01', revenue: 1234.56, number_of_orders: 10},
|
56
|
+
{id: 2, month: '2012-02', revenue: 2234.56, number_of_orders: 20},
|
57
|
+
{id: 2, month: '2012-03', revenue: 3234.56, number_of_orders: 30},
|
58
|
+
{id: 2, month: '2012-04', revenue: 2234.56, number_of_orders: 20},
|
59
|
+
]
|
60
|
+
RevenueReport.add_data(data)
|
61
|
+
```
|
62
|
+
|
63
|
+
Getting all data:
|
64
|
+
```ruby
|
65
|
+
data = RevenueReport.get_data()
|
66
|
+
```
|
67
|
+
|
68
|
+
Getting data for a given id:
|
69
|
+
```ruby
|
70
|
+
data = RevenueReport.get_data(2)
|
71
|
+
```
|
72
|
+
|
73
|
+
Getting data for a given id within range:
|
74
|
+
```ruby
|
75
|
+
data = RevenueReport.get_sorted_data(2, :month, '2012-02', '2012-04')
|
76
|
+
# or
|
77
|
+
data = RevenueReport.get_sorted_data(2, :number_of_orders, 20, 30)
|
78
|
+
```
|
79
|
+
|
80
|
+
|
81
|
+
## Contributing
|
82
|
+
|
83
|
+
1. Fork it
|
84
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
85
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
86
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
87
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/light_store.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "light_store/version"
|
2
|
+
require "light_store/configuration"
|
3
|
+
require "light_store/class_methods"
|
4
|
+
|
5
|
+
module LightStore
|
6
|
+
|
7
|
+
def self.configuration
|
8
|
+
@configuration ||= Configuration.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.configure
|
12
|
+
yield(configuration) if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def included(base)
|
17
|
+
base.extend ClassMethods
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Data
|
22
|
+
include LightStore
|
23
|
+
|
24
|
+
def namespace
|
25
|
+
self.class.namespace
|
26
|
+
end
|
27
|
+
|
28
|
+
def primary_key
|
29
|
+
self.class.primary_key
|
30
|
+
end
|
31
|
+
|
32
|
+
def secondary_key
|
33
|
+
self.class.secondary_key
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
module LightStore
|
2
|
+
module ClassMethods
|
3
|
+
def datastore
|
4
|
+
LightStore.configuration.redis
|
5
|
+
end
|
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
|
+
def marshal(h)
|
53
|
+
Marshal.dump(h)
|
54
|
+
end
|
55
|
+
|
56
|
+
def unmarshal(h)
|
57
|
+
Marshal.load(h)
|
58
|
+
end
|
59
|
+
|
60
|
+
def base_key
|
61
|
+
"#{namespace}:#{prefix}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def base_data_key
|
65
|
+
"#{base_key}:data"
|
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
|
111
|
+
else
|
112
|
+
raise ArgumentError, "score value for #{sortable_field_type} must be in proper format"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def add_member_id(h)
|
117
|
+
member_id_key = get_member_id_key(h)
|
118
|
+
data_row_key = get_data_row_key(h)
|
119
|
+
datastore.sadd(member_id_key, data_row_key)
|
120
|
+
end
|
121
|
+
|
122
|
+
def add_sorted_member_ids(h)
|
123
|
+
sortable_fields.each do |s|
|
124
|
+
field_name = s[:field_name]
|
125
|
+
field_type = s[:field_type]
|
126
|
+
add_sorted_member_id(h, field_name, field_type)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def add_sorted_member_id(h, field_name, field_type)
|
131
|
+
primary_id = get_primary_id(h)
|
132
|
+
sorted_member_id = get_sorted_member_id_key(primary_id, field_name)
|
133
|
+
data_row_key = get_data_row_key(h)
|
134
|
+
value = h[field_name]
|
135
|
+
score = get_score(value, field_type)
|
136
|
+
datastore.zadd(sorted_member_id, score, data_row_key)
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_member_ids(_primary_key = nil)
|
140
|
+
key = _primary_key ? "#{base_member_ids_key}:#{_primary_key}" : base_member_ids_key
|
141
|
+
member_keys = datastore.keys("#{key}*")
|
142
|
+
member_keys.collect{ |k| datastore.smembers(k) }.flatten
|
143
|
+
end
|
144
|
+
|
145
|
+
def add_data(data)
|
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)
|
154
|
+
else
|
155
|
+
old_value = datastore.getset(data_row_key, marshaled_h)
|
156
|
+
if old_value != marshaled_h
|
157
|
+
add_sorted_member_ids(h)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
added_records_count += 1 if new_member_check
|
161
|
+
end
|
162
|
+
#plural_records = added_records_count == 1 ? "record" : "records"
|
163
|
+
return added_records_count
|
164
|
+
end
|
165
|
+
|
166
|
+
def get_data(_primary_key = nil)
|
167
|
+
data_keys = _primary_key ? get_member_ids(_primary_key) : get_member_ids()
|
168
|
+
return [] if data_keys.empty?
|
169
|
+
marshaled_data = datastore.mget(data_keys)
|
170
|
+
unmarshaled_data = marshaled_data.collect{ |d| unmarshal(d) }
|
171
|
+
end
|
172
|
+
|
173
|
+
def get_sorted_data(_primary_key, _sortable_field, min, max, options = {})
|
174
|
+
grouped_sortable_fields = sortable_fields.group_by { |f| f[:field_name] }
|
175
|
+
raise ArgumentError, "No sortable fields declared" if grouped_sortable_fields.empty?
|
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) }
|
185
|
+
end
|
186
|
+
|
187
|
+
def clear_all_data
|
188
|
+
data_keys = datastore.keys("#{base_member_ids_key}*")
|
189
|
+
data_keys = data_keys.concat(datastore.keys("#{base_sorted_member_ids_key}*"))
|
190
|
+
data_keys = data_keys.concat(datastore.keys("#{base_data_key}*"))
|
191
|
+
clear(data_keys)
|
192
|
+
end
|
193
|
+
|
194
|
+
def clear_all
|
195
|
+
all_keys = datastore.keys("#{base_key}*")
|
196
|
+
clear(all_keys)
|
197
|
+
end
|
198
|
+
|
199
|
+
# make private.
|
200
|
+
def clear(keys)
|
201
|
+
deleted_count = datastore.del(keys) unless keys.empty?
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
end
|
data/light_store.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'light_store/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "light_store"
|
8
|
+
spec.version = LightStore::VERSION
|
9
|
+
spec.authors = ["Michael Kompanets"]
|
10
|
+
spec.email = ["mkompanets@gmail.com"]
|
11
|
+
spec.description = %q{A library for storing data about an object in spreadsheet-like format (array of hashes).}
|
12
|
+
spec.summary = %q{This library aims to provide an easy way to store report data about objects. Data that is typically generated with complex queries and methods could be stored in a flat and accessible 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 secondary id relevant to the report.}
|
13
|
+
spec.homepage = "https://github.com/mkompanets/light_store"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
|
25
|
+
spec.add_dependency "redis"
|
26
|
+
end
|
@@ -0,0 +1,299 @@
|
|
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
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: light_store
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael Kompanets
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-03-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.3'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.3'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: redis
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: A library for storing data about an object in spreadsheet-like format
|
79
|
+
(array of hashes).
|
80
|
+
email:
|
81
|
+
- mkompanets@gmail.com
|
82
|
+
executables: []
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- LICENSE.txt
|
89
|
+
- README.md
|
90
|
+
- Rakefile
|
91
|
+
- lib/light_store.rb
|
92
|
+
- lib/light_store/class_methods.rb
|
93
|
+
- lib/light_store/configuration.rb
|
94
|
+
- lib/light_store/version.rb
|
95
|
+
- light_store.gemspec
|
96
|
+
- spec/light_store_spec.rb
|
97
|
+
homepage: https://github.com/mkompanets/light_store
|
98
|
+
licenses:
|
99
|
+
- MIT
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ! '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
requirements: []
|
117
|
+
rubyforge_project:
|
118
|
+
rubygems_version: 1.8.25
|
119
|
+
signing_key:
|
120
|
+
specification_version: 3
|
121
|
+
summary: This library aims to provide an easy way to store report data about objects. Data
|
122
|
+
that is typically generated with complex queries and methods could be stored in
|
123
|
+
a flat and accessible format. This is meant to be a general purpose library, but
|
124
|
+
it was created to improve performance of dynamic report generators. This comes
|
125
|
+
from an idea that each 'row' of report data could be identified by the object id
|
126
|
+
and a secondary id relevant to the report.
|
127
|
+
test_files:
|
128
|
+
- spec/light_store_spec.rb
|
129
|
+
has_rdoc:
|