active_model_serializers 0.10.5 → 0.10.6

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/CHANGELOG.md +16 -2
  4. data/README.md +2 -2
  5. data/Rakefile +1 -30
  6. data/active_model_serializers.gemspec +1 -1
  7. data/bin/rubocop +38 -0
  8. data/docs/general/adapters.md +25 -9
  9. data/docs/general/getting_started.md +1 -1
  10. data/docs/general/rendering.md +18 -4
  11. data/docs/general/serializers.md +21 -2
  12. data/docs/howto/add_pagination_links.md +1 -1
  13. data/docs/integrations/ember-and-json-api.md +3 -0
  14. data/lib/active_model/serializer.rb +252 -74
  15. data/lib/active_model/serializer/association.rb +51 -14
  16. data/lib/active_model/serializer/belongs_to_reflection.rb +5 -1
  17. data/lib/active_model/serializer/concerns/caching.rb +29 -21
  18. data/lib/active_model/serializer/has_many_reflection.rb +4 -1
  19. data/lib/active_model/serializer/has_one_reflection.rb +1 -1
  20. data/lib/active_model/serializer/lazy_association.rb +95 -0
  21. data/lib/active_model/serializer/reflection.rb +119 -75
  22. data/lib/active_model/serializer/version.rb +1 -1
  23. data/lib/active_model_serializers/adapter/json_api.rb +30 -17
  24. data/lib/active_model_serializers/adapter/json_api/relationship.rb +38 -9
  25. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +9 -0
  26. data/lib/active_model_serializers/model.rb +5 -4
  27. data/lib/tasks/rubocop.rake +53 -0
  28. data/test/action_controller/adapter_selector_test.rb +2 -2
  29. data/test/serializers/associations_test.rb +64 -31
  30. data/test/serializers/reflection_test.rb +427 -0
  31. metadata +9 -12
  32. data/lib/active_model/serializer/collection_reflection.rb +0 -7
  33. data/lib/active_model/serializer/concerns/associations.rb +0 -102
  34. data/lib/active_model/serializer/concerns/attributes.rb +0 -82
  35. data/lib/active_model/serializer/concerns/configuration.rb +0 -59
  36. data/lib/active_model/serializer/concerns/links.rb +0 -35
  37. data/lib/active_model/serializer/concerns/meta.rb +0 -29
  38. data/lib/active_model/serializer/concerns/type.rb +0 -25
  39. data/lib/active_model/serializer/singular_reflection.rb +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e38c0b5ccb4d32da05808ebff7112d20d7d8d451
4
- data.tar.gz: 7e50bf4858b40a9f988d112a95f6afc34c38156b
3
+ metadata.gz: fbc0f811b036c328dccf411b6f8080b6605d8740
4
+ data.tar.gz: d4a0f6f7799b074aff45bc103c9c19b9ea873ab1
5
5
  SHA512:
6
- metadata.gz: 25710e210284167d23ebf77b024d31b06326d71a68c8bcbc1f4f4439baf0b50018a02060da44ebefcb077c9abc2a74bd632cb54662942cb3d17c464d2b420b79
7
- data.tar.gz: 712546ebd0b8fd5d1243b32c76349fc92ab0610e2562848c42c7faff4e1fc5820282ea1492c5526d721137fda7eb6dc1dacc422720f493d36e75c9185c3bff79
6
+ metadata.gz: 121ed054788e585e48e76af3e98490930fd26f18ebdd28d80d07cba78fe9b7c3aed8ea1e1f15ace59debb73562383657f89e4d52fa2c54391424ea53c3701a38
7
+ data.tar.gz: 88ab9022b49348ec463547196300480bcf02139d67cc11adfcbf8c5e4e38c3e44b27cdde7687baab54f8f8ce4bbe4fa606079dffae51ad7be42427dc15955d6e
@@ -1,10 +1,13 @@
1
1
  AllCops:
2
2
  TargetRubyVersion: 2.1
3
3
  Exclude:
4
- - config/initializers/forbidden_yaml.rb
5
4
  - !ruby/regexp /(vendor|bundle|bin|db|tmp)\/.*/
6
5
  DisplayCopNames: true
7
6
  DisplayStyleGuide: true
7
+ # https://github.com/bbatsov/rubocop/blob/master/manual/caching.md
8
+ # https://github.com/bbatsov/rubocop/blob/e8680418b351491e111a18cf5b453fc07a3c5239/config/default.yml#L60-L77
9
+ UseCache: true
10
+ CacheRootDirectory: tmp
8
11
 
9
12
  Rails:
