jsonapi-resources 0.7.1.beta1 → 0.7.1.beta2

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +248 -74
  3. data/lib/jsonapi-resources.rb +5 -3
  4. data/lib/jsonapi/acts_as_resource_controller.rb +77 -14
  5. data/lib/jsonapi/configuration.rb +77 -16
  6. data/lib/jsonapi/error.rb +12 -0
  7. data/lib/jsonapi/error_codes.rb +2 -0
  8. data/lib/jsonapi/exceptions.rb +29 -9
  9. data/lib/jsonapi/formatter.rb +29 -4
  10. data/lib/jsonapi/link_builder.rb +18 -18
  11. data/lib/jsonapi/mime_types.rb +25 -6
  12. data/lib/jsonapi/naive_cache.rb +30 -0
  13. data/lib/jsonapi/operation.rb +10 -342
  14. data/lib/jsonapi/operation_dispatcher.rb +87 -0
  15. data/lib/jsonapi/operation_result.rb +2 -1
  16. data/lib/jsonapi/paginator.rb +6 -2
  17. data/lib/jsonapi/processor.rb +283 -0
  18. data/lib/jsonapi/relationship.rb +6 -4
  19. data/lib/jsonapi/{request.rb → request_parser.rb} +46 -35
  20. data/lib/jsonapi/resource.rb +88 -13
  21. data/lib/jsonapi/resource_controller.rb +2 -14
  22. data/lib/jsonapi/resource_controller_metal.rb +17 -0
  23. data/lib/jsonapi/resource_serializer.rb +62 -47
  24. data/lib/jsonapi/resources/version.rb +1 -1
  25. data/lib/jsonapi/response_document.rb +13 -2
  26. data/lib/jsonapi/routing_ext.rb +49 -11
  27. metadata +37 -129
  28. data/.gitignore +0 -22
  29. data/.travis.yml +0 -9
  30. data/Gemfile +0 -23
  31. data/Rakefile +0 -20
  32. data/jsonapi-resources.gemspec +0 -29
  33. data/lib/jsonapi/active_record_operations_processor.rb +0 -35
  34. data/lib/jsonapi/operations_processor.rb +0 -120
  35. data/locales/en.yml +0 -80
  36. data/test/config/database.yml +0 -5
  37. data/test/controllers/controller_test.rb +0 -3312
  38. data/test/fixtures/active_record.rb +0 -1486
  39. data/test/fixtures/author_details.yml +0 -9
  40. data/test/fixtures/book_authors.yml +0 -3
  41. data/test/fixtures/book_comments.yml +0 -12
  42. data/test/fixtures/books.yml +0 -7
  43. data/test/fixtures/categories.yml +0 -35
  44. data/test/fixtures/comments.yml +0 -21
  45. data/test/fixtures/comments_tags.yml +0 -20
  46. data/test/fixtures/companies.yml +0 -4
  47. data/test/fixtures/craters.yml +0 -9
  48. data/test/fixtures/customers.yml +0 -11
  49. data/test/fixtures/documents.yml +0 -3
  50. data/test/fixtures/expense_entries.yml +0 -13
  51. data/test/fixtures/facts.yml +0 -11
  52. data/test/fixtures/hair_cuts.yml +0 -3
  53. data/test/fixtures/iso_currencies.yml +0 -17
  54. data/test/fixtures/line_items.yml +0 -37
  55. data/test/fixtures/makes.yml +0 -2
  56. data/test/fixtures/moons.yml +0 -6
  57. data/test/fixtures/numeros_telefone.yml +0 -3
  58. data/test/fixtures/order_flags.yml +0 -7
  59. data/test/fixtures/people.yml +0 -31
  60. data/test/fixtures/pictures.yml +0 -15
  61. data/test/fixtures/planet_types.yml +0 -19
  62. data/test/fixtures/planets.yml +0 -47
  63. data/test/fixtures/posts.yml +0 -102
  64. data/test/fixtures/posts_tags.yml +0 -59
  65. data/test/fixtures/preferences.yml +0 -14
  66. data/test/fixtures/products.yml +0 -3
  67. data/test/fixtures/purchase_orders.yml +0 -23
  68. data/test/fixtures/sections.yml +0 -8
  69. data/test/fixtures/tags.yml +0 -39
  70. data/test/fixtures/vehicles.yml +0 -17
  71. data/test/fixtures/web_pages.yml +0 -3
  72. data/test/helpers/assertions.rb +0 -13
  73. data/test/helpers/functional_helpers.rb +0 -59
  74. data/test/helpers/value_matchers.rb +0 -60
  75. data/test/helpers/value_matchers_test.rb +0 -40
  76. data/test/integration/requests/namespaced_model_test.rb +0 -13
  77. data/test/integration/requests/request_test.rb +0 -932
  78. data/test/integration/routes/routes_test.rb +0 -218
  79. data/test/integration/sti_fields_test.rb +0 -18
  80. data/test/lib/generators/jsonapi/controller_generator_test.rb +0 -25
  81. data/test/lib/generators/jsonapi/resource_generator_test.rb +0 -30
  82. data/test/test_helper.rb +0 -342
  83. data/test/unit/formatters/dasherized_key_formatter_test.rb +0 -8
  84. data/test/unit/jsonapi_request/jsonapi_request_test.rb +0 -199
  85. data/test/unit/operation/operations_processor_test.rb +0 -528
  86. data/test/unit/pagination/offset_paginator_test.rb +0 -245
  87. data/test/unit/pagination/paged_paginator_test.rb +0 -242
  88. data/test/unit/resource/resource_test.rb +0 -560
  89. data/test/unit/serializer/include_directives_test.rb +0 -113
  90. data/test/unit/serializer/link_builder_test.rb +0 -244
  91. data/test/unit/serializer/polymorphic_serializer_test.rb +0 -383
  92. data/test/unit/serializer/response_document_test.rb +0 -61
  93. data/test/unit/serializer/serializer_test.rb +0 -1939
