quiver 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
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