endymion 0.0.1
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.
- 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: []
|