@@ -0,0 +1,87 @@
1
+ module JSONAPI
2
+ class OperationDispatcher
3
+
4
+ def initialize(transaction: lambda { |&block| block.yield },
5
+ rollback: lambda { },
6
+ server_error_callbacks: [])
7
+
8
+ @transaction = transaction
9
+ @rollback = rollback
10
+ @server_error_callbacks = server_error_callbacks
11
+ end
12
+
13
+ def process(operations)
14
+ results = JSONAPI::OperationResults.new
15
+
16
+ # Use transactions if more than one operation and if one of the operations can be transactional
17
+ # Even if transactional transactions won't be used unless the derived OperationsProcessor supports them.
18
+ transactional = false
19
+ operations.each do |operation|
20
+ transactional |= operation.transactional?
21
+ end
22
+
23
+ transaction(transactional) do
24
+ # Links and meta data global to the set of operations
25
+ operations_meta = {}
26
+ operations_links = {}
27
+ operations.each do |operation|
28
+ results.add_result(process_operation(operation))
29
+ rollback(transactional) if results.has_errors?
30
+ end
31
+ results.meta = operations_meta
32
+ results.links = operations_links
33
+ end
34
+ results
35
+ end
36
+
37
+ private
38
+
39
+ def transaction(transactional)
40
+ if transactional
41
+ @transaction.call do
42
+ yield
43
+ end
44
+ else
45
+ yield
46
+ end
47
+ end
48
+
49
+ def rollback(transactional)
50
+ if transactional
51
+ @rollback.call
52
+ end
53
+ end
54
+
55
+ def process_operation(operation)
56
+ with_default_handling do
57
+ operation.process
58
+ end
59
+ end
60
+
61
+ def with_default_handling(&block)
62
+ block.yield
63
+ rescue => e
64
+ if JSONAPI.configuration.exception_class_whitelisted?(e)
65
+ raise e
66
+ else
67
+ @server_error_callbacks.each { |callback|
68
+ safe_run_callback(callback, e)
69
+ }
70
+
71
+ internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
72
+ Rails.logger.error { "Internal Server Error: #{e.message} #{e.backtrace.join("\n")}" }
73
+ return JSONAPI::ErrorsOperationResult.new(internal_server_error.errors[0].code, internal_server_error.errors)
74
+ end
75
+ end
76
+
77
+ def safe_run_callback(callback, error)
78
+ begin
79
+ callback.call(error)
80
+ rescue => e
81
+ Rails.logger.error { "Error in error handling callback: #{e.message} #{e.backtrace.join("\n")}" }
82
+ internal_server_error = JSONAPI::Exceptions::InternalServerError.new(e)
83
+ return JSONAPI::ErrorsOperationResult.new(internal_server_error.errors[0].code, internal_server_error.errors)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -32,12 +32,13 @@ module JSONAPI
32
32
  end
33
33
 
34
34
  class ResourcesOperationResult < OperationResult
35
- attr_accessor :resources, :pagination_params, :record_count
35
+ attr_accessor :resources, :pagination_params, :record_count, :page_count
36
36
 
37
37
  def initialize(code, resources, options = {})
