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,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