hyperion-api 0.0.1.alpha5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/hyperion.rb +283 -0
- data/lib/hyperion/dev/ds_spec.rb +14 -2
- data/lib/hyperion/filter.rb +8 -0
- data/lib/hyperion/format.rb +57 -0
- data/lib/hyperion/key.rb +13 -5
- data/lib/hyperion/memory.rb +2 -2
- data/lib/hyperion/query.rb +10 -0
- data/lib/hyperion/sort.rb +7 -0
- data/lib/hyperion/util.rb +10 -1
- data/spec/hyperion/key_spec.rb +7 -0
- data/spec/hyperion/memory_spec.rb +1 -1
- data/spec/hyperion/shared_examples.rb +191 -0
- data/spec/hyperion/util_spec.rb +41 -0
- data/spec/{hyperion/api_spec.rb → hyperion_spec.rb} +57 -42
- metadata +10 -10
- data/lib/hyperion/api.rb +0 -189
data/lib/hyperion.rb
ADDED
@@ -0,0 +1,283 @@
|
|
1
|
+
require 'hyperion/query'
|
2
|
+
require 'hyperion/filter'
|
3
|
+
require 'hyperion/sort'
|
4
|
+
require 'hyperion/util'
|
5
|
+
require 'hyperion/format'
|
6
|
+
|
7
|
+
module Hyperion
|
8
|
+
|
9
|
+
def self.defentity(kind)
|
10
|
+
kind = Format.format_kind(kind)
|
11
|
+
kind_spec = KindSpec.new(kind)
|
12
|
+
yield(kind_spec)
|
13
|
+
save_kind_spec(kind_spec)
|
14
|
+
pack(kind.to_sym) {|value| pack_record((value || {}).merge(:kind => kind))}
|
15
|
+
unpack(kind.to_sym) {|value| unpack_record((value || {}).merge(:kind => kind))}
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.pack(type, &block)
|
19
|
+
@packers ||= {}
|
20
|
+
@packers[type] = block
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.unpack(type, &block)
|
24
|
+
@unpackers ||= {}
|
25
|
+
@unpackers[type] = block
|
26
|
+
end
|
27
|
+
|
28
|
+
# Sets the active datastore
|
29
|
+
def self.datastore=(datastore)
|
30
|
+
@datastore = datastore
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the current datastore instance
|
34
|
+
def self.datastore
|
35
|
+
Thread.current[:datastore] || @datastore || raise('No Datastore installed')
|
36
|
+
end
|
37
|
+
|
38
|
+
# Assigns the datastore within the given block
|
39
|
+
def self.with_datastore(name, opts={})
|
40
|
+
Util.bind(:datastore, new_datastore(name, opts)) do
|
41
|
+
yield
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.new_datastore(name, opts={})
|
46
|
+
begin
|
47
|
+
require "hyperion/#{name}"
|
48
|
+
rescue LoadError
|
49
|
+
raise "Can't find datastore implementation: #{name}"
|
50
|
+
end
|
51
|
+
ds_klass = Hyperion.const_get(Util.class_name(name.to_s))
|
52
|
+
ds_klass.new(opts)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Saves a record. Any additional parameters will get merged onto the record before it is saved.
|
56
|
+
#
|
57
|
+
# Hyperion.save({:kind => :foo})
|
58
|
+
# => {:kind=>"foo", :key=>"<generated key>"}
|
59
|
+
# Hyperion.save({:kind => :foo}, :value => :bar)
|
60
|
+
# => {:kind=>"foo", :value=>:bar, :key=>"<generated key>"}
|
61
|
+
def self.save(record, attrs={})
|
62
|
+
save_many([record.merge(attrs || {})]).first
|
63
|
+
end
|
64
|
+
|
65
|
+
# Saves multiple records at once.
|
66
|
+
def self.save_many(records)
|
67
|
+
unpack_records(datastore.save(pack_records(records)))
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns true if the record is new (not saved/doesn't have a :key), false otherwise.
|
71
|
+
def self.new?(record)
|
72
|
+
!record.has_key?(:key)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Retrieves the value associated with the given key from the datastore. nil if it doesn't exist.
|
76
|
+
def self.find_by_key(key)
|
77
|
+
unpack_record(datastore.find_by_key(key))
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns all records of the specified kind that match the filters provided.
|
81
|
+
#
|
82
|
+
# find_by_kind(:dog) # returns all records with :kind of \"dog\"
|
83
|
+
# find_by_kind(:dog, :filters => [[:name, '=', "Fido"]]) # returns all dogs whos name is Fido
|
84
|
+
# find_by_kind(:dog, :filters => [[:age, '>', 2], [:age, '<', 5]]) # returns all dogs between the age of 2 and 5 (exclusive)
|
85
|
+
# find_by_kind(:dog, :sorts => [[:name, :asc]]) # returns all dogs in alphebetical order of their name
|
86
|
+
# 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
|
87
|
+
# find_by_kind(:dog, :limit => 10) # returns upto 10 dogs in undefined order
|
88
|
+
# find_by_kind(:dog, :sorts => [[:name, :asc]], :limit => 10) # returns upto the first 10 dogs in alphebetical order of their name
|
89
|
+
# find_by_kind(:dog, :sorts => [[:name, :asc]], :limit => 10, :offset => 10) # returns the second set of 10 dogs in alphebetical order of their name
|
90
|
+
#
|
91
|
+
# Filter operations and acceptable syntax:
|
92
|
+
# "=" "eq"
|
93
|
+
# "<" "lt"
|
94
|
+
# "<=" "lte"
|
95
|
+
# ">" "gt"
|
96
|
+
# ">=" "gte"
|
97
|
+
# "!=" "not"
|
98
|
+
# "contains?" "contains" "in?" "in"
|
99
|
+
#
|
100
|
+
# Sort orders and acceptable syntax:
|
101
|
+
# :asc "asc" :ascending "ascending"
|
102
|
+
# :desc "desc" :descending "descending"
|
103
|
+
def self.find_by_kind(kind, args={})
|
104
|
+
unpack_records(datastore.find(build_query(kind, args)))
|
105
|
+
end
|
106
|
+
|
107
|
+
# Removes the record stored with the given key. Returns nil no matter what.
|
108
|
+
def self.delete_by_key(key)
|
109
|
+
datastore.delete_by_key(key)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Deletes all records of the specified kind that match the filters provided.
|
113
|
+
def self.delete_by_kind(kind, args={})
|
114
|
+
datastore.delete(build_query(kind, args))
|
115
|
+
end
|
116
|
+
|
117
|
+
# Counts records of the specified kind that match the filters provided.
|
118
|
+
def self.count_by_kind(kind, args={})
|
119
|
+
datastore.count(build_query(kind, args))
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def self.build_query(kind, args)
|
125
|
+
kind = Format.format_kind(kind)
|
126
|
+
filters = build_filters(args[:filters])
|
127
|
+
sorts = build_sorts(args[:sorts])
|
128
|
+
Query.new(kind, filters, sorts, args[:limit], args[:offset])
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.build_filters(filters)
|
132
|
+
(filters || []).map do |(field, operator, value)|
|
133
|
+
operator = Format.format_operator(operator)
|
134
|
+
field = Format.format_field(field)
|
135
|
+
Filter.new(field, operator, value)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.build_sorts(sorts)
|
140
|
+
(sorts || []).map do |(field, order)|
|
141
|
+
field = Format.format_field(field)
|
142
|
+
order = Format.format_order(order)
|
143
|
+
Sort.new(field, order)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def self.unpack_records(records)
|
148
|
+
records.map do |record|
|
149
|
+
unpack_record(record)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.unpack_record(record)
|
154
|
+
if record
|
155
|
+
create_entity(record) do |field_spec, value|
|
156
|
+
field_spec.unpack(value)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.pack_records(records)
|
162
|
+
records.map do |record|
|
163
|
+
pack_record(record)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.pack_record(record)
|
168
|
+
if record
|
169
|
+
entity = create_entity(record) do |field_spec, value|
|
170
|
+
field_spec.pack(value || field_spec.default)
|
171
|
+
end
|
172
|
+
update_timestamps(entity)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.packer_for(type)
|
177
|
+
@packers[type]
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.unpacker_for(type)
|
181
|
+
@unpackers[type]
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.update_timestamps(record)
|
185
|
+
new?(record) ? update_created_at(record) : update_updated_at(record)
|
186
|
+
end
|
187
|
+
|
188
|
+
def self.update_updated_at(record)
|
189
|
+
spec = kind_spec_for(record[:kind])
|
190
|
+
if spec && spec.fields.include?(:updated_at)
|
191
|
+
record[:updated_at] = Time.now
|
192
|
+
end
|
193
|
+
record
|
194
|
+
end
|
195
|
+
|
196
|
+
def self.update_created_at(record)
|
197
|
+
spec = kind_spec_for(record[:kind])
|
198
|
+
if spec && spec.fields.include?(:created_at)
|
199
|
+
record[:created_at] = Time.now
|
200
|
+
end
|
201
|
+
record
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.create_entity(record)
|
205
|
+
record = Format.format_record(record)
|
206
|
+
kind = record[:kind]
|
207
|
+
spec = kind_spec_for(kind)
|
208
|
+
unless spec
|
209
|
+
record
|
210
|
+
else
|
211
|
+
key = record[:key]
|
212
|
+
base_record = {:kind => kind}
|
213
|
+
base_record[:key] = key if key
|
214
|
+
spec.fields.reduce(base_record) do |new_record, (name, spec)|
|
215
|
+
new_record[name] = yield(spec, record[name])
|
216
|
+
new_record
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.kind_spec_for(kind)
|
222
|
+
@kind_specs ||= {}
|
223
|
+
@kind_specs[kind]
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.save_kind_spec(kind_spec)
|
227
|
+
@kind_specs ||= {}
|
228
|
+
@kind_specs[kind_spec.kind] = kind_spec
|
229
|
+
end
|
230
|
+
|
231
|
+
class FieldSpec
|
232
|
+
|
233
|
+
attr_reader :name, :default
|
234
|
+
|
235
|
+
def initialize(name, opts={})
|
236
|
+
@name = name
|
237
|
+
@default = opts[:default]
|
238
|
+
@type = opts[:type]
|
239
|
+
@packer = opts[:packer]
|
240
|
+
@unpacker = opts[:unpacker]
|
241
|
+
end
|
242
|
+
|
243
|
+
def pack(value)
|
244
|
+
if @packer && @packer.respond_to?(:call)
|
245
|
+
@packer.call(value)
|
246
|
+
elsif @type
|
247
|
+
type_packer = Hyperion.packer_for(@type)
|
248
|
+
type_packer ? type_packer.call(value) : value
|
249
|
+
else
|
250
|
+
value
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def unpack(value)
|
255
|
+
if @unpacker && @unpacker.respond_to?(:call)
|
256
|
+
@unpacker.call(value)
|
257
|
+
elsif @type
|
258
|
+
type_packer = Hyperion.unpacker_for(@type)
|
259
|
+
type_packer ? type_packer.call(value) : value
|
260
|
+
else
|
261
|
+
value
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
|
267
|
+
class KindSpec
|
268
|
+
|
269
|
+
attr_reader :kind, :fields
|
270
|
+
|
271
|
+
def initialize(kind)
|
272
|
+
@kind = kind
|
273
|
+
@fields = {}
|
274
|
+
end
|
275
|
+
|
276
|
+
def field(name, opts={})
|
277
|
+
name = Format.format_field(name)
|
278
|
+
@fields[name] = FieldSpec.new(name, opts)
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
data/lib/hyperion/dev/ds_spec.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
shared_examples_for 'Datastore' do
|
2
2
|
|
3
3
|
def api
|
4
|
-
Hyperion
|
4
|
+
Hyperion
|
5
5
|
end
|
6
6
|
|
7
7
|
context 'save' do
|
@@ -99,6 +99,12 @@ shared_examples_for 'Datastore' do
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
|
+
it "can't filter on old values" do
|
103
|
+
record = api.find_by_kind('testing', :filters => [[:inti, '=', 12]]).first
|
104
|
+
api.save(record, :inti => 2)
|
105
|
+
api.find_by_kind('testing', :filters => [[:inti, '=', 12]]).should == []
|
106
|
+
end
|
107
|
+
|
102
108
|
context 'filters' do
|
103
109
|
|
104
110
|
[
|
@@ -108,6 +114,12 @@ shared_examples_for 'Datastore' do
|
|
108
114
|
[[[:inti, '=', 34]], [34], :inti],
|
109
115
|
[[[:inti, '!=', 34]], [1, 12, 23, 44, 45], :inti],
|
110
116
|
[[[:inti, 'in', [12, 34]]], [12, 34], :inti],
|
117
|
+
[[[:inti, '>', 10], [:inti, '<', 25]], [12, 23], :inti],
|
118
|
+
[[[:inti, '<', 25], [:inti, '>', 10]], [12, 23], :inti],
|
119
|
+
[[[:inti, '>', 25], [:data, '<', 'thirty4']], [44, 45], :inti],
|
120
|
+
[[[:data, '<', 'thirty4'], [:inti, '>', 25]], [44, 45], :inti],
|
121
|
+
[[[:inti, '>', 10], [:inti, '<', 25], [:inti, '=', 23]], [23], :inti],
|
122
|
+
[[[:inti, '=', 23], [:inti, '>', 10], [:inti, '<', 25]], [23], :inti],
|
111
123
|
[[[:inti, '<', 24], [:inti, '>', 25]], [], :inti],
|
112
124
|
[[[:inti, '!=', 12], [:inti, '!=', 23], [:inti, '!=', 34]], [1, 44, 45], :inti],
|
113
125
|
[[[:data, '<', 'qux']], ['one', 'forty4', 'forty5'], :data],
|
@@ -212,7 +224,7 @@ shared_examples_for 'Datastore' do
|
|
212
224
|
it filters.inspect do
|
213
225
|
api.delete_by_kind('testing', :filters => filters)
|
214
226
|
intis = api.find_by_kind('testing').map {|r| r[:inti]}
|
215
|
-
intis.should
|
227
|
+
intis.should =~ result
|
216
228
|
end
|
217
229
|
end
|
218
230
|
|
data/lib/hyperion/filter.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'hyperion/util'
|
2
|
+
|
3
|
+
module Hyperion
|
4
|
+
class Format
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def format_kind(kind)
|
9
|
+
Util.snake_case(kind.to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
def format_field(field)
|
13
|
+
Util.snake_case(field.to_s).to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def format_record(record)
|
17
|
+
record = record.reduce({}) do |new_record, (field_name, value)|
|
18
|
+
new_record[Util.snake_case(field_name.to_s).to_sym] = value
|
19
|
+
new_record
|
20
|
+
end
|
21
|
+
record[:kind] = format_kind(record[:kind])
|
22
|
+
record
|
23
|
+
end
|
24
|
+
|
25
|
+
def format_order(order)
|
26
|
+
order.to_sym
|
27
|
+
case order
|
28
|
+
when :desc, 'desc', 'descending'
|
29
|
+
:desc
|
30
|
+
when :asc, 'asc', 'ascending'
|
31
|
+
:asc
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def format_operator(operator)
|
36
|
+
case operator
|
37
|
+
when '=', 'eq'
|
38
|
+
'='
|
39
|
+
when '!=', 'not'
|
40
|
+
'!='
|
41
|
+
when '<', 'lt'
|
42
|
+
'<'
|
43
|
+
when '>', 'gt'
|
44
|
+
'>'
|
45
|
+
when '<=', 'lte'
|
46
|
+
'<='
|
47
|
+
when '>=', 'gte'
|
48
|
+
'>='
|
49
|
+
when 'contains?', 'contains', 'in?', 'in'
|
50
|
+
'contains?'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
data/lib/hyperion/key.rb
CHANGED
@@ -6,11 +6,11 @@ module Hyperion
|
|
6
6
|
class << self
|
7
7
|
|
8
8
|
def encode_key(value)
|
9
|
-
normalize(
|
9
|
+
normalize(encode(value))
|
10
10
|
end
|
11
11
|
|
12
12
|
def decode_key(value)
|
13
|
-
|
13
|
+
decode(denormalize(value))
|
14
14
|
end
|
15
15
|
|
16
16
|
def compose_key(kind, id=nil)
|
@@ -22,14 +22,22 @@ module Hyperion
|
|
22
22
|
decode_key(key).split(/:/).map {|part| decode_key(part)}
|
23
23
|
end
|
24
24
|
|
25
|
-
private
|
26
|
-
|
27
25
|
def generate_id
|
28
26
|
UUIDTools::UUID.random_create.to_s.gsub(/-/, '')
|
29
27
|
end
|
30
28
|
|
29
|
+
private
|
30
|
+
|
31
|
+
def encode(str)
|
32
|
+
[str].pack('m').tr('+/','-_').gsub("\n",'')
|
33
|
+
end
|
34
|
+
|
35
|
+
def decode(str)
|
36
|
+
str.tr('-_','+/').unpack('m')[0]
|
37
|
+
end
|
38
|
+
|
31
39
|
def normalize(value)
|
32
|
-
value.gsub(/=/, '')
|
40
|
+
value.chomp.gsub(/=/, '')
|
33
41
|
end
|
34
42
|
|
35
43
|
def denormalize(value)
|
data/lib/hyperion/memory.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'hyperion
|
1
|
+
require 'hyperion'
|
2
2
|
|
3
3
|
module Hyperion
|
4
4
|
class Memory
|
@@ -10,7 +10,7 @@ module Hyperion
|
|
10
10
|
|
11
11
|
def save(records)
|
12
12
|
records.map do |record|
|
13
|
-
key =
|
13
|
+
key = Hyperion.new?(record) ? generate_key : record[:key]
|
14
14
|
record[:key] = key
|
15
15
|
store[key] = record
|
16
16
|
record
|
data/lib/hyperion/query.rb
CHANGED
data/lib/hyperion/sort.rb
CHANGED
data/lib/hyperion/util.rb
CHANGED
data/spec/hyperion/key_spec.rb
CHANGED
@@ -36,6 +36,13 @@ describe Hyperion::Key do
|
|
36
36
|
Hyperion::Key.compose_key(:foo, 1).should == Hyperion::Key.compose_key(:foo, 1)
|
37
37
|
end
|
38
38
|
|
39
|
+
it 'composes and decomposes' do
|
40
|
+
kind = "testing"
|
41
|
+
id = "BLODQF0Z1DMEfQr7S3eBwfsX4ku"
|
42
|
+
key = Hyperion::Key.compose_key(kind, id)
|
43
|
+
Hyperion::Key.decompose_key(key).should == [kind, id]
|
44
|
+
end
|
45
|
+
|
39
46
|
it 'decomposes keys' do
|
40
47
|
key = Hyperion::Key.compose_key(:thing, 1)
|
41
48
|
Hyperion::Key.decompose_key(key).should == ['thing', '1']
|
@@ -50,6 +50,197 @@ shared_examples_for 'record formatting' do |actor|
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
+
shared_examples_for 'record packing' do |actor|
|
54
|
+
include_examples 'record formatting', lambda { |record|
|
55
|
+
actor.call(record)
|
56
|
+
}
|
57
|
+
|
58
|
+
Hyperion.defentity(:one_field) do |kind|
|
59
|
+
kind.field(:field, :default => 'ABC')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'only packs fields defined in the entity' do
|
63
|
+
result = actor.call(:kind => :one_field, :field => 'value', :foo => 'bar')
|
64
|
+
result.should == {:kind => 'one_field', :field => 'value'}
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'packs the key field if not defined' do
|
68
|
+
result = actor.call(:kind => :one_field, :key => '1234', :field => 'value', :foo => 'bar')
|
69
|
+
result.should == {:kind => 'one_field', :key => '1234', :field => 'value'}
|
70
|
+
end
|
71
|
+
|
72
|
+
Hyperion.defentity(:two_fields) do |kind|
|
73
|
+
kind.field(:field1, :default => 'ABC')
|
74
|
+
kind.field(:field2, :default => 'CBD')
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'applies defaults' do
|
78
|
+
actor.call(:kind => :two_fields).should == {:kind => 'two_fields', :field1 => 'ABC', :field2 => 'CBD'}
|
79
|
+
actor.call(:kind => :two_fields, :field1 => 'john').should == {:kind => 'two_fields', :field1 => 'john', :field2 => 'CBD'}
|
80
|
+
end
|
81
|
+
|
82
|
+
Hyperion.pack(Integer) do |value|
|
83
|
+
value ? value.to_i : value
|
84
|
+
end
|
85
|
+
|
86
|
+
Hyperion.pack(:up) do |value|
|
87
|
+
value ? value.upcase : value
|
88
|
+
end
|
89
|
+
|
90
|
+
Hyperion.pack(:down) do |value|
|
91
|
+
value ? value.downcase : value
|
92
|
+
end
|
93
|
+
|
94
|
+
Hyperion.defentity('nestedType') do |kind|
|
95
|
+
kind.field(:thingy, :type => Integer, :default => '2')
|
96
|
+
kind.field(:upped, :type => :up, :default => 'asdf')
|
97
|
+
end
|
98
|
+
|
99
|
+
Hyperion.defentity(:packable) do |kind|
|
100
|
+
kind.field(:widget, :type => Integer)
|
101
|
+
kind.field(:downed, :type => :down)
|
102
|
+
kind.field(:thing, :type => :nested_type)
|
103
|
+
kind.field(:bauble, :packer => lambda {|value| value ? value.reverse : value})
|
104
|
+
kind.field(:bad_packer, :packer => true)
|
105
|
+
kind.field(:two_packer, :type => Integer, :packer => lambda {|value| value ? value.reverse : value})
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'packs the given type' do
|
109
|
+
result = actor.call(:kind => :packable, :downed => 'ABC', :widget => '1')
|
110
|
+
result[:downed].should == 'abc'
|
111
|
+
result[:widget].should == 1
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'packs nested types' do
|
115
|
+
result = actor.call(:kind => :packable, :downed => 'ABC', :widget => '1')
|
116
|
+
result[:thing].should == {:kind => 'nested_type', :thingy => 2, :upped => 'ASDF'}
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'packs nested types and merges existing data' do
|
120
|
+
result = actor.call(:kind => :packable, :downed => 'ABC', :widget => '1', :thing => {:upped => 'FDAS'})
|
121
|
+
result[:thing].should == {:kind => 'nested_type', :thingy => 2, :upped => 'FDAS'}
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'packs with custom callables' do
|
125
|
+
result = actor.call(:kind => :packable, :bauble => 'cba')
|
126
|
+
result[:bauble].should == 'abc'
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'custom callable must respond to `call`' do
|
130
|
+
result = actor.call(:kind => :packable, :bad_packer => 'thing')
|
131
|
+
result[:bad_packer].should == 'thing'
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'prefers the custom packer over the type packer' do
|
135
|
+
result = actor.call(:kind => :packable, :two_packer => 'thing')
|
136
|
+
result[:two_packer].should == 'gniht'
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'Timestamps' do
|
140
|
+
|
141
|
+
Hyperion.defentity(:with_time) do |kind|
|
142
|
+
kind.field(:created_at)
|
143
|
+
kind.field(:updated_at)
|
144
|
+
end
|
145
|
+
|
146
|
+
Hyperion.defentity(:without_time) do |kind|
|
147
|
+
end
|
148
|
+
|
149
|
+
before :each do
|
150
|
+
@now = mock(:now)
|
151
|
+
Time.stub(:now).and_return(@now)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'auto populates created_at if it exists and if the record is new' do
|
155
|
+
old_time = mock(:old_time)
|
156
|
+
actor.call(:kind => :without_time).should == {:kind => 'without_time'}
|
157
|
+
actor.call(:kind => :with_time, :key => '1234', :created_at => old_time)[:created_at].should == old_time
|
158
|
+
actor.call(:kind => :with_time)[:created_at].should == @now
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'auto populates updated_at if it exists and if the record is not new' do
|
162
|
+
old_time = mock(:old_time)
|
163
|
+
actor.call(:kind => :without_time).should == {:kind => 'without_time'}
|
164
|
+
result = actor.call(:kind => :with_time, :key => '1234', :created_at => old_time, :updated_at => old_time)
|
165
|
+
result[:created_at].should == old_time
|
166
|
+
result[:updated_at].should == @now
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
shared_examples_for 'record unpacking' do |actor|
|
172
|
+
include_examples 'record formatting', lambda { |record|
|
173
|
+
actor.call(record)
|
174
|
+
}
|
175
|
+
|
176
|
+
it 'only unpacks defined fields' do
|
177
|
+
result = actor.call(:kind => :one_field, :field => 'value', :foo => 'bar')
|
178
|
+
result.should == {:kind=>"one_field", :field=>"value"}
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'unpacks the key field if not defined' do
|
182
|
+
result = actor.call(:kind => :one_field, :key => '1234', :field => 'value', :foo => 'bar')
|
183
|
+
result.should == {:kind=>"one_field", :key => '1234', :field=>"value"}
|
184
|
+
end
|
185
|
+
|
186
|
+
Hyperion.unpack(Integer) do |value|
|
187
|
+
value ? value.to_i : value
|
188
|
+
end
|
189
|
+
|
190
|
+
Hyperion.unpack(:up) do |value|
|
191
|
+
value ? value.upcase : value
|
192
|
+
end
|
193
|
+
|
194
|
+
Hyperion.unpack(:down) do |value|
|
195
|
+
value ? value.downcase : value
|
196
|
+
end
|
197
|
+
|
198
|
+
Hyperion.defentity('nested') do |kind|
|
199
|
+
kind.field(:thingy, :type => Integer, :default => '2')
|
200
|
+
kind.field(:upped, :type => :up, :default => 'asdf')
|
201
|
+
end
|
202
|
+
|
203
|
+
Hyperion.defentity(:unpackable) do |kind|
|
204
|
+
kind.field(:widget, :type => Integer)
|
205
|
+
kind.field(:downed, :type => :down)
|
206
|
+
kind.field(:thing, :type => :nested_type)
|
207
|
+
kind.field(:bauble, :unpacker => lambda {|value| value ? value.reverse : value})
|
208
|
+
kind.field(:bad_packer, :unpacker => true)
|
209
|
+
kind.field(:two_packer, :type => Integer, :unpacker => lambda {|value| value ? value.reverse : value})
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'unpacks the given type' do
|
213
|
+
result = actor.call(:kind => :unpackable, :downed => 'ABC', :widget => '1')
|
214
|
+
result[:downed].should == 'abc'
|
215
|
+
result[:widget].should == 1
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'unpacks nested types' do
|
219
|
+
result = actor.call(:kind => :unpackable, :downed => 'ABC', :widget => '1')
|
220
|
+
result[:thing].should == {:kind => 'nested_type', :thingy => nil, :upped => nil}
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'unpacks nested types and merges existing data' do
|
224
|
+
result = actor.call(:kind => :unpackable, :downed => 'ABC', :widget => '1', :thing => {:upped => 'FDAS'})
|
225
|
+
result[:thing].should == {:kind => 'nested_type', :thingy => nil, :upped => 'FDAS'}
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'unpacks with custom callables' do
|
229
|
+
result = actor.call(:kind => :unpackable, :bauble => 'cba')
|
230
|
+
result[:bauble].should == 'abc'
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'custom callable must respond to `call`' do
|
234
|
+
result = actor.call(:kind => :unpackable, :bad_packer => 'thing')
|
235
|
+
result[:bad_packer].should == 'thing'
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'prefers the custom packer over the type packer' do
|
239
|
+
result = actor.call(:kind => :unpackable, :two_packer => 'thing')
|
240
|
+
result[:two_packer].should == 'gniht'
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
53
244
|
shared_examples_for 'filtering' do |actor|
|
54
245
|
|
55
246
|
context 'field' do
|
data/spec/hyperion/util_spec.rb
CHANGED
@@ -51,4 +51,45 @@ describe Hyperion::Util do
|
|
51
51
|
util.class_name('').should == ''
|
52
52
|
util.class_name(nil).should == nil
|
53
53
|
end
|
54
|
+
|
55
|
+
context 'binding' do
|
56
|
+
it 'assigns the thread local var within the block' do
|
57
|
+
called = false
|
58
|
+
util.bind(:thing, 1) do
|
59
|
+
called = true
|
60
|
+
Thread.current[:thing].should == 1
|
61
|
+
end
|
62
|
+
called.should be_true
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'reassigns the previous value' do
|
66
|
+
Thread.current[:thing].should == nil
|
67
|
+
util.bind(:thing, 1) do
|
68
|
+
util.bind(:thing, 2) do
|
69
|
+
Thread.current[:thing].should == 2
|
70
|
+
end
|
71
|
+
Thread.current[:thing].should == 1
|
72
|
+
end
|
73
|
+
Thread.current[:thing].should == nil
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'reassigns when an exception is thrown' do
|
77
|
+
Thread.current[:thing].should == nil
|
78
|
+
util.bind(:thing, 1) do
|
79
|
+
expect {
|
80
|
+
util.bind(:thing, 2) do
|
81
|
+
raise 'my exception'
|
82
|
+
end
|
83
|
+
}.to raise_error('my exception')
|
84
|
+
Thread.current[:thing].should == 1
|
85
|
+
end
|
86
|
+
Thread.current[:thing].should == nil
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'return the result of the block' do
|
90
|
+
util.bind(:thing, 1) do
|
91
|
+
:return
|
92
|
+
end.should == :return
|
93
|
+
end
|
94
|
+
end
|
54
95
|
end
|
@@ -1,11 +1,11 @@
|
|
1
|
-
require 'hyperion
|
1
|
+
require 'hyperion'
|
2
2
|
require 'hyperion/shared_examples'
|
3
3
|
require 'hyperion/fake_ds'
|
4
4
|
|
5
|
-
describe Hyperion
|
5
|
+
describe Hyperion do
|
6
6
|
|
7
7
|
def api
|
8
|
-
Hyperion
|
8
|
+
Hyperion
|
9
9
|
end
|
10
10
|
|
11
11
|
context 'datastore' do
|
@@ -13,11 +13,27 @@ describe Hyperion::API do
|
|
13
13
|
expect{ subject.datastore }.to raise_error
|
14
14
|
end
|
15
15
|
|
16
|
-
it 'assigns datastore
|
16
|
+
it 'assigns the datastore with brute force' do
|
17
|
+
api.datastore = :something
|
18
|
+
api.datastore.should == :something
|
19
|
+
api.datastore = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'assigns datastore with elegance and returns the result' do
|
17
23
|
api.with_datastore(:memory) do
|
24
|
+
api.datastore.should be_a(Hyperion::Memory)
|
18
25
|
:return
|
19
26
|
end.should == :return
|
20
27
|
end
|
28
|
+
|
29
|
+
it 'prefers the thread-local datastore over the global datastore' do
|
30
|
+
api.datastore = :something_else
|
31
|
+
api.with_datastore(:memory) do
|
32
|
+
api.datastore.should be_a(Hyperion::Memory)
|
33
|
+
end
|
34
|
+
api.datastore = :something_else
|
35
|
+
api.datastore = nil
|
36
|
+
end
|
21
37
|
end
|
22
38
|
|
23
39
|
context 'factory' do
|
@@ -41,10 +57,9 @@ describe Hyperion::API do
|
|
41
57
|
end
|
42
58
|
|
43
59
|
context 'with fake datastore' do
|
44
|
-
attr_reader :fake_ds
|
45
60
|
|
46
61
|
def fake_ds
|
47
|
-
|
62
|
+
api.datastore
|
48
63
|
end
|
49
64
|
|
50
65
|
around :each do |example|
|
@@ -71,34 +86,34 @@ describe Hyperion::API do
|
|
71
86
|
api.datastore.saved_records.first.should == {:kind => 'one'}
|
72
87
|
end
|
73
88
|
|
74
|
-
context 'record
|
75
|
-
include_examples 'record
|
76
|
-
Hyperion
|
77
|
-
Hyperion
|
89
|
+
context 'record packing on save' do
|
90
|
+
include_examples 'record packing', lambda { |record|
|
91
|
+
Hyperion.save(record)
|
92
|
+
Hyperion.datastore.saved_records.last
|
78
93
|
}
|
79
94
|
end
|
80
95
|
|
81
|
-
context 'record
|
82
|
-
include_examples 'record
|
83
|
-
Hyperion
|
84
|
-
Hyperion
|
96
|
+
context 'record unpacking on return from datastore' do
|
97
|
+
include_examples 'record unpacking', lambda {|record|
|
98
|
+
Hyperion.datastore.returns = [[record]]
|
99
|
+
Hyperion.save({})
|
85
100
|
}
|
86
101
|
end
|
87
102
|
end
|
88
103
|
|
89
104
|
context 'save many' do
|
90
105
|
|
91
|
-
context 'record
|
92
|
-
include_examples 'record
|
93
|
-
Hyperion
|
94
|
-
Hyperion
|
106
|
+
context 'record packing on save' do
|
107
|
+
include_examples 'record packing', lambda { |record|
|
108
|
+
Hyperion.save_many([record])
|
109
|
+
Hyperion.datastore.saved_records.last
|
95
110
|
}
|
96
111
|
end
|
97
112
|
|
98
|
-
context 'record
|
99
|
-
include_examples 'record
|
100
|
-
Hyperion
|
101
|
-
Hyperion
|
113
|
+
context 'record unpacking on return from datastore' do
|
114
|
+
include_examples 'record unpacking', lambda { |record|
|
115
|
+
Hyperion.datastore.returns = [[record]]
|
116
|
+
Hyperion.save_many([{}]).last
|
102
117
|
}
|
103
118
|
end
|
104
119
|
end
|
@@ -106,15 +121,15 @@ describe Hyperion::API do
|
|
106
121
|
context 'find by kind' do
|
107
122
|
context 'parses kind' do
|
108
123
|
include_examples 'kind formatting', lambda { |kind|
|
109
|
-
Hyperion
|
110
|
-
Hyperion
|
124
|
+
Hyperion.find_by_kind(kind)
|
125
|
+
Hyperion.datastore.queries.last.kind
|
111
126
|
}
|
112
127
|
end
|
113
128
|
|
114
129
|
context 'parses filters' do
|
115
130
|
include_examples 'filtering', lambda { |filter|
|
116
|
-
Hyperion
|
117
|
-
Hyperion
|
131
|
+
Hyperion.find_by_kind('kind', :filters => [filter])
|
132
|
+
Hyperion.datastore.queries.last.filters.first
|
118
133
|
}
|
119
134
|
end
|
120
135
|
|
@@ -128,8 +143,8 @@ describe Hyperion::API do
|
|
128
143
|
|
129
144
|
context 'field' do
|
130
145
|
include_examples 'field formatting', lambda { |field|
|
131
|
-
Hyperion
|
132
|
-
Hyperion
|
146
|
+
Hyperion.find_by_kind('kind', :sorts => [[field, 'desc']])
|
147
|
+
Hyperion.datastore.queries.first.sorts.first.field
|
133
148
|
}
|
134
149
|
end
|
135
150
|
|
@@ -162,9 +177,9 @@ describe Hyperion::API do
|
|
162
177
|
end
|
163
178
|
|
164
179
|
context 'formats records on return from ds' do
|
165
|
-
include_examples 'record
|
166
|
-
Hyperion
|
167
|
-
Hyperion
|
180
|
+
include_examples 'record unpacking', lambda {|record|
|
181
|
+
Hyperion.datastore.returns = [[record]]
|
182
|
+
Hyperion.find_by_kind('kind').first
|
168
183
|
}
|
169
184
|
end
|
170
185
|
end
|
@@ -172,15 +187,15 @@ describe Hyperion::API do
|
|
172
187
|
context 'delete by kind' do
|
173
188
|
context 'parses kind' do
|
174
189
|
include_examples 'kind formatting', lambda { |kind|
|
175
|
-
Hyperion
|
176
|
-
Hyperion
|
190
|
+
Hyperion.delete_by_kind(kind)
|
191
|
+
Hyperion.datastore.queries.last.kind
|
177
192
|
}
|
178
193
|
end
|
179
194
|
|
180
195
|
context 'parses filters' do
|
181
196
|
include_examples 'filtering', lambda { |filter|
|
182
|
-
Hyperion
|
183
|
-
Hyperion
|
197
|
+
Hyperion.delete_by_kind('kind', :filters => [filter])
|
198
|
+
Hyperion.datastore.queries.last.filters.first
|
184
199
|
}
|
185
200
|
end
|
186
201
|
end
|
@@ -193,15 +208,15 @@ describe Hyperion::API do
|
|
193
208
|
context 'count by kind' do
|
194
209
|
context 'parses kind' do
|
195
210
|
include_examples 'kind formatting', lambda { |kind|
|
196
|
-
Hyperion
|
197
|
-
Hyperion
|
211
|
+
Hyperion.count_by_kind(kind)
|
212
|
+
Hyperion.datastore.queries.last.kind
|
198
213
|
}
|
199
214
|
end
|
200
215
|
|
201
216
|
context 'parses filters' do
|
202
217
|
include_examples 'filtering', lambda { |filter|
|
203
|
-
Hyperion
|
204
|
-
Hyperion
|
218
|
+
Hyperion.count_by_kind('kind', :filters => [filter])
|
219
|
+
Hyperion.datastore.queries.last.filters.first
|
205
220
|
}
|
206
221
|
end
|
207
222
|
end
|
@@ -213,9 +228,9 @@ describe Hyperion::API do
|
|
213
228
|
end
|
214
229
|
|
215
230
|
context 'formats records on return from ds' do
|
216
|
-
include_examples 'record
|
217
|
-
Hyperion
|
218
|
-
Hyperion
|
231
|
+
include_examples 'record unpacking', lambda {|record|
|
232
|
+
Hyperion.datastore.returns = [record]
|
233
|
+
Hyperion.find_by_key('key')
|
219
234
|
}
|
220
235
|
end
|
221
236
|
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hyperion-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
|
-
-
|
8
|
+
- Myles Megyesi
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-10-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -46,24 +46,24 @@ dependencies:
|
|
46
46
|
description: A Generic Persistence API for Ruby
|
47
47
|
email:
|
48
48
|
- myles@8thlight.com
|
49
|
-
- skim@8thlight.com
|
50
49
|
executables: []
|
51
50
|
extensions: []
|
52
51
|
extra_rdoc_files: []
|
53
52
|
files:
|
54
53
|
- lib/hyperion/dev/ds_spec.rb
|
54
|
+
- lib/hyperion/format.rb
|
55
55
|
- lib/hyperion/util.rb
|
56
56
|
- lib/hyperion/filter.rb
|
57
|
-
- lib/hyperion/api.rb
|
58
57
|
- lib/hyperion/key.rb
|
59
58
|
- lib/hyperion/memory.rb
|
60
59
|
- lib/hyperion/sort.rb
|
61
60
|
- lib/hyperion/query.rb
|
61
|
+
- lib/hyperion.rb
|
62
|
+
- spec/hyperion_spec.rb
|
62
63
|
- spec/hyperion/util_spec.rb
|
63
64
|
- spec/hyperion/key_spec.rb
|
64
65
|
- spec/hyperion/shared_examples.rb
|
65
66
|
- spec/hyperion/memory_spec.rb
|
66
|
-
- spec/hyperion/api_spec.rb
|
67
67
|
- spec/hyperion/fake_ds.rb
|
68
68
|
homepage: https://github.com/mylesmegyesi/hyperion-ruby
|
69
69
|
licenses:
|
@@ -81,9 +81,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
82
|
none: false
|
83
83
|
requirements:
|
84
|
-
- - ! '
|
84
|
+
- - ! '>='
|
85
85
|
- !ruby/object:Gem::Version
|
86
|
-
version:
|
86
|
+
version: '0'
|
87
87
|
requirements: []
|
88
88
|
rubyforge_project:
|
89
89
|
rubygems_version: 1.8.24
|
@@ -91,9 +91,9 @@ signing_key:
|
|
91
91
|
specification_version: 3
|
92
92
|
summary: A Generic Persistence API for Ruby
|
93
93
|
test_files:
|
94
|
+
- spec/hyperion_spec.rb
|
94
95
|
- spec/hyperion/util_spec.rb
|
95
96
|
- spec/hyperion/key_spec.rb
|
96
97
|
- spec/hyperion/shared_examples.rb
|
97
98
|
- spec/hyperion/memory_spec.rb
|
98
|
-
- spec/hyperion/api_spec.rb
|
99
99
|
- spec/hyperion/fake_ds.rb
|
data/lib/hyperion/api.rb
DELETED
@@ -1,189 +0,0 @@
|
|
1
|
-
require 'hyperion/query'
|
2
|
-
require 'hyperion/filter'
|
3
|
-
require 'hyperion/sort'
|
4
|
-
require 'hyperion/util'
|
5
|
-
|
6
|
-
module Hyperion
|
7
|
-
class API
|
8
|
-
|
9
|
-
class << self
|
10
|
-
|
11
|
-
attr_writer :datastore
|
12
|
-
|
13
|
-
# Sets the thread-local active datastore
|
14
|
-
def datastore=(datastore)
|
15
|
-
Thread.current[:datastore] = datastore
|
16
|
-
end
|
17
|
-
|
18
|
-
# Returns the current thread-local datastore instance
|
19
|
-
def datastore
|
20
|
-
Thread.current[:datastore] || raise('No Datastore installed')
|
21
|
-
end
|
22
|
-
|
23
|
-
# Assigns the datastore within the given block
|
24
|
-
def with_datastore(name, opts={})
|
25
|
-
self.datastore = new_datastore(name, opts)
|
26
|
-
result = yield
|
27
|
-
self.datastore = nil
|
28
|
-
result
|
29
|
-
end
|
30
|
-
|
31
|
-
def new_datastore(name, opts={})
|
32
|
-
begin
|
33
|
-
require "hyperion/#{name}"
|
34
|
-
rescue LoadError
|
35
|
-
raise "Can't find datastore implementation: #{name}"
|
36
|
-
end
|
37
|
-
ds_klass = Hyperion.const_get(Util.class_name(name.to_s))
|
38
|
-
ds_klass.new(opts)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Saves a record. Any additional parameters will get merged onto the record before it is saved.
|
42
|
-
|
43
|
-
# Hyperion::API.save({:kind => :foo})
|
44
|
-
# => {:kind=>"foo", :key=>"<generated key>"}
|
45
|
-
# Hyperion::API.save({:kind => :foo}, :value => :bar)
|
46
|
-
# => {:kind=>"foo", :value=>:bar, :key=>"<generated key>"}
|
47
|
-
def save(record, attrs={})
|
48
|
-
save_many([record.merge(attrs || {})]).first
|
49
|
-
end
|
50
|
-
|
51
|
-
# Saves multiple records at once.
|
52
|
-
def save_many(records)
|
53
|
-
format_records(datastore.save(format_records(records)))
|
54
|
-
end
|
55
|
-
|
56
|
-
# Returns true if the record is new (not saved/doesn't have a :key), false otherwise.
|
57
|
-
def new?(record)
|
58
|
-
!record.has_key?(:key)
|
59
|
-
end
|
60
|
-
|
61
|
-
# Retrieves the value associated with the given key from the datastore. nil if it doesn't exist.
|
62
|
-
def find_by_key(key)
|
63
|
-
format_record(datastore.find_by_key(key))
|
64
|
-
end
|
65
|
-
|
66
|
-
# Returns all records of the specified kind that match the filters provided.
|
67
|
-
#
|
68
|
-
# find_by_kind(:dog) # returns all records with :kind of \"dog\"
|
69
|
-
# find_by_kind(:dog, :filters => [[:name, '=', "Fido"]]) # returns all dogs whos name is Fido
|
70
|
-
# find_by_kind(:dog, :filters => [[:age, '>', 2], [:age, '<', 5]]) # returns all dogs between the age of 2 and 5 (exclusive)
|
71
|
-
# find_by_kind(:dog, :sorts => [[:name, :asc]]) # returns all dogs in alphebetical order of their name
|
72
|
-
# 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
|
73
|
-
# find_by_kind(:dog, :limit => 10) # returns upto 10 dogs in undefined order
|
74
|
-
# find_by_kind(:dog, :sorts => [[:name, :asc]], :limit => 10) # returns upto the first 10 dogs in alphebetical order of their name
|
75
|
-
# find_by_kind(:dog, :sorts => [[:name, :asc]], :limit => 10, :offset => 10) # returns the second set of 10 dogs in alphebetical order of their name
|
76
|
-
#
|
77
|
-
# Filter operations and acceptable syntax:
|
78
|
-
# "=" "eq"
|
79
|
-
# "<" "lt"
|
80
|
-
# "<=" "lte"
|
81
|
-
# ">" "gt"
|
82
|
-
# ">=" "gte"
|
83
|
-
# "!=" "not"
|
84
|
-
# "contains?" "contains" "in?" "in"
|
85
|
-
#
|
86
|
-
# Sort orders and acceptable syntax:
|
87
|
-
# :asc "asc" :ascending "ascending"
|
88
|
-
# :desc "desc" :descending "descending"
|
89
|
-
def find_by_kind(kind, args={})
|
90
|
-
format_records(datastore.find(build_query(kind, args)))
|
91
|
-
end
|
92
|
-
|
93
|
-
# Removes the record stored with the given key. Returns nil no matter what.
|
94
|
-
def delete_by_key(key)
|
95
|
-
datastore.delete_by_key(key)
|
96
|
-
end
|
97
|
-
|
98
|
-
# Deletes all records of the specified kind that match the filters provided.
|
99
|
-
def delete_by_kind(kind, args={})
|
100
|
-
datastore.delete(build_query(kind, args))
|
101
|
-
end
|
102
|
-
|
103
|
-
# Counts records of the specified kind that match the filters provided.
|
104
|
-
def count_by_kind(kind, args={})
|
105
|
-
datastore.count(build_query(kind, args))
|
106
|
-
end
|
107
|
-
|
108
|
-
private
|
109
|
-
|
110
|
-
def build_query(kind, args)
|
111
|
-
kind = format_kind(kind)
|
112
|
-
filters = build_filters(args[:filters])
|
113
|
-
sorts = build_sorts(args[:sorts])
|
114
|
-
Query.new(kind, filters, sorts, args[:limit], args[:offset])
|
115
|
-
end
|
116
|
-
|
117
|
-
def build_filters(filters)
|
118
|
-
(filters || []).map do |(field, operator, value)|
|
119
|
-
operator = format_operator(operator)
|
120
|
-
field = format_field(field)
|
121
|
-
Filter.new(field, operator, value)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
def build_sorts(sorts)
|
126
|
-
(sorts || []).map do |(field, order)|
|
127
|
-
field = format_field(field)
|
128
|
-
order = format_order(order)
|
129
|
-
Sort.new(field, order)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def format_order(order)
|
134
|
-
order.to_sym
|
135
|
-
case order
|
136
|
-
when :desc, 'desc', 'descending'
|
137
|
-
:desc
|
138
|
-
when :asc, 'asc', 'ascending'
|
139
|
-
:asc
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def format_operator(operator)
|
144
|
-
case operator
|
145
|
-
when '=', 'eq'
|
146
|
-
'='
|
147
|
-
when '!=', 'not'
|
148
|
-
'!='
|
149
|
-
when '<', 'lt'
|
150
|
-
'<'
|
151
|
-
when '>', 'gt'
|
152
|
-
'>'
|
153
|
-
when '<=', 'lte'
|
154
|
-
'<='
|
155
|
-
when '>=', 'gte'
|
156
|
-
'>='
|
157
|
-
when 'contains?', 'contains', 'in?', 'in'
|
158
|
-
'contains?'
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def format_records(records)
|
163
|
-
records.map do |record|
|
164
|
-
format_record(record)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def format_record(record)
|
169
|
-
if record
|
170
|
-
record = record.reduce({}) do |new_record, (key, value)|
|
171
|
-
new_record[Util.snake_case(key.to_s).to_sym] = value
|
172
|
-
new_record
|
173
|
-
end
|
174
|
-
record[:kind] = format_kind(record[:kind])
|
175
|
-
record
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
def format_kind(kind)
|
180
|
-
Util.snake_case(kind.to_s)
|
181
|
-
end
|
182
|
-
|
183
|
-
def format_field(field)
|
184
|
-
Util.snake_case(field.to_s).to_sym
|
185
|
-
end
|
186
|
-
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|