quiver 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +6 -0
  6. data/README.md +22 -0
  7. data/Rakefile +17 -0
  8. data/bin/quiver +9 -0
  9. data/lib/quiver.rb +46 -0
  10. data/lib/quiver/abstract_action.rb +31 -0
  11. data/lib/quiver/action.rb +208 -0
  12. data/lib/quiver/action/filter_error.rb +33 -0
  13. data/lib/quiver/action/filter_value.rb +152 -0
  14. data/lib/quiver/action/invalid_request_body_error.rb +30 -0
  15. data/lib/quiver/action/pagination_link_builder.rb +67 -0
  16. data/lib/quiver/adapter.rb +51 -0
  17. data/lib/quiver/adapter/active_record_adapter_filter.rb +102 -0
  18. data/lib/quiver/adapter/active_record_helpers.rb +258 -0
  19. data/lib/quiver/adapter/arec_low_level_creator.rb +82 -0
  20. data/lib/quiver/adapter/arec_low_level_deleter.rb +74 -0
  21. data/lib/quiver/adapter/arec_low_level_updater.rb +105 -0
  22. data/lib/quiver/adapter/filter_helpers.rb +58 -0
  23. data/lib/quiver/adapter/helpers_helpers.rb +53 -0
  24. data/lib/quiver/adapter/memory_adapter_filter.rb +71 -0
  25. data/lib/quiver/adapter/memory_adapter_store.rb +34 -0
  26. data/lib/quiver/adapter/memory_helpers.rb +182 -0
  27. data/lib/quiver/adapter/memory_uuid_primary_key.rb +25 -0
  28. data/lib/quiver/application.rb +128 -0
  29. data/lib/quiver/cli/app.rb +25 -0
  30. data/lib/quiver/cli/generators/endpoint.rb +17 -0
  31. data/lib/quiver/cli/generators/new_application.rb +166 -0
  32. data/lib/quiver/cli/generators/new_application_cli.rb +25 -0
  33. data/lib/quiver/cli/server.rb +37 -0
  34. data/lib/quiver/cli/templates/Gemfile.tt +8 -0
  35. data/lib/quiver/cli/templates/Rakefile.tt +12 -0
  36. data/lib/quiver/cli/templates/config.tt +3 -0
  37. data/lib/quiver/cli/templates/config/database.tt +21 -0
  38. data/lib/quiver/cli/templates/gemspec.tt +33 -0
  39. data/lib/quiver/cli/templates/gitignore.tt +10 -0
  40. data/lib/quiver/cli/templates/lib/application.tt +14 -0
  41. data/lib/quiver/cli/templates/lib/application/config/router.tt +11 -0
  42. data/lib/quiver/cli/templates/lib/application/version.tt +3 -0
  43. data/lib/quiver/cli/templates/spec/spec_helper.tt +19 -0
  44. data/lib/quiver/duty.rb +34 -0
  45. data/lib/quiver/duty_master.rb +23 -0
  46. data/lib/quiver/duty_master/delayed_job_adapter.rb +15 -0
  47. data/lib/quiver/duty_master/memory_adapter.rb +18 -0
  48. data/lib/quiver/duty_test_helper.rb +23 -0
  49. data/lib/quiver/duty_test_helper/delayed_job_helper.rb +9 -0
  50. data/lib/quiver/duty_test_helper/memory_helper.rb +15 -0
  51. data/lib/quiver/error.rb +24 -0
  52. data/lib/quiver/error_collection.rb +60 -0
  53. data/lib/quiver/json_parser.rb +17 -0
  54. data/lib/quiver/logger.rb +26 -0
  55. data/lib/quiver/mapper.rb +311 -0
  56. data/lib/quiver/mapper/hook.rb +21 -0
  57. data/lib/quiver/mapper/mapper_result.rb +7 -0
  58. data/lib/quiver/mapper/not_found_error.rb +27 -0
  59. data/lib/quiver/mapper/simple_query_builder.rb +70 -0
  60. data/lib/quiver/mapper/soft_delete.rb +15 -0
  61. data/lib/quiver/mappers.rb +75 -0
  62. data/lib/quiver/middleware_stack.rb +35 -0
  63. data/lib/quiver/model.rb +63 -0
  64. data/lib/quiver/model/soft_delete.rb +14 -0
  65. data/lib/quiver/model/validation_error.rb +27 -0
  66. data/lib/quiver/model/validations.rb +45 -0
  67. data/lib/quiver/patcher.rb +94 -0
  68. data/lib/quiver/result.rb +44 -0
  69. data/lib/quiver/route_helper.rb +16 -0
  70. data/lib/quiver/router.rb +37 -0
  71. data/lib/quiver/serialization.rb +6 -0
  72. data/lib/quiver/serialization/json_api.rb +7 -0
  73. data/lib/quiver/serialization/json_api/item_type_handler.rb +96 -0
  74. data/lib/quiver/serialization/json_api/serializer.rb +77 -0
  75. data/lib/quiver/tasks.rb +31 -0
  76. data/lib/quiver/validator.rb +83 -0
  77. data/lib/quiver/validators/base.rb +21 -0
  78. data/lib/quiver/validators/presence.rb +34 -0
  79. data/lib/quiver/validators/unique.rb +33 -0
  80. data/lib/quiver/version.rb +3 -0
  81. data/quiver.gemspec +42 -0
  82. 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,7 @@
1
+ module Quiver
2
+ module Mapper
3
+ class MapperResult
4
+ include Quiver::Result
5
+ end
6
+ end
7
+ 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