active_model_serializers 0.10.5 → 0.10.6

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