quiver 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +6 -0
- data/README.md +22 -0
- data/Rakefile +17 -0
- data/bin/quiver +9 -0
- data/lib/quiver.rb +46 -0
- data/lib/quiver/abstract_action.rb +31 -0
- data/lib/quiver/action.rb +208 -0
- data/lib/quiver/action/filter_error.rb +33 -0
- data/lib/quiver/action/filter_value.rb +152 -0
- data/lib/quiver/action/invalid_request_body_error.rb +30 -0
- data/lib/quiver/action/pagination_link_builder.rb +67 -0
- data/lib/quiver/adapter.rb +51 -0
- data/lib/quiver/adapter/active_record_adapter_filter.rb +102 -0
- data/lib/quiver/adapter/active_record_helpers.rb +258 -0
- data/lib/quiver/adapter/arec_low_level_creator.rb +82 -0
- data/lib/quiver/adapter/arec_low_level_deleter.rb +74 -0
- data/lib/quiver/adapter/arec_low_level_updater.rb +105 -0
- data/lib/quiver/adapter/filter_helpers.rb +58 -0
- data/lib/quiver/adapter/helpers_helpers.rb +53 -0
- data/lib/quiver/adapter/memory_adapter_filter.rb +71 -0
- data/lib/quiver/adapter/memory_adapter_store.rb +34 -0
- data/lib/quiver/adapter/memory_helpers.rb +182 -0
- data/lib/quiver/adapter/memory_uuid_primary_key.rb +25 -0
- data/lib/quiver/application.rb +128 -0
- data/lib/quiver/cli/app.rb +25 -0
- data/lib/quiver/cli/generators/endpoint.rb +17 -0
- data/lib/quiver/cli/generators/new_application.rb +166 -0
- data/lib/quiver/cli/generators/new_application_cli.rb +25 -0
- data/lib/quiver/cli/server.rb +37 -0
- data/lib/quiver/cli/templates/Gemfile.tt +8 -0
- data/lib/quiver/cli/templates/Rakefile.tt +12 -0
- data/lib/quiver/cli/templates/config.tt +3 -0
- data/lib/quiver/cli/templates/config/database.tt +21 -0
- data/lib/quiver/cli/templates/gemspec.tt +33 -0
- data/lib/quiver/cli/templates/gitignore.tt +10 -0
- data/lib/quiver/cli/templates/lib/application.tt +14 -0
- data/lib/quiver/cli/templates/lib/application/config/router.tt +11 -0
- data/lib/quiver/cli/templates/lib/application/version.tt +3 -0
- data/lib/quiver/cli/templates/spec/spec_helper.tt +19 -0
- data/lib/quiver/duty.rb +34 -0
- data/lib/quiver/duty_master.rb +23 -0
- data/lib/quiver/duty_master/delayed_job_adapter.rb +15 -0
- data/lib/quiver/duty_master/memory_adapter.rb +18 -0
- data/lib/quiver/duty_test_helper.rb +23 -0
- data/lib/quiver/duty_test_helper/delayed_job_helper.rb +9 -0
- data/lib/quiver/duty_test_helper/memory_helper.rb +15 -0
- data/lib/quiver/error.rb +24 -0
- data/lib/quiver/error_collection.rb +60 -0
- data/lib/quiver/json_parser.rb +17 -0
- data/lib/quiver/logger.rb +26 -0
- data/lib/quiver/mapper.rb +311 -0
- data/lib/quiver/mapper/hook.rb +21 -0
- data/lib/quiver/mapper/mapper_result.rb +7 -0
- data/lib/quiver/mapper/not_found_error.rb +27 -0
- data/lib/quiver/mapper/simple_query_builder.rb +70 -0
- data/lib/quiver/mapper/soft_delete.rb +15 -0
- data/lib/quiver/mappers.rb +75 -0
- data/lib/quiver/middleware_stack.rb +35 -0
- data/lib/quiver/model.rb +63 -0
- data/lib/quiver/model/soft_delete.rb +14 -0
- data/lib/quiver/model/validation_error.rb +27 -0
- data/lib/quiver/model/validations.rb +45 -0
- data/lib/quiver/patcher.rb +94 -0
- data/lib/quiver/result.rb +44 -0
- data/lib/quiver/route_helper.rb +16 -0
- data/lib/quiver/router.rb +37 -0
- data/lib/quiver/serialization.rb +6 -0
- data/lib/quiver/serialization/json_api.rb +7 -0
- data/lib/quiver/serialization/json_api/item_type_handler.rb +96 -0
- data/lib/quiver/serialization/json_api/serializer.rb +77 -0
- data/lib/quiver/tasks.rb +31 -0
- data/lib/quiver/validator.rb +83 -0
- data/lib/quiver/validators/base.rb +21 -0
- data/lib/quiver/validators/presence.rb +34 -0
- data/lib/quiver/validators/unique.rb +33 -0
- data/lib/quiver/version.rb +3 -0
- data/quiver.gemspec +42 -0
- metadata +393 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
class JsonParser < Lotus::Routing::Parsing::Parser
|
2
|
+
def mime_types
|
3
|
+
['application/json', 'application/vnd.api+json']
|
4
|
+
end
|
5
|
+
|
6
|
+
def parse(body)
|
7
|
+
parsed = JSON.parse(body)
|
8
|
+
|
9
|
+
if parsed.is_a?(Array)
|
10
|
+
{ request_data: parsed }
|
11
|
+
else
|
12
|
+
parsed
|
13
|
+
end
|
14
|
+
rescue JSON::ParserError => e
|
15
|
+
{terrible_hack: e}
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Quiver
|
4
|
+
class Logger < ::Logger
|
5
|
+
def initialize(logdev, shift_age = 0, shift_size = 1048576)
|
6
|
+
@progname = nil
|
7
|
+
@level = DEBUG
|
8
|
+
@default_formatter = Formatter.new
|
9
|
+
@formatter = nil
|
10
|
+
@logdev = nil
|
11
|
+
|
12
|
+
if logdev
|
13
|
+
@logdev = LogDevice.new(
|
14
|
+
logdev,
|
15
|
+
:shift_age => shift_age,
|
16
|
+
:shift_size => shift_size
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class LogDevice < ::Logger::LogDevice
|
22
|
+
def add_log_header(file)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,311 @@
|
|
1
|
+
module Quiver
|
2
|
+
module Mapper
|
3
|
+
def self.included(host)
|
4
|
+
host.send(:extend, ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def type_attribute(attr_name=nil)
|
9
|
+
if attr_name
|
10
|
+
@type_attribute = attr_name
|
11
|
+
end
|
12
|
+
|
13
|
+
@type_attribute
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def filters(*args)
|
19
|
+
raise 'Mapper.filters is unused, it should be removed'
|
20
|
+
end
|
21
|
+
|
22
|
+
def default_filter(h=nil)
|
23
|
+
if h
|
24
|
+
h.each do |k, v|
|
25
|
+
v.each do |comparator, _|
|
26
|
+
unless %i|nil not_nil ~nil|.include?(comparator)
|
27
|
+
raise 'Default filter only supports the nil and ~nil comparators'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@default_filter = h
|
33
|
+
end
|
34
|
+
|
35
|
+
@default_filter || {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def sorts(*args)
|
39
|
+
@sorts ||= []
|
40
|
+
|
41
|
+
if args.any?
|
42
|
+
@sorts += args.map(&:to_sym)
|
43
|
+
end
|
44
|
+
|
45
|
+
@sorts.dup.freeze
|
46
|
+
end
|
47
|
+
|
48
|
+
def maps(model_klass=nil)
|
49
|
+
if model_klass
|
50
|
+
@maps = model_klass
|
51
|
+
end
|
52
|
+
|
53
|
+
@maps
|
54
|
+
end
|
55
|
+
|
56
|
+
def hooks(hook_array=nil)
|
57
|
+
if hook_array
|
58
|
+
@hooks = hook_array
|
59
|
+
end
|
60
|
+
|
61
|
+
@hooks
|
62
|
+
end
|
63
|
+
|
64
|
+
def adapter_for(adapter_type)
|
65
|
+
raise ArgumentError, 'adapter_type must not be nil' if adapter_type.nil?
|
66
|
+
unnamespaced_name = name.split('::').last
|
67
|
+
adapters_namespace = self.parents[-2]::Adapters.const_get(unnamespaced_name)
|
68
|
+
adapters_namespace.const_get(adapter_type.to_s.camelize + 'Adapter')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(adapter_type=nil)
|
73
|
+
adapter_type ||= self.class.parents[-2]::Application.default_adapter_type
|
74
|
+
self.adapter_type = adapter_type
|
75
|
+
self.adapter = self.class.send(:adapter_for, adapter_type).new
|
76
|
+
end
|
77
|
+
|
78
|
+
def save(model)
|
79
|
+
self.class.parents[-2]::Mappers.transaction do |transaction|
|
80
|
+
hooks = self.class.send(:hooks) || []
|
81
|
+
|
82
|
+
if self.class.type_attribute
|
83
|
+
type_key = model.send(self.class.type_attribute)
|
84
|
+
end
|
85
|
+
|
86
|
+
if hooks.is_a?(Hash)
|
87
|
+
hooks = hooks[type_key.to_sym] || []
|
88
|
+
end
|
89
|
+
|
90
|
+
hooks = hooks.map(&:new)
|
91
|
+
|
92
|
+
model = hooks.inject(model) do |model, hook|
|
93
|
+
hook.before_save(model)
|
94
|
+
end
|
95
|
+
|
96
|
+
if model.persisted_by.include?(adapter_type)
|
97
|
+
result = update(model, transaction)
|
98
|
+
else
|
99
|
+
result = create(model, transaction)
|
100
|
+
|
101
|
+
if result.success?
|
102
|
+
# model.persisted_by!(adapter_type)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
hooks.inject(result) do |result, hook|
|
107
|
+
hook.after_save(result)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def find(pk)
|
113
|
+
adapter.find(pk).when(
|
114
|
+
success: -> (attributes, result) {
|
115
|
+
Quiver::Mapper::MapperResult.new { |e| hydrate(attributes) }
|
116
|
+
},
|
117
|
+
failure: -> (errors, result) { result }
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
def all
|
122
|
+
query({})
|
123
|
+
end
|
124
|
+
|
125
|
+
def hard_delete(model)
|
126
|
+
self.class.parents[-2]::Mappers.transaction do |transaction|
|
127
|
+
attributes = dehydrate(model)
|
128
|
+
|
129
|
+
Quiver::Mapper::MapperResult.new(nil, nil, {adapter_op: :hard_delete}) do |errors|
|
130
|
+
adapter.hard_delete(attributes, transaction).when(
|
131
|
+
success: -> (attributes, result) {
|
132
|
+
{}
|
133
|
+
},
|
134
|
+
failure: -> (adapter_errors, result) { errors.add(adapter_errors); nil }
|
135
|
+
)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def soft_delete(model)
|
141
|
+
self.class.parents[-2]::Mappers.transaction do |transaction|
|
142
|
+
model.deleted_at = Time.now
|
143
|
+
|
144
|
+
attributes = dehydrate(model)
|
145
|
+
|
146
|
+
Quiver::Mapper::MapperResult.new(nil, nil, {adapter_op: :soft_delete}) do |errors|
|
147
|
+
adapter.update(attributes, transaction).when(
|
148
|
+
success: -> (attributes, result) {
|
149
|
+
hydrate(attributes)
|
150
|
+
},
|
151
|
+
failure: -> (adapter_errors, result) { errors.add(adapter_errors); nil }
|
152
|
+
)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def restore(model)
|
158
|
+
self.class.parents[-2]::Mappers.transaction do |transaction|
|
159
|
+
model.deleted_at = nil
|
160
|
+
|
161
|
+
attributes = dehydrate(model)
|
162
|
+
|
163
|
+
Quiver::Mapper::MapperResult.new(nil, nil, {adapter_op: :restore}) do |errors|
|
164
|
+
adapter.update(attributes, transaction).when(
|
165
|
+
success: -> (attributes, result) {
|
166
|
+
hydrate(attributes)
|
167
|
+
},
|
168
|
+
failure: -> (adapter_errors, result) { errors.add(adapter_errors); nil }
|
169
|
+
)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def count
|
175
|
+
adapter.count
|
176
|
+
end
|
177
|
+
|
178
|
+
def filter(params)
|
179
|
+
SimpleQueryBuilder.new(self).filter(params)
|
180
|
+
end
|
181
|
+
|
182
|
+
def sort(params)
|
183
|
+
SimpleQueryBuilder.new(self).sort(params)
|
184
|
+
end
|
185
|
+
|
186
|
+
def paginate(params)
|
187
|
+
SimpleQueryBuilder.new(self).paginate(params)
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
attr_accessor :adapter, :adapter_type
|
193
|
+
|
194
|
+
def when_valid(model, adapter_op)
|
195
|
+
raise ArgumentError, "requires block" unless block_given?
|
196
|
+
|
197
|
+
errors = model.validate(
|
198
|
+
tags: [adapter_op],
|
199
|
+
mapper: self,
|
200
|
+
model: model
|
201
|
+
)
|
202
|
+
|
203
|
+
if errors.success?
|
204
|
+
current_time = Time.now.utc
|
205
|
+
model.updated_at = current_time if model.respond_to?(:updated_at=)
|
206
|
+
|
207
|
+
if adapter_op == :create
|
208
|
+
model.created_at ||= current_time if model.respond_to?(:created_at)
|
209
|
+
end
|
210
|
+
|
211
|
+
object = yield(model, errors)
|
212
|
+
end
|
213
|
+
|
214
|
+
Quiver::Mapper::MapperResult.new(object, errors, adapter_op: adapter_op)
|
215
|
+
end
|
216
|
+
|
217
|
+
def create(model, transaction)
|
218
|
+
when_valid(model, :create) do |model, errors|
|
219
|
+
attributes = dehydrate(model)
|
220
|
+
|
221
|
+
adapter.create(attributes, transaction).when({
|
222
|
+
success: -> (attributes, result) {
|
223
|
+
model.persisted_by!(adapter_type)
|
224
|
+
model.send(:"#{adapter.send(:primary_key_name)}=", attributes[adapter.send(:primary_key_name)])
|
225
|
+
hydrate(attributes)
|
226
|
+
},
|
227
|
+
failure: -> (adapter_errors, result) { errors.add(adapter_errors) }
|
228
|
+
})
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def update(model, transaction)
|
233
|
+
when_valid(model, :update) do |model, errors|
|
234
|
+
attributes = dehydrate(model)
|
235
|
+
|
236
|
+
adapter.update(attributes, transaction).when(
|
237
|
+
success: -> (attributes, result) {
|
238
|
+
hydrate(attributes)
|
239
|
+
},
|
240
|
+
failure: -> (adapter_errors, result) { errors.add(adapter_errors); nil }
|
241
|
+
)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def query(q={})
|
246
|
+
adapter.query(q).when(
|
247
|
+
success: -> (items, result) {
|
248
|
+
Quiver::Mapper::MapperResult.new(nil, nil, result.data) { |e|
|
249
|
+
items.map do |attributes|
|
250
|
+
hydrate(attributes)
|
251
|
+
end
|
252
|
+
}
|
253
|
+
},
|
254
|
+
failure: -> (errors, result) { result }
|
255
|
+
)
|
256
|
+
end
|
257
|
+
|
258
|
+
def hydrate(attributes)
|
259
|
+
attributes = attributes.dup
|
260
|
+
|
261
|
+
hooks = self.class.send(:hooks) || []
|
262
|
+
|
263
|
+
type_key = attributes.delete(self.class.type_attribute) || attributes.delete(self.class.type_attribute.to_s)
|
264
|
+
|
265
|
+
if hooks.is_a?(Hash)
|
266
|
+
hooks = hooks[type_key.to_sym] || []
|
267
|
+
end
|
268
|
+
|
269
|
+
attributes = hooks.inject(attributes) do |attrs, hook|
|
270
|
+
hook.new.before_hydrate(attrs)
|
271
|
+
end
|
272
|
+
|
273
|
+
if self.class.send(:maps).is_a?(Hash)
|
274
|
+
object = self.class.send(:maps)[type_key.to_sym].new(attributes)
|
275
|
+
else
|
276
|
+
object = self.class.send(:maps).new(attributes)
|
277
|
+
end
|
278
|
+
|
279
|
+
object.persisted_by!(adapter_type)
|
280
|
+
|
281
|
+
object
|
282
|
+
end
|
283
|
+
|
284
|
+
def dehydrate(model)
|
285
|
+
attributes = model.attributes
|
286
|
+
|
287
|
+
if self.class.type_attribute
|
288
|
+
attributes = attributes.merge(:__type__ => {
|
289
|
+
value: model.send(self.class.type_attribute),
|
290
|
+
name: self.class.type_attribute
|
291
|
+
})
|
292
|
+
end
|
293
|
+
|
294
|
+
hooks = self.class.send(:hooks) || []
|
295
|
+
|
296
|
+
if hooks.is_a?(Hash)
|
297
|
+
hooks = hooks[model.send(self.class.type_attribute)] || []
|
298
|
+
end
|
299
|
+
|
300
|
+
hooks.inject(attributes) do |attrs, hook|
|
301
|
+
hook.new.after_dehydrate(attrs)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
require 'quiver/mapper/mapper_result'
|
308
|
+
require 'quiver/mapper/not_found_error'
|
309
|
+
require 'quiver/mapper/simple_query_builder'
|
310
|
+
require 'quiver/mapper/soft_delete'
|
311
|
+
require 'quiver/mapper/hook'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Quiver
|
2
|
+
module Mapper
|
3
|
+
module Hook
|
4
|
+
def before_hydrate(attributes)
|
5
|
+
attributes
|
6
|
+
end
|
7
|
+
|
8
|
+
def after_dehydrate(attributes)
|
9
|
+
attributes
|
10
|
+
end
|
11
|
+
|
12
|
+
def before_save(model)
|
13
|
+
model
|
14
|
+
end
|
15
|
+
|
16
|
+
def after_save(result)
|
17
|
+
result
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Quiver::Mapper
|
2
|
+
class NotFoundError < Quiver::Error
|
3
|
+
def title
|
4
|
+
type
|
5
|
+
end
|
6
|
+
|
7
|
+
def detail
|
8
|
+
type
|
9
|
+
end
|
10
|
+
|
11
|
+
def path
|
12
|
+
"/#{subject}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def status
|
16
|
+
404
|
17
|
+
end
|
18
|
+
|
19
|
+
def code
|
20
|
+
:not_found_error
|
21
|
+
end
|
22
|
+
|
23
|
+
def serialization_type
|
24
|
+
'Error'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Quiver
|
2
|
+
module Mapper
|
3
|
+
class SimpleQueryBuilder
|
4
|
+
def initialize(mapper)
|
5
|
+
self.mapper = mapper
|
6
|
+
|
7
|
+
self._filter = {}
|
8
|
+
self._sort = {}
|
9
|
+
self._paginate = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_result
|
13
|
+
filter_errors = _filter.reduce(Quiver::ErrorCollection.new) do |errors, (k, v)|
|
14
|
+
if v
|
15
|
+
errors + v.errors.each_with_object(Quiver::ErrorCollection.new) do |error, collection|
|
16
|
+
collection << Quiver::Action::FilterError.new("#{k}: #{error.detail}")
|
17
|
+
end
|
18
|
+
else
|
19
|
+
errors
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if filter_errors.any?
|
24
|
+
return Quiver::Mapper::MapperResult.new([], filter_errors)
|
25
|
+
end
|
26
|
+
|
27
|
+
mapper.send(:query,
|
28
|
+
filter: mapper.class.send(:default_filter).merge(_filter),
|
29
|
+
sort: _sort,
|
30
|
+
page: _paginate
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def filter(params)
|
35
|
+
self._filter = params.to_h
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def sort(params)
|
40
|
+
params ||= ''
|
41
|
+
self._sort = params.split(',').map do |k|
|
42
|
+
asc = k[0] != '-'
|
43
|
+
k = k[1..-1] if !asc
|
44
|
+
|
45
|
+
[k, asc]
|
46
|
+
end.reject do |(k, _)|
|
47
|
+
!allowed_sorts.include?(k.to_sym)
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def paginate(params)
|
53
|
+
self._paginate = params.to_h.slice('limit', 'offset')
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
attr_accessor :mapper, :adapter, :_filter, :_sort, :_paginate
|
60
|
+
|
61
|
+
def allowed_filters
|
62
|
+
mapper.class.send(:filters)
|
63
|
+
end
|
64
|
+
|
65
|
+
def allowed_sorts
|
66
|
+
mapper.class.send(:sorts)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|