jsonapi-resources 0.7.1.beta1 → 0.7.1.beta2

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