jsonapi-resources 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 885923e290e5ac1db4d49ed616f8f93b978edabe
4
- data.tar.gz: f9ba1571591967f00bd09b274b2aae34b01153e0
3
+ metadata.gz: 233ad0db650881125288636b8fc022ccace40b01
4
+ data.tar.gz: 9c28dc89ef3f227398d6eecb9e4e274bcbd40d59
5
5
  SHA512:
6
- metadata.gz: 305b0ae0b9f37ec829b178769044957f12f90c0272b69339b930deabac8f8f34bf310539597eeb01adc911c3bb897fd9aa60b52b3b077c9d925e926fa9fca157
7
- data.tar.gz: d9716961f3daeb72d56ae12b37c42960f8dcd99042f8cf6d884d67081ceebfc1ae20510eef1d682fe504e726433ce443dc5007c6cd3da0a1065c1eef708948c3
6
+ metadata.gz: 57bca518364bab67494516f10166275905eeb115709113601eb96c691d6bbf1ee2d1db4aa0c17ec83093f746cc8d38f37d4f8f11e1b59b2a2ae68a35922585ca
7
+ data.tar.gz: 30c3a7125c2eccbcda03e0daa8adab71ac8542afa599d8787087ee9fa461aa1efaff9e95fc8544f6762b8405000d82cec1911adb6d3ec7e7a1e269f60bf387e2
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # JSONAPI::Resources [![Build Status](https://secure.travis-ci.org/cerebris/jsonapi-resources.png?branch=master)](http://travis-ci.org/cerebris/jsonapi-resources) [![Code Climate](https://codeclimate.com/github/cerebris/jsonapi-resources/badges/gpa.svg)](https://codeclimate.com/github/cerebris/jsonapi-resources)
1
+ # JSONAPI::Resources [![Build Status](https://secure.travis-ci.org/cerebris/jsonapi-resources.svg?branch=master)](http://travis-ci.org/cerebris/jsonapi-resources) [![Code Climate](https://codeclimate.com/github/cerebris/jsonapi-resources/badges/gpa.svg)](https://codeclimate.com/github/cerebris/jsonapi-resources)
2
2
 
