hyperion-api 0.0.1.alpha5 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|