38
38
  @resources = resources
39
39
  @pagination_params = options.fetch(:pagination_params, {})
40
40
  @record_count = options[:record_count]
41
+ @page_count = options[:page_count]
41
42
  super(code, options)
42
43
  end
43
44
  end
@@ -58,7 +58,7 @@ class OffsetPaginator < JSONAPI::Paginator
58
58
 
59
59
  previous_offset = 0 if previous_offset < 0
60
60
 
61
- links_page_params['previous'] = {
61
+ links_page_params['prev'] = {
62
62
  'offset' => previous_offset,
63
63
  'limit' => @limit
64
64
  }
@@ -131,6 +131,10 @@ class PagedPaginator < JSONAPI::Paginator
131
131
  true
132
132
  end
133
133
 
134
+ def calculate_page_count(record_count)
135
+ (record_count / @size.to_f).ceil
136
+ end
137
+
134
138
  def apply(relation, _order_options)
135
139
  offset = (@number - 1) * @size
136
140
  relation.offset(offset).limit(@size)
@@ -138,7 +142,7 @@ class PagedPaginator < JSONAPI::Paginator
138
142
 
139
143
  def links_page_params(options = {})
140
144
  record_count = options[:record_count]
141
- page_count = (record_count / @size.to_f).ceil
145
+ page_count = calculate_page_count(record_count)
142
146
 
143
147
  links_page_params = {}
144
148
 
@@ -0,0 +1,283 @@
1
+ module JSONAPI
2
+ class Processor
3
+ include Callbacks
4
+ define_jsonapi_resources_callbacks :find,
5
+ :show,
6
+ :show_relationship,
7
+ :show_related_resource,
8
+ :show_related_resources,
9
+ :create_resource,
10
+ :remove_resource,
11
+ :replace_fields,
12
+ :replace_to_one_relationship,
13
+ :replace_polymorphic_to_one_relationship,
14
+ :create_to_many_relationship,
15
+ :replace_to_many_relationship,
16
+ :remove_to_many_relationship,
17
+ :remove_to_one_relationship,
18
+ :operation
19
+
20
+ class << self
21
+ def processor_instance_for(resource_klass, operation_type, params)
22
+ _processor_from_resource_type(resource_klass).new(resource_klass, operation_type, params)
23
+ end
24
+
25
+ def _processor_from_resource_type(resource_klass)
26
+ processor = resource_klass.name.gsub(/Resource$/,'Processor').safe_constantize
27
+ if processor.nil?
28
+ processor = JSONAPI.configuration.default_processor_klass
29
+ end
30
+
31
+ return processor
32
+ end
33
+
34
+ def transactional_operation_type?(operation_type)
35
+ case operation_type
36
+ when :find, :show, :show_related_resource, :show_related_resources
37
+ return false
38
+ else
39
+ return true
40
+ end
41
+ end
42
+ end
43
+
44
+ attr_reader :resource_klass, :operation_type, :params, :context, :result, :result_options
45
+
46
+ def initialize(resource_klass, operation_type, params)
47
+ @resource_klass = resource_klass
48
+ @operation_type = operation_type
49
+ @params = params
50
+ @context = params[:context]
51
+ @result = nil
52
+ @result_options = {}
53
+ end
54
+
55
+ def process
56
+ run_callbacks :operation do
57
+ run_callbacks operation_type do
58
+ @result = send(operation_type)
59
+ end
60
+ end
61
+
62
+ rescue JSONAPI::Exceptions::Error => e
63
+ @result = JSONAPI::ErrorsOperationResult.new(e.errors[0].code, e.errors)
64
+ end
65
+
66
+ def find
67
+ filters = params[:filters]
68
+ include_directives = params[:include_directives]
69
+ sort_criteria = params.fetch(:sort_criteria, [])
70
+ paginator = params[:paginator]
71
+ fields = params[:fields]
72
+
73
+ verified_filters = resource_klass.verify_filters(filters, context)
74
+ resource_records = resource_klass.find(verified_filters,
75
+ context: context,
76
+ include_directives: include_directives,
77
+ sort_criteria: sort_criteria,
78
+ paginator: paginator,
79
+ fields: fields)
80
+
81
+ page_options = {}
82
+ if (JSONAPI.configuration.top_level_meta_include_record_count ||
83
+ (paginator && paginator.class.requires_record_count))
84
+ page_options[:record_count] = resource_klass.find_count(verified_filters,
85
+ context: context,
86
+ include_directives: include_directives)
87
+ end
88
+
89
+ if (JSONAPI.configuration.top_level_meta_include_page_count && page_options[:record_count])
90
+ page_options[:page_count] = paginator.calculate_page_count(page_options[:record_count])
91
+ end
92
+
93
+ if JSONAPI.configuration.top_level_links_include_pagination && paginator
94
+ page_options[:pagination_params] = paginator.links_page_params(page_options)
95
+ end
96
+
97
+ return JSONAPI::ResourcesOperationResult.new(:ok, resource_records, page_options)
98
+ end
99
+
100
+ def show
101
+ include_directives = params[:include_directives]
102
+ fields = params[:fields]
103
+ id = params[:id]
104
+
105
+ key = resource_klass.verify_key(id, context)
106
+
107
+ resource_record = resource_klass.find_by_key(key,
108
+ context: context,
109
+ include_directives: include_directives,
110
+ fields: fields)
111
+
112
+ return JSONAPI::ResourceOperationResult.new(:ok, resource_record)
113
+ end
114
+
115
+ def show_relationship
116
+ parent_key = params[:parent_key]
117
+ relationship_type = params[:relationship_type].to_sym
118
+
119
+ parent_resource = resource_klass.find_by_key(parent_key, context: context)
120
+
121
+ return JSONAPI::LinksObjectOperationResult.new(:ok,
122
+ parent_resource,
123
+ resource_klass._relationship(relationship_type))
124
+ end
125
+
126
+ def show_related_resource
127
+ source_klass = params[:source_klass]
128
+ source_id = params[:source_id]
129
+ relationship_type = params[:relationship_type].to_sym
130
+ fields = params[:fields]
131
+
132
+ source_resource = source_klass.find_by_key(source_id, context: context, fields: fields)
133
+
134
+ related_resource = source_resource.public_send(relationship_type)
135
+
136
+ return JSONAPI::ResourceOperationResult.new(:ok, related_resource)
137
+ end
138
+
139
+ def show_related_resources
140
+ source_klass = params[:source_klass]
141
+ source_id = params[:source_id]
142
+ relationship_type = params[:relationship_type]
143
+ filters = params[:filters]
144
+ sort_criteria = params[:sort_criteria]
145
+ paginator = params[:paginator]
146
+ fields = params[:fields]
147
+
148
+ source_resource ||= source_klass.find_by_key(source_id, context: context, fields: fields)
149
+
150
+ related_resources = source_resource.public_send(relationship_type,
151
+ filters: filters,
152
+ sort_criteria: sort_criteria,
153
+ paginator: paginator,
154
+ fields: fields)
155
+
156
+ if ((JSONAPI.configuration.top_level_meta_include_record_count) ||
157
+ (paginator && paginator.class.requires_record_count) ||
158
+ (JSONAPI.configuration.top_level_meta_include_page_count))
159
+ related_resource_records = source_resource.public_send("records_for_" + relationship_type)
160
+ records = resource_klass.filter_records(filters, {},
161
+ related_resource_records)
162
+
163
+ record_count = resource_klass.count_records(records)
164
+ end
165
+
166
+ if (JSONAPI.configuration.top_level_meta_include_page_count && record_count)
167
+ page_count = paginator.calculate_page_count(record_count)
168
+ end
169
+
170
+ pagination_params = if paginator && JSONAPI.configuration.top_level_links_include_pagination
171
+ page_options = {}
172
+ page_options[:record_count] = record_count if paginator.class.requires_record_count
173
+ paginator.links_page_params(page_options)
174
+ else
175
+ {}
176
+ end
177
+
178
+ opts = {}
179
+ opts.merge!(pagination_params: pagination_params) if JSONAPI.configuration.top_level_links_include_pagination
180
+ opts.merge!(record_count: record_count) if JSONAPI.configuration.top_level_meta_include_record_count
181
+ opts.merge!(page_count: page_count) if JSONAPI.configuration.top_level_meta_include_page_count
182
+
183
+ return JSONAPI::RelatedResourcesOperationResult.new(:ok,
184
+ source_resource,
185
+ relationship_type,
186
+ related_resources,
187
+ opts)
188
+ end
189
+
190
+ def create_resource
191
+ data = params[:data]
192
+ resource = resource_klass.create(context)
193
+ result = resource.replace_fields(data)
194
+
195
+ return JSONAPI::ResourceOperationResult.new((result == :completed ? :created : :accepted), resource)
196
+ end
197
+
198
+ def remove_resource
199
+ resource_id = params[:resource_id]
200
+
201
+ resource = resource_klass.find_by_key(resource_id, context: context)
202
+ result = resource.remove
203
+
204
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
205
+ end
206
+
207
+ def replace_fields
208
+ resource_id = params[:resource_id]
209
+ data = params[:data]
210
+
211
+ resource = resource_klass.find_by_key(resource_id, context: context)
212
+ result = resource.replace_fields(data)
213
+
214
+ return JSONAPI::ResourceOperationResult.new(result == :completed ? :ok : :accepted, resource)
215
+ end
216
+
217
+ def replace_to_one_relationship
218
+ resource_id = params[:resource_id]
219
+ relationship_type = params[:relationship_type].to_sym
220
+ key_value = params[:key_value]
221
+
222
+ resource = resource_klass.find_by_key(resource_id, context: context)
223
+ result = resource.replace_to_one_link(relationship_type, key_value)
224
+
225
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
226
+ end
227
+
228
+ def replace_polymorphic_to_one_relationship
229
+ resource_id = params[:resource_id]
230
+ relationship_type = params[:relationship_type].to_sym
231
+ key_value = params[:key_value]
232
+ key_type = params[:key_type]
233
+
234
+ resource = resource_klass.find_by_key(resource_id, context: context)
235
+ result = resource.replace_polymorphic_to_one_link(relationship_type, key_value, key_type)
236
+
237
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
238
+ end
239
+
240
+ def create_to_many_relationship
241
+ resource_id = params[:resource_id]
242
+ relationship_type = params[:relationship_type].to_sym
243
+ data = params[:data]
244
+
245
+ resource = resource_klass.find_by_key(resource_id, context: context)
246
+ result = resource.create_to_many_links(relationship_type, data)
247
+
248
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
249
+ end
250
+
251
+ def replace_to_many_relationship
252
+ resource_id = params[:resource_id]
253
+ relationship_type = params[:relationship_type].to_sym
254
+ data = params.fetch(:data)
255
+
256
+ resource = resource_klass.find_by_key(resource_id, context: context)
257
+ result = resource.replace_to_many_links(relationship_type, data)
258
+
259
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
260
+ end
261
+
262
+ def remove_to_many_relationship
263
+ resource_id = params[:resource_id]
264
+ relationship_type = params[:relationship_type].to_sym
265
+ associated_key = params[:associated_key]
266
+
267
+ resource = resource_klass.find_by_key(resource_id, context: context)
268
+ result = resource.remove_to_many_link(relationship_type, associated_key)
269
+
270
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
271
+ end
272
+
273
+ def remove_to_one_relationship
274
+ resource_id = params[:resource_id]
275
+ relationship_type = params[:relationship_type].to_sym
276
+
277
+ resource = resource_klass.find_by_key(resource_id, context: context)
278
+ result = resource.remove_to_one_link(relationship_type)
279
+
280
+ return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
281
+ end
282
+ end
283
+ end
@@ -1,6 +1,6 @@
1
1
  module JSONAPI
2
2
  class Relationship
3
- attr_reader :acts_as_set, :foreign_key, :type, :options, :name,
3
+ attr_reader :acts_as_set, :foreign_key, :options, :name,
4
4
  :class_name, :polymorphic, :always_include_linkage_data,
5
5
  :parent_resource
6
6
 
@@ -22,13 +22,17 @@ module JSONAPI
22
22
  end
23
23
 
24
24
  def resource_klass
25
- @resource_klass = @parent_resource.resource_for(@class_name)
25
+ @resource_klass ||= @parent_resource.resource_for(@class_name)
26
26
  end
27
27
 
28
28
  def table_name
29
29
  @table_name ||= resource_klass._table_name
30
30
  end
31
31
 
32
+ def type
33
+ @type ||= resource_klass._type.to_sym
34
+ end
35
+
32
36
  def relation_name(options)
33
37
  case @relation_name
34
38
  when Symbol
@@ -61,7 +65,6 @@ module JSONAPI
61
65
  def initialize(name, options = {})
62
66
  super
63
67
  @class_name = options.fetch(:class_name, name.to_s.camelize)
64
- @type = class_name.underscore.pluralize.to_sym
65
68
  @foreign_key ||= "#{name}_id".to_sym
66
69
  @foreign_key_on = options.fetch(:foreign_key_on, :self)
67
70
  end
@@ -79,7 +82,6 @@ module JSONAPI
79
82
  def initialize(name, options = {})
80
83
  super
81
84
  @class_name = options.fetch(:class_name, name.to_s.camelize.singularize)
82
- @type = class_name.underscore.pluralize.to_sym
83
85
  @foreign_key ||= "#{name.to_s.singularize}_ids".to_sym
84
86
  end
85
87
  end