3
3
  [![Join the chat at https://gitter.im/cerebris/jsonapi-resources](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cerebris/jsonapi-resources?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
4
 
@@ -26,6 +26,7 @@ backed by ActiveRecord models or by custom objects.
26
26
  * [Filters] (#filters)
27
27
  * [Pagination] (#pagination)
28
28
  * [Included relationships (side-loading resources)] (#included-relationships-side-loading-resources)
29
+ * [Resource meta] (#resource-meta)
29
30
  * [Callbacks] (#callbacks)
30
31
  * [Controllers] (#controllers)
31
32
  * [Namespaces] (#namespaces)
@@ -186,7 +187,7 @@ end
186
187
  ##### Fetchable Attributes
187
188
 
188
189
  By default all attributes are assumed to be fetchable. The list of fetchable attributes can be filtered by overriding
189
- the `fetchable_fields` method.
190
+ the `self.fetchable_fields` method.
190
191
 
191
192
  Here's an example that prevents guest users from seeing the `email` field:
192
193
 
@@ -196,7 +197,7 @@ class AuthorResource < JSONAPI::Resource
196
197
  model_name 'Person'
197
198
  has_many :posts
198
199
 
199
- def fetchable_fields
200
+ def self.fetchable_fields(context)
200
201
  if (context[:current_user].guest)
201
202
  super - [:email]
202
203
  else
@@ -370,7 +371,7 @@ posts:
370
371
 
371
372
  ```ruby
372
373
  class PostResource < JSONAPI::Resource
373
- attribute :title, :body
374
+ attributes :title, :body
374
375
 
375
376
  relationship :author, to: :one
376
377
  end
@@ -390,7 +391,7 @@ And here's the equivalent resources using the `has_one` and `has_many` methods:
390
391
 
391
392
  ```ruby
392
393
  class PostResource < JSONAPI::Resource
393
- attribute :title, :body
394
+ attributes :title, :body
394
395
 
395
396
  has_one :author
396
397
  end
@@ -523,7 +524,7 @@ For example to allow a user to only retrieve his own posts you can do the follow
523
524
 
524
525
  ```ruby
525
526
  class PostResource < JSONAPI::Resource
526
- attribute :title, :body
527
+ attributes :title, :body
527
528
 
528
529
  def self.records(options = {})
529
530
  context = options[:context]
@@ -793,6 +794,33 @@ Will get you the following payload by default:
793
794
  }
794
795
  ```
795
796
 
797
+ #### Resource Meta
798
+
799
+ Meta information can be included for each resource using the meta method in the resource declaration. For example:
800
+
801
+ ```ruby
802
+ class BookResource < JSONAPI::Resource
803
+ attribute :title
804
+ attribute :isbn
805
+
806
+ def meta(options)
807
+ {
808
+ copyright: 'API Copyright 2015 - XYZ Corp.',
809
+ computed_copyright: options[:serialization_options][:copyright]
810
+ last_updated_at: _model.updated_at
811
+ }
812
+ end
813
+ end
814
+
815
+ ```
816
+
817
+ The `meta` method will be called for each resource instance. Override the `meta` method on a resource class to control
818
+ the meta information for the resource. If a non empty hash is returned from `meta` this will be serialized. The `meta`
819
+ method is called with an `options` has. The `options` hash will contain the following:
820
+
821
+ * `:serializer` -> the serializer instance
822
+ * `:serialization_options` -> the contents of the `serialization_options` method on the controller.
823
+
796
824
  #### Callbacks
797
825
 
798
826
  `ActiveSupport::Callbacks` is used to provide callback functionality, so the behavior is very similar to what you may be
@@ -907,6 +935,9 @@ end
907
935
 
908
936
  Of course you are free to extend this as needed and override action handlers or other methods.
909
937
 
938
+
939
+ ###### Context
940
+
910
941
  The context that's used for serialization and resource configuration is set by the controller's `context` method.
911
942
 
912
943
  For example:
@@ -925,7 +956,21 @@ class PeopleController < ApplicationController
925
956
  end
926
957
  ```
927
958
 
928
- > __Note__: This gem [uses the filter chain to set up the request](https://github.com/cerebris/jsonapi-resources/issues/458#issuecomment-143297055). In some instances, variables that are set in the filter chain (such as `current_user`) may not be set at the right time. If this happens (i.e. `current_user` is `nil` in `context` but it's set properly everywhere else), you may want to have your authentication occur earlier in the filter chain, using `prepend_before_action` instead of `before_action`.
959
+ ###### Serialization Options
960
+
961
+ Additional options can be passed to the serializer using the `serialization_options` method.
962
+
963
+ For example:
964
+
965
+ ```ruby
966
+ class ApplicationController < JSONAPI::ResourceController
967
+ def serialization_options
968
+ {copyright: 'Copyright 2015'}
969
+ end
970
+ end
971
+ ```
972
+
973
+ These `serialization_options` are passed to the `meta` method used to generate resource `meta` values.
929
974
 
930
975
  ##### ActsAsResourceController
931
976
 
@@ -2,56 +2,74 @@ require 'csv'
2
2
 
3
3
  module JSONAPI
4
4
  module ActsAsResourceController
5
- extend ActiveSupport::Concern
6
5
 
7
- included do
8
- before_action :ensure_correct_media_type, only: [:create, :update, :create_relationship, :update_relationship]
9
- append_before_action :setup_request
10
- after_action :setup_response
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ base.before_action :ensure_correct_media_type, only: [:create, :update, :create_relationship, :update_relationship]
9
+ base.cattr_reader :server_error_callbacks
11
10
  end
12
11
 
13
12
  def index
14
- process_request_operations
13
+ process_request
15
14
  end
16
15
 
17
16
  def show
18
- process_request_operations
17
+ process_request
19
18
  end
20
19
 
21
20
  def show_relationship
22
- process_request_operations
21
+ process_request
23
22
  end
24
23
 
25
24
  def create
26
- process_request_operations
25
+ process_request
27
26
  end
28
27
 
29
28
  def create_relationship
30
- process_request_operations
29
+ process_request
31
30
  end
32
31
 
33
32
  def update_relationship
34
- process_request_operations
33
+ process_request
35
34
  end
36
35
 
37
36
  def update
38
- process_request_operations
37
+ process_request
39
38
  end
40
39
 
41
40
  def destroy
42
- process_request_operations
41
+ process_request
43
42
  end
44
43
 
45
44
  def destroy_relationship
46
- process_request_operations
45
+ process_request
47
46
  end
48
47
 
49
48
  def get_related_resource
50
- process_request_operations
49
+ process_request
51
50
  end
52
51
 
53
52
  def get_related_resources
54
- process_request_operations
53
+ process_request
54
+ end
55
+
56
+ def process_request
57
+ @request = JSONAPI::Request.new(params, context: context,
58
+ key_formatter: key_formatter,
59
+ server_error_callbacks: (self.class.server_error_callbacks || []))
60
+ unless @request.errors.empty?
61
+ render_errors(@request.errors)
62
+ else
63
+ operation_results = create_operations_processor.process(@request)
64
+ render_results(operation_results)
65
+ end
66
+
67
+ if response.body.size > 0
68
+ response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
69
+ end
70
+
71
+ rescue => e
72
+ handle_exceptions(e)
55
73
  end
56
74
 
57
75
  # set the operations processor in the configuration or override this to use another operations processor
@@ -85,25 +103,15 @@ module JSONAPI
85
103
  handle_exceptions(e)
86
104
  end
87
105
 
88
- def setup_request
89
- @request = JSONAPI::Request.new(params, context: context, key_formatter: key_formatter)
90
-
91
- render_errors(@request.errors) unless @request.errors.empty?
92
- rescue => e
93
- handle_exceptions(e)
94
- end
95
-
96
- def setup_response
97
- if response.body.size > 0
98
- response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
99
- end
100
- end
101
-
102
106
  # override to set context
103
107
  def context
104
108
  {}
105
109
  end
106
110
 
111
+ def serialization_options
112
+ {}
113
+ end
114
+
107
115
  # Control by setting in an initializer:
108
116
  # JSONAPI.configuration.json_key_format = :camelized_key
109
117
  # JSONAPI.configuration.route = :camelized_route
@@ -159,17 +167,11 @@ module JSONAPI
159
167
  base_meta: base_meta,
160
168
  base_links: base_response_links,
161
169
  resource_serializer_klass: resource_serializer_klass,
162
- request: @request
170
+ request: @request,
171
+ serialization_options: serialization_options
163
172
  )
164
173
  end
165
174
 
166
- def process_request_operations
167
- operation_results = create_operations_processor.process(@request)
168
- render_results(operation_results)
169
- rescue => e
170
- handle_exceptions(e)
171
- end
172
-
173
175
  # override this to process other exceptions
174
176
  # Note: Be sure to either call super(e) or handle JSONAPI::Exceptions::Error and raise unhandled exceptions
175
177
  def handle_exceptions(e)
@@ -183,10 +185,6 @@ module JSONAPI
183
185
  end
184
186
  end
185
187
 
186
- def add_error_callbacks(callbacks)
187
- @request.server_error_callbacks = callbacks || []
188
- end
189
-
190
188
  # Pass in a methods or a block to be run when an exception is
191
189
  # caught that is not a JSONAPI::Exceptions::Error
192
190
  # Useful for additional logging or notification configuration that
@@ -194,8 +192,9 @@ module JSONAPI
194
192
  # Ignores whitelist exceptions from config
195
193
 
196
194
  module ClassMethods
195
+
197
196
  def on_server_error(*args, &callback_block)
198
- callbacks = []
197
+ callbacks ||= []
199
198
 
200
199
  if callback_block
201
200
  callbacks << callback_block
@@ -211,8 +210,9 @@ module JSONAPI
211
210
  end
212
211
  end.compact
213
212
  callbacks += method_callbacks
214
- append_before_action { add_error_callbacks(callbacks) }
213
+ self.class_variable_set :@@server_error_callbacks, callbacks
215
214
  end
215
+
216
216
  end
217
217
  end
218
218
  end
@@ -297,7 +297,7 @@ module JSONAPI
297
297
  attr_reader :error_messages, :resource_relationships
298
298
 
299
299
  def initialize(resource)
300
- @error_messages = resource._model.errors.messages
300
+ @error_messages = resource.model_error_messages
301
301
  @resource_relationships = resource.class._relationships.keys
302
302
  @key_formatter = JSONAPI.configuration.key_formatter
303
303
  end
@@ -76,7 +76,7 @@ end
76
76
  class DasherizedKeyFormatter < JSONAPI::KeyFormatter
77
77
  class << self
78
78
  def format(key)
79
- super.dasherize
79
+ super.underscore.dasherize
80
80
  end
81
81
 
82
82
  def unformat(formatted_key)
@@ -5,5 +5,7 @@ end
5
5
  Mime::Type.register JSONAPI::MEDIA_TYPE, :api_json
6
6
 
7
7
  ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup(JSONAPI::MEDIA_TYPE)] = lambda do |body|
8
- JSON.parse(body)
8
+ data = JSON.parse(body)
9
+ data = {:_json => data} unless data.is_a?(Hash)
10
+ data.with_indifferent_access
9
11
  end
@@ -79,9 +79,9 @@ module JSONAPI
79
79
  def apply
80
80
  key = @resource_klass.verify_key(@id, @context)
81
81
 
82
- resource_record = resource_klass.find_by_key(key,
83
- context: @context,
84
- include_directives: @include_directives)
82
+ resource_record = @resource_klass.find_by_key(key,
83
+ context: @context,
84
+ include_directives: @include_directives)
85
85
 
86
86
  return JSONAPI::ResourceOperationResult.new(:ok, resource_record)
87
87
 
@@ -22,7 +22,7 @@ module JSONAPI
22
22
  @include_directives = nil
23
23
  @paginator = nil
24
24
  @id = nil
25
- @server_error_callbacks = []
25
+ @server_error_callbacks = options.fetch(:server_error_callbacks, [])
26
26
 
27
27
  setup_action(@params)
28
28
  end
@@ -107,9 +107,8 @@ module JSONAPI
107
107
  end
108
108
  end
109
109
 
110
- # Override this on a resource instance to override the fetchable keys
111
110
  def fetchable_fields
112
- self.class.fields
111
+ self.class.fetchable_fields(context)
113
112
  end
114
113
 
115
114
  # Override this on a resource to customize how the associated records
@@ -118,6 +117,19 @@ module JSONAPI
118
117
  _model.public_send relation_name
119
118
  end
120
119
 
120
+ def model_error_messages
121
+ _model.errors.messages
122
+ end
123
+
124
+ # Override this to return resource level meta data
125
+ # must return a hash, and if the hash is empty the meta section will not be serialized with the resource
126
+ # meta keys will be not be formatted with the key formatter for the serializer by default. They can however use the
127
+ # serializer's format_key and format_value methods if desired
128
+ # the _options hash will contain the serializer and the serialization_options
129
+ def meta(_options)
130
+ {}
131
+ end
132
+
121
133
  private
122
134
 
123
135
  def save
@@ -145,8 +157,14 @@ module JSONAPI
145
157
  end
146
158
 
147
159
  if defined? @model.save
148
- saved = @model.save
149
- fail JSONAPI::Exceptions::SaveFailed.new unless saved
160
+ saved = @model.save(validate: false)
161
+ unless saved
162
+ if @model.errors.present?
163
+ fail JSONAPI::Exceptions::ValidationErrors.new(self)
164
+ else
165
+ fail JSONAPI::Exceptions::SaveFailed.new
166
+ end
167
+ end
150
168
  else
151
169
  saved = true
152
170
  end
@@ -274,13 +292,37 @@ module JSONAPI
274
292
  check_reserved_resource_name(base._type, base.name)
275
293
  end
276
294
 
277
- def resource_for(type)
278
- resource_name = JSONAPI::Resource._resource_name_from_type(type)
279
- resource = resource_name.safe_constantize if resource_name
280
- if resource.nil?
281
- fail NameError, "JSONAPI: Could not find resource '#{type}'. (Class #{resource_name} not found)"
295
+ def resource_for(resource_path)
296
+ unless @@resource_types.key? resource_path
297
+ klass_name = "#{resource_path.to_s.underscore.singularize}_resource".camelize
298
+ klass = (klass_name.safe_constantize or
299
+ fail NameError,
300
+ "JSONAPI: Could not find resource '#{resource_path}'. (Class #{klass_name} not found)")
301
+ normalized_path = resource_path.rpartition('/').first
302
+ normalized_model = klass._model_name.to_s.gsub(/\A::/, '')
303
+ @@resource_types[resource_path] = {
304
+ resource: klass,
305
+ path: normalized_path,
306
+ model: normalized_model,
307
+ }
308
+ end
309
+ @@resource_types[resource_path][:resource]
310
+ end
311
+
312
+ def resource_for_model_path(model, path)
313
+ normalized_model = model.class.to_s.gsub(/\A::/, '')
314
+ normalized_path = path.gsub(/\/\z/, '')
315
+ resource = @@resource_types.find { |_, h|
316
+ h[:path] == normalized_path && h[:model] == normalized_model
317
+ }
318
+ if resource
319
+ resource.last[:resource]
320
+ else
321
+ #:nocov:#
322
+ fail NameError,
323
+ "JSONAPI: Could not find resource for model '#{path}#{normalized_model}'"
324
+ #:nocov:#
282
325
  end
283
- resource
284
326
  end
285
327
 
286
328
  attr_accessor :_attributes, :_relationships, :_allowed_filters, :_type, :_paginator
@@ -385,6 +427,11 @@ module JSONAPI
385
427
  end
386
428
  # :nocov:
387
429
 
430
+ # Override in your resource to filter the fetchable keys
431
+ def fetchable_fields(_context = nil)
432
+ fields
433
+ end
434
+
388
435
  # Override in your resource to filter the updatable keys
389
436
  def updatable_fields(_context = nil)
390
437
  _updatable_relationships | _attributes.keys - [:id]
@@ -503,7 +550,7 @@ module JSONAPI
503
550
 
504
551
  resources = []
505
552
  records.each do |model|
506
- resources.push resource_for(resource_type_for(model)).new(model, context)
553
+ resources.push resource_for_model_path(model, self.module_path).new(model, context)
507
554
  end
508
555
 
509
556
  resources
@@ -515,11 +562,7 @@ module JSONAPI
515
562
  records = apply_includes(records, options)
516
563
  model = records.where({_primary_key => key}).first
517
564
  fail JSONAPI::Exceptions::RecordNotFound.new(key) if model.nil?
518
- resource_for(resource_type_for(model)).new(model, context)
519
- end
520
-
521
- def resource_type_for(model)
522
- self.module_path + model.class.to_s.underscore
565
+ resource_for_model_path(model, self.module_path).new(model, context)
523
566
  end
524
567
 
525
568
  # Override this method if you want to customize the relation for
@@ -635,15 +678,6 @@ module JSONAPI
635
678
  !@_allowed_filters.nil? ? @_allowed_filters : { id: {} }
636
679
  end
637
680
 
638
- def _resource_name_from_type(type)
639
- class_name = @@resource_types[type]
640
- if class_name.nil?
641
- class_name = "#{type.to_s.underscore.singularize}_resource".camelize
642
- @@resource_types[type] = class_name
643
- end
644
- return class_name
645
- end
646
-
647
681
  def _paginator
648
682
  @_paginator ||= JSONAPI.configuration.default_paginator
649
683
  end
@@ -763,7 +797,7 @@ module JSONAPI
763
797
  define_method attr do |options = {}|
764
798
  if relationship.polymorphic?
765
799
  associated_model = public_send(associated_records_method_name)
766
- resource_klass = Resource.resource_for(self.class.resource_type_for(associated_model)) if associated_model
800
+ resource_klass = self.class.resource_for_model_path(associated_model, self.class.module_path) if associated_model
767
801
  return resource_klass.new(associated_model, @context) if resource_klass
768
802
  else
769
803
  resource_klass = relationship.resource_klass
@@ -816,7 +850,7 @@ module JSONAPI
816
850
  end
817
851
 
818
852
  return records.collect do |record|
819
- resource_klass = Resource.resource_for(self.class.resource_type_for(record))
853
+ resource_klass = self.class.resource_for_model_path(record, self.class.module_path)
820
854
  resource_klass.new(record, @context)
821
855
  end
822
856
  end unless method_defined?(attr)
@@ -1,6 +1,9 @@
1
1
  module JSONAPI
2
2
  class ResourceSerializer
3
3
 
4
+ attr_reader :url_generator, :key_formatter, :serialization_options, :primary_class_name
5
+
6
+ # initialize
4
7
  # Options can include
5
8
  # include:
6
9
  # Purpose: determines which objects will be side loaded with the source objects in a linked section
@@ -10,9 +13,7 @@ module JSONAPI
10
13
  # relationship ids in the links section for a resource. Fields are global for a resource type.
11
14
  # Example: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}
12
15
  # key_formatter: KeyFormatter class to override the default configuration
13
- # base_url: a string to prepend to generated resource links
14
-
15
- attr_reader :url_generator
16
+ # serializer_options: additional options that will be passed to resource meta and links lambdas
16
17
 
17
18
  def initialize(primary_resource_klass, options = {})
18
19
  @primary_class_name = primary_resource_klass._type
@@ -25,6 +26,7 @@ module JSONAPI
25
26
  JSONAPI.configuration.always_include_to_one_linkage_data)
26
27
  @always_include_to_many_linkage_data = options.fetch(:always_include_to_many_linkage_data,
27
28
  JSONAPI.configuration.always_include_to_many_linkage_data)
29
+ @serialization_options = options.fetch(:serialization_options, {})
28
30
  end
29
31
 
30
32
  # Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
@@ -74,6 +76,15 @@ module JSONAPI
74
76
  url_generator.query_link(query_params)
75
77
  end
76
78
 
79
+ def format_key(key)
80
+ @key_formatter.format(key)
81
+ end
82
+
83
+ def format_value(value, format)
84
+ value_formatter = JSONAPI::ValueFormatter.value_formatter_for(format)
85
+ value_formatter.format(value)
86
+ end
87
+
77
88
  private
78
89
 
79
90
  # Process the primary source object(s). This will then serialize associated object recursively based on the
@@ -119,6 +130,10 @@ module JSONAPI
119
130
  relationships = relationship_data(source, include_directives)
120
131
  obj_hash['relationships'] = relationships unless relationships.nil? || relationships.empty?
121
132
 
133
+ meta = source.meta(custom_generation_options)
134
+ if meta.is_a?(Hash) && !meta.empty?
135
+ obj_hash['meta'] = meta
136
+ end
122
137
  obj_hash
123
138
  end
124
139
 
@@ -144,6 +159,13 @@ module JSONAPI
144
159
  end
145
160
  end
146
161
 
162
+ def custom_generation_options
163
+ {
164
+ serializer: self,
165
+ serialization_options: @serialization_options
166
+ }
167
+ end
168
+
147
169
  def relationship_data(source, include_directives)
148
170
  relationships = source.class._relationships
149
171
  requested = requested_fields(source.class)
@@ -313,15 +335,6 @@ module JSONAPI
313
335
  end
314
336
  end
315
337
 
316
- def format_key(key)
317
- @key_formatter.format(key)
318
- end
319
-
320
- def format_value(value, format)
321
- value_formatter = JSONAPI::ValueFormatter.value_formatter_for(format)
322
- value_formatter.format(value)
323
- end
324
-
325
338
  def generate_link_builder(primary_resource_klass, options)
326
339
  LinkBuilder.new(
327
340
  base_url: options.fetch(:base_url, ''),
@@ -1,5 +1,5 @@
1
1
  module JSONAPI
2
2
  module Resources
3
- VERSION = '0.6.1'
3
+ VERSION = '0.6.2'
4
4
  end
5
5
  end
@@ -36,7 +36,8 @@ module JSONAPI
36
36
  fields: @options[:fields],
37
37
  base_url: @options.fetch(:base_url, ''),
38
38
  key_formatter: @key_formatter,
39
- route_formatter: @options.fetch(:route_formatter, JSONAPI.configuration.route_formatter)
39
+ route_formatter: @options.fetch(:route_formatter, JSONAPI.configuration.route_formatter),
40
+ serialization_options: @options.fetch(:serialization_options, {})
40
41
  )
41
42
  end
42
43
 
@@ -37,10 +37,10 @@ class PostsControllerTest < ActionController::TestCase
37
37
  JSONAPI.configuration.exception_class_whitelist = []
38
38
 
39
39
  @controller.class.instance_variable_set(:@callback_message, "none")
40
- @controller.class.on_server_error do
40
+ BaseController.on_server_error do
41
41
  @controller.class.instance_variable_set(:@callback_message, "Sent from block")
42
42
  end
43
-
43
+
44
44
  get :index
45
45
  assert_equal @controller.class.instance_variable_get(:@callback_message), "Sent from block"
46
46
 
@@ -54,7 +54,7 @@ class PostsControllerTest < ActionController::TestCase
54
54
  def test_on_server_error_method_callback_with_exception
55
55
  original_config = JSONAPI.configuration.dup
56
56
  JSONAPI.configuration.operations_processor = :error_raising
57
- JSONAPI.configuration.exception_class_whitelist = []
57
+ JSONAPI.configuration.exception_class_whitelist = []
58
58
 
59
59
  #ignores methods that don't exist
60
60
  @controller.class.on_server_error :set_callback_message, :a_bogus_method
@@ -70,7 +70,7 @@ class PostsControllerTest < ActionController::TestCase
70
70
  end
71
71
 
72
72
  def test_on_server_error_callback_without_exception
73
-
73
+
74
74
  callback = Proc.new { @controller.class.instance_variable_set(:@callback_message, "Sent from block") }
75
75
  @controller.class.on_server_error callback
76
76
  @controller.class.instance_variable_set(:@callback_message, "none")
@@ -1222,7 +1222,7 @@ class PostsControllerTest < ActionController::TestCase
1222
1222
 
1223
1223
  #check the relationship was created successfully
1224
1224
  assert_equal 1, Post.find(14).special_tags.count
1225
- before_tags = Post.find(14).tags.count
1225
+ before_tags = Post.find(14).tags.count
1226
1226
 
1227
1227
  delete :destroy_relationship, {post_id: 14, relationship: 'special_tags', data: [{type: 'tags', id: 2}]}
1228
1228
  assert_equal 0, Post.find(14).special_tags.count, "Relationship that matches URL relationship not destroyed"
@@ -2264,6 +2264,15 @@ class Api::V5::AuthorsControllerTest < ActionController::TestCase
2264
2264
  assert_equal nil, json_response['data'][0]['attributes']['email']
2265
2265
  end
2266
2266
 
2267
+ def test_show_person_as_author
2268
+ get :show, {id: '1'}
2269
+ assert_response :success
2270
+ assert_equal '1', json_response['data']['id']
2271
+ assert_equal 'authors', json_response['data']['type']
2272
+ assert_equal 'Joe Author', json_response['data']['attributes']['name']
2273
+ assert_equal nil, json_response['data']['attributes']['email']
2274
+ end
2275
+
2267
2276
  def test_get_person_as_author_by_name_filter
2268
2277
  get :index, {filter: {name: 'thor'}}
2269
2278
  assert_response :success
@@ -2271,6 +2280,74 @@ class Api::V5::AuthorsControllerTest < ActionController::TestCase
2271
2280
  assert_equal '1', json_response['data'][0]['id']
2272
2281
  assert_equal 'Joe Author', json_response['data'][0]['attributes']['name']
2273
2282
  end
2283
+
2284
+ def test_meta_serializer_options
2285
+ JSONAPI.configuration.json_key_format = :camelized_key
2286
+
2287
+ Api::V5::AuthorResource.class_eval do
2288
+ def meta(options)
2289
+ {
2290
+ fixed: 'Hardcoded value',
2291
+ computed: "#{self.class._type.to_s}: #{options[:serializer].url_generator.self_link(self)}",
2292
+ computed_foo: options[:serialization_options][:foo],
2293
+ options[:serializer].format_key('test_key') => 'test value'
2294
+ }
2295
+ end
2296
+ end
2297
+
2298
+ get :show, {id: '1'}
2299
+ assert_response :success
2300
+ assert_equal '1', json_response['data']['id']
2301
+ assert_equal 'Hardcoded value', json_response['data']['meta']['fixed']
2302
+ assert_equal 'authors: http://test.host/api/v5/authors/1', json_response['data']['meta']['computed']
2303
+ assert_equal 'bar', json_response['data']['meta']['computed_foo']
2304
+ assert_equal 'test value', json_response['data']['meta']['testKey']
2305
+
2306
+ ensure
2307
+ JSONAPI.configuration.json_key_format = :dasherized_key
2308
+ Api::V5::AuthorResource.class_eval do
2309
+ def meta(options)
2310
+ # :nocov:
2311
+ { }
2312
+ # :nocov:
2313
+ end
2314
+ end
2315
+ end
2316
+
2317
+ def test_meta_serializer_hash_data
2318
+ JSONAPI.configuration.json_key_format = :camelized_key
2319
+
2320
+ Api::V5::AuthorResource.class_eval do
2321
+ def meta(options)
2322
+ {
2323
+ custom_hash: {
2324
+ fixed: 'Hardcoded value',
2325
+ computed: "#{self.class._type.to_s}: #{options[:serializer].url_generator.self_link(self)}",
2326
+ computed_foo: options[:serialization_options][:foo],
2327
+ options[:serializer].format_key('test_key') => 'test value'
2328
+ }
2329
+ }
2330
+ end
2331
+ end
2332
+
2333
+ get :show, {id: '1'}
2334
+ assert_response :success
2335
+ assert_equal '1', json_response['data']['id']
2336
+ assert_equal 'Hardcoded value', json_response['data']['meta']['custom_hash']['fixed']
2337
+ assert_equal 'authors: http://test.host/api/v5/authors/1', json_response['data']['meta']['custom_hash']['computed']
2338
+ assert_equal 'bar', json_response['data']['meta']['custom_hash']['computed_foo']
2339
+ assert_equal 'test value', json_response['data']['meta']['custom_hash']['testKey']
2340
+
2341
+ ensure
2342
+ JSONAPI.configuration.json_key_format = :dasherized_key
2343
+ Api::V5::AuthorResource.class_eval do
2344
+ def meta(options)
2345
+ # :nocov:
2346
+ { }
2347
+ # :nocov:
2348
+ end
2349
+ end
2350
+ end
2274
2351
  end
2275
2352
 
2276
2353
  class BreedsControllerTest < ActionController::TestCase
@@ -3158,4 +3235,4 @@ class VehiclesControllerTest < ActionController::TestCase
3158
3235
  }
3159
3236
  end
3160
3237
  end
3161
- end
3238
+ end
@@ -530,8 +530,12 @@ end
530
530
  class PeopleController < JSONAPI::ResourceController
531
531
  end
532
532
 
533
- class PostsController < ActionController::Base
533
+ class BaseController < ActionController::Base
534
534
  include JSONAPI::ActsAsResourceController
535
+ end
536
+
537
+ class PostsController < BaseController
538
+
535
539
  class SpecialError < StandardError; end
536
540
  class SubSpecialError < PostsController::SpecialError; end
537
541
 
@@ -683,6 +687,9 @@ module Api
683
687
 
684
688
  module V5
685
689
  class AuthorsController < JSONAPI::ResourceController
690
+ def serialization_options
691
+ {foo: 'bar'}
692
+ end
686
693
  end
687
694
 
688
695
  class PostsController < JSONAPI::ResourceController
@@ -1242,6 +1249,15 @@ module Api
1242
1249
 
1243
1250
  filter :name
1244
1251
 
1252
+ def self.find_by_key(key, options = {})
1253
+ context = options[:context]
1254
+ records = records(options)
1255
+ records = apply_includes(records, options)
1256
+ model = records.where({_primary_key => key}).first
1257
+ fail JSONAPI::Exceptions::RecordNotFound.new(key) if model.nil?
1258
+ self.new(model, context)
1259
+ end
1260
+
1245
1261
  def self.find(filters, options = {})
1246
1262
  resources = []
1247
1263
 
@@ -1369,6 +1385,20 @@ module MyEngine
1369
1385
  end
1370
1386
  end
1371
1387
 
1388
+ module Legacy
1389
+ class FlatPost < ActiveRecord::Base
1390
+ self.table_name = "posts"
1391
+ end
1392
+ end
1393
+
1394
+ class FlatPostResource < JSONAPI::Resource
1395
+ model_name "::Legacy::FlatPost"
1396
+ attribute :title
1397
+ end
1398
+
1399
+ class FlatPostsController < JSONAPI::ResourceController
1400
+ end
1401
+
1372
1402
  ### PORO Data - don't do this in a production app
1373
1403
  $breed_data = BreedData.new
1374
1404
  $breed_data.add(Breed.new(0, 'persian'))
@@ -0,0 +1,13 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ class NamedspacedModelTest < ActionDispatch::IntegrationTest
4
+ def setup
5
+ JSONAPI.configuration.json_key_format = :underscored_key
6
+ end
7
+
8
+ def test_get_flat_posts
9
+ get '/flat_posts'
10
+ assert_equal 200, status
11
+ assert_equal "flat_posts", json_response["data"].first["type"]
12
+ end
13
+ end
@@ -140,6 +140,7 @@ TestApp.routes.draw do
140
140
  jsonapi_resources :vehicles
141
141
  jsonapi_resources :cars
142
142
  jsonapi_resources :boats
143
+ jsonapi_resources :flat_posts
143
144
 
144
145
  namespace :api do
145
146
  namespace :v1 do
@@ -0,0 +1,8 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ class DasherizedKeyFormatterTest < ActiveSupport::TestCase
4
+ def test_dasherize_camelize
5
+ formatted = DasherizedKeyFormatter.format("CarWash")
6
+ assert_equal formatted, "car-wash"
7
+ end
8
+ end
@@ -8,6 +8,21 @@ class ArticleResource < JSONAPI::Resource
8
8
  end
9
9
  end
10
10
 
11
+ class PostWithBadAfterSave < ActiveRecord::Base
12
+ self.table_name = 'posts'
13
+ after_save :do_some_after_save_stuff
14
+
15
+ def do_some_after_save_stuff
16
+ errors[:base] << 'Boom! Error added in after_save callback.'
17
+ raise ActiveRecord::RecordInvalid.new(self)
18
+ end
19
+ end
20
+
21
+ class ArticleWithBadAfterSaveResource < JSONAPI::Resource
22
+ model_name 'PostWithBadAfterSave'
23
+ attribute :title
24
+ end
25
+
11
26
  class NoMatchResource < JSONAPI::Resource
12
27
  end
13
28
 
@@ -447,4 +462,13 @@ class ResourceTest < ActiveSupport::TestCase
447
462
  end
448
463
  assert_match "", err
449
464
  end
465
+
466
+ def test_correct_error_surfaced_if_validation_errors_in_after_save_callback
467
+ post = PostWithBadAfterSave.find(1)
468
+ post_resource = ArticleWithBadAfterSaveResource.new(post, nil)
469
+ err = assert_raises JSONAPI::Exceptions::ValidationErrors do
470
+ post_resource.replace_fields({:attributes => {:title => 'Some title'}})
471
+ end
472
+ assert_equal(err.error_messages[:base], ['Boom! Error added in after_save callback.'])
473
+ end
450
474
  end
@@ -1737,6 +1737,77 @@ class SerializerTest < ActionDispatch::IntegrationTest
1737
1737
  )
1738
1738
  end
1739
1739
 
1740
+ def test_serializer_resource_meta_fixed_value
1741
+ Api::V5::AuthorResource.class_eval do
1742
+ def meta(options)
1743
+ {
1744
+ fixed: 'Hardcoded value',
1745
+ computed: "#{self.class._type.to_s}: #{options[:serializer].url_generator.self_link(self)}"
1746
+ }
1747
+ end
1748
+ end
1749
+
1750
+ serialized = JSONAPI::ResourceSerializer.new(
1751
+ Api::V5::AuthorResource,
1752
+ include: ['author_detail']
1753
+ ).serialize_to_hash(Api::V5::AuthorResource.new(Person.find(1), nil))
1754
+
1755
+ assert_hash_equals(
1756
+ {
1757
+ data: {
1758
+ type: 'authors',
1759
+ id: '1',
1760
+ attributes: {
1761
+ name: 'Joe Author',
1762
+ },
1763
+ links: {
1764
+ self: '/api/v5/authors/1'
1765
+ },
1766
+ relationships: {
1767
+ posts: {
1768
+ links: {
1769
+ self: '/api/v5/authors/1/relationships/posts',
1770
+ related: '/api/v5/authors/1/posts'
1771
+ }
1772
+ },
1773
+ authorDetail: {
1774
+ links: {
1775
+ self: '/api/v5/authors/1/relationships/authorDetail',
1776
+ related: '/api/v5/authors/1/authorDetail'
1777
+ },
1778
+ data: {type: 'authorDetails', id: '1'}
1779
+ }
1780
+ },
1781
+ meta: {
1782
+ fixed: 'Hardcoded value',
1783
+ computed: 'authors: /api/v5/authors/1'
1784
+ }
1785
+ },
1786
+ included: [
1787
+ {
1788
+ type: 'authorDetails',
1789
+ id: '1',
1790
+ attributes: {
1791
+ authorStuff: 'blah blah'
1792
+ },
1793
+ links: {
1794
+ self: '/api/v5/authorDetails/1'
1795
+ }
1796
+ }
1797
+ ]
1798
+ },
1799
+ serialized
1800
+ )
1801
+ ensure
1802
+ Api::V5::AuthorResource.class_eval do
1803
+ def meta(options)
1804
+ # :nocov:
1805
+ { }
1806
+ # :nocov:
1807
+ end
1808
+ end
1809
+ end
1810
+
1740
1811
  def test_serialize_model_attr
1741
1812
  @make = Make.first
1742
1813
  serialized = JSONAPI::ResourceSerializer.new(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi-resources
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Gebhardt
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-10-21 00:00:00.000000000 Z
12
+ date: 2015-11-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -192,11 +192,13 @@ files:
192
192
  - test/helpers/functional_helpers.rb
193
193
  - test/helpers/value_matchers.rb
194
194
  - test/helpers/value_matchers_test.rb
195
+ - test/integration/requests/namespaced_model_test.rb
195
196
  - test/integration/requests/request_test.rb
196
197
  - test/integration/routes/routes_test.rb
197
198
  - test/integration/sti_fields_test.rb
198
199
  - test/lib/generators/jsonapi/resource_generator_test.rb
199
200
  - test/test_helper.rb
201
+ - test/unit/formatters/dasherized_key_formatter_test.rb
200
202
  - test/unit/jsonapi_request/jsonapi_request_test.rb
201
203
  - test/unit/operation/operations_processor_test.rb
202
204
  - test/unit/pagination/offset_paginator_test.rb
@@ -271,11 +273,13 @@ test_files:
271
273
  - test/helpers/functional_helpers.rb
272
274
  - test/helpers/value_matchers.rb
273
275
  - test/helpers/value_matchers_test.rb
276
+ - test/integration/requests/namespaced_model_test.rb
274
277
  - test/integration/requests/request_test.rb
275
278
  - test/integration/routes/routes_test.rb
276
279
  - test/integration/sti_fields_test.rb
277
280
  - test/lib/generators/jsonapi/resource_generator_test.rb
278
281
  - test/test_helper.rb
282
+ - test/unit/formatters/dasherized_key_formatter_test.rb
279
283
  - test/unit/jsonapi_request/jsonapi_request_test.rb
280
284
  - test/unit/operation/operations_processor_test.rb
281
285
  - test/unit/pagination/offset_paginator_test.rb