10
13
  Enabled: true
@@ -1,6 +1,6 @@
1
1
  ## 0.10.x
2
2
 
3
- ### [master (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.5...master)
3
+ ### [master (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.6...master)
4
4
 
5
5
  Breaking changes:
6
6
 
@@ -10,6 +10,20 @@ Fixes:
10
10
 
11
11
  Misc:
12
12
 
13
+ ### [v0.10.6 (2017-05-01)](https://github.com/rails-api/active_model_serializers/compare/v0.10.5...v0.10.6)
14
+
15
+ Fixes:
16
+
17
+ - [#1857](https://github.com/rails-api/active_model_serializers/pull/1857) JSON:API does not load belongs_to relation to get identifier id. (@bf4)
18
+ - [#2119](https://github.com/rails-api/active_model_serializers/pull/2119) JSON:API returns null resource object identifier when 'id' is null. (@bf4)
19
+ - [#2093](https://github.com/rails-api/active_model_serializers/pull/2093) undef problematic Serializer methods: display, select. (@bf4)
20
+
21
+ Misc:
22
+
23
+ - [#2104](https://github.com/rails-api/active_model_serializers/pull/2104) Documentation for serializers and rendering. (@cassidycodes)
24
+ - [#2081](https://github.com/rails-api/active_model_serializers/pull/2081) Documentation for `include` option in adapters. (@charlie-wasp)
25
+ - [#2120](https://github.com/rails-api/active_model_serializers/pull/2120) Documentation for association options: foreign_key, type, class_name, namespace. (@bf4)
26
+
13
27
  ### [v0.10.5 (2017-03-07)](https://github.com/rails-api/active_model_serializers/compare/v0.10.4...v0.10.5)
14
28
 
15
29
  Breaking changes:
@@ -77,7 +91,7 @@ Misc:
77
91
 
78
92
  - [#1878](https://github.com/rails-api/active_model_serializers/pull/1878) Cache key generation for serializers now uses `ActiveSupport::Cache.expand_cache_key` instead of `Array#join` by default and is also overridable. This change should be backward-compatible. (@markiz)
79
93
 
80
- - [#1799](https://github.com/rails-api/active_model_serializers/pull/1799) Add documentation for setting the adapter. (@ScottKbka)
94
+ - [#1799](https://github.com/rails-api/active_model_serializers/pull/1799) Add documentation for setting the adapter. (@cassidycodes)
81
95
  - [#1909](https://github.com/rails-api/active_model_serializers/pull/1909) Add documentation for relationship links. (@vasilakisfil, @NullVoxPopuli)
82
96
  - [#1959](https://github.com/rails-api/active_model_serializers/pull/1959) Add documentation for root. (@shunsuke227ono)
83
97
  - [#1967](https://github.com/rails-api/active_model_serializers/pull/1967) Improve type method documentation. (@yukideluxe)
data/README.md CHANGED
@@ -90,8 +90,8 @@ reading documentation for our `master`, which may include features that have not
90
90
  been released yet. Please see below for the documentation relevant to you.
91
91
 
92
92
  - [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master)
93
- - [0.10.4 (latest release) Documentation](https://github.com/rails-api/active_model_serializers/tree/v0.10.4)
94
- - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/active_model_serializers/0.10.4)
93
+ - [0.10.6 (latest release) Documentation](https://github.com/rails-api/active_model_serializers/tree/v0.10.6)
94
+ - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/active_model_serializers/0.10.6)
95
95
  - [Guides](docs)
96
96
  - [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable)
97
97
  - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable)
data/Rakefile CHANGED
@@ -7,6 +7,7 @@ begin
7
7
  require 'simplecov'
8
8
  rescue LoadError # rubocop:disable Lint/HandleExceptions
9
9
  end
10
+ import('lib/tasks/rubocop.rake')
10
11
 
11
12
  Bundler::GemHelper.install_tasks
12
13
 
@@ -30,36 +31,6 @@ namespace :yard do
30
31
  end
31
32
  end
32
33
 
33
- begin
34
- require 'rubocop'
35
- require 'rubocop/rake_task'
36
- rescue LoadError # rubocop:disable Lint/HandleExceptions
37
- else
38
- Rake::Task[:rubocop].clear if Rake::Task.task_defined?(:rubocop)
39
- require 'rbconfig'
40
- # https://github.com/bundler/bundler/blob/1b3eb2465a/lib/bundler/constants.rb#L2
41
- windows_platforms = /(msdos|mswin|djgpp|mingw)/
42
- if RbConfig::CONFIG['host_os'] =~ windows_platforms
43
- desc 'No-op rubocop on Windows-- unsupported platform'
44
- task :rubocop do
45
- puts 'Skipping rubocop on Windows'
46
- end
47
- elsif defined?(::Rubinius)
48
- desc 'No-op rubocop to avoid rbx segfault'
49
- task :rubocop do
50
- puts 'Skipping rubocop on rbx due to segfault'
51
- puts 'https://github.com/rubinius/rubinius/issues/3499'
52
- end
53
- else
54
- Rake::Task[:rubocop].clear if Rake::Task.task_defined?(:rubocop)
55
- desc 'Execute rubocop'
56
- RuboCop::RakeTask.new(:rubocop) do |task|
57
- task.options = ['--rails', '--display-cop-names', '--display-style-guide']
58
- task.fail_on_error = true
59
- end
60
- end
61
- end
62
-
63
34
  require 'rake/testtask'
64
35
 
65
36
  Rake::TestTask.new(:test) do |t|
@@ -57,7 +57,7 @@ Gem::Specification.new do |spec|
57
57
  spec.add_development_dependency 'bundler', '~> 1.6'
58
58
  spec.add_development_dependency 'simplecov', '~> 0.11'
59
59
  spec.add_development_dependency 'timecop', '~> 0.7'
60
- spec.add_development_dependency 'grape', ['>= 0.13', '< 1.0']
60
+ spec.add_development_dependency 'grape', ['>= 0.13', '< 0.19.1']
61
61
  spec.add_development_dependency 'json_schema'
62
62
  spec.add_development_dependency 'rake', ['>= 10.0', '< 12.0']
63
63
  end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Usage:
4
+ # bin/rubocop [-A|-t|-h]
5
+ # bin/rubocop [file or path] [cli options]
6
+ #
7
+ # Options:
8
+ # Autocorrect -A
9
+ # AutoGenConfig -t
10
+ # Usage -h,--help,help
11
+
12
+ set -e
13
+
14
+ case $1 in
15
+ -A)
16
+ echo "Rubocop autocorrect is ON" >&2
17
+ bundle exec rake -f lib/tasks/rubocop.rake rubocop:auto_correct
18
+ ;;
19
+
20
+ -t)
21
+ echo "Rubocop is generating a new TODO" >&2
22
+ bundle exec rake -f lib/tasks/rubocop.rake rubocop:auto_gen_config
23
+ ;;
24
+
25
+ -h|--help|help)
26
+ sed -ne '/^#/!q;s/.\{1,2\}//;1d;p' < "$0"
27
+ ;;
28
+
29
+ *)
30
+ # with no args, run vanilla rubocop
31
+ # else assume we're passing in arbitrary arguments
32
+ if [ -z "$1" ]; then
33
+ bundle exec rake -f lib/tasks/rubocop.rake rubocop
34
+ else
35
+ bundle exec rubocop "$@"
36
+ fi
37
+ ;;
38
+ esac
@@ -141,18 +141,25 @@ This adapter follows **version 1.0** of the [format specified](../jsonapi/schema
141
141
  }
142
142
  ```
143
143
 
144
- #### Included
144
+ ### Include option
145
145
 
146
- It will include the associated resources in the `"included"` member
147
- when the resource names are included in the `include` option.
148
- Including nested associated resources is also supported.
146
+ Which [serializer associations](https://github.com/rails-api/active_model_serializers/blob/master/docs/general/serializers.md#associations) are rendered can be specified using the `include` option. The option usage is consistent with [the include option in the JSON API spec](http://jsonapi.org/format/#fetching-includes), and is available in all adapters.
149
147
 
148
+ Example of the usage:
150
149
  ```ruby
151
150
  render json: @posts, include: ['author', 'comments', 'comments.author']
152
151
  # or
153
152
  render json: @posts, include: 'author,comments,comments.author'
154
153
  ```
155
154
 
155
+ The format of the `include` option can be either:
156
+
157
+ - a String composed of a comma-separated list of [relationship paths](http://jsonapi.org/format/#fetching-includes).
158
+ - an Array of Symbols and Hashes.
159
+ - a mix of both.
160
+
161
+ An empty string or an empty array will prevent rendering of any associations.
162
+
156
163
  In addition, two types of wildcards may be used:
157
164
 
158
165
  - `*` includes one level of associations.
@@ -164,11 +171,6 @@ These can be combined with other paths.
164
171
  render json: @posts, include: '**' # or '*' for a single layer
165
172
  ```
166
173
 
167
- The format of the `include` option can be either:
168
-
169
- - a String composed of a comma-separated list of [relationship paths](http://jsonapi.org/format/#fetching-includes).
170
- - an Array of Symbols and Hashes.
171
- - a mix of both.
172
174
 
173
175
  The following would render posts and include:
174
176
 
@@ -182,6 +184,20 @@ It could be combined, like above, with other paths in any combination desired.
182
184
  render json: @posts, include: 'author.comments.**'
183
185
  ```
184
186
 
187
+ **Note:** Wildcards are ActiveModelSerializers-specific, they are not part of the JSON API spec.
188
+
189
+ The default include for the JSON API adapter is no associations. The default for the JSON and Attributes adapters is all associations.
190
+
191
+ For the JSON API adapter associated resources will be gathered in the `"included"` member. For the JSON and Attributes
192
+ adapters associated resources will be rendered among the other attributes.
193
+
194
+ Only for the JSON API adapter you can specify, which attributes of associated resources will be rendered. This feature
195
+ is called [sparse fieldset](http://jsonapi.org/format/#fetching-sparse-fieldsets):
196
+
197
+ ```ruby
198
+ render json: @posts, include: 'comments', fields: { comments: ['content', 'created_at'] }
199
+ ```
200
+
185
201
  ##### Security Considerations
186
202
 
187
203
  Since the included options may come from the query params (i.e. user-controller):
@@ -37,7 +37,7 @@ and
37
37
  class CommentSerializer < ActiveModel::Serializer
38
38
  attributes :name, :body
39
39
 
40
- belongs_to :post_id
40
+ belongs_to :post
41
41
  end
42
42
  ```
43
43
 
@@ -203,7 +203,7 @@ link(:link_name) { url_for(controller: 'controller_name', action: 'index', only_
203
203
 
204
204
  #### include
205
205
 
206
- PR please :)
206
+ See [Adapters: Include Option](/docs/general/adapters.md#include-option).
207
207
 
208
208
  #### Overriding the root key
209
209
 
@@ -260,15 +260,29 @@ Note that by using a string and symbol, Ruby will assume the namespace is define
260
260
 
261
261
  #### serializer
262
262
 
263
- PR please :)
263
+ Specify which serializer to use if you want to use a serializer other than the default.
264
+
265
+ For a single resource:
266
+
267
+ ```ruby
268
+ @post = Post.first
269
+ render json: @post, serializer: SpecialPostSerializer
270
+ ```
271
+
272
+ To specify which serializer to use on individual items in a collection (i.e., an `index` action), use `each_serializer`:
273
+
274
+ ```ruby
275
+ @posts = Post.all
276
+ render json: @posts, each_serializer: SpecialPostSerializer
277
+ ```
264
278
 
265
279
  #### scope
266
280
 
267
- PR please :)
281
+ See [Serializers: Scope](/docs/general/serializers.md#scope).
268
282
 
269
283
  #### scope_name
270
284
 
271
- PR please :)
285
+ See [Serializers: Scope](/docs/general/serializers.md#scope).
272
286
 
273
287
  ## Using a serializer without `render`
274
288
 
@@ -64,6 +64,10 @@ Where:
64
64
  - `unless:`
65
65
  - `virtual_value:`
66
66
  - `polymorphic:` defines if polymorphic relation type should be nested in serialized association.
67
+ - `type:` the resource type as used by JSON:API, especially on a `belongs_to` relationship.
68
+ - `class_name:` used to determine `type` when `type` not given
69
+ - `foreign_key:` used by JSON:API on a `belongs_to` relationship to avoid unnecessarily loading the association object.
70
+ - `namespace:` used when looking up the serializer and `serializer` is not given. Falls back to the parent serializer's `:namespace` instance options, which, when present, comes from the render options. See [Rendering#namespace](rendering.md#namespace] for more details.
67
71
  - optional: `&block` is a context that returns the association's attributes.
68
72
  - prevents `association_name` method from being called.
69
73
  - return value of block is used as the association value.
@@ -382,11 +386,26 @@ The serialized value for a given key. e.g. `read_attribute_for_serialization(:ti
382
386
 
383
387
  #### #links
384
388
 
385
- PR please :)
389
+ Allows you to modify the `links` node. By default, this node will be populated with the attributes set using the [::link](#link) method. Using `links: nil` will remove the `links` node.
390
+
391
+ ```ruby
392
+ ActiveModelSerializers::SerializableResource.new(
393
+ @post,
394
+ adapter: :json_api,
395
+ links: {
396
+ self: {
397
+ href: 'http://example.com/posts',
398
+ meta: {
399
+ stuff: 'value'
400
+ }
401
+ }
402
+ }
403
+ )
404
+ ```
386
405
 
387
406
  #### #json_key
388
407
 
389
- PR please :)
408
+ Returns the key used by the adapter as the resource root. See [root](#root) for more information.
390
409
 
391
410
  ## Examples
392
411
 
@@ -72,7 +72,7 @@ ActiveModelSerializers pagination relies on a paginated collection with the meth
72
72
 
73
73
  ### JSON adapter
74
74
 
75
- If you are using `JSON` adapter, pagination links will not be included automatically, but it is possible to do so using `meta` key.
75
+ If you are not using `JSON` adapter, pagination links will not be included automatically, but it is possible to do so using `meta` key.
76
76
 
77
77
  Add this method to your base API controller.
78
78
 
@@ -74,6 +74,9 @@ Then, in your controller you can tell rails you're accepting and rendering the j
74
74
  end
75
75
  ```
76
76
 
77
+ #### Note:
78
+ In Rails 5, the "unsafe" method ( `jsonapi_parse!` vs the safe `jsonapi_parse`) throws an `InvalidDocument` exception when the payload does not meet basic criteria for JSON API deserialization.
79
+
77
80
 
78
81
  ### Adapter Changes
79
82
 
@@ -4,13 +4,7 @@ require 'active_model/serializer/collection_serializer'
4
4
  require 'active_model/serializer/array_serializer'
5
5
  require 'active_model/serializer/error_serializer'
6
6
  require 'active_model/serializer/errors_serializer'
7
- require 'active_model/serializer/concerns/associations'
8
- require 'active_model/serializer/concerns/attributes'
9
7
  require 'active_model/serializer/concerns/caching'
10
- require 'active_model/serializer/concerns/configuration'
11
- require 'active_model/serializer/concerns/links'
12
- require 'active_model/serializer/concerns/meta'
13
- require 'active_model/serializer/concerns/type'
14
8
  require 'active_model/serializer/fieldset'
15
9
  require 'active_model/serializer/lint'
16
10
 
@@ -18,33 +12,40 @@ require 'active_model/serializer/lint'
18
12
  # reified when subclassed to decorate a resource.
19
13
  module ActiveModel
20
14
  class Serializer
15
+ undef_method :select, :display # These IO methods, which are mixed into Kernel,
16
+ # sometimes conflict with attribute names. We don't need these IO methods.
17
+
21
18
  # @see #serializable_hash for more details on these valid keys.
22
19
  SERIALIZABLE_HASH_VALID_KEYS = [:only, :except, :methods, :include, :root].freeze
23
20
  extend ActiveSupport::Autoload
24
21
  autoload :Adapter
25
22
  autoload :Null
26
- include Configuration
27
- include Associations
28
- include Attributes
23
+ autoload :Attribute
24
+ autoload :Association
25
+ autoload :Reflection
26
+ autoload :SingularReflection
27
+ autoload :CollectionReflection
28
+ autoload :BelongsToReflection
29
+ autoload :HasOneReflection
30
+ autoload :HasManyReflection
31
+ include ActiveSupport::Configurable
29
32
  include Caching
30
- include Links
31
- include Meta
32
- include Type
33
33
 
34
34
  # @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
35
35
  # @return [ActiveModel::Serializer]
36
36
  # Preferentially returns
37
- # 1. resource.serializer
37
+ # 1. resource.serializer_class
38
38
  # 2. ArraySerializer when resource is a collection
39
39
  # 3. options[:serializer]
40
40
  # 4. lookup serializer when resource is a Class
41
- def self.serializer_for(resource, options = {})
42
- if resource.respond_to?(:serializer_class)
43
- resource.serializer_class
44
- elsif resource.respond_to?(:to_ary)
41
+ def self.serializer_for(resource_or_class, options = {})
42
+ if resource_or_class.respond_to?(:serializer_class)
43
+ resource_or_class.serializer_class
44
+ elsif resource_or_class.respond_to?(:to_ary)
45
45
  config.collection_serializer
46
46
  else
47
- options.fetch(:serializer) { get_serializer_for(resource.class, options[:namespace]) }
47
+ resource_class = resource_or_class.class == Class ? resource_or_class : resource_or_class.class
48
+ options.fetch(:serializer) { get_serializer_for(resource_class, options[:namespace]) }
48
49
  end
49
50
  end
50
51
 
@@ -91,6 +92,8 @@ module ActiveModel
91
92
  serializer_class
92
93
  elsif klass.superclass
93
94
  get_serializer_for(klass.superclass)
95
+ else
96
+ nil # No serializer found
94
97
  end
95
98
  end
96
99
  end
@@ -111,6 +114,193 @@ module ActiveModel
111
114
  @serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
112
115
  end
113
116
 
117
+ # Preferred interface is ActiveModelSerializers.config
118
+ # BEGIN DEFAULT CONFIGURATION
119
+ config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
120
+ config.serializer_lookup_enabled = true
121
+
122
+ # @deprecated Use {#config.collection_serializer=} instead of this. Is
123
+ # compatibility layer for ArraySerializer.
124
+ def config.array_serializer=(collection_serializer)
125
+ self.collection_serializer = collection_serializer
126
+ end
127
+
128
+ # @deprecated Use {#config.collection_serializer} instead of this. Is
129
+ # compatibility layer for ArraySerializer.
130
+ def config.array_serializer
131
+ collection_serializer
132
+ end
133
+
134
+ config.default_includes = '*'
135
+ config.adapter = :attributes
136
+ config.key_transform = nil
137
+ config.jsonapi_pagination_links_enabled = true
138
+ config.jsonapi_resource_type = :plural
139
+ config.jsonapi_namespace_separator = '-'.freeze
140
+ config.jsonapi_version = '1.0'
141
+ config.jsonapi_toplevel_meta = {}
142
+ # Make JSON API top-level jsonapi member opt-in
143
+ # ref: http://jsonapi.org/format/#document-top-level
144
+ config.jsonapi_include_toplevel_object = false
145
+ config.include_data_default = true
146
+
147
+ # For configuring how serializers are found.
148
+ # This should be an array of procs.
149
+ #
150
+ # The priority of the output is that the first item
151
+ # in the evaluated result array will take precedence
152
+ # over other possible serializer paths.
153
+ #
154
+ # i.e.: First match wins.
155
+ #
156
+ # @example output
157
+ # => [
158
+ # "CustomNamespace::ResourceSerializer",
159
+ # "ParentSerializer::ResourceSerializer",
160
+ # "ResourceNamespace::ResourceSerializer" ,
161
+ # "ResourceSerializer"]
162
+ #
163
+ # If CustomNamespace::ResourceSerializer exists, it will be used
164
+ # for serialization
165
+ config.serializer_lookup_chain = ActiveModelSerializers::LookupChain::DEFAULT.dup
166
+
167
+ config.schema_path = 'test/support/schemas'
168
+ # END DEFAULT CONFIGURATION
169
+
170
+ with_options instance_writer: false, instance_reader: false do |serializer|
171
+ serializer.class_attribute :_attributes_data # @api private
172
+ self._attributes_data ||= {}
173
+ end
174
+ with_options instance_writer: false, instance_reader: true do |serializer|
175
+ serializer.class_attribute :_reflections
176
+ self._reflections ||= {}
177
+ serializer.class_attribute :_links # @api private
178
+ self._links ||= {}
179
+ serializer.class_attribute :_meta # @api private
180
+ serializer.class_attribute :_type # @api private
181
+ end
182
+
183
+ def self.inherited(base)
184
+ super
185
+ base._attributes_data = _attributes_data.dup
186
+ base._reflections = _reflections.dup
187
+ base._links = _links.dup
188
+ end
189
+
190
+ # @return [Array<Symbol>] Key names of declared attributes
191
+ # @see Serializer::attribute
192
+ def self._attributes
193
+ _attributes_data.keys
194
+ end
195
+
196
+ # BEGIN SERIALIZER MACROS
197
+
198
+ # @example
199
+ # class AdminAuthorSerializer < ActiveModel::Serializer
200
+ # attributes :id, :name, :recent_edits
201
+ def self.attributes(*attrs)
202
+ attrs = attrs.first if attrs.first.class == Array
203
+
204
+ attrs.each do |attr|
205
+ attribute(attr)
206
+ end
207
+ end
208
+
209
+ # @example
210
+ # class AdminAuthorSerializer < ActiveModel::Serializer
211
+ # attributes :id, :recent_edits
212
+ # attribute :name, key: :title
213
+ #
214
+ # attribute :full_name do
215
+ # "#{object.first_name} #{object.last_name}"
216
+ # end
217
+ #
218
+ # def recent_edits
219
+ # object.edits.last(5)
220
+ # end
221
+ def self.attribute(attr, options = {}, &block)
222
+ key = options.fetch(:key, attr)
223
+ _attributes_data[key] = Attribute.new(attr, options, block)
224
+ end
225
+
226
+ # @param [Symbol] name of the association
227
+ # @param [Hash<Symbol => any>] options for the reflection
228
+ # @return [void]
229
+ #
230
+ # @example
231
+ # has_many :comments, serializer: CommentSummarySerializer
232
+ #
233
+ def self.has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName
234
+ associate(HasManyReflection.new(name, options, block))
235
+ end
236
+
237
+ # @param [Symbol] name of the association
238
+ # @param [Hash<Symbol => any>] options for the reflection
239
+ # @return [void]
240
+ #
241
+ # @example
242
+ # belongs_to :author, serializer: AuthorSerializer
243
+ #
244
+ def self.belongs_to(name, options = {}, &block)
245
+ associate(BelongsToReflection.new(name, options, block))
246
+ end
247
+
248
+ # @param [Symbol] name of the association
249
+ # @param [Hash<Symbol => any>] options for the reflection
250
+ # @return [void]
251
+ #
252
+ # @example
253
+ # has_one :author, serializer: AuthorSerializer
254
+ #
255
+ def self.has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName
256
+ associate(HasOneReflection.new(name, options, block))
257
+ end
258
+
259
+ # Add reflection and define {name} accessor.
260
+ # @param [ActiveModel::Serializer::Reflection] reflection
261
+ # @return [void]
262
+ #
263
+ # @api private
264
+ def self.associate(reflection)
265
+ key = reflection.options[:key] || reflection.name
266
+ self._reflections[key] = reflection
267
+ end
268
+ private_class_method :associate
269
+
270
+ # Define a link on a serializer.
271
+ # @example
272
+ # link(:self) { resource_url(object) }
273
+ # @example
274
+ # link(:self) { "http://example.com/resource/#{object.id}" }
275
+ # @example
276
+ # link :resource, "http://example.com/resource"
277
+ #
278
+ def self.link(name, value = nil, &block)
279
+ _links[name] = block || value
280
+ end
281
+
282
+ # Set the JSON API meta attribute of a serializer.
283
+ # @example
284
+ # class AdminAuthorSerializer < ActiveModel::Serializer
285
+ # meta { stuff: 'value' }
286
+ # @example
287
+ # meta do
288
+ # { comment_count: object.comments.count }
289
+ # end
290
+ def self.meta(value = nil, &block)
291
+ self._meta = block || value
292
+ end
293
+
294
+ # Set the JSON API type of a serializer.
295
+ # @example
296
+ # class AdminAuthorSerializer < ActiveModel::Serializer
297
+ # type 'authors'
298
+ def self.type(type)
299
+ self._type = type && type.to_s
300
+ end
301
+
302
+ # END SERIALIZER MACROS
303
+
114
304
  attr_accessor :object, :root, :scope
115
305
 
116
306
  # `scope_name` is set as :current_user by default in the controller.
@@ -131,53 +321,49 @@ module ActiveModel
131
321
  true
132
322
  end
133
323
 
324
+ # Return the +attributes+ of +object+ as presented
325
+ # by the serializer.
326
+ def attributes(requested_attrs = nil, reload = false)
327
+ @attributes = nil if reload
328
+ @attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash|
329
+ next if attr.excluded?(self)
330
+ next unless requested_attrs.nil? || requested_attrs.include?(key)
331
+ hash[key] = attr.value(self)
332
+ end
333
+ end
334
+
335
+ # @param [JSONAPI::IncludeDirective] include_directive (defaults to the
336
+ # +default_include_directive+ config value when not provided)
337
+ # @return [Enumerator<Association>]
338
+ def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil)
339
+ include_slice ||= include_directive
340
+ return Enumerator.new unless object
341
+
342
+ Enumerator.new do |y|
343
+ self.class._reflections.each do |key, reflection|
344
+ next if reflection.excluded?(self)
345
+ next unless include_directive.key?(key)
346
+
347
+ association = reflection.build_association(self, instance_options, include_slice)
348
+ y.yield association
349
+ end
350
+ end
351
+ end
352
+
134
353
  # @return [Hash] containing the attributes and first level
135
354
  # associations, similar to how ActiveModel::Serializers::JSON is used
136
355
  # in ActiveRecord::Base.
137
- #
138
- # TODO: Include <tt>ActiveModel::Serializers::JSON</tt>.
139
- # So that the below is true:
140
- # @param options [nil, Hash] The same valid options passed to `serializable_hash`
141
- # (:only, :except, :methods, and :include).
142
- #
143
- # See
144
- # https://github.com/rails/rails/blob/v5.0.0.beta2/activemodel/lib/active_model/serializers/json.rb#L17-L101
145
- # https://github.com/rails/rails/blob/v5.0.0.beta2/activemodel/lib/active_model/serialization.rb#L85-L123
146
- # https://github.com/rails/rails/blob/v5.0.0.beta2/activerecord/lib/active_record/serialization.rb#L11-L17
147
- # https://github.com/rails/rails/blob/v5.0.0.beta2/activesupport/lib/active_support/core_ext/object/json.rb#L147-L162
148
- #
149
- # @example
150
- # # The :only and :except options can be used to limit the attributes included, and work
151
- # # similar to the attributes method.
152
- # serializer.as_json(only: [:id, :name])
153
- # serializer.as_json(except: [:id, :created_at, :age])
154
- #
155
- # # To include the result of some method calls on the model use :methods:
156
- # serializer.as_json(methods: :permalink)
157
- #
158
- # # To include associations use :include:
159
- # serializer.as_json(include: :posts)
160
- # # Second level and higher order associations work as well:
161
- # serializer.as_json(include: { posts: { include: { comments: { only: :body } }, only: :title } })
162
356
  def serializable_hash(adapter_options = nil, options = {}, adapter_instance = self.class.serialization_adapter_instance)
163
357
  adapter_options ||= {}
164
358
  options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
165
- cached_attributes = adapter_options[:cached_attributes] ||= {}
166
- resource = fetch_attributes(options[:fields], cached_attributes, adapter_instance)
167
- relationships = resource_relationships(adapter_options, options, adapter_instance)
359
+ resource = attributes_hash(adapter_options, options, adapter_instance)
360
+ relationships = associations_hash(adapter_options, options, adapter_instance)
168
361
  resource.merge(relationships)
169
362
  end
170
363
  alias to_hash serializable_hash
171
364
  alias to_h serializable_hash
172
365
 
173
366
  # @see #serializable_hash
174
- # TODO: When moving attributes adapter logic here, @see #serializable_hash
175
- # So that the below is true:
176
- # @param options [nil, Hash] The same valid options passed to `as_json`
177
- # (:root, :only, :except, :methods, and :include).
178
- # The default for `root` is nil.
179
- # The default value for include_root is false. You can change it to true if the given
180
- # JSON string includes a single root node.
181
367
  def as_json(adapter_opts = nil)
182
368
  serializable_hash(adapter_opts)
183
369
  end
@@ -196,32 +382,24 @@ module ActiveModel
196
382
  end
197
383
 
198
384
  # @api private
199
- def resource_relationships(adapter_options, options, adapter_instance)
200
- relationships = {}
201
- include_directive = options.fetch(:include_directive)
202
- associations(include_directive).each do |association|
203
- adapter_opts = adapter_options.merge(include_directive: include_directive[association.key])
204
- relationships[association.key] ||= relationship_value_for(association, adapter_opts, adapter_instance)
385
+ def attributes_hash(_adapter_options, options, adapter_instance)
386
+ if self.class.cache_enabled?
387
+ fetch_attributes(options[:fields], options[:cached_attributes] || {}, adapter_instance)
388
+ elsif self.class.fragment_cache_enabled?
389
+ fetch_attributes_fragment(adapter_instance, options[:cached_attributes] || {})
390
+ else
391
+ attributes(options[:fields], true)
205
392
  end
206
-
207
- relationships
208
393
  end
209
394
 
210
395
  # @api private
211
- def relationship_value_for(association, adapter_options, adapter_instance)
212
- return association.options[:virtual_value] if association.options[:virtual_value]
213
- association_serializer = association.serializer
214
- association_object = association_serializer && association_serializer.object
215
- return unless association_object
216
-
217
- relationship_value = association_serializer.serializable_hash(adapter_options, {}, adapter_instance)
218
-
219
- if association.options[:polymorphic] && relationship_value
220
- polymorphic_type = association_object.class.name.underscore
221
- relationship_value = { type: polymorphic_type, polymorphic_type.to_sym => relationship_value }
396
+ def associations_hash(adapter_options, options, adapter_instance)
397
+ include_directive = options.fetch(:include_directive)
398
+ include_slice = options[:include_slice]
399
+ associations(include_directive, include_slice).each_with_object({}) do |association, relationships|
400
+ adapter_opts = adapter_options.merge(include_directive: include_directive[association.key], adapter_instance: adapter_instance)
401
+ relationships[association.key] = association.serializable_hash(adapter_opts, adapter_instance)
222
402
  end
223
-
224
- relationship_value
225
403
  end
226
404
 
227
405
  protected