active_model_serializers 0.10.0.rc2 → 0.10.0.rc3

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +82 -0
  4. data/.rubocop_todo.yml +315 -0
  5. data/.simplecov +99 -0
  6. data/.travis.yml +8 -0
  7. data/CHANGELOG.md +9 -3
  8. data/Gemfile +39 -8
  9. data/README.md +55 -31
  10. data/Rakefile +29 -2
  11. data/active_model_serializers.gemspec +37 -13
  12. data/appveyor.yml +25 -0
  13. data/docs/README.md +29 -0
  14. data/docs/general/adapters.md +110 -0
  15. data/docs/general/configuration_options.md +11 -0
  16. data/docs/general/getting_started.md +73 -0
  17. data/docs/howto/add_pagination_links.md +112 -0
  18. data/docs/howto/add_root_key.md +51 -0
  19. data/docs/howto/outside_controller_use.md +42 -0
  20. data/lib/action_controller/serialization.rb +24 -33
  21. data/lib/active_model/serializable_resource.rb +70 -0
  22. data/lib/active_model/serializer.rb +50 -131
  23. data/lib/active_model/serializer/adapter.rb +84 -21
  24. data/lib/active_model/serializer/adapter/flatten_json.rb +9 -9
  25. data/lib/active_model/serializer/adapter/fragment_cache.rb +10 -13
  26. data/lib/active_model/serializer/adapter/json.rb +25 -28
  27. data/lib/active_model/serializer/adapter/json/fragment_cache.rb +2 -12
  28. data/lib/active_model/serializer/adapter/json_api.rb +100 -98
  29. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +4 -14
  30. data/lib/active_model/serializer/adapter/json_api/pagination_links.rb +50 -0
  31. data/lib/active_model/serializer/adapter/null.rb +2 -8
  32. data/lib/active_model/serializer/array_serializer.rb +22 -17
  33. data/lib/active_model/serializer/association.rb +20 -0
  34. data/lib/active_model/serializer/associations.rb +97 -0
  35. data/lib/active_model/serializer/belongs_to_reflection.rb +10 -0
  36. data/lib/active_model/serializer/collection_reflection.rb +7 -0
  37. data/lib/active_model/serializer/configuration.rb +1 -0
  38. data/lib/active_model/serializer/fieldset.rb +7 -7
  39. data/lib/active_model/serializer/has_many_reflection.rb +10 -0
  40. data/lib/active_model/serializer/has_one_reflection.rb +10 -0
  41. data/lib/active_model/serializer/lint.rb +129 -0
  42. data/lib/active_model/serializer/railtie.rb +7 -0
  43. data/lib/active_model/serializer/reflection.rb +74 -0
  44. data/lib/active_model/serializer/singular_reflection.rb +7 -0
  45. data/lib/active_model/serializer/utils.rb +35 -0
  46. data/lib/active_model/serializer/version.rb +1 -1
  47. data/lib/active_model_serializers.rb +28 -14
  48. data/lib/generators/serializer/serializer_generator.rb +7 -7
  49. data/lib/generators/serializer/templates/{serializer.rb → serializer.rb.erb} +2 -2
  50. data/lib/tasks/rubocop.rake +0 -0
  51. data/test/action_controller/adapter_selector_test.rb +3 -3
  52. data/test/action_controller/explicit_serializer_test.rb +9 -9
  53. data/test/action_controller/json_api/linked_test.rb +179 -0
  54. data/test/action_controller/json_api/pagination_test.rb +116 -0
  55. data/test/action_controller/serialization_scope_name_test.rb +10 -6
  56. data/test/action_controller/serialization_test.rb +149 -112
  57. data/test/active_record_test.rb +9 -0
  58. data/test/adapter/fragment_cache_test.rb +11 -1
  59. data/test/adapter/json/belongs_to_test.rb +4 -5
  60. data/test/adapter/json/collection_test.rb +30 -21
  61. data/test/adapter/json/has_many_test.rb +20 -9
  62. data/test/adapter/json_api/belongs_to_test.rb +38 -38
  63. data/test/adapter/json_api/collection_test.rb +22 -23
  64. data/test/adapter/json_api/has_many_embed_ids_test.rb +2 -2
  65. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +4 -4
  66. data/test/adapter/json_api/has_many_test.rb +54 -19
  67. data/test/adapter/json_api/has_one_test.rb +28 -8
  68. data/test/adapter/json_api/json_api_test.rb +37 -0
  69. data/test/adapter/json_api/linked_test.rb +75 -75
  70. data/test/adapter/json_api/pagination_links_test.rb +115 -0
  71. data/test/adapter/json_api/resource_type_config_test.rb +59 -0
  72. data/test/adapter/json_test.rb +18 -5
  73. data/test/adapter_test.rb +10 -11
  74. data/test/array_serializer_test.rb +63 -5
  75. data/test/capture_warnings.rb +65 -0
  76. data/test/fixtures/active_record.rb +56 -0
  77. data/test/fixtures/poro.rb +60 -29
  78. data/test/generators/scaffold_controller_generator_test.rb +1 -2
  79. data/test/generators/serializer_generator_test.rb +17 -12
  80. data/test/lint_test.rb +37 -0
  81. data/test/logger_test.rb +18 -0
  82. data/test/poro_test.rb +9 -0
  83. data/test/serializable_resource_test.rb +27 -0
  84. data/test/serializers/adapter_for_test.rb +123 -3
  85. data/test/serializers/association_macros_test.rb +36 -0
  86. data/test/serializers/associations_test.rb +70 -47
  87. data/test/serializers/attribute_test.rb +28 -4
  88. data/test/serializers/attributes_test.rb +8 -14
  89. data/test/serializers/cache_test.rb +58 -31
  90. data/test/serializers/fieldset_test.rb +3 -4
  91. data/test/serializers/meta_test.rb +42 -28
  92. data/test/serializers/root_test.rb +21 -0
  93. data/test/serializers/serializer_for_test.rb +1 -1
  94. data/test/support/rails_app.rb +21 -0
  95. data/test/support/serialization_testing.rb +13 -0
  96. data/test/support/simplecov.rb +6 -0
  97. data/test/support/stream_capture.rb +50 -0
  98. data/test/support/test_case.rb +5 -0
  99. data/test/test_helper.rb +41 -29
  100. data/test/utils/include_args_to_hash_test.rb +79 -0
  101. metadata +123 -17
  102. data/test/action_controller/json_api_linked_test.rb +0 -179
  103. data/test/action_controller/rescue_from_test.rb +0 -32
  104. data/test/serializers/urls_test.rb +0 -26
@@ -0,0 +1,110 @@
1
+ # Adapters
2
+
3
+ AMS does this through two components: **serializers** and **adapters**.
4
+ Serializers describe _which_ attributes and relationships should be serialized.
5
+ Adapters describe _how_ attributes and relationships should be serialized.
6
+ You can use one of the built-in adapters (```FlattenJSON``` is the default one) or create one by yourself, but you won't need to implement an adapter unless you wish to use a new format or media type with AMS.
7
+
8
+ ## Built in Adapters
9
+
10
+ ### FlattenJSON - Default
11
+
12
+ It's the default adapter, it generates a json response without a root key.
13
+ Doesn't follow any specifc convention.
14
+
15
+ ### JSON
16
+
17
+ It also generates a json response but always with a root key. The root key **can't be overridden**, and will be automatically defined accordingly to the objects being serialized.
18
+ Doesn't follow any specifc convention.
19
+
20
+ ### JSONAPI
21
+
22
+ This adapter follows **version 1.0** of the format specified in
23
+ [jsonapi.org/format](http://jsonapi.org/format). It will include the associated
24
+ resources in the `"included"` member when the resource names are included in the
25
+ `include` option.
26
+
27
+ ```ruby
28
+ render @posts, include: ['authors', 'comments']
29
+ ```
30
+
31
+ or
32
+
33
+ ```ruby
34
+ render @posts, include: 'authors,comments'
35
+ ```
36
+
37
+ The format of the `include` option can be either a String composed of a comma-separated list of [relationship paths](http://jsonapi.org/format/#fetching-includes), an Array of Symbols and Hashes, or a mix of both.
38
+
39
+ ## Choosing an adapter
40
+
41
+ If you want to use a specify a default adapter, such as JsonApi, you can change this in an initializer:
42
+
43
+ ```ruby
44
+ ActiveModel::Serializer.config.adapter = ActiveModel::Serializer::Adapter::JsonApi
45
+ ```
46
+
47
+ or
48
+
49
+ ```ruby
50
+ ActiveModel::Serializer.config.adapter = :json_api
51
+ ```
52
+
53
+ If you want to have a root key for each resource in your responses, you should use the Json or
54
+ JsonApi adapters instead of the default FlattenJson:
55
+
56
+ ```ruby
57
+ ActiveModel::Serializer.config.adapter = :json
58
+ ```
59
+
60
+ ## Advanced adapter configuration
61
+
62
+ ### Registering an adapter
63
+
64
+ The default adapter can be configured, as above, to use any class given to it.
65
+
66
+ An adapter may also be specified, e.g. when rendering, as a class or as a symbol.
67
+ If a symbol, then the adapter must be, e.g. `:great_example`,
68
+ `ActiveModel::Serializer::Adapter::GreatExample`, or registered.
69
+
70
+ There are two ways to register an adapter:
71
+
72
+ 1) The simplest, is to subclass `ActiveModel::Serializer::Adapter`, e.g. the below will
73
+ register the `Example::UsefulAdapter` as `:useful_adapter`.
74
+
75
+ ```ruby
76
+ module Example
77
+ class UsefulAdapter < ActiveModel::Serializer::Adapter
78
+ end
79
+ end
80
+ ```
81
+
82
+ You'll notice that the name it registers is the class name underscored, not the full namespace.
83
+
84
+ Under the covers, when the `ActiveModel::Serializer::Adapter` is subclassed, it registers
85
+ the subclass as `register(:useful_adapter, Example::UsefulAdapter)`
86
+
87
+ 2) Any class can be registered as an adapter by calling `register` directly on the
88
+ `ActiveModel::Serializer::Adapter` class. e.g., the below registers `MyAdapter` as
89
+ `:special_adapter`.
90
+
91
+ ```ruby
92
+ class MyAdapter; end
93
+ ActiveModel::Serializer::Adapter.register(:special_adapter, MyAdapter)
94
+ ```
95
+
96
+ ### Looking up an adapter
97
+
98
+ | Method | Return value |
99
+ | :------------ |:---------------|
100
+ | `ActiveModel::Serializer::Adapter.adapter_map` | A Hash of all known adapters `{ adapter_name => adapter_class }` |
101
+ | `ActiveModel::Serializer::Adapter.adapters` | A (sorted) Array of all known `adapter_names` |
102
+ | `ActiveModel::Serializer::Adapter.lookup(name_or_klass)` | The `adapter_class`, else raises an `ActiveModel::Serializer::Adapter::UnknownAdapter` error |
103
+ | `ActiveModel::Serializer::Adapter.adapter_class(adapter)` | Delegates to `ActiveModel::Serializer::Adapter.lookup(adapter)` |
104
+ | `ActiveModel::Serializer.adapter` | A convenience method for `ActiveModel::Serializer::Adapter.lookup(config.adapter)` |
105
+
106
+ The registered adapter name is always a String, but may be looked up as a Symbol or String.
107
+ Helpfully, the Symbol or String is underscored, so that `get(:my_adapter)` and `get("MyAdapter")`
108
+ may both be used.
109
+
110
+ For more information, see [the Adapter class on GitHub](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/adapter.rb)
@@ -0,0 +1,11 @@
1
+ # Configuration Options
2
+
3
+ The following configuration options can be set on `ActiveModel::Serializer.config` inside an initializer.
4
+
5
+ ## General
6
+
7
+ - `adapter`: The [adapter](adapters.md) to use. Possible values: `:flatten_json, :json, :json_api`. Default: `:flatten_json`.
8
+
9
+ ## JSON API
10
+
11
+ - `jsonapi_resource_type`: Whether the `type` attributes of resources should be singular or plural. Possible values: `:singular, :plural`. Default: `:plural`.
@@ -0,0 +1,73 @@
1
+ # Getting Started
2
+
3
+ ## Installation
4
+
5
+ ### ActiveModel::Serializer is already included on Rails >= 5
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```
10
+ gem 'active_model_serializers'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```
16
+ $ bundle
17
+ ```
18
+
19
+ ## Creating a Serializer
20
+
21
+ The easiest way to create a new serializer is to generate a new resource, which
22
+ will generate a serializer at the same time:
23
+
24
+ ```
25
+ $ rails g resource post title:string body:string
26
+ ```
27
+
28
+ This will generate a serializer in `app/serializers/post_serializer.rb` for
29
+ your new model. You can also generate a serializer for an existing model with
30
+ the serializer generator:
31
+
32
+ ```
33
+ $ rails g serializer post
34
+ ```
35
+
36
+ The generated seralizer will contain basic `attributes` and
37
+ `has_many`/`has_one`/`belongs_to` declarations, based on the model. For example:
38
+
39
+ ```ruby
40
+ class PostSerializer < ActiveModel::Serializer
41
+ attributes :title, :body
42
+
43
+ has_many :comments
44
+ has_one :author
45
+
46
+ end
47
+ ```
48
+
49
+ and
50
+
51
+ ```ruby
52
+ class CommentSerializer < ActiveModel::Serializer
53
+ attributes :name, :body
54
+
55
+ belongs_to :post_id
56
+
57
+ end
58
+ ```
59
+
60
+ ## Rails Integration
61
+
62
+ AMS will automatically integrate with you Rails app, you won't need to update your controller, this is a example of how it will look like:
63
+
64
+ ```ruby
65
+ class PostsController < ApplicationController
66
+
67
+ def show
68
+ @post = Post.find(params[:id])
69
+ render json: @post
70
+ end
71
+
72
+ end
73
+ ```
@@ -0,0 +1,112 @@
1
+ # How to add pagination links
2
+
3
+ ### JSON-API adapter
4
+
5
+ Pagination links will be included in your response automatically as long as the resource is paginated and if you are using a ```JSON-API``` adapter.
6
+
7
+ If you want pagination links in your response, use [Kaminari](https://github.com/amatsuda/kaminari) or [WillPaginate](https://github.com/mislav/will_paginate).
8
+
9
+ ###### Kaminari examples
10
+ ```ruby
11
+ #array
12
+ @posts = Kaminari.paginate_array([1, 2, 3]).page(3).per(1)
13
+ render json: @posts
14
+
15
+ #active_record
16
+ @posts = Post.page(3).per(1)
17
+ render json: @posts
18
+ ```
19
+
20
+ ###### WillPaginate examples
21
+
22
+ ```ruby
23
+ #array
24
+ @posts = [1,2,3].paginate(page: 3, per_page: 1)
25
+ render json: @posts
26
+
27
+ #active_record
28
+ @posts = Post.page(3).per_page(1)
29
+ render json: @posts
30
+ ```
31
+
32
+ ```ruby
33
+ ActiveModel::Serializer.config.adapter = :json_api
34
+ ```
35
+
36
+ ex:
37
+ ```json
38
+ {
39
+ "data": [
40
+ {
41
+ "type": "articles",
42
+ "id": "3",
43
+ "attributes": {
44
+ "title": "JSON API paints my bikeshed!",
45
+ "body": "The shortest article. Ever.",
46
+ "created": "2015-05-22T14:56:29.000Z",
47
+ "updated": "2015-05-22T14:56:28.000Z"
48
+ }
49
+ }
50
+ ],
51
+ "links": {
52
+ "self": "http://example.com/articles?page[number]=3&page[size]=1",
53
+ "first": "http://example.com/articles?page[number]=1&page[size]=1",
54
+ "prev": "http://example.com/articles?page[number]=2&page[size]=1",
55
+ "next": "http://example.com/articles?page[number]=4&page[size]=1",
56
+ "last": "http://example.com/articles?page[number]=13&page[size]=1"
57
+ }
58
+ }
59
+ ```
60
+
61
+ AMS pagination relies on a paginated collection with the methods `current_page`, `total_pages`, and `size`, such as are supported by both [Kaminari](https://github.com/amatsuda/kaminari) or [WillPaginate](https://github.com/mislav/will_paginate).
62
+
63
+
64
+ ### JSON adapter
65
+
66
+ If you are using `JSON` adapter, pagination links will not be included automatically, but it is possible to do so using `meta` key.
67
+
68
+ In your action specify a custom serializer.
69
+ ```ruby
70
+ render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer
71
+ ```
72
+
73
+ And then, you could do something like the following class.
74
+ ```ruby
75
+ class PaginatedSerializer < ActiveModel::Serializer::ArraySerializer
76
+ def initialize(object, options={})
77
+ meta_key = options[:meta_key] || :meta
78
+ options[meta_key] ||= {}
79
+ options[meta_key] = {
80
+ current_page: object.current_page,
81
+ next_page: object.next_page,
82
+ prev_page: object.prev_page,
83
+ total_pages: object.total_pages,
84
+ total_count: object.total_count
85
+ }
86
+ super(object, options)
87
+ end
88
+ end
89
+ ```
90
+ ex.
91
+ ```json
92
+ {
93
+ "articles": [
94
+ {
95
+ "id": 2,
96
+ "title": "JSON API paints my bikeshed!",
97
+ "body": "The shortest article. Ever."
98
+ }
99
+ ],
100
+ "meta": {
101
+ "current_page": 3,
102
+ "next_page": 4,
103
+ "prev_page": 2,
104
+ "total_pages": 10,
105
+ "total_count": 10
106
+ }
107
+ }
108
+ ```
109
+
110
+ ### FlattenJSON adapter
111
+
112
+ This adapter does not allow us to use `meta` key, due to that it is not possible to add pagination links.
@@ -0,0 +1,51 @@
1
+ # How to add root key
2
+
3
+ Add the root key to your API is quite simple with AMS. The **Adapter** is what determines the format of your JSON response. The default adapter is the ```FlattenJSON``` which doesn't have the root key, so your response is something similar to:
4
+
5
+ ```json
6
+ {
7
+ "id": 1,
8
+ "title": "Awesome Post Tile",
9
+ "content": "Post content"
10
+ }
11
+ ```
12
+
13
+ In order to add the root key you need to use the ```JSON``` Adapter, you can change this in an initializer:
14
+
15
+ ```ruby
16
+ ActiveModel::Serializer.config.adapter = :json
17
+ ```
18
+
19
+ You can also specify a class as adapter, as long as it complies with the AMS adapters interface.
20
+ It will add the root key to all your serialized endpoints.
21
+
22
+ ex:
23
+
24
+ ```json
25
+ {
26
+ "post": {
27
+ "id": 1,
28
+ "title": "Awesome Post Tile",
29
+ "content": "Post content"
30
+ }
31
+ }
32
+ ```
33
+
34
+ or if it returns a collection:
35
+
36
+ ```json
37
+ {
38
+ "posts": [
39
+ {
40
+ "id": 1,
41
+ "title": "Awesome Post Tile",
42
+ "content": "Post content"
43
+ },
44
+ {
45
+ "id": 2,
46
+ "title": "Another Post Tile",
47
+ "content": "Another post content"
48
+ }
49
+ ]
50
+ }
51
+ ```
@@ -0,0 +1,42 @@
1
+ ## Using AMS Outside Of A Controller
2
+
3
+ ### Serializing a resource
4
+
5
+ In AMS versions 0.10 or later, serializing resources outside of the controller context is fairly simple:
6
+
7
+ ```ruby
8
+ # Create our resource
9
+ post = Post.create(title: "Sample post", body: "I love Active Model Serializers!")
10
+
11
+ # Optional options parameters
12
+ options = {}
13
+
14
+ # Create a serializable resource instance
15
+ serializable_resource = ActiveModel::SerializableResource.new(post, options)
16
+
17
+ # Convert your resource into json
18
+ model_json = serializable_resource.as_json
19
+ ```
20
+
21
+ ### Retrieving a Resource's Active Model Serializer
22
+
23
+ If you want to retrieve a serializer for a specific resource, you can do the following:
24
+
25
+ ```ruby
26
+ # Create our resource
27
+ post = Post.create(title: "Another Example", body: "So much fun.")
28
+
29
+ # Optional options parameters
30
+ options = {}
31
+
32
+ # Retrieve the default serializer for posts
33
+ serializer = ActiveModel::Serializer.serializer_for(post, options)
34
+ ```
35
+
36
+ You could also retrieve the serializer via:
37
+
38
+ ```ruby
39
+ ActiveModel::SerializableResource.new(post, options).serializer
40
+ ```
41
+
42
+ Both approaches will return an instance, if any, of the resource's serializer.
@@ -6,7 +6,8 @@ module ActionController
6
6
 
7
7
  include ActionController::Renderers
8
8
 
9
- ADAPTER_OPTION_KEYS = [:include, :fields, :adapter]
9
+ # Deprecated
10
+ ADAPTER_OPTION_KEYS = ActiveModel::SerializableResource::ADAPTER_OPTION_KEYS
10
11
 
11
12
  included do
12
13
  class_attribute :_serialization_scope
@@ -18,49 +19,39 @@ module ActionController
18
19
  respond_to?(_serialization_scope, true)
19
20
  end
20
21
 
21
- def get_serializer(resource)
22
- @_serializer ||= @_serializer_opts.delete(:serializer)
23
- @_serializer ||= ActiveModel::Serializer.serializer_for(resource)
24
-
25
- if @_serializer_opts.key?(:each_serializer)
26
- @_serializer_opts[:serializer] = @_serializer_opts.delete(:each_serializer)
22
+ def get_serializer(resource, options = {})
23
+ if !use_adapter?
24
+ warn 'ActionController::Serialization#use_adapter? has been removed. '\
25
+ "Please pass 'adapter: false' or see ActiveSupport::SerializableResource.new"
26
+ options[:adapter] = false
27
+ end
28
+ serializable_resource = ActiveModel::SerializableResource.new(resource, options)
29
+ if serializable_resource.serializer?
30
+ serializable_resource.serialization_scope ||= serialization_scope
31
+ serializable_resource.serialization_scope_name = _serialization_scope
32
+ begin
33
+ serializable_resource.adapter
34
+ rescue ActiveModel::Serializer::ArraySerializer::NoSerializerError
35
+ resource
36
+ end
37
+ else
38
+ resource
27
39
  end
28
-
29
- @_serializer
30
40
  end
31
41
 
42
+ # Deprecated
32
43
  def use_adapter?
33
- !(@_adapter_opts.key?(:adapter) && !@_adapter_opts[:adapter])
44
+ true
34
45
  end
35
46
 
36
47
  [:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
37
48
  define_method renderer_method do |resource, options|
38
- @_adapter_opts, @_serializer_opts =
39
- options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
40
-
41
- if use_adapter? && (serializer = get_serializer(resource))
42
-
43
- @_serializer_opts[:scope] ||= serialization_scope
44
- @_serializer_opts[:scope_name] = _serialization_scope
45
-
46
- # omg hax
47
- object = serializer.new(resource, @_serializer_opts)
48
- adapter = ActiveModel::Serializer::Adapter.create(object, @_adapter_opts)
49
- super(adapter, options)
50
- else
51
- super(resource, options)
52
- end
49
+ options.fetch(:context) { options[:context] = request }
50
+ serializable_resource = get_serializer(resource, options)
51
+ super(serializable_resource, options)
53
52
  end
54
53
  end
55
54
 
56
- def rescue_with_handler(exception)
57
- @_serializer = nil
58
- @_serializer_opts = nil
59
- @_adapter_opts = nil
60
-
61
- super(exception)
62
- end
63
-
64
55
  module ClassMethods
65
56
  def serialization_scope(scope)
66
57
  self._serialization_scope = scope