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,30 @@
1
+ module Quiver::Action
2
+ class InvalidRequestBodyError < Quiver::Error
3
+ def initialize
4
+ end
5
+
6
+ def title
7
+ 'invalid_request_body'
8
+ end
9
+
10
+ def detail
11
+ 'request body must be valid json'
12
+ end
13
+
14
+ def path
15
+ "/"
16
+ end
17
+
18
+ def status
19
+ 400
20
+ end
21
+
22
+ def code
23
+ :invalid_request_body
24
+ end
25
+
26
+ def serialization_type
27
+ 'Error'
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,67 @@
1
+ module Quiver
2
+ module Action
3
+ class PaginationLinkBuilder
4
+ def initialize(request_path_with_query, offset, limit, total_count)
5
+ self.request_path_with_query = request_path_with_query
6
+ self.offset = offset
7
+ self.limit = limit
8
+ self.total_count = total_count
9
+ end
10
+
11
+ def pagination_links
12
+ links = {}
13
+ links[:self] = request_path_with_query
14
+ links[:last] = build_link(last_page)
15
+
16
+ links[:next] = build_link(next_page) if next_page
17
+ links[:prev] = build_link(previous_page) if previous_page
18
+
19
+ links
20
+ end
21
+
22
+ private
23
+
24
+ attr_accessor :request_path_with_query, :offset, :limit, :total_count
25
+
26
+ def build_link(offset)
27
+ uri = parsed_uri
28
+ query_params = Rack::Utils.parse_nested_query(uri.query)
29
+ query_params['page'] ||= {}
30
+ query_params['page']['limit'] = limit
31
+ query_params['page']['offset'] = offset
32
+ uri.query = Rack::Utils.build_nested_query(query_params)
33
+ uri.to_s
34
+ end
35
+
36
+ def parsed_uri
37
+ URI.parse(request_path_with_query)
38
+ end
39
+
40
+ def next_page
41
+ if limit != -1 && total_count > offset + limit
42
+ offset + limit
43
+ end
44
+ end
45
+
46
+ def previous_page
47
+ if offset > 0
48
+ if limit == -1
49
+ 0
50
+ else
51
+ [offset - limit, 0].max
52
+ end
53
+ end
54
+ end
55
+
56
+ def last_page
57
+ if limit == -1
58
+ 0
59
+ else
60
+ # rounds total_count down to the offset that
61
+ # represents the last page of the resources
62
+ (total_count / limit) * limit
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,51 @@
1
+ module Quiver
2
+ module Adapter
3
+ class AdapterResult
4
+ include Quiver::Result
5
+ end
6
+
7
+ def self.included(host)
8
+ host.send(:extend, ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ def adapter_type(val=nil)
13
+ if val
14
+ @adapter_type = val
15
+ end
16
+
17
+ @adapter_type
18
+ end
19
+
20
+ def primary_key_name(val=nil)
21
+ if val
22
+ @primary_key_name = val
23
+ end
24
+
25
+ if @primary_key_name.nil?
26
+ raise RuntimeError, 'mapper adapters must specify primary_key_name'
27
+ end
28
+
29
+ @primary_key_name
30
+ end
31
+ end
32
+
33
+ def adapter_type
34
+ self.class.adapter_type
35
+ end
36
+
37
+ private
38
+
39
+ attr_accessor :mapper_klass
40
+
41
+ def primary_key_name
42
+ self.class.primary_key_name
43
+ end
44
+ end
45
+ end
46
+
47
+ require 'quiver/adapter/filter_helpers'
48
+ require 'quiver/adapter/helpers_helpers'
49
+ require 'quiver/adapter/memory_helpers'
50
+ require 'quiver/adapter/memory_uuid_primary_key'
51
+ require 'quiver/adapter/active_record_helpers'
@@ -0,0 +1,102 @@
1
+ module Quiver
2
+ module Adapter
3
+ module ActiveRecordAdapterFilterDefaults
4
+ include FilterHelpers
5
+
6
+ def self.included(host)
7
+ host.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def find(attr_name, opts)
12
+ raise ArgumentError, ':on must be a table name specified as a symbol' unless opts[:on].is_a?(Symbol)
13
+ # in the future we want to support arrays, but right now we don't need
14
+ # it so it is hard to justify the extra time it would take
15
+ #
16
+ # tables = opts[:on].is_a?(Array) ? opts[:on] : [opts[:on]]
17
+ attr_lookup_table[attr_name.to_sym] = opts[:on]
18
+ end
19
+
20
+ private
21
+
22
+ def attr_lookup_table
23
+ @attr_lookup_table ||= {}
24
+ end
25
+ end
26
+
27
+ def equal_filter(memo, attr, value)
28
+ memo.where(hash_style_attr(attr, value))
29
+ end
30
+
31
+ def not_equal_filter(memo, attr, value)
32
+ memo.where.not(hash_style_attr(attr, value))
33
+ end
34
+
35
+ def in_filter(memo, attr, value)
36
+ memo.where(hash_style_attr(attr, value))
37
+ end
38
+
39
+ def not_in_filter(memo, attr, value)
40
+ memo.where.not(hash_style_attr(attr, value))
41
+ end
42
+
43
+ def less_than_filter(memo, attr, value)
44
+ memo.where("#{string_style_attr(attr)} < ?", value)
45
+ end
46
+
47
+ def greater_than_filter(memo, attr, value)
48
+ memo.where("#{string_style_attr(attr)} > ?", value)
49
+ end
50
+
51
+ def less_than_or_equal_filter(memo, attr, value)
52
+ memo.where("#{string_style_attr(attr)} <= ?", value)
53
+ end
54
+
55
+ def greater_than_or_equal_filter(memo, attr, value)
56
+ memo.where("#{string_style_attr(attr)} >= ?", value)
57
+ end
58
+
59
+ def nil_filter(memo, attr, value)
60
+ if value == 'true' || value == true
61
+ memo.where("#{string_style_attr(attr)} IS NULL")
62
+ else
63
+ memo.where("#{string_style_attr(attr)} IS NOT NULL")
64
+ end
65
+ end
66
+
67
+ def not_nil_filter(memo, attr, value)
68
+ if value == 'true' || value == true
69
+ memo.where("#{string_style_attr(attr)} IS NOT NULL")
70
+ else
71
+ memo.where("#{string_style_attr(attr)} IS NULL")
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def attr_lookup(attr)
78
+ self.class.send(:attr_lookup_table)[attr]
79
+ end
80
+
81
+ def hash_style_attr(attr, value)
82
+ if table = attr_lookup(attr)
83
+ {table => {attr => value}}
84
+ else
85
+ {attr => value}
86
+ end
87
+ end
88
+
89
+ def string_style_attr(attr)
90
+ if table = attr_lookup(attr)
91
+ "#{table}.#{attr}"
92
+ else
93
+ attr
94
+ end
95
+ end
96
+ end
97
+
98
+ class ActiveRecordAdapterFilter
99
+ include ActiveRecordAdapterFilterDefaults
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,258 @@
1
+ require 'quiver/adapter/active_record_adapter_filter'
2
+ require 'quiver/adapter/arec_low_level_creator'
3
+ require 'quiver/adapter/arec_low_level_updater'
4
+ require 'quiver/adapter/arec_low_level_deleter'
5
+ require 'quiver/mappers'
6
+
7
+ module Quiver
8
+ module Adapter
9
+ module ActiveRecord
10
+ include Quiver::Adapter::HelpersHelpers
11
+
12
+ def self.included(host)
13
+ super
14
+
15
+ host.adapter_type(:active_record)
16
+ host.send(:extend, ClassMethods)
17
+ end
18
+
19
+ module ClassMethods
20
+ def define_record_class(name, options)
21
+ return if !self.parents[-2]::Application.using_active_record
22
+
23
+ table_name = options.fetch(:table)
24
+
25
+ record_classes[name] = Class.new(::ActiveRecord::Base) do
26
+ self.table_name = table_name
27
+ self.inheritance_column = 'a_name_that_will_never_be_used'
28
+
29
+ def self.name=(val)
30
+ @reported_name = val
31
+ end
32
+
33
+ def self.name
34
+ @reported_name
35
+ end
36
+ end.tap do |klass|
37
+ klass.name = name
38
+ end
39
+ end
40
+
41
+ def record_classes
42
+ @record_classes ||= {}
43
+ end
44
+
45
+ def use_record_class(val=nil)
46
+ if val
47
+ @use_record_class = val
48
+ end
49
+
50
+ @use_record_class
51
+ end
52
+ end
53
+
54
+ def find(primary_key)
55
+ Quiver::Adapter::AdapterResult.new do |errors|
56
+ if record = base_query.find_by(primary_key_name => primary_key)
57
+ load_additional([record.attributes.symbolize_keys]).first
58
+ else
59
+ errors << Quiver::Mapper::NotFoundError.new('record', 'not_found')
60
+ nil
61
+ end
62
+ end
63
+ end
64
+
65
+ def count
66
+ count = default_record_class.count
67
+
68
+ Quiver::Adapter::AdapterResult.new(count)
69
+ end
70
+
71
+ def create(attributes, transaction)
72
+ persister = ARecLowLevelCreator.new(self.class, attributes)
73
+
74
+ if attributes[:__type__]
75
+ attributes = attributes.merge(
76
+ attributes[:__type__][:name] => attributes[:__type__][:value]
77
+ )
78
+
79
+ attributes.delete(:__type__)
80
+ end
81
+
82
+ begin
83
+ create_mappings(attributes, persister)
84
+ rescue ::ActiveRecord::ActiveRecordError
85
+ transaction.rollback!
86
+ persister.failed!
87
+ end
88
+
89
+ Quiver::Adapter::AdapterResult.new do |errors|
90
+ if persister.success?
91
+ persister.result
92
+ else
93
+ errors << Quiver::Error.new('record', 'not_persisted')
94
+ nil
95
+ end
96
+ end
97
+ end
98
+
99
+ def update(attributes, transaction)
100
+ persister = ARecLowLevelUpdater.new(self.class, attributes)
101
+
102
+ if attributes[:__type__]
103
+ attributes = attributes.merge(
104
+ attributes[:__type__][:name] => attributes[:__type__][:value]
105
+ )
106
+
107
+ attributes.delete(:__type__)
108
+ end
109
+
110
+ begin
111
+ update_mappings(attributes, persister)
112
+ rescue ::ActiveRecord::ActiveRecordError
113
+ transaction.rollback!
114
+ persister.failed!
115
+ end
116
+
117
+ Quiver::Adapter::AdapterResult.new do |errors|
118
+ if persister.success?
119
+ persister.result
120
+ else
121
+ errors << Quiver::Error.new('record', 'not_updated')
122
+ nil
123
+ end
124
+ end
125
+ end
126
+
127
+ def hard_delete(attributes, transaction)
128
+ unpersister = ARecLowLevelDeleter.new(self.class, attributes)
129
+
130
+ if attributes[:__type__]
131
+ attributes = attributes.merge(
132
+ attributes[:__type__][:name] => attributes[:__type__][:value]
133
+ )
134
+
135
+ attributes.delete(:__type__)
136
+ end
137
+
138
+ begin
139
+ delete_mappings(attributes, unpersister)
140
+ rescue ::ActiveRecord::ActiveRecordError
141
+ transaction.rollback!
142
+ unpersister.failed!
143
+ end
144
+
145
+ Quiver::Adapter::AdapterResult.new do |errors|
146
+ if unpersister.success?
147
+ {}
148
+ else
149
+ errors << Quiver::Error.new('record', 'not_deleted')
150
+ nil
151
+ end
152
+ end
153
+ end
154
+
155
+ def query(q={})
156
+ filter_params = q[:filter] || {}
157
+ sort_params = q[:sort] || {}
158
+ pagination_params = q[:page] || {}
159
+
160
+ query = filter_klass.new(
161
+ base_query,
162
+ filter_params
163
+ ).filter
164
+
165
+ count_query = filter_klass.new(
166
+ base_count_query,
167
+ filter_params
168
+ ).filter
169
+
170
+ sort_params.each do |attr, asc|
171
+ order = asc ? 'ASC' : 'DESC'
172
+ query = query.order("#{attr} #{order}")
173
+ count_query = count_query.order("#{attr} #{order}")
174
+ end
175
+
176
+ total_count = count_query.count
177
+
178
+ if pagination_params['limit'] && pagination_params['limit'] != -1
179
+ query = query.limit(pagination_params['limit'])
180
+ end
181
+
182
+ if pagination_params['offset']
183
+ query = query.offset(pagination_params['offset'])
184
+ end
185
+
186
+ objects = load_additional(query.map(&:attributes).map(&:symbolize_keys))
187
+ result = Quiver::Adapter::AdapterResult.new(objects)
188
+
189
+ if pagination_params.any?
190
+ result.data[:pagination_offset] = pagination_params['offset'] || 0
191
+ result.data[:pagination_limit] = pagination_params['limit'] || -1
192
+ result.data[:total_count] = total_count
193
+ end
194
+
195
+ result
196
+ end
197
+
198
+ private
199
+
200
+ def default_record_class
201
+ self.class.record_classes[self.class.use_record_class]
202
+ end
203
+
204
+ def fetch_and_hydrate(record_klass, primary_key)
205
+ errors = Quiver::ErrorCollection.new
206
+
207
+ if record = record_klass.find_by(primary_key_name => primary_key)
208
+ attributes = load_additional([record.attributes]).first
209
+
210
+ object = hydrate(attributes)
211
+ else
212
+ errors << Quiver::Mapper::NotFoundError.new('record', 'not_found')
213
+ object = nil
214
+ end
215
+
216
+ Quiver::Mapper::MapperResult.new(object, errors)
217
+ end
218
+
219
+ def base_query
220
+ default_record_class.where({})
221
+ end
222
+
223
+ def base_count_query
224
+ default_record_class.where({})
225
+ end
226
+
227
+ def filter_klass
228
+ Quiver::Adapter::ActiveRecordAdapterFilter
229
+ end
230
+
231
+ def load_additional(items)
232
+ items
233
+ end
234
+
235
+ def mappings(attributes, p)
236
+ p.map(
237
+ attributes,
238
+ to: self.class.use_record_class,
239
+ primary: true
240
+ )
241
+ end
242
+
243
+ def create_mappings(attributes, p)
244
+ mappings(attributes, p)
245
+ end
246
+
247
+ def update_mappings(attributes, p)
248
+ mappings(attributes, p)
249
+ end
250
+
251
+ def delete_mappings(attributes, p)
252
+ mappings(attributes, p)
253
+ end
254
+ end
255
+
256
+ ActiveRecordHelpers = ActiveRecord
257
+ end
258
+ end