active_model_serializers 0.10.0.rc2 → 0.10.0.rc3

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