endymion 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/endymion.rb +21 -0
- data/lib/endymion/api.rb +284 -0
- data/lib/endymion/dev/ds_spec.rb +422 -0
- data/lib/endymion/fake_ds.rb +53 -0
- data/lib/endymion/field_spec.rb +41 -0
- data/lib/endymion/filter.rb +20 -0
- data/lib/endymion/format.rb +57 -0
- data/lib/endymion/key.rb +14 -0
- data/lib/endymion/kind_spec.rb +21 -0
- data/lib/endymion/memory.rb +75 -0
- data/lib/endymion/memory/helper.rb +52 -0
- data/lib/endymion/query.rb +24 -0
- data/lib/endymion/sort.rb +26 -0
- data/lib/endymion/types.rb +22 -0
- data/lib/endymion/util.rb +55 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 7111de9207c18485a31aece27f1ed4f68ef6399d
|
4
|
+
data.tar.gz: 6643fae41c6caafee6a1192fa2ebaa5ef23d37e1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a9857cb58fba27edae36ebddd1b48e0dd27001752bde1dac7c74c0bbc4c2935f64d3dcaa60b5a86afd29b0a763e2725bd919db9959c7a4020a17a7ea96c16510
|
7
|
+
data.tar.gz: ee254058fd74cb6a91c59526f09ac5fde80aa95afbc20cd5b103135efe59f6f2c0957ed07545f96bdf998bff2ea997fd21290fe6ae81ba55f5d536af4ee688eb
|
data/lib/endymion.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'endymion/api'
|
2
|
+
module Endymion
|
3
|
+
|
4
|
+
def self.new(datastore_name, datastore_opts={})
|
5
|
+
Endymion::API.new(new_datastore(datastore_name, datastore_opts))
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.new_datastore(name, opts={})
|
9
|
+
begin
|
10
|
+
require "endymion/#{name}"
|
11
|
+
rescue LoadError
|
12
|
+
raise "Can't find datastore implementation: #{name}"
|
13
|
+
end
|
14
|
+
ds_klass = Endymion.const_get(Util.class_name(name.to_s))
|
15
|
+
ds_klass.new(opts)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.new?(record)
|
19
|
+
!record.has_key?(:key)
|
20
|
+
end
|
21
|
+
end
|
data/lib/endymion/api.rb
ADDED
@@ -0,0 +1,284 @@
|
|
1
|
+
require 'endymion/format'
|
2
|
+
require 'endymion/field_spec'
|
3
|
+
require 'endymion/kind_spec'
|
4
|
+
require 'endymion/query'
|
5
|
+
require 'endymion/filter'
|
6
|
+
require 'endymion/sort'
|
7
|
+
|
8
|
+
module Endymion
|
9
|
+
class API
|
10
|
+
|
11
|
+
attr_reader :datastore
|
12
|
+
|
13
|
+
def initialize(datastore)
|
14
|
+
@datastore = datastore
|
15
|
+
end
|
16
|
+
|
17
|
+
def defentity(kind)
|
18
|
+
kind = Format.format_kind(kind)
|
19
|
+
kind_spec = KindSpec.new(kind, self)
|
20
|
+
yield(kind_spec)
|
21
|
+
save_kind_spec(kind_spec)
|
22
|
+
pack(kind.to_sym) {|value| pack_record((value || {}).merge(:kind => kind))}
|
23
|
+
unpack(kind.to_sym) {|value| unpack_record((value || {}).merge(:kind => kind))}
|
24
|
+
end
|
25
|
+
|
26
|
+
def pack(type, &block)
|
27
|
+
packers[type] = block
|
28
|
+
end
|
29
|
+
|
30
|
+
def packer_defined?(type)
|
31
|
+
packers.has_key?(type)
|
32
|
+
end
|
33
|
+
|
34
|
+
def unpack(type, &block)
|
35
|
+
unpackers[type] = block
|
36
|
+
end
|
37
|
+
|
38
|
+
def unpacker_defined?(type)
|
39
|
+
unpackers.has_key?(type)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Assigns the datastore within the given block
|
43
|
+
def with_datastore(name, opts={})
|
44
|
+
old_datastore = @datastore
|
45
|
+
@datastore = Endymion.new_datastore(name, opts)
|
46
|
+
begin
|
47
|
+
yield
|
48
|
+
rescue
|
49
|
+
@datastore = old_datastore
|
50
|
+
raise
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
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 save(record, attrs={})
|
62
|
+
save_many([record.merge(attrs || {})]).first
|
63
|
+
end
|
64
|
+
|
65
|
+
# Saves multiple records at once.
|
66
|
+
def save_many(records)
|
67
|
+
unpack_records(datastore.save(pack_records(records)))
|
68
|
+
end
|
69
|
+
|
70
|
+
# Creates a record. While save delegates to a create or update based on the result
|
71
|
+
# of new?, this always creates. This allows you to set the key explicitly
|
72
|
+
def create(record)
|
73
|
+
create_many([record]).first
|
74
|
+
end
|
75
|
+
|
76
|
+
# Create many records at once
|
77
|
+
def create_many(records)
|
78
|
+
unpack_records(datastore.create(pack_records(records)))
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns true if the record is new (not saved/doesn't have a :key), false otherwise.
|
82
|
+
def new?(record)
|
83
|
+
!record.has_key?(:key)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Retrieves the value associated with the given key from the datastore. nil if it doesn't exist.
|
87
|
+
def find_by_key(kind, key)
|
88
|
+
unpack_record(datastore.find_by_key(kind, key))
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns all records of the specified kind that match the filters provided.
|
92
|
+
#
|
93
|
+
# find_by_kind(:dog) # returns all records with :kind of \"dog\"
|
94
|
+
# find_by_kind(:dog, :filters => [[:name, '=', "Fido"]]) # returns all dogs whos name is Fido
|
95
|
+
# find_by_kind(:dog, :filters => [[:age, '>', 2], [:age, '<', 5]]) # returns all dogs between the age of 2 and 5 (exclusive)
|
96
|
+
# find_by_kind(:dog, :sorts => [[:name, :asc]]) # returns all dogs in alphebetical order of their name
|
97
|
+
# 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
|
98
|
+
# find_by_kind(:dog, :limit => 10) # returns upto 10 dogs in undefined order
|
99
|
+
# find_by_kind(:dog, :sorts => [[:name, :asc]], :limit => 10) # returns upto the first 10 dogs in alphebetical order of their name
|
100
|
+
# find_by_kind(:dog, :sorts => [[:name, :asc]], :limit => 10, :offset => 10) # returns the second set of 10 dogs in alphebetical order of their name
|
101
|
+
#
|
102
|
+
# Filter operations and acceptable syntax:
|
103
|
+
# "=" "eq"
|
104
|
+
# "<" "lt"
|
105
|
+
# "<=" "lte"
|
106
|
+
# ">" "gt"
|
107
|
+
# ">=" "gte"
|
108
|
+
# "!=" "not"
|
109
|
+
# "contains?" "contains" "in?" "in"
|
110
|
+
#
|
111
|
+
# Sort orders and acceptable syntax:
|
112
|
+
# :asc "asc" :ascending "ascending"
|
113
|
+
# :desc "desc" :descending "descending"
|
114
|
+
def find_by_kind(kind, args={})
|
115
|
+
unpack_records(datastore.find(build_query(kind, args)))
|
116
|
+
end
|
117
|
+
|
118
|
+
# Removes the record stored with the given key. Returns nil no matter what.
|
119
|
+
def delete_by_key(kind, key)
|
120
|
+
datastore.delete_by_key(kind, key)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Deletes all records of the specified kind that match the filters provided.
|
124
|
+
def delete_by_kind(kind, args={})
|
125
|
+
datastore.delete(build_query(kind, args))
|
126
|
+
end
|
127
|
+
|
128
|
+
# Counts records of the specified kind that match the filters provided.
|
129
|
+
def count_by_kind(kind, args={})
|
130
|
+
datastore.count(build_query(kind, args))
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
# NOTE: these methods were marked as private in Hyperion, but were actually
|
136
|
+
# accessed from outside, in the FieldSpec class. Temporarily made public until
|
137
|
+
# things are cleand up
|
138
|
+
def packer_for(type)
|
139
|
+
@packers[type]
|
140
|
+
end
|
141
|
+
|
142
|
+
def unpacker_for(type)
|
143
|
+
@unpackers[type]
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def packers
|
150
|
+
@packers ||= {}
|
151
|
+
end
|
152
|
+
|
153
|
+
def unpackers
|
154
|
+
@unpackers ||= {}
|
155
|
+
end
|
156
|
+
|
157
|
+
def build_query(kind, args)
|
158
|
+
kind = Format.format_kind(kind)
|
159
|
+
filters = build_filters(kind, args[:filters])
|
160
|
+
sorts = build_sorts(kind, args[:sorts])
|
161
|
+
Query.new(kind, filters, sorts, args[:limit], args[:offset])
|
162
|
+
end
|
163
|
+
|
164
|
+
def build_filters(kind, filters)
|
165
|
+
(filters || []).map do |(field, operator, value)|
|
166
|
+
operator = Format.format_operator(operator)
|
167
|
+
packed_field = pack_field(kind, field)
|
168
|
+
value = pack_value(kind, field, value)
|
169
|
+
Filter.new(packed_field, operator, value)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def build_sorts(kind, sorts)
|
174
|
+
(sorts || []).map do |(field, order)|
|
175
|
+
field = pack_field(kind, field)
|
176
|
+
order = Format.format_order(order)
|
177
|
+
Sort.new(field, order)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def unpack_records(records)
|
182
|
+
records.map do |record|
|
183
|
+
unpack_record(record)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def unpack_record(record)
|
188
|
+
if record
|
189
|
+
create_entity(record) do |record, entity, field, field_spec|
|
190
|
+
value = record[field_spec.db_name]
|
191
|
+
entity[field] = field_spec.unpack(value)
|
192
|
+
entity
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def pack_records(records)
|
198
|
+
records.map do |record|
|
199
|
+
pack_record(record)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def pack_record(record)
|
204
|
+
if record
|
205
|
+
entity = create_entity(record) do |record, entity, field, field_spec|
|
206
|
+
value = record[field]
|
207
|
+
entity[field_spec.db_name] = field_spec.pack(value || field_spec.default)
|
208
|
+
entity
|
209
|
+
end
|
210
|
+
update_timestamps(entity)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def pack_field(kind, field)
|
215
|
+
field = Format.format_field(field)
|
216
|
+
kind_spec = kind_spec_for(kind)
|
217
|
+
return field unless kind_spec
|
218
|
+
field_spec = kind_spec.fields[field]
|
219
|
+
return field unless field_spec
|
220
|
+
return field_spec.db_name
|
221
|
+
end
|
222
|
+
|
223
|
+
def pack_value(kind, field, value)
|
224
|
+
kind_spec = kind_spec_for(kind)
|
225
|
+
return value unless kind_spec
|
226
|
+
field_spec = kind_spec.fields[field]
|
227
|
+
return value unless field_spec
|
228
|
+
return field_spec.pack(value)
|
229
|
+
end
|
230
|
+
|
231
|
+
def update_timestamps(record)
|
232
|
+
new?(record) ? update_created_at(record) : update_updated_at(record)
|
233
|
+
end
|
234
|
+
|
235
|
+
def update_updated_at(record)
|
236
|
+
spec = kind_spec_for(record[:kind])
|
237
|
+
if spec && spec.fields.include?(:updated_at)
|
238
|
+
record[:updated_at] = Time.now
|
239
|
+
end
|
240
|
+
record
|
241
|
+
end
|
242
|
+
|
243
|
+
def update_created_at(record)
|
244
|
+
spec = kind_spec_for(record[:kind])
|
245
|
+
if spec && spec.fields.include?(:created_at)
|
246
|
+
record[:created_at] = Time.now
|
247
|
+
end
|
248
|
+
record
|
249
|
+
end
|
250
|
+
|
251
|
+
def create_entity(record)
|
252
|
+
record = Format.format_record(record)
|
253
|
+
kind = record[:kind]
|
254
|
+
spec = kind_spec_for(kind)
|
255
|
+
unless spec
|
256
|
+
record
|
257
|
+
else
|
258
|
+
key = record[:key]
|
259
|
+
base_record = {:kind => kind}
|
260
|
+
base_record[:key] = key if key
|
261
|
+
spec.fields.reduce(base_record) do |new_record, (name, spec)|
|
262
|
+
yield(record, new_record, name, spec)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def kind_spec_for(kind)
|
268
|
+
@kind_specs ||= {}
|
269
|
+
@kind_specs[kind]
|
270
|
+
end
|
271
|
+
|
272
|
+
def save_kind_spec(kind_spec)
|
273
|
+
@kind_specs ||= {}
|
274
|
+
@kind_specs[kind_spec.kind] = kind_spec
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
|
279
|
+
|
280
|
+
|
281
|
+
|
282
|
+
|
283
|
+
end
|
284
|
+
end
|
@@ -0,0 +1,422 @@
|
|
1
|
+
require 'endymion/types'
|
2
|
+
|
3
|
+
shared_examples_for 'Datastore' do
|
4
|
+
|
5
|
+
let(:api) do
|
6
|
+
Endymion::API.new(datastore)
|
7
|
+
end
|
8
|
+
|
9
|
+
# before(:each) do
|
10
|
+
# api.defentity(:shirt) do |kind|
|
11
|
+
# kind.field(:account_key, :type => Hyperion::Types.foreign_key(:account), :db_name => :account_id)
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# api.defentity(:account) do |kind|
|
15
|
+
# kind.field(:first_name)
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# end
|
20
|
+
|
21
|
+
context 'save' do
|
22
|
+
|
23
|
+
it 'saves a hash and returns it' do
|
24
|
+
record = api.save({:kind => 'testing', :name => 'ann'})
|
25
|
+
record[:kind].should == 'testing'
|
26
|
+
record[:name].should == 'ann'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'saves an empty record' do
|
30
|
+
record = api.save({:kind => 'testing'})
|
31
|
+
record[:kind].should == 'testing'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'assigns a key to new records' do
|
35
|
+
record = api.save({:kind => 'testing', :name => 'ann'})
|
36
|
+
record[:key].should_not be_nil
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'saves an existing record' do
|
40
|
+
record1 = api.save({:kind => 'other_testing', :name => 'ann'})
|
41
|
+
record2 = api.save({:kind => 'other_testing', :key => record1[:key]}, :name => 'james')
|
42
|
+
record1[:key].should == record2[:key]
|
43
|
+
api.find_by_kind('other_testing').length.should == 1
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'saves an existing record to be empty' do
|
47
|
+
record1 = api.save({:kind => 'other_testing', :name => 'ann'})
|
48
|
+
record2 = record1.dup
|
49
|
+
record2.delete(:name)
|
50
|
+
record2 = api.save(record2)
|
51
|
+
record1[:key].should == record2[:key]
|
52
|
+
api.find_by_kind('other_testing').length.should == 1
|
53
|
+
end
|
54
|
+
|
55
|
+
def ten_testing_records(kind = 'testing')
|
56
|
+
(1..10).to_a.map do |i|
|
57
|
+
{:kind => kind, :name => i.to_s}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'assigns unique keys to each record' do
|
62
|
+
keys = ten_testing_records.map do |record|
|
63
|
+
api.save(record)[:key]
|
64
|
+
end
|
65
|
+
unique_keys = Set.new(keys)
|
66
|
+
unique_keys.length.should == 10
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'can save many records' do
|
70
|
+
saved_records = api.save_many(ten_testing_records)
|
71
|
+
saved_records.length.should == 10
|
72
|
+
saved_names = Set.new(saved_records.map { |record| record[:name] })
|
73
|
+
found_records = api.find_by_kind('testing')
|
74
|
+
found_records.length.should == 10
|
75
|
+
found_names = Set.new(found_records.map { |record| record[:name] })
|
76
|
+
found_names.should == saved_names
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'create' do
|
82
|
+
|
83
|
+
it 'saves a hash without a key and returns it' do
|
84
|
+
record = api.create({:kind => 'testing', :name => 'ann'})
|
85
|
+
record[:kind].should == 'testing'
|
86
|
+
record[:name].should == 'ann'
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'assigns a key to records without keys' do
|
90
|
+
record = api.create({:kind => 'testing', :name => 'ann'})
|
91
|
+
record[:key].should_not be_nil
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'saves a hash with an unpacked key and returns it' do
|
95
|
+
unpacked_key = "123"
|
96
|
+
record = api.create({:kind => 'testing', :key => unpacked_key, :name => 'ann'})
|
97
|
+
record[:kind].should == 'testing'
|
98
|
+
record[:name].should == 'ann'
|
99
|
+
record[:key].to_s.should == unpacked_key
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'saves an empty record' do
|
103
|
+
record = api.create({:kind => 'testing'})
|
104
|
+
record[:kind].should == 'testing'
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'raises an error with an existing record' do
|
108
|
+
record = api.create({:kind => 'other_testing', :name => 'ann'})
|
109
|
+
expect do
|
110
|
+
api.create({:kind => 'other_testing', :key => record[:key]}, :name => 'james')
|
111
|
+
end.to raise_error
|
112
|
+
end
|
113
|
+
|
114
|
+
def ten_testing_records(kind = 'testing')
|
115
|
+
(1..10).to_a.map do |i|
|
116
|
+
{:kind => kind, :name => i.to_s}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'assigns unique keys to each record' do
|
121
|
+
keys = ten_testing_records.map do |record|
|
122
|
+
api.create(record)[:key]
|
123
|
+
end
|
124
|
+
unique_keys = Set.new(keys)
|
125
|
+
unique_keys.length.should == 10
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'can create many records' do
|
129
|
+
saved_records = api.create_many(ten_testing_records)
|
130
|
+
saved_records.length.should == 10
|
131
|
+
saved_names = Set.new(saved_records.map { |record| record[:name] })
|
132
|
+
found_records = api.find_by_kind('testing')
|
133
|
+
found_records.length.should == 10
|
134
|
+
found_names = Set.new(found_records.map { |record| record[:name] })
|
135
|
+
found_names.should == saved_names
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def remove_nils(record)
|
140
|
+
record.reduce({}) do |non_nil_record, (field, value)|
|
141
|
+
non_nil_record[field] = value unless value.nil?
|
142
|
+
non_nil_record
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context 'find by key' do
|
147
|
+
it 'finds by key' do
|
148
|
+
record = api.save({:kind => 'testing', :inti => 5})
|
149
|
+
remove_nils(api.find_by_key('testing', record[:key])).should == remove_nils(record)
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'returns nil on non-existent keys' do
|
153
|
+
expect(api.find_by_key('testing', 1)).to be_nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'find by kind' do
|
158
|
+
|
159
|
+
it 'filters by the kind' do
|
160
|
+
api.save({:kind => 'other_testing', :inti => 5})
|
161
|
+
found_records = api.find_by_kind('testing')
|
162
|
+
found_records.each do |record|
|
163
|
+
record[:kind].should == 'testing'
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it "can't filter on old values" do
|
168
|
+
record = api.save(:kind => 'testing', :inti => 12)
|
169
|
+
api.save(record, :inti => 2)
|
170
|
+
api.find_by_kind('testing', :filters => [[:inti, '=', 12]]).should == []
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'filters' do
|
174
|
+
before :each do
|
175
|
+
api.save_many([
|
176
|
+
{:kind => 'testing', :inti => 1, :data => 'one' },
|
177
|
+
{:kind => 'testing', :inti => 12, :data => 'twelve' },
|
178
|
+
{:kind => 'testing', :inti => 23, :data => 'twenty3'},
|
179
|
+
{:kind => 'testing', :inti => 34, :data => 'thirty4'},
|
180
|
+
{:kind => 'testing', :inti => 45, :data => 'forty5' },
|
181
|
+
{:kind => 'testing', :inti => 1, :data => 'the one'},
|
182
|
+
{:kind => 'testing', :inti => 44, :data => 'forty4' },
|
183
|
+
{:kind => 'testing', :inti => nil, :data => 'forty4' }
|
184
|
+
])
|
185
|
+
end
|
186
|
+
|
187
|
+
[
|
188
|
+
[[[:inti, '<', 25]], [1, 12, 23], :inti],
|
189
|
+
[[[:inti, '<=', 25]], [1, 12, 23], :inti],
|
190
|
+
[[[:inti, '>', 25]], [34, 44, 45], :inti],
|
191
|
+
[[[:inti, '=', 34]], [34], :inti],
|
192
|
+
[[[:inti, '=', nil]], [nil], :inti],
|
193
|
+
[[[:inti, '!=', nil]], [1, 1, 12, 23, 34, 44, 45], :inti],
|
194
|
+
[[[:inti, '!=', 34]], [1, 12, 23, 44, 45, nil], :inti],
|
195
|
+
[[[:inti, 'in', [12, 34]]], [12, 34], :inti],
|
196
|
+
[[[:inti, '>', 10], [:inti, '<', 25]], [12, 23], :inti],
|
197
|
+
[[[:inti, '<', 25], [:inti, '>', 10]], [12, 23], :inti],
|
198
|
+
[[[:inti, '>', 25], [:data, '<', 'thirty4']], [44, 45], :inti],
|
199
|
+
[[[:data, '<', 'thirty4'], [:inti, '>', 25]], [44, 45], :inti],
|
200
|
+
[[[:inti, '>', 10], [:inti, '<', 25], [:inti, '=', 23]], [23], :inti],
|
201
|
+
[[[:inti, '=', 23], [:inti, '>', 10], [:inti, '<', 25]], [23], :inti],
|
202
|
+
[[[:inti, '<', 24], [:inti, '>', 25]], [], :inti],
|
203
|
+
[[[:inti, '!=', 12], [:inti, '!=', 23], [:inti, '!=', 34]], [1, 44, 45, nil], :inti],
|
204
|
+
[[[:data, '<', 'qux']], ['one', 'forty4', 'forty5'], :data],
|
205
|
+
[[[:data, '<=', 'one']], ['one', 'forty4', 'forty5'], :data],
|
206
|
+
[[[:data, '>=', 'thirty4']], ['twelve', 'twenty3', 'thirty4'], :data],
|
207
|
+
[[[:data, '=', 'one']], ['one'], :data],
|
208
|
+
[[[:data, '!=', 'one']], ['the one', 'twelve', 'twenty3', 'thirty4', 'forty4', 'forty5'], :data],
|
209
|
+
[[[:data, 'in', ['one', 'twelve']]], ['one', 'twelve'], :data],
|
210
|
+
[[[:data, '>', 'qux'], [:data, '<', 'qux']], [], :data],
|
211
|
+
[[[:data, '!=', 'one'], [:data, '!=', 'twelve'], [:data, '!=', 'twenty3']], ['the one', 'thirty4', 'forty4', 'forty5'], :data],
|
212
|
+
].each do |filters, result, field|
|
213
|
+
|
214
|
+
it filters.map(&:to_s).join(', ') do
|
215
|
+
found_records = api.find_by_kind('testing', :filters => filters)
|
216
|
+
ints = Set.new(found_records.map {|record| record[field]})
|
217
|
+
ints.should == Set.new(result)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'sorts' do
|
224
|
+
before :each do
|
225
|
+
api.save_many([
|
226
|
+
{:kind => 'testing', :inti => 1, :data => 'one' },
|
227
|
+
{:kind => 'testing', :inti => 12, :data => 'twelve' },
|
228
|
+
{:kind => 'testing', :inti => 23, :data => 'twenty3'},
|
229
|
+
{:kind => 'testing', :inti => 34, :data => 'thirty4'},
|
230
|
+
{:kind => 'testing', :inti => 45, :data => 'forty5' },
|
231
|
+
{:kind => 'testing', :inti => 1, :data => 'the one'},
|
232
|
+
{:kind => 'testing', :inti => 44, :data => 'forty4' },
|
233
|
+
])
|
234
|
+
end
|
235
|
+
|
236
|
+
[
|
237
|
+
[[[:inti, :asc]], [1, 1, 12, 23, 34, 44, 45], :inti],
|
238
|
+
[[[:inti, :desc]], [45, 44, 34, 23, 12, 1, 1], :inti],
|
239
|
+
[[[:data, :asc]], [44, 45, 1, 1, 34, 12, 23], :inti],
|
240
|
+
[[[:data, :desc]], [23, 12, 34, 1, 1, 45, 44], :inti],
|
241
|
+
[[[:inti, :asc], [:data, :asc]], ['one', 'the one', 'twelve', 'twenty3', 'thirty4', 'forty4', 'forty5'], :data],
|
242
|
+
[[[:data, :asc], [:inti, :asc]], [44, 45, 1, 1, 34, 12, 23], :inti]
|
243
|
+
].each do |sorts, result, field|
|
244
|
+
|
245
|
+
it sorts.map(&:to_s).join(', ') do
|
246
|
+
found_records = api.find_by_kind('testing', :sorts => sorts)
|
247
|
+
ints = found_records.map {|record| record[field]}
|
248
|
+
ints.should == result
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
context 'limit and offset' do
|
254
|
+
before :each do
|
255
|
+
api.save_many([
|
256
|
+
{:kind => 'testing', :inti => 1, :data => 'one' },
|
257
|
+
{:kind => 'testing', :inti => 12, :data => 'twelve' },
|
258
|
+
{:kind => 'testing', :inti => 23, :data => 'twenty3'},
|
259
|
+
{:kind => 'testing', :inti => 34, :data => 'thirty4'},
|
260
|
+
{:kind => 'testing', :inti => 45, :data => 'forty5' },
|
261
|
+
{:kind => 'testing', :inti => 1, :data => 'the one'},
|
262
|
+
{:kind => 'testing', :inti => 44, :data => 'forty4' },
|
263
|
+
])
|
264
|
+
end
|
265
|
+
|
266
|
+
specify 'offset n returns results starting at the nth record' do
|
267
|
+
found_records = api.find_by_kind('testing', :sorts => [[:inti, :asc]], :offset => 2)
|
268
|
+
ints = found_records.map {|record| record[:inti]}
|
269
|
+
ints.should == [12, 23, 34, 44, 45]
|
270
|
+
end
|
271
|
+
|
272
|
+
specify 'limit n takes only the first n records' do
|
273
|
+
found_records = api.find_by_kind('testing', :sorts => [[:inti, :asc]], :limit => 2)
|
274
|
+
found_records.map {|record| record[:inti]}.should == [1, 1]
|
275
|
+
|
276
|
+
found_records = api.find_by_kind('testing', :sorts => [[:inti, :asc]], :limit => 1_000_000)
|
277
|
+
found_records.map {|record| record[:inti]}.should == [1, 1, 12, 23, 34, 44, 45]
|
278
|
+
end
|
279
|
+
|
280
|
+
[
|
281
|
+
[{:limit => 2, :offset => 2}, [[:inti, :asc]], [12, 23]],
|
282
|
+
[{:limit => 2, :offset => 4}, [[:inti, :asc]], [34, 44]],
|
283
|
+
[{:limit => 2} , [[:inti, :desc]], [45, 44]],
|
284
|
+
[{:limit => 2, :offset => 2}, [[:inti, :desc]], [34, 23]],
|
285
|
+
[{:limit => 2, :offset => 4}, [[:inti, :desc]], [12, 1]],
|
286
|
+
].each do |constraints, sorts, result|
|
287
|
+
example constraints.inspect do
|
288
|
+
found_records = api.find_by_kind 'testing', constraints.merge(:sorts => sorts)
|
289
|
+
found_records.map { |record| record[:inti] }.should == result
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
context 'delete' do
|
296
|
+
|
297
|
+
before :each do
|
298
|
+
api.save_many([
|
299
|
+
{:kind => 'testing', :inti => 1, :data => 'one' },
|
300
|
+
{:kind => 'testing', :inti => 12, :data => 'twelve' },
|
301
|
+
{:kind => 'testing', :inti => nil, :data => 'twelve' }
|
302
|
+
])
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'deletes by key' do
|
306
|
+
records = api.find_by_kind('testing')
|
307
|
+
record_to_delete = records.first
|
308
|
+
api.delete_by_key(record_to_delete[:kind], record_to_delete[:key]).should be_nil
|
309
|
+
api.find_by_kind('testing').should_not include(record_to_delete)
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'returns nil when deleting non-existent keys' do
|
313
|
+
expect(api.delete_by_key('testing', 1)).to be_nil
|
314
|
+
end
|
315
|
+
|
316
|
+
context 'filters' do
|
317
|
+
|
318
|
+
[
|
319
|
+
[[], []],
|
320
|
+
[[[:inti, '=', 1]], [12, nil]],
|
321
|
+
[[[:data, '=', 'one']], [12, nil]],
|
322
|
+
[[[:inti, '!=', 1]], [1]],
|
323
|
+
[[[:inti, '<=', 1]], [12, nil]],
|
324
|
+
[[[:inti, '<=', 2]], [12, nil]],
|
325
|
+
[[[:inti, '>=', 2]], [1, nil]],
|
326
|
+
[[[:inti, '>', 1]], [1, nil]],
|
327
|
+
[[[:inti, 'in', [1]]], [12, nil]],
|
328
|
+
[[[:inti, 'in', [1, nil]]], [12]],
|
329
|
+
[[[:inti, 'in', [1, 12]]], [nil]],
|
330
|
+
[[[:inti, '=', 2]], [1, 12, nil]],
|
331
|
+
[[[:inti, '=', nil]], [1, 12]],
|
332
|
+
[[[:inti, '!=', nil]], [nil]],
|
333
|
+
].each do |filters, result|
|
334
|
+
it filters.inspect do
|
335
|
+
api.delete_by_kind('testing', :filters => filters)
|
336
|
+
intis = api.find_by_kind('testing').map {|r| r[:inti]}
|
337
|
+
intis.should =~ result
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
context 'count' do
|
345
|
+
|
346
|
+
before :each do
|
347
|
+
api.save_many([
|
348
|
+
{:kind => 'testing', :inti => 1, :data => 'one' },
|
349
|
+
{:kind => 'testing', :inti => 12, :data => 'twelve' },
|
350
|
+
{:kind => 'testing', :inti => nil, :data => 'twelve' }
|
351
|
+
])
|
352
|
+
end
|
353
|
+
|
354
|
+
context 'filters' do
|
355
|
+
|
356
|
+
[
|
357
|
+
[[], 3],
|
358
|
+
[[[:inti, '=', 1]], 1],
|
359
|
+
[[[:data, '=', 'one']], 1],
|
360
|
+
[[[:inti, '!=', 1]], 2],
|
361
|
+
[[[:inti, '<=', 1]], 1],
|
362
|
+
[[[:inti, '<=', 2]], 1],
|
363
|
+
[[[:inti, '>=', 2]], 1],
|
364
|
+
[[[:inti, '>', 1]], 1],
|
365
|
+
[[[:inti, 'in', [1]]], 1],
|
366
|
+
[[[:inti, 'in', [1, 12]]], 2],
|
367
|
+
[[[:inti, '=', 2]], 0],
|
368
|
+
[[[:inti, '=', nil]], 1],
|
369
|
+
[[[:inti, '!=', nil]], 2],
|
370
|
+
].each do |filters, result|
|
371
|
+
it filters.inspect do
|
372
|
+
api.count_by_kind('testing', :filters => filters).should == result
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
# context 'foreign_keys' do
|
380
|
+
# it 'saves records with foreign keys' do
|
381
|
+
# account = api.save(:kind => :account)
|
382
|
+
# account_key = account[:key]
|
383
|
+
# shirt = api.save(:kind => :shirt, :account_key => account_key)
|
384
|
+
# found_shirt = api.find_by_key(:shirt, shirt[:key])
|
385
|
+
# found_account = api.find_by_key(:account, account_key)
|
386
|
+
# shirt[:account_key].should == account_key
|
387
|
+
# found_shirt[:account_key].should == account_key
|
388
|
+
# found_account[:key].should == account_key
|
389
|
+
# end
|
390
|
+
#
|
391
|
+
# it 'filters on foreign keys' do
|
392
|
+
# account = api.save(:kind => :account)
|
393
|
+
# account_key = account[:key]
|
394
|
+
# shirt = api.save(:kind => :shirt, :account_key => account_key)
|
395
|
+
# found_shirts = api.find_by_kind(:shirt, :filters => [[:account_key, '=', account_key]])
|
396
|
+
# found_shirts[0].should == shirt
|
397
|
+
# end
|
398
|
+
#
|
399
|
+
# it 'filters on multiple foreign keys' do
|
400
|
+
# account_keys = (1..2).map { api.save(:kind => :account)[:key] }
|
401
|
+
# shirt1 = api.save(:kind => :shirt, :account_key => account_keys[0])
|
402
|
+
# shirt2 = api.save(:kind => :shirt, :account_key => account_keys[1])
|
403
|
+
# found_shirts = api.find_by_kind(:shirt, :filters => [[:account_key, 'in', account_keys]])
|
404
|
+
# found_shirts.should include(shirt1)
|
405
|
+
# found_shirts.should include(shirt2)
|
406
|
+
# end
|
407
|
+
#
|
408
|
+
# it 'unpacks nil foreign keys' do
|
409
|
+
# shirt = api.save(:kind => :shirt, :account_key => nil)
|
410
|
+
# shirt[:account_key].should be_nil
|
411
|
+
# found_shirt = api.find_by_kind(:shirt, :filters => [[:account_key, '=', nil]]).first
|
412
|
+
# found_shirt[:account_key].should be_nil
|
413
|
+
# end
|
414
|
+
#
|
415
|
+
# it 'filters on nil foreign key' do
|
416
|
+
# account = api.save(:kind => :account)
|
417
|
+
# shirt = api.save(:kind => :shirt, :account_key => account[:key])
|
418
|
+
# nil_shirt = api.save(:kind => :shirt, :account_key => nil)
|
419
|
+
# api.find_by_kind(:shirt, :filters => [[:account_key, '=', nil]]).should == [nil_shirt]
|
420
|
+
# end
|
421
|
+
# end
|
422
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Endymion
|
2
|
+
class FakeDs
|
3
|
+
|
4
|
+
attr_accessor :saved_records, :created_records, :queries, :key_queries,
|
5
|
+
:returns, :key_pack_queries,:key_unpack_queries
|
6
|
+
|
7
|
+
def initialize(opts={})
|
8
|
+
@saved_records = []
|
9
|
+
@created_records = []
|
10
|
+
@returns = []
|
11
|
+
@queries = []
|
12
|
+
@key_queries = []
|
13
|
+
@key_pack_queries = []
|
14
|
+
@key_unpack_queries = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def save(records)
|
18
|
+
@saved_records += records
|
19
|
+
returns.shift || []
|
20
|
+
end
|
21
|
+
|
22
|
+
def create(records)
|
23
|
+
@created_records += records
|
24
|
+
returns.shift || []
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_by_key(kind, key)
|
28
|
+
@key_queries << [kind, key]
|
29
|
+
returns.shift || nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def find(query)
|
33
|
+
@queries << query
|
34
|
+
returns.shift || []
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete_by_key(kind, key)
|
38
|
+
@key_queries << [kind, key]
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete(query)
|
43
|
+
@queries << query
|
44
|
+
returns.shift || nil
|
45
|
+
end
|
46
|
+
|
47
|
+
def count(query)
|
48
|
+
@queries << query
|
49
|
+
returns.shift || 0
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'endymion/format'
|
2
|
+
|
3
|
+
module Endymion
|
4
|
+
class FieldSpec
|
5
|
+
|
6
|
+
attr_reader :name, :default, :db_name
|
7
|
+
|
8
|
+
def initialize(name, opts={})
|
9
|
+
@name = name
|
10
|
+
@default = opts[:default]
|
11
|
+
@type = opts[:type]
|
12
|
+
@packer = opts[:packer]
|
13
|
+
@unpacker = opts[:unpacker]
|
14
|
+
@db_name = opts[:db_name] ? Format.format_field(opts[:db_name]) : name
|
15
|
+
@api = opts[:api]
|
16
|
+
end
|
17
|
+
|
18
|
+
def pack(value)
|
19
|
+
if @packer && @packer.respond_to?(:call)
|
20
|
+
@packer.call(value)
|
21
|
+
elsif @type
|
22
|
+
type_packer = @api.packer_for(@type)
|
23
|
+
type_packer ? type_packer.call(value) : value
|
24
|
+
else
|
25
|
+
value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def unpack(value)
|
30
|
+
if @unpacker && @unpacker.respond_to?(:call)
|
31
|
+
@unpacker.call(value)
|
32
|
+
elsif @type
|
33
|
+
type_packer = @api.unpacker_for(@type)
|
34
|
+
type_packer ? type_packer.call(value) : value
|
35
|
+
else
|
36
|
+
value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Endymion
|
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
|
+
|
12
|
+
def to_h
|
13
|
+
{
|
14
|
+
:operator => operator,
|
15
|
+
:field => field,
|
16
|
+
:value => value
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'endymion/util'
|
2
|
+
|
3
|
+
module Endymion
|
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
|
+
field.to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def format_record(record)
|
17
|
+
record = record.reduce({}) do |new_record, (field_name, value)|
|
18
|
+
new_record[format_field(field_name)] = 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/endymion/key.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'endymion/format'
|
2
|
+
|
3
|
+
module Endymion
|
4
|
+
class KindSpec
|
5
|
+
|
6
|
+
attr_reader :kind, :fields, :api
|
7
|
+
|
8
|
+
def initialize(kind, api)
|
9
|
+
@kind = kind
|
10
|
+
@fields = {}
|
11
|
+
@api = api
|
12
|
+
end
|
13
|
+
|
14
|
+
def field(name, opts={})
|
15
|
+
name = Format.format_field(name)
|
16
|
+
@fields[name] = FieldSpec.new(name, opts.merge(api: api))
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'endymion'
|
2
|
+
require 'endymion/key'
|
3
|
+
require 'endymion/memory/helper'
|
4
|
+
|
5
|
+
module Endymion
|
6
|
+
class Memory
|
7
|
+
|
8
|
+
def initialize(opts={})
|
9
|
+
@store = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def save(records)
|
13
|
+
records.map do |record|
|
14
|
+
key = Endymion.new?(record) ? generate_key : record[:key]
|
15
|
+
record[:key] = key
|
16
|
+
store[key] = record
|
17
|
+
record
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create(records)
|
22
|
+
records.map do |record|
|
23
|
+
raise 'duplicate key' if record[:key] && store.key?(record[:key])
|
24
|
+
record[:key] ||= generate_key
|
25
|
+
store[record[:key]] = record
|
26
|
+
record
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_by_key(kind, key)
|
31
|
+
store[key]
|
32
|
+
end
|
33
|
+
|
34
|
+
def find(query)
|
35
|
+
records = store.values
|
36
|
+
records = filter_kind(query.kind, records)
|
37
|
+
records = Helper.apply_filters(query.filters, records)
|
38
|
+
records = Helper.apply_sorts(query.sorts, records)
|
39
|
+
records = Helper.apply_offset(query.offset, records)
|
40
|
+
records = Helper.apply_limit(query.limit, records)
|
41
|
+
records
|
42
|
+
end
|
43
|
+
|
44
|
+
def delete_by_key(kind, key)
|
45
|
+
store.delete(key)
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete(query)
|
50
|
+
records = find(query)
|
51
|
+
store.delete_if do |key, record|
|
52
|
+
records.include?(record)
|
53
|
+
end
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def count(query)
|
58
|
+
find(query).length
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
attr_reader :store
|
64
|
+
|
65
|
+
def filter_kind(kind, records)
|
66
|
+
records.select do |record|
|
67
|
+
record[:kind] == kind
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def generate_key
|
72
|
+
Endymion::Key.generate_id
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Endymion
|
2
|
+
class Memory
|
3
|
+
module Helper
|
4
|
+
def self.apply_filters(filters, records)
|
5
|
+
records.select do |record|
|
6
|
+
filters.all? do |filter|
|
7
|
+
value = record[filter.field]
|
8
|
+
case filter.operator
|
9
|
+
when '<'; value && value < filter.value
|
10
|
+
when '<='; value && value <= filter.value
|
11
|
+
when '>'; value && value > filter.value
|
12
|
+
when '>='; value && value >= filter.value
|
13
|
+
when '='; value == filter.value
|
14
|
+
when '!='; value != filter.value
|
15
|
+
when 'contains?'; filter.value.include?(value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.apply_sorts(sorts, records)
|
22
|
+
records.sort { |record1, record2| compare_records record1, record2, sorts }
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.compare_records(record1, record2, sorts)
|
26
|
+
sorts.each do |sort|
|
27
|
+
result = compare_record record1, record2, sort
|
28
|
+
return result if result
|
29
|
+
end
|
30
|
+
0
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.compare_record(record1, record2, sort)
|
34
|
+
field1, field2 = record1[sort.field], record2[sort.field]
|
35
|
+
field1 == field2 ? nil :
|
36
|
+
field1 < field2 && sort.ascending? ? -1 :
|
37
|
+
field1 > field2 && sort.descending? ? -1 : 1
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.apply_offset(offset, records)
|
41
|
+
return records unless offset
|
42
|
+
records.drop offset
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.apply_limit(limit, records)
|
46
|
+
return records unless limit
|
47
|
+
records.take(limit)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Endymion
|
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
|
+
def to_h
|
14
|
+
{
|
15
|
+
:kind => kind,
|
16
|
+
:filters => filters.map(&:to_h),
|
17
|
+
:sorts => sorts.map(&:to_h),
|
18
|
+
:limit => limit,
|
19
|
+
:offset => offset
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Endymion
|
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
|
+
|
19
|
+
def to_h
|
20
|
+
{
|
21
|
+
:field => field,
|
22
|
+
:order => order
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'endymion'
|
2
|
+
require 'endymion/format'
|
3
|
+
|
4
|
+
module Endymion
|
5
|
+
class Types
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# def foreign_key(kind)
|
9
|
+
# kind_key = "#{Format.format_kind(kind)}_key".to_sym
|
10
|
+
# unless Hyperion.packer_defined?(kind_key)
|
11
|
+
# Hyperion.pack(kind_key) do |key|
|
12
|
+
# key
|
13
|
+
# end
|
14
|
+
# Hyperion.unpack(kind_key) do |key|
|
15
|
+
# key
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
# kind_key
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Endymion
|
2
|
+
class Util
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def camel_case(str)
|
7
|
+
cameled = str.gsub(/[_| |\-][A-Za-z]/) { |a| a[1..-1].upcase } if str
|
8
|
+
uncapitalize(cameled)
|
9
|
+
end
|
10
|
+
|
11
|
+
def class_name(str)
|
12
|
+
capitalize(camel_case(str))
|
13
|
+
end
|
14
|
+
|
15
|
+
def snake_case(str)
|
16
|
+
str.gsub(/([a-z0-9])([A-Z])/, '\1 \2').downcase.gsub(/[ |\-]/, '_') if str
|
17
|
+
end
|
18
|
+
|
19
|
+
def capitalize(str)
|
20
|
+
do_to_first(str) do |first_letter|
|
21
|
+
first_letter.upcase
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def uncapitalize(str)
|
26
|
+
do_to_first(str) do |first_letter|
|
27
|
+
first_letter.downcase
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def do_to_first(str)
|
32
|
+
if str
|
33
|
+
first = yield(str[0, 1])
|
34
|
+
if str.length > 1
|
35
|
+
last = str[1..-1]
|
36
|
+
first + last
|
37
|
+
else
|
38
|
+
first
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def bind(name, value)
|
44
|
+
old_value = Thread.current[name]
|
45
|
+
begin
|
46
|
+
Thread.current[name] = value
|
47
|
+
yield
|
48
|
+
ensure
|
49
|
+
Thread.current[name] = old_value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: endymion
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- David Faber
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-04-29 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Key/Value persistence interface. Forked from Hyperion.
|
14
|
+
email: dafaber@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/endymion.rb
|
20
|
+
- lib/endymion/api.rb
|
21
|
+
- lib/endymion/dev/ds_spec.rb
|
22
|
+
- lib/endymion/fake_ds.rb
|
23
|
+
- lib/endymion/field_spec.rb
|
24
|
+
- lib/endymion/filter.rb
|
25
|
+
- lib/endymion/format.rb
|
26
|
+
- lib/endymion/key.rb
|
27
|
+
- lib/endymion/kind_spec.rb
|
28
|
+
- lib/endymion/memory.rb
|
29
|
+
- lib/endymion/memory/helper.rb
|
30
|
+
- lib/endymion/query.rb
|
31
|
+
- lib/endymion/sort.rb
|
32
|
+
- lib/endymion/types.rb
|
33
|
+
- lib/endymion/util.rb
|
34
|
+
homepage:
|
35
|
+
licenses:
|
36
|
+
- MIT
|
37
|
+
metadata: {}
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 2.2.2
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: Key/Value persistence interface. Forked from Hyperion.
|
58
|
+
test_files: []
|