hyperion-api 0.0.1.alpha1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,175 @@
1
+ require 'hyperion/query'
2
+ require 'hyperion/filter'
3
+ require 'hyperion/sort'
4
+
5
+ module Hyperion
6
+ class API
7
+
8
+ class << self
9
+
10
+ attr_writer :datastore
11
+
12
+ # Sets the thread-local active datastore
13
+ def datastore=(datastore)
14
+ Thread.current[:datastore] = datastore
15
+ end
16
+
17
+ # Returns the current thread-local datastore instance
18
+ def datastore
19
+ Thread.current[:datastore] || raise('No Datastore installed')
20
+ end
21
+
22
+ # Saves a record. Any additional parameters will get merged onto the record before it is saved.
23
+
24
+ # Hyperion::API.save({:kind => :foo})
25
+ # => {:kind=>"foo", :key=>"<generated key>"}
26
+ # Hyperion::API.save({:kind => :foo}, :value => :bar)
27
+ # => {:kind=>"foo", :value=>:bar, :key=>"<generated key>"}
28
+ def save(record, attrs={})
29
+ save_many([record.merge(attrs || {})]).first
30
+ end
31
+
32
+ # Saves multiple records at once.
33
+ def save_many(records)
34
+ format_records(datastore.save(format_records(records)))
35
+ end
36
+
37
+ # Returns true if the record is new (not saved/doesn't have a :key), false otherwise.
38
+ def new?(record)
39
+ !record.has_key?(:key)
40
+ end
41
+
42
+ # Retrieves the value associated with the given key from the datastore. nil if it doesn't exist.
43
+ def find_by_key(key)
44
+ format_record(datastore.find_by_key(key))
45
+ end
46
+
47
+ # Returns all records of the specified kind that match the filters provided.
48
+ #
49
+ # find_by_kind(:dog) # returns all records with :kind of \"dog\"
50
+ # find_by_kind(:dog, :filters => [[:name, '=', "Fido"]]) # returns all dogs whos name is Fido
51
+ # find_by_kind(:dog, :filters => [[:age, '>', 2], [:age, '<', 5]]) # returns all dogs between the age of 2 and 5 (exclusive)
52
+ # find_by_kind(:dog, :sorts => [[:name, :asc]]) # returns all dogs in alphebetical order of their name
53
+ # find_by_kind(:dog, :sorts => [[:age, :desc], [:name, :asc]]) # returns all dogs ordered from oldest to youngest, and gos of the same age ordered by name
54
+ # find_by_kind(:dog, :limit => 10) # returns upto 10 dogs in undefined order
55
+ # find_by_kind(:dog, :sorts => [[:name, :asc]], :limit => 10) # returns upto the first 10 dogs in alphebetical order of their name
56
+ # find_by_kind(:dog, :sorts => [[:name, :asc]], :limit => 10, :offset => 10) # returns the second set of 10 dogs in alphebetical order of their name
57
+ #
58
+ # Filter operations and acceptable syntax:
59
+ # "=" "eq"
60
+ # "<" "lt"
61
+ # "<=" "lte"
62
+ # ">" "gt"
63
+ # ">=" "gte"
64
+ # "!=" "not"
65
+ # "contains?" "contains" "in?" "in"
66
+ #
67
+ # Sort orders and acceptable syntax:
68
+ # :asc "asc" :ascending "ascending"
69
+ # :desc "desc" :descending "descending"
70
+ def find_by_kind(kind, args={})
71
+ format_records(datastore.find(build_query(kind, args)))
72
+ end
73
+
74
+ # Removes the record stored with the given key. Returns nil no matter what.
75
+ def delete_by_key(key)
76
+ datastore.delete_by_key(key)
77
+ end
78
+
79
+ # Deletes all records of the specified kind that match the filters provided.
80
+ def delete_by_kind(kind, args={})
81
+ datastore.delete(build_query(kind, args))
82
+ end
83
+
84
+ # Counts records of the specified kind that match the filters provided.
85
+ def count_by_kind(kind, args={})
86
+ datastore.count(build_query(kind, args))
87
+ end
88
+
89
+ private
90
+
91
+ def build_query(kind, args)
92
+ kind = format_kind(kind)
93
+ filters = build_filters(args[:filters])
94
+ sorts = build_sorts(args[:sorts])
95
+ Query.new(kind, filters, sorts, args[:limit], args[:offset])
96
+ end
97
+
98
+ def build_filters(filters)
99
+ (filters || []).map do |(field, operator, value)|
100
+ operator = format_operator(operator)
101
+ field = format_field(field)
102
+ Filter.new(field, operator, value)
103
+ end
104
+ end
105
+
106
+ def build_sorts(sorts)
107
+ (sorts || []).map do |(field, order)|
108
+ field = format_field(field)
109
+ order = format_order(order)
110
+ Sort.new(field, order)
111
+ end
112
+ end
113
+
114
+ def format_order(order)
115
+ order.to_sym
116
+ case order
117
+ when :desc, 'desc', 'descending'
118
+ :desc
119
+ when :asc, 'asc', 'ascending'
120
+ :asc
121
+ end
122
+ end
123
+
124
+ def format_operator(operator)
125
+ case operator
126
+ when '=', 'eq'
127
+ '='
128
+ when '!=', 'not'
129
+ '!='
130
+ when '<', 'lt'
131
+ '<'
132
+ when '>', 'gt'
133
+ '>'
134
+ when '<=', 'lte'
135
+ '<='
136
+ when '>=', 'gte'
137
+ '>='
138
+ when 'contains?', 'contains', 'in?', 'in'
139
+ 'contains?'
140
+ end
141
+ end
142
+
143
+ def format_records(records)
144
+ records.map do |record|
145
+ format_record(record)
146
+ end
147
+ end
148
+
149
+ def format_record(record)
150
+ if record
151
+ record = record.reduce({}) do |new_record, (key, value)|
152
+ new_record[snake_case(key.to_s).to_sym] = value
153
+ new_record
154
+ end
155
+ record[:kind] = format_kind(record[:kind])
156
+ record
157
+ end
158
+ end
159
+
160
+ def format_kind(kind)
161
+ snake_case(kind.to_s)
162
+ end
163
+
164
+ def format_field(field)
165
+ snake_case(field.to_s).to_sym
166
+ end
167
+
168
+ def snake_case(str)
169
+ separate_camel_humps = str.gsub(/([a-z0-9])([A-Z])/, '\1 \2').downcase
170
+ separate_camel_humps.gsub(/[ |\-]/, '_')
171
+ end
172
+
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,253 @@
1
+ shared_examples_for 'Datastore' do
2
+
3
+ def api
4
+ Hyperion::API
5
+ end
6
+
7
+ context 'save' do
8
+
9
+ it 'saves a hash and returns it' do
10
+ record = api.save({:kind => 'testing', :name => 'ann'})
11
+ record[:kind].should == 'testing'
12
+ record[:name].should == 'ann'
13
+ end
14
+
15
+ it 'saves an empty record' do
16
+ record = api.save({:kind => 'testing'})
17
+ record[:kind].should == 'testing'
18
+ end
19
+
20
+ it 'assigns a key to new records' do
21
+ record = api.save({:kind => 'testing', :name => 'ann'})
22
+ record[:key].should_not be_nil
23
+ end
24
+
25
+ it 'saves an existing record' do
26
+ record1 = api.save({:kind => 'other_testing', :name => 'ann'})
27
+ record2 = api.save(record1.merge(:name => 'james'))
28
+ record1[:key].should == record2[:key]
29
+ api.find_by_kind('other_testing').length.should == 1
30
+ end
31
+
32
+ it 'saves an existing record to be empty' do
33
+ record1 = api.save({:kind => 'other_testing', :name => 'ann'})
34
+ record2 = record1.dup
35
+ record2.delete(:name)
36
+ record2 = api.save(record2)
37
+ record1[:key].should == record2[:key]
38
+ api.find_by_kind('other_testing').length.should == 1
39
+ end
40
+
41
+ def ten_testing_records(kind = 'testing')
42
+ (1..10).to_a.map do |i|
43
+ {:kind => kind, :name => i.to_s}
44
+ end
45
+ end
46
+
47
+ it 'assigns unique keys to each record' do
48
+ keys = ten_testing_records.map do |record|
49
+ api.save(record)[:key]
50
+ end
51
+ unique_keys = Set.new(keys)
52
+ unique_keys.length.should == 10
53
+ end
54
+
55
+ it 'can save many records' do
56
+ saved_records = api.save_many(ten_testing_records)
57
+ saved_records.length.should == 10
58
+ saved_names = Set.new(saved_records.map { |record| record[:name] })
59
+ found_records = api.find_by_kind('testing')
60
+ found_records.length.should == 10
61
+ found_names = Set.new(found_records.map { |record| record[:name] })
62
+ found_names.should == saved_names
63
+ end
64
+
65
+ end
66
+
67
+ def remove_nils(record)
68
+ record.reduce({}) do |non_nil_record, (field, value)|
69
+ non_nil_record[field] = value unless value.nil?
70
+ non_nil_record
71
+ end
72
+ end
73
+
74
+ context 'find by key' do
75
+ it 'finds by key' do
76
+ record = api.save({:kind => 'testing', :inti => 5})
77
+ remove_nils(api.find_by_key(record[:key])).should == remove_nils(record)
78
+ end
79
+ end
80
+
81
+ context 'find by kind' do
82
+ before :each do
83
+ api.save_many([
84
+ {:kind => 'testing', :inti => 1, :data => 'one' },
85
+ {:kind => 'testing', :inti => 12, :data => 'twelve' },
86
+ {:kind => 'testing', :inti => 23, :data => 'twenty3'},
87
+ {:kind => 'testing', :inti => 34, :data => 'thirty4'},
88
+ {:kind => 'testing', :inti => 45, :data => 'forty5' },
89
+ {:kind => 'testing', :inti => 1, :data => 'the one'},
90
+ {:kind => 'testing', :inti => 44, :data => 'forty4' }
91
+ ])
92
+ end
93
+
94
+ it 'filters by the kind' do
95
+ api.save({:kind => 'other_testing', :inti => 5})
96
+ found_records = api.find_by_kind('testing')
97
+ found_records.each do |record|
98
+ record[:kind].should == 'testing'
99
+ end
100
+ end
101
+
102
+ context 'filters' do
103
+
104
+ [
105
+ [[[:inti, '<', 25]], [1, 12, 23], :inti],
106
+ [[[:inti, '<=', 25]], [1, 12, 23], :inti],
107
+ [[[:inti, '>', 25]], [34, 44, 45], :inti],
108
+ [[[:inti, '=', 34]], [34], :inti],
109
+ [[[:inti, '!=', 34]], [1, 12, 23, 44, 45], :inti],
110
+ [[[:inti, 'in', [12, 34]]], [12, 34], :inti],
111
+ [[[:inti, '<', 24], [:inti, '>', 25]], [], :inti],
112
+ [[[:inti, '!=', 12], [:inti, '!=', 23], [:inti, '!=', 34]], [1, 44, 45], :inti],
113
+ [[[:data, '<', 'qux']], ['one', 'forty4', 'forty5'], :data],
114
+ [[[:data, '<=', 'one']], ['one', 'forty4', 'forty5'], :data],
115
+ [[[:data, '>=', 'thirty4']], ['twelve', 'twenty3', 'thirty4'], :data],
116
+ [[[:data, '=', 'one']], ['one'], :data],
117
+ [[[:data, '!=', 'one']], ['the one', 'twelve', 'twenty3', 'thirty4', 'forty4', 'forty5'], :data],
118
+ [[[:data, 'in', ['one', 'twelve']]], ['one', 'twelve'], :data],
119
+ [[[:data, '>', 'qux'], [:data, '<', 'qux']], [], :data],
120
+ [[[:data, '!=', 'one'], [:data, '!=', 'twelve'], [:data, '!=', 'twenty3']], ['the one', 'thirty4', 'forty4', 'forty5'], :data],
121
+ ].each do |filters, result, field|
122
+
123
+ it filters.map(&:to_s).join(', ') do
124
+ found_records = api.find_by_kind('testing', :filters => filters)
125
+ ints = Set.new(found_records.map {|record| record[field]})
126
+ ints.should == Set.new(result)
127
+ end
128
+ end
129
+
130
+ end
131
+
132
+ context 'sorts' do
133
+
134
+ [
135
+ [[[:inti, :asc]], [1, 1, 12, 23, 34, 44, 45], :inti],
136
+ [[[:inti, :desc]], [45, 44, 34, 23, 12, 1, 1], :inti],
137
+ [[[:data, :asc]], [44, 45, 1, 1, 34, 12, 23], :inti],
138
+ [[[:data, :desc]], [23, 12, 34, 1, 1, 45, 44], :inti],
139
+ [[[:inti, :asc], [:data, :asc]], ['one', 'the one', 'twelve', 'twenty3', 'thirty4', 'forty4', 'forty5'], :data],
140
+ [[[:data, :asc], [:inti, :asc]], [44, 45, 1, 1, 34, 12, 23], :inti]
141
+ ].each do |sorts, result, field|
142
+
143
+ it sorts.map(&:to_s).join(', ') do
144
+ found_records = api.find_by_kind('testing', :sorts => sorts)
145
+ ints = found_records.map {|record| record[field]}
146
+ ints.should == result
147
+ end
148
+ end
149
+ end
150
+
151
+ context 'limit and offset' do
152
+ specify 'offset n returns results starting at the nth record' do
153
+ found_records = api.find_by_kind('testing', :sorts => [[:inti, :asc]], :offset => 2)
154
+ ints = found_records.map {|record| record[:inti]}
155
+ ints.should == [12, 23, 34, 44, 45]
156
+ end
157
+
158
+ specify 'limit n takes only the first n records' do
159
+ found_records = api.find_by_kind('testing', :sorts => [[:inti, :asc]], :limit => 2)
160
+ found_records.map {|record| record[:inti]}.should == [1, 1]
161
+
162
+ found_records = api.find_by_kind('testing', :sorts => [[:inti, :asc]], :limit => 1_000_000)
163
+ found_records.map {|record| record[:inti]}.should == [1, 1, 12, 23, 34, 44, 45]
164
+ end
165
+
166
+ [
167
+ [{:limit => 2, :offset => 2}, [[:inti, :asc]], [12, 23]],
168
+ [{:limit => 2, :offset => 4}, [[:inti, :asc]], [34, 44]],
169
+ [{:limit => 2} , [[:inti, :desc]], [45, 44]],
170
+ [{:limit => 2, :offset => 2}, [[:inti, :desc]], [34, 23]],
171
+ [{:limit => 2, :offset => 4}, [[:inti, :desc]], [12, 1]],
172
+ ].each do |constraints, sorts, result|
173
+ example constraints.inspect do
174
+ found_records = api.find_by_kind 'testing', constraints.merge(:sorts => sorts)
175
+ found_records.map { |record| record[:inti] }.should == result
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ context 'delete' do
182
+
183
+ before :each do
184
+ api.save_many([
185
+ {:kind => 'testing', :inti => 1, :data => 'one' },
186
+ {:kind => 'testing', :inti => 12, :data => 'twelve' }
187
+ ])
188
+ end
189
+
190
+ it 'deletes by key' do
191
+ records = api.find_by_kind('testing')
192
+ record_to_delete = records.first
193
+ api.delete_by_key(record_to_delete[:key]).should be_nil
194
+ api.find_by_kind('testing').should_not include(record_to_delete)
195
+ end
196
+
197
+ context 'filters' do
198
+
199
+ [
200
+ [[], []],
201
+ [[[:inti, '=', 1]], [12]],
202
+ [[[:data, '=', 'one']], [12]],
203
+ [[[:inti, '!=', 1]], [1]],
204
+ [[[:inti, '<=', 1]], [12]],
205
+ [[[:inti, '<=', 2]], [12]],
206
+ [[[:inti, '>=', 2]], [1]],
207
+ [[[:inti, '>', 1]], [1]],
208
+ [[[:inti, 'in', [1]]], [12]],
209
+ [[[:inti, 'in', [1, 12]]], []],
210
+ [[[:inti, '=', 2]], [1, 12]]
211
+ ].each do |filters, result|
212
+ it filters.inspect do
213
+ api.delete_by_kind('testing', :filters => filters)
214
+ intis = api.find_by_kind('testing').map {|r| r[:inti]}
215
+ intis.should == result
216
+ end
217
+ end
218
+
219
+ end
220
+ end
221
+
222
+ context 'count' do
223
+
224
+ before :each do
225
+ api.save_many([
226
+ {:kind => 'testing', :inti => 1, :data => 'one' },
227
+ {:kind => 'testing', :inti => 12, :data => 'twelve' }
228
+ ])
229
+ end
230
+
231
+ context 'filters' do
232
+
233
+ [
234
+ [[], 2],
235
+ [[[:inti, '=', 1]], 1],
236
+ [[[:data, '=', 'one']], 1],
237
+ [[[:inti, '!=', 1]], 1],
238
+ [[[:inti, '<=', 1]], 1],
239
+ [[[:inti, '<=', 2]], 1],
240
+ [[[:inti, '>=', 2]], 1],
241
+ [[[:inti, '>', 1]], 1],
242
+ [[[:inti, 'in', [1]]], 1],
243
+ [[[:inti, 'in', [1, 12]]], 2],
244
+ [[[:inti, '=', 2]], 0]
245
+ ].each do |filters, result|
246
+ it filters.inspect do
247
+ api.count_by_kind('testing', :filters => filters).should == result
248
+ end
249
+ end
250
+
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,113 @@
1
+ require 'hyperion/api'
2
+
3
+ module Hyperion
4
+ module Dev
5
+ class Memory
6
+
7
+ def initialize
8
+ @id_counter = 0
9
+ @store = {}
10
+ end
11
+
12
+ def save(records)
13
+ records.map do |record|
14
+ key = API.new?(record) ? generate_key : record[:key]
15
+ record[:key] = key
16
+ store[key] = record
17
+ record
18
+ end
19
+ end
20
+
21
+ def find_by_key(key)
22
+ store[key]
23
+ end
24
+
25
+ def find(query)
26
+ records = store.values
27
+ records = filter_kind(query.kind, records)
28
+ records = apply_filters(query.filters, records)
29
+ records = apply_sorts(query.sorts, records)
30
+ records = apply_offset(query.offset, records)
31
+ records = apply_limit(query.limit, records)
32
+ records
33
+ end
34
+
35
+ def delete_by_key(key)
36
+ store.delete(key)
37
+ nil
38
+ end
39
+
40
+ def delete(query)
41
+ records = find(query)
42
+ store.delete_if do |key, record|
43
+ records.include?(record)
44
+ end
45
+ nil
46
+ end
47
+
48
+ def count(query)
49
+ find(query).length
50
+ end
51
+
52
+ private
53
+
54
+ attr_reader :store
55
+
56
+ def filter_kind(kind, records)
57
+ records.select do |record|
58
+ record[:kind] == kind
59
+ end
60
+ end
61
+
62
+ def apply_filters(filters, records)
63
+ records.select do |record|
64
+ filters.all? do |filter|
65
+ value = record[filter.field]
66
+ case filter.operator
67
+ when '<'; value < filter.value
68
+ when '<='; value <= filter.value
69
+ when '>'; value > filter.value
70
+ when '>='; value >= filter.value
71
+ when '='; value == filter.value
72
+ when '!='; value != filter.value
73
+ when 'contains?'; filter.value.include?(value)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ def apply_sorts(sorts, records)
80
+ records.sort { |record1, record2| compare_records record1, record2, sorts }
81
+ end
82
+
83
+ def compare_records(record1, record2, sorts)
84
+ sorts.each do |sort|
85
+ result = compare_record record1, record2, sort
86
+ return result if result
87
+ end
88
+ 0
89
+ end
90
+
91
+ def compare_record(record1, record2, sort)
92
+ field1, field2 = record1[sort.field], record2[sort.field]
93
+ field1 == field2 ? nil :
94
+ field1 < field2 && sort.ascending? ? -1 :
95
+ field1 > field2 && sort.descending? ? -1 : 1
96
+ end
97
+
98
+ def generate_key
99
+ @id_counter += 1
100
+ end
101
+
102
+ def apply_offset(offset, records)
103
+ return records unless offset
104
+ records.drop offset
105
+ end
106
+
107
+ def apply_limit(limit, records)
108
+ return records unless limit
109
+ records.take(limit)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,12 @@
1
+ module Hyperion
2
+ class Filter
3
+
4
+ attr_reader :operator, :field, :value
5
+
6
+ def initialize(field, operator, value)
7
+ @operator = operator
8
+ @field = field
9
+ @value = value
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module Hyperion
2
+ class Query
3
+ attr_reader :kind, :filters, :sorts, :limit, :offset
4
+
5
+ def initialize(kind, filters, sorts, limit, offset)
6
+ @kind = kind
7
+ @filters = filters || []
8
+ @sorts = sorts || []
9
+ @limit = limit
10
+ @offset = offset
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module Hyperion
2
+ class Sort
3
+
4
+ attr_reader :field, :order
5
+
6
+ def initialize(field, order)
7
+ @field = field
8
+ @order = order
9
+ end
10
+
11
+ def ascending?
12
+ order == :asc
13
+ end
14
+
15
+ def descending?
16
+ order == :desc
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,203 @@
1
+ require 'hyperion/api'
2
+ require 'hyperion/shared_examples'
3
+ require 'hyperion/fake_ds'
4
+
5
+ describe Hyperion::API do
6
+
7
+ def api
8
+ Hyperion::API
9
+ end
10
+
11
+ context 'datastore' do
12
+ it 'will throw an error if the datastore is called before assignment' do
13
+ expect{ subject.datastore }.to raise_error
14
+ end
15
+ end
16
+
17
+ context 'new?' do
18
+ it 'false if a record exists' do
19
+ api.new?({:key => 1}).should be_false
20
+ end
21
+
22
+ it 'true if a record does not exist' do
23
+ api.new?({}).should be_true
24
+ end
25
+ end
26
+
27
+ context 'with fake datastore' do
28
+ attr_reader :fake_ds
29
+
30
+ before :each do
31
+ @fake_ds = FakeDatastore.new
32
+ api.datastore = @fake_ds
33
+ end
34
+
35
+ context 'save' do
36
+
37
+ it 'saves a record' do
38
+ record = {:kind => 'one'}
39
+ api.save(record)
40
+ api.datastore.saved_records.first.should == record
41
+ end
42
+
43
+ it 'merges the given attrs' do
44
+ api.save({:kind => 'one'}, :attr =>'value')
45
+ api.datastore.saved_records.first.should == {:kind => 'one', :attr => 'value'}
46
+ end
47
+
48
+ it 'handles nil input to attrs' do
49
+ api.save({:kind => 'one'}, nil)
50
+ api.datastore.saved_records.first.should == {:kind => 'one'}
51
+ end
52
+
53
+ context 'record formatting on save' do
54
+ include_examples 'record formatting', lambda { |record|
55
+ Hyperion::API.save(record)
56
+ Hyperion::API.datastore.saved_records.first
57
+ }
58
+ end
59
+
60
+ context 'record formatting on return from datastore' do
61
+ include_examples 'record formatting', lambda {|record|
62
+ Hyperion::API.datastore.returns = [[record]]
63
+ Hyperion::API.save({})
64
+ }
65
+ end
66
+ end
67
+
68
+ context 'save many' do
69
+
70
+ context 'record formatting on save' do
71
+ include_examples 'record formatting', lambda { |record|
72
+ Hyperion::API.save_many([record])
73
+ Hyperion::API.datastore.saved_records.first
74
+ }
75
+ end
76
+
77
+ context 'record formatting on return from datastore' do
78
+ include_examples 'record formatting', lambda { |record|
79
+ Hyperion::API.datastore.returns = [[record]]
80
+ Hyperion::API.save_many([{}]).first
81
+ }
82
+ end
83
+ end
84
+
85
+ context 'find by kind' do
86
+ context 'parses kind' do
87
+ include_examples 'kind formatting', lambda { |kind|
88
+ Hyperion::API.find_by_kind(kind)
89
+ Hyperion::API.datastore.queries.last.kind
90
+ }
91
+ end
92
+
93
+ context 'parses filters' do
94
+ include_examples 'filtering', lambda { |filter|
95
+ Hyperion::API.find_by_kind('kind', :filters => [filter])
96
+ Hyperion::API.datastore.queries.last.filters.first
97
+ }
98
+ end
99
+
100
+ context 'parses sorts' do
101
+
102
+ def do_find(sort)
103
+ api.find_by_kind('kind', :sorts => [sort])
104
+ query = fake_ds.queries.last
105
+ query.sorts.first
106
+ end
107
+
108
+ context 'field' do
109
+ include_examples 'field formatting', lambda { |field|
110
+ Hyperion::API.find_by_kind('kind', :sorts => [[field, 'desc']])
111
+ Hyperion::API.datastore.queries.first.sorts.first.field
112
+ }
113
+ end
114
+
115
+ context 'order' do
116
+ {
117
+ 'desc' => :desc,
118
+ :desc => :desc,
119
+ 'descending' => :desc,
120
+ 'asc' => :asc,
121
+ :asc => :asc,
122
+ 'ascending' => :asc
123
+ }.each_pair do |order, result|
124
+
125
+ it "#{order.inspect} to #{result.inspect}" do
126
+ do_find([:attr, order]).order.should == result
127
+ end
128
+
129
+ end
130
+ end
131
+ end
132
+
133
+ it 'passes limit to the query' do
134
+ api.find_by_kind('kind', :limit => 1)
135
+ fake_ds.queries.first.limit.should == 1
136
+ end
137
+
138
+ it 'passes offset to the query' do
139
+ api.find_by_kind('kind', :offset => 10)
140
+ fake_ds.queries.first.offset.should == 10
141
+ end
142
+
143
+ context 'formats records on return from ds' do
144
+ include_examples 'record formatting', lambda {|record|
145
+ Hyperion::API.datastore.returns = [[record]]
146
+ Hyperion::API.find_by_kind('kind').first
147
+ }
148
+ end
149
+ end
150
+
151
+ context 'delete by kind' do
152
+ context 'parses kind' do
153
+ include_examples 'kind formatting', lambda { |kind|
154
+ Hyperion::API.delete_by_kind(kind)
155
+ Hyperion::API.datastore.queries.last.kind
156
+ }
157
+ end
158
+
159
+ context 'parses filters' do
160
+ include_examples 'filtering', lambda { |filter|
161
+ Hyperion::API.delete_by_kind('kind', :filters => [filter])
162
+ Hyperion::API.datastore.queries.last.filters.first
163
+ }
164
+ end
165
+ end
166
+
167
+ it 'deletes by key' do
168
+ api.delete_by_key('delete_key')
169
+ fake_ds.key_queries.first.should == 'delete_key'
170
+ end
171
+
172
+ context 'count by kind' do
173
+ context 'parses kind' do
174
+ include_examples 'kind formatting', lambda { |kind|
175
+ Hyperion::API.count_by_kind(kind)
176
+ Hyperion::API.datastore.queries.last.kind
177
+ }
178
+ end
179
+
180
+ context 'parses filters' do
181
+ include_examples 'filtering', lambda { |filter|
182
+ Hyperion::API.count_by_kind('kind', :filters => [filter])
183
+ Hyperion::API.datastore.queries.last.filters.first
184
+ }
185
+ end
186
+ end
187
+
188
+ context 'find by key' do
189
+ it 'finds by key' do
190
+ api.find_by_key('key')
191
+ fake_ds.key_queries.first.should == 'key'
192
+ end
193
+
194
+ context 'formats records on return from ds' do
195
+ include_examples 'record formatting', lambda {|record|
196
+ Hyperion::API.datastore.returns = [record]
197
+ Hyperion::API.find_by_key('key')
198
+ }
199
+ end
200
+
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,11 @@
1
+ require 'hyperion/dev/memory'
2
+ require 'hyperion/dev/ds_spec'
3
+
4
+ describe Hyperion::Dev::Memory do
5
+
6
+ before :each do
7
+ Hyperion::API.datastore = Hyperion::Dev::Memory.new
8
+ end
9
+
10
+ it_behaves_like 'Datastore'
11
+ end
@@ -0,0 +1,43 @@
1
+ class FakeDatastore
2
+
3
+ attr_accessor :saved_records, :queries, :key_queries, :returns
4
+
5
+ def initialize
6
+ @saved_records = []
7
+ @returns = []
8
+ @queries = []
9
+ @key_queries = []
10
+ end
11
+
12
+ def save(records)
13
+ @saved_records += records
14
+ returns.shift || []
15
+ end
16
+
17
+ def find_by_key(key)
18
+ @key_queries << key
19
+ returns.shift || nil
20
+ end
21
+
22
+ def find(query)
23
+ @queries << query
24
+ returns.shift || []
25
+ end
26
+
27
+ def delete_by_key(key)
28
+ @key_queries << key
29
+ nil
30
+ end
31
+
32
+ def delete(query)
33
+ @queries << query
34
+ returns.shift || nil
35
+ end
36
+
37
+ def count(query)
38
+ @queries << query
39
+ returns.shift || 0
40
+ end
41
+
42
+ end
43
+
@@ -0,0 +1,93 @@
1
+ shared_examples_for 'kind formatting' do |actor|
2
+ {
3
+ 'one' => 'one',
4
+ :one => 'one',
5
+ :TheKind => 'the_kind',
6
+ 'TheKind' => 'the_kind'
7
+ }.each_pair do |kind, result|
8
+
9
+ it "#{kind} to #{result}" do
10
+ actor.call(kind).should == result
11
+ end
12
+
13
+ end
14
+ end
15
+
16
+ shared_examples_for 'field formatting' do |actor|
17
+
18
+ {
19
+ 'field' => :field,
20
+ 'Field' => :field,
21
+ 'FieldOne' => :field_one,
22
+ 'SomeBigAttr' => :some_big_attr,
23
+ :SomeBigAttr => :some_big_attr,
24
+ 'one-two-three' => :one_two_three,
25
+ 'one two three' => :one_two_three
26
+ }.each_pair do |field, result|
27
+
28
+ it "#{field.inspect} to #{result.inspect}" do
29
+ actor.call(field).should == result
30
+ end
31
+
32
+ end
33
+ end
34
+
35
+ shared_examples_for 'record formatting' do |actor|
36
+
37
+ context 'formats kind' do
38
+ include_examples 'kind formatting', lambda { |kind|
39
+ record = actor.call({:kind => kind})
40
+ record[:kind]
41
+ }
42
+ end
43
+
44
+ context 'formats fields' do
45
+ include_examples 'field formatting', lambda { |field|
46
+ record = actor.call({field => 'value'})
47
+ record.delete(:kind)
48
+ record.keys.first
49
+ }
50
+ end
51
+ end
52
+
53
+ shared_examples_for 'filtering' do |actor|
54
+
55
+ context 'field' do
56
+ include_examples 'field formatting', lambda { |field|
57
+ actor.call([field, '=', 0]).field
58
+ }
59
+ end
60
+
61
+ context 'operator' do
62
+
63
+ {
64
+ '=' => '=',
65
+ 'eq' => '=',
66
+ '<' => '<',
67
+ 'lt' => '<',
68
+ '>' => '>',
69
+ 'gt' => '>',
70
+ '<=' => '<=',
71
+ 'lte' => '<=',
72
+ '>=' => '>=',
73
+ 'gte' => '>=',
74
+ '!=' => '!=',
75
+ 'not' => '!=',
76
+ 'contains' => 'contains?',
77
+ 'contains?' => 'contains?',
78
+ 'in?' => 'contains?',
79
+ 'in' => 'contains?',
80
+ }.each_pair do |filter, result|
81
+
82
+ it "#{filter} to #{result}" do
83
+ actor.call([:attr, filter, 0]).operator.should == result
84
+ end
85
+
86
+ end
87
+ end
88
+
89
+ it 'passes the value to the filter' do
90
+ actor.call([:attr, '=', 0]).value.should == 0
91
+ end
92
+ end
93
+
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hyperion-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.alpha1
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - 8th Light, Inc.
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 2.11.0
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: 2.11.0
30
+ description: A Generic Persistence API for Ruby
31
+ email:
32
+ - myles@8thlight.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - lib/hyperion/dev/memory.rb
38
+ - lib/hyperion/dev/ds_spec.rb
39
+ - lib/hyperion/filter.rb
40
+ - lib/hyperion/api.rb
41
+ - lib/hyperion/sort.rb
42
+ - lib/hyperion/query.rb
43
+ - spec/hyperion/dev/memory_spec.rb
44
+ - spec/hyperion/shared_examples.rb
45
+ - spec/hyperion/api_spec.rb
46
+ - spec/hyperion/fake_ds.rb
47
+ homepage: https://github.com/mylesmegyesi/hyperion-ruby
48
+ licenses:
49
+ - Eclipse Public License
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: 1.8.7
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>'
64
+ - !ruby/object:Gem::Version
65
+ version: 1.3.1
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 1.8.24
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: A Generic Persistence API for Ruby
72
+ test_files:
73
+ - spec/hyperion/dev/memory_spec.rb
74
+ - spec/hyperion/shared_examples.rb
75
+ - spec/hyperion/api_spec.rb
76
+ - spec/hyperion/fake_ds.rb