From 772b7999c54e5935f2f31d9cf85c785689351f66 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Sun, 20 Nov 2016 23:25:09 -0600 Subject: [PATCH 01/43] Better AMS Model attributes interface --- CHANGELOG.md | 2 ++ README.md | 2 +- docs/ARCHITECTURE.md | 2 +- docs/howto/serialize_poro.md | 2 +- lib/active_model_serializers/model.rb | 4 ++++ test/action_controller/serialization_scope_name_test.rb | 6 +++--- test/active_model_serializers/model_test.rb | 2 +- test/adapter/json_api/relationship_test.rb | 2 +- test/cache_test.rb | 2 +- test/fixtures/poro.rb | 2 +- test/serializers/read_attribute_for_serialization_test.rb | 6 +++--- test/serializers/serialization_test.rb | 4 ++-- 12 files changed, 21 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe24b77ec..66289ace9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Breaking changes: Features: +- [#1982](https://github.com/rails-api/active_model_serializers/pull/1982) Add ActiveModelSerializers::Model.attributes to configure PORO attributes (@bf4). + Fixes: Misc: diff --git a/README.md b/README.md index cfcf84124..300d2e5a9 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ class SomeResource < ActiveRecord::Base end # or class SomeResource < ActiveModelSerializers::Model - attr_accessor :title, :body + attributes :title, :body end ``` diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 3d566e5cb..02f782950 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -105,7 +105,7 @@ ActiveModelSerializers::Model may be used either as a template, or in production ```ruby class MyModel < ActiveModelSerializers::Model - attr_accessor :id, :name, :level + attributes :id, :name, :level end ``` diff --git a/docs/howto/serialize_poro.md b/docs/howto/serialize_poro.md index 98caed6a5..fa9c9bf84 100644 --- a/docs/howto/serialize_poro.md +++ b/docs/howto/serialize_poro.md @@ -25,7 +25,7 @@ Fortunately, ActiveModelSerializers provides a [`ActiveModelSerializers::Model`] ```ruby # my_model.rb class MyModel < ActiveModelSerializers::Model - attr_accessor :id, :name, :level + attributes :id, :name, :level end ``` diff --git a/lib/active_model_serializers/model.rb b/lib/active_model_serializers/model.rb index b9937cb5c..2abe33344 100644 --- a/lib/active_model_serializers/model.rb +++ b/lib/active_model_serializers/model.rb @@ -6,6 +6,10 @@ class Model include ActiveModel::Model include ActiveModel::Serializers::JSON + def self.attributes(*names) + attr_accessor(*names) + end + attr_reader :attributes, :errors def initialize(attributes = {}) diff --git a/test/action_controller/serialization_scope_name_test.rb b/test/action_controller/serialization_scope_name_test.rb index 62959455f..33ac88f4f 100644 --- a/test/action_controller/serialization_scope_name_test.rb +++ b/test/action_controller/serialization_scope_name_test.rb @@ -2,16 +2,16 @@ module SerializationScopeTesting class User < ActiveModelSerializers::Model - attr_accessor :id, :name, :admin + attributes :id, :name, :admin def admin? admin end end class Comment < ActiveModelSerializers::Model - attr_accessor :id, :body + attributes :id, :body end class Post < ActiveModelSerializers::Model - attr_accessor :id, :title, :body, :comments + attributes :id, :title, :body, :comments end class PostSerializer < ActiveModel::Serializer attributes :id, :title, :body, :comments diff --git a/test/active_model_serializers/model_test.rb b/test/active_model_serializers/model_test.rb index 7bfb2edf4..abebb1d26 100644 --- a/test/active_model_serializers/model_test.rb +++ b/test/active_model_serializers/model_test.rb @@ -10,7 +10,7 @@ def setup def test_initialization_with_string_keys klass = Class.new(ActiveModelSerializers::Model) do - attr_accessor :key + attributes :key end value = 'value' diff --git a/test/adapter/json_api/relationship_test.rb b/test/adapter/json_api/relationship_test.rb index 45d2ac8e4..cfd5be85e 100644 --- a/test/adapter/json_api/relationship_test.rb +++ b/test/adapter/json_api/relationship_test.rb @@ -384,7 +384,7 @@ def build_serializer_and_serialize_relationship(model, relationship_name, &block def new_model(model_attributes) Class.new(ActiveModelSerializers::Model) do - attr_accessor(*model_attributes.keys) + attributes(*model_attributes.keys) def self.name 'TestModel' diff --git a/test/cache_test.rb b/test/cache_test.rb index c0770cda1..445273786 100644 --- a/test/cache_test.rb +++ b/test/cache_test.rb @@ -244,7 +244,7 @@ def test_uses_adapter_in_cache_key # rubocop:disable Metrics/AbcSize def test_a_serializer_rendered_by_two_adapter_returns_differently_fetch_attributes Object.const_set(:Alert, Class.new(ActiveModelSerializers::Model) do - attr_accessor :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at + attributes :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at end) Object.const_set(:UncachedAlertSerializer, Class.new(ActiveModel::Serializer) do attributes :id, :status, :resource, :started_at, :ended_at, :updated_at, :created_at diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 39d2a9381..b7b82dff9 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -31,7 +31,7 @@ def respond_to_missing?(method_name, _include_private = false) # model.validate! # => ["cannot be nil"] # model.errors.full_messages # => ["name cannot be nil"] class ModelWithErrors < ::ActiveModelSerializers::Model - attr_accessor :name + attributes :name end class Profile < Model diff --git a/test/serializers/read_attribute_for_serialization_test.rb b/test/serializers/read_attribute_for_serialization_test.rb index 21677e657..02911c0e6 100644 --- a/test/serializers/read_attribute_for_serialization_test.rb +++ b/test/serializers/read_attribute_for_serialization_test.rb @@ -5,10 +5,10 @@ class Serializer class ReadAttributeForSerializationTest < ActiveSupport::TestCase # https://github.com/rails-api/active_model_serializers/issues/1653 class Parent < ActiveModelSerializers::Model - attr_accessor :id + attributes :id end class Child < Parent - attr_accessor :name + attributes :name end class ParentSerializer < ActiveModel::Serializer attributes :$id @@ -30,7 +30,7 @@ def test_child_serializer_calls_dynamic_method_in_parent_serializer # https://github.com/rails-api/active_model_serializers/issues/1658 class ErrorResponse < ActiveModelSerializers::Model - attr_accessor :error + attributes :error end class ApplicationSerializer < ActiveModel::Serializer attributes :status diff --git a/test/serializers/serialization_test.rb b/test/serializers/serialization_test.rb index 8ba19f707..3c1884e62 100644 --- a/test/serializers/serialization_test.rb +++ b/test/serializers/serialization_test.rb @@ -2,10 +2,10 @@ module ActiveModel class Serializer class SerializationTest < ActiveSupport::TestCase class Blog < ActiveModelSerializers::Model - attr_accessor :id, :name, :authors + attributes :id, :name, :authors end class Author < ActiveModelSerializers::Model - attr_accessor :id, :name + attributes :id, :name end class BlogSerializer < ActiveModel::Serializer attributes :id From 4f72cc7d8c57aa8939dfb4a9dae0854a497b5a0f Mon Sep 17 00:00:00 2001 From: Melissa Xie Date: Tue, 29 Nov 2016 10:38:12 -0500 Subject: [PATCH 02/43] Fix "result" typo --- docs/howto/add_relationship_links.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/howto/add_relationship_links.md b/docs/howto/add_relationship_links.md index b942acc75..12e98ff78 100644 --- a/docs/howto/add_relationship_links.md +++ b/docs/howto/add_relationship_links.md @@ -37,7 +37,7 @@ class Api::V1::UserSerializer < ActiveModel::Serializer end ``` -This will resilt in (example is in jsonapi adapter): +This will result in (example is in jsonapi adapter): ```json { "data": { @@ -69,7 +69,7 @@ class Api::V1::UserSerializer < ActiveModel::Serializer end ``` -This will resilt in (example is in jsonapi adapter): +This will result in (example is in jsonapi adapter): ```json { "data": { From 3733efaf99333aaeee79b49582f5553324ed906b Mon Sep 17 00:00:00 2001 From: Melissa Xie Date: Tue, 29 Nov 2016 10:41:16 -0500 Subject: [PATCH 03/43] Use consistent capitalization for JSONAPI adapter references --- docs/howto/add_relationship_links.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/howto/add_relationship_links.md b/docs/howto/add_relationship_links.md index 12e98ff78..a27f7e2da 100644 --- a/docs/howto/add_relationship_links.md +++ b/docs/howto/add_relationship_links.md @@ -37,7 +37,7 @@ class Api::V1::UserSerializer < ActiveModel::Serializer end ``` -This will result in (example is in jsonapi adapter): +This will result in (example is in JSONAPI adapter): ```json { "data": { @@ -69,7 +69,7 @@ class Api::V1::UserSerializer < ActiveModel::Serializer end ``` -This will result in (example is in jsonapi adapter): +This will result in (example is in JSONAPI adapter): ```json { "data": { From 095ad9c82c63d3dae206a416ef31cfeae1396b35 Mon Sep 17 00:00:00 2001 From: Ryoji Yoshioka Date: Sun, 4 Dec 2016 00:14:01 +0900 Subject: [PATCH 04/43] Run tests by Ruby 2.2.6 and 2.3.3 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d18c084dd..0cd358e43 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,8 @@ sudo: false rvm: - 2.1 - - 2.2.3 - - 2.3.0 + - 2.2.6 + - 2.3.3 - ruby-head - jruby-9.0.4.0 - jruby-head From 15a8f2c1ebc7b48c22dc48650ba6c2c74677b696 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Sat, 26 Nov 2016 23:24:01 -0600 Subject: [PATCH 05/43] Promote important architecture description that answers a lot of questions we get --- README.md | 136 +++++++++++++++++++++++++++++++++++++- docs/ARCHITECTURE.md | 125 ----------------------------------- docs/README.md | 1 - docs/general/rendering.md | 19 +----- 4 files changed, 137 insertions(+), 144 deletions(-) delete mode 100644 docs/ARCHITECTURE.md diff --git a/README.md b/README.md index 300d2e5a9..0400bb33e 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,141 @@ serializer = SomeSerializer.new(resource, serializer_options) serializer.attributes serializer.associations ``` -See [ARCHITECTURE.md](docs/ARCHITECTURE.md) for more information. + +## Architecture + +This section focuses on architecture the 0.10.x version of ActiveModelSerializers. If you are interested in the architecture of the 0.8 or 0.9 versions, +please refer to the [0.8 README](https://github.com/rails-api/active_model_serializers/blob/0-8-stable/README.md) or +[0.9 README](https://github.com/rails-api/active_model_serializers/blob/0-9-stable/README.md). + +The original design is also available [here](https://github.com/rails-api/active_model_serializers/blob/d72b66d4c5355b0ff0a75a04895fcc4ea5b0c65e/README.textile). + +### ActiveModel::Serializer + +An **`ActiveModel::Serializer`** wraps a [serializable resource](https://github.com/rails/rails/blob/4-2-stable/activemodel/lib/active_model/serialization.rb) +and exposes an `attributes` method, among a few others. +It allows you to specify which attributes and associations should be represented in the serializatation of the resource. +It requires an adapter to transform its attributes into a JSON document; it cannot be serialized itself. +It may be useful to think of it as a +[presenter](http://blog.steveklabnik.com/posts/2011-09-09-better-ruby-presenters). + +#### ActiveModel::CollectionSerializer + +The **`ActiveModel::CollectionSerializer`** represents a collection of resources as serializers +and, if there is no serializer, primitives. + +### ActiveModelSerializers::Adapter::Base + +The **`ActiveModelSerializeres::Adapter::Base`** describes the structure of the JSON document generated from a +serializer. For example, the `Attributes` example represents each serializer as its +unmodified attributes. The `JsonApi` adapter represents the serializer as a [JSON +API](http://jsonapi.org/) document. + +### ActiveModelSerializers::SerializableResource + +The **`ActiveModelSerializers::SerializableResource`** acts to coordinate the serializer(s) and adapter +to an object that responds to `to_json`, and `as_json`. It is used in the controller to +encapsulate the serialization resource when rendered. However, it can also be used on its own +to serialize a resource outside of a controller, as well. + +### Primitive handling + +Definitions: A primitive is usually a String or Array. There is no serializer +defined for them; they will be serialized when the resource is converted to JSON (`as_json` or +`to_json`). (The below also applies for any object with no serializer.) + +- ActiveModelSerializers doesn't handle primitives passed to `render json:` at all. + +Internally, if no serializer can be found in the controller, the resource is not decorated by +ActiveModelSerializers. + +- However, when a primitive value is an attribute or in a collection, it is not modified. + +When serializing a collection and the collection serializer (CollectionSerializer) cannot +identify a serializer for a resource in its collection, it throws [`:no_serializer`](https://github.com/rails-api/active_model_serializers/issues/1191#issuecomment-142327128). +For example, when caught by `Reflection#build_association`, and the association value is set directly: + +```ruby +reflection_options[:virtual_value] = association_value.try(:as_json) || association_value +``` + +(which is called by the adapter as `serializer.associations(*)`.) + +### How options are parsed + +High-level overview: + +- For a **collection** + - `:serializer` specifies the collection serializer and + - `:each_serializer` specifies the serializer for each resource in the collection. +- For a **single resource**, the `:serializer` option is the resource serializer. +- Options are partitioned in serializer options and adapter options. Keys for adapter options are specified by + [`ADAPTER_OPTION_KEYS`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/serializable_resource.rb#L5). + The remaining options are serializer options. + +Details: + +1. **ActionController::Serialization** + 1. `serializable_resource = ActiveModelSerializers::SerializableResource.new(resource, options)` + 1. `options` are partitioned into `adapter_opts` and everything else (`serializer_opts`). + The `adapter_opts` keys are defined in [`ActiveModelSerializers::SerializableResource::ADAPTER_OPTION_KEYS`](lib/active_model_serializers/serializable_resource.rb#L5). +1. **ActiveModelSerializers::SerializableResource** + 1. `if serializable_resource.serializer?` (there is a serializer for the resource, and an adapter is used.) + - Where `serializer?` is `use_adapter? && !!(serializer)` + - Where `use_adapter?`: 'True when no explicit adapter given, or explicit value is truthy (non-nil); + False when explicit adapter is falsy (nil or false)' + - Where `serializer`: + 1. from explicit `:serializer` option, else + 2. implicitly from resource `ActiveModel::Serializer.serializer_for(resource)` + 1. A side-effect of checking `serializer` is: + - The `:serializer` option is removed from the serializer_opts hash + - If the `:each_serializer` option is present, it is removed from the serializer_opts hash and set as the `:serializer` option + 1. The serializer and adapter are created as + 1. `serializer_instance = serializer.new(resource, serializer_opts)` + 2. `adapter_instance = ActiveModel::Serializer::Adapter.create(serializer_instance, adapter_opts)` +1. **ActiveModel::Serializer::CollectionSerializer#new** + 1. If the `serializer_instance` was a `CollectionSerializer` and the `:serializer` serializer_opts + is present, then [that serializer is passed into each resource](https://github.com/rails-api/active_model_serializers/blob/a54d237e2828fe6bab1ea5dfe6360d4ecc8214cd/lib/active_model/serializer/array_serializer.rb#L14-L16). +1. **ActiveModel::Serializer#attributes** is used by the adapter to get the attributes for + resource as defined by the serializer. + +(In Rails, the `options` are also passed to the `as_json(options)` or `to_json(options)` +methods on the resource serialization by the Rails JSON renderer. They are, therefore, important +to know about, but not part of ActiveModelSerializers.) + +### What does a 'serializable resource' look like? + +- An `ActiveRecord::Base` object. +- Any Ruby object that passes the + [Lint](http://www.rubydoc.info/github/rails-api/active_model_serializers/ActiveModel/Serializer/Lint/Tests) + [code](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/lint.rb). + +ActiveModelSerializers provides a +[`ActiveModelSerializers::Model`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/model.rb), +which is a simple serializable PORO (Plain-Old Ruby Object). + +`ActiveModelSerializers::Model` may be used either as a reference implementation, or in production code. + +```ruby +class MyModel < ActiveModelSerializers::Model + attributes :id, :name, :level +end +``` + +The default serializer for `MyModel` would be `MyModelSerializer` whether MyModel is an +ActiveRecord::Base object or not. + +Outside of the controller the rules are **exactly** the same as for records. For example: + +```ruby +render json: MyModel.new(level: 'awesome'), adapter: :json +``` + +would be serialized the same as + +```ruby +ActiveModelSerializers::SerializableResource.new(MyModel.new(level: 'awesome'), adapter: :json).as_json +``` ## Semantic Versioning diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md deleted file mode 100644 index 02f782950..000000000 --- a/docs/ARCHITECTURE.md +++ /dev/null @@ -1,125 +0,0 @@ -[Back to Guides](README.md) - -This document focuses on architecture the 0.10.x version of ActiveModelSerializers. If you are interested in the architecture of the 0.8 or 0.9 versions, -please refer to the [0.8 README](https://github.com/rails-api/active_model_serializers/blob/0-8-stable/README.md) or -[0.9 README](https://github.com/rails-api/active_model_serializers/blob/0-9-stable/README.md). - -The original design is also available [here](https://github.com/rails-api/active_model_serializers/blob/d72b66d4c5355b0ff0a75a04895fcc4ea5b0c65e/README.textile). - -# ARCHITECTURE - -An **`ActiveModel::Serializer`** wraps a [serializable resource](https://github.com/rails/rails/blob/4-2-stable/activemodel/lib/active_model/serialization.rb) -and exposes an `attributes` method, among a few others. -It allows you to specify which attributes and associations should be represented in the serializatation of the resource. -It requires an adapter to transform its attributes into a JSON document; it cannot be serialized itself. -It may be useful to think of it as a -[presenter](http://blog.steveklabnik.com/posts/2011-09-09-better-ruby-presenters). - -The **`ActiveModel::CollectionSerializer`** represents a collection of resources as serializers -and, if there is no serializer, primitives. - -The **`ActiveModel::Adapter`** describes the structure of the JSON document generated from a -serializer. For example, the `Attributes` example represents each serializer as its -unmodified attributes. The `JsonApi` adapter represents the serializer as a [JSON -API](http://jsonapi.org/) document. - -The **`ActiveModelSerializers::SerializableResource`** acts to coordinate the serializer(s) and adapter -to an object that responds to `to_json`, and `as_json`. It is used in the controller to -encapsulate the serialization resource when rendered. However, it can also be used on its own -to serialize a resource outside of a controller, as well. - -## Primitive handling - -Definitions: A primitive is usually a String or Array. There is no serializer -defined for them; they will be serialized when the resource is converted to JSON (`as_json` or -`to_json`). (The below also applies for any object with no serializer.) - -ActiveModelSerializers doesn't handle primitives passed to `render json:` at all. - -However, when a primitive value is an attribute or in a collection, -it is not modified. - -Internally, if no serializer can be found in the controller, the resource is not decorated by -ActiveModelSerializers. - -If the collection serializer (CollectionSerializer) cannot -identify a serializer for a resource in its collection, it throws [`:no_serializer`](https://github.com/rails-api/active_model_serializers/issues/1191#issuecomment-142327128). -For example, when caught by `Reflection#build_association`, the association value is set directly: - -```ruby -reflection_options[:virtual_value] = association_value.try(:as_json) || association_value -``` - -(which is called by the adapter as `serializer.associations(*)`.) - -## How options are parsed - -High-level overview: - -- For a collection - - `:serializer` specifies the collection serializer and - - `:each_serializer` specifies the serializer for each resource in the collection. -- For a single resource, the `:serializer` option is the resource serializer. -- Options are partitioned in serializer options and adapter options. Keys for adapter options are specified by - [`ADAPTER_OPTION_KEYS`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/serializable_resource.rb#L5). - The remaining options are serializer options. - -Details: - -1. **ActionController::Serialization** - 1. `serializable_resource = ActiveModelSerializers::SerializableResource.new(resource, options)` - 1. `options` are partitioned into `adapter_opts` and everything else (`serializer_opts`). - The `adapter_opts` keys are defined in `ActiveModelSerializers::SerializableResource::ADAPTER_OPTION_KEYS`. -1. **ActiveModelSerializers::SerializableResource** - 1. `if serializable_resource.serializer?` (there is a serializer for the resource, and an adapter is used.) - - Where `serializer?` is `use_adapter? && !!(serializer)` - - Where `use_adapter?`: 'True when no explicit adapter given, or explicit value is truthy (non-nil); - False when explicit adapter is falsy (nil or false)' - - Where `serializer`: - 1. from explicit `:serializer` option, else - 2. implicitly from resource `ActiveModel::Serializer.serializer_for(resource)` - 1. A side-effect of checking `serializer` is: - - The `:serializer` option is removed from the serializer_opts hash - - If the `:each_serializer` option is present, it is removed from the serializer_opts hash and set as the `:serializer` option - 1. The serializer and adapter are created as - 1. `serializer_instance = serializer.new(resource, serializer_opts)` - 2. `adapter_instance = ActiveModel::Serializer::Adapter.create(serializer_instance, adapter_opts)` -1. **ActiveModel::Serializer::CollectionSerializer#new** - 1. If the `serializer_instance` was a `CollectionSerializer` and the `:serializer` serializer_opts - is present, then [that serializer is passed into each resource](https://github.com/rails-api/active_model_serializers/blob/a54d237e2828fe6bab1ea5dfe6360d4ecc8214cd/lib/active_model/serializer/array_serializer.rb#L14-L16). -1. **ActiveModel::Serializer#attributes** is used by the adapter to get the attributes for - resource as defined by the serializer. - -## What does a 'serializable resource' look like? - -- An `ActiveRecord::Base` object. -- Any Ruby object that passes the - [Lint](http://www.rubydoc.info/github/rails-api/active_model_serializers/ActiveModel/Serializer/Lint/Tests) - [code](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model/serializer/lint.rb). - -ActiveModelSerializers provides a -[`ActiveModelSerializers::Model`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/model.rb), -which is a simple serializable PORO (Plain-Old Ruby Object). - -ActiveModelSerializers::Model may be used either as a template, or in production code. - -```ruby -class MyModel < ActiveModelSerializers::Model - attributes :id, :name, :level -end -``` - -The default serializer for `MyModel` would be `MyModelSerializer` whether MyModel is an -ActiveRecord::Base object or not. - -Outside of the controller the rules are **exactly** the same as for records. For example: - -```ruby -render json: MyModel.new(level: 'awesome'), adapter: :json -``` - -would be serialized the same as - -```ruby -ActiveModelSerializers::SerializableResource.new(MyModel.new(level: 'awesome'), adapter: :json).as_json -``` diff --git a/docs/README.md b/docs/README.md index b7d8c1523..94460ec12 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,7 +18,6 @@ This is the documentation of ActiveModelSerializers, it's focused on the **0.10. - JSON API - [Schema](jsonapi/schema.md) - [Errors](jsonapi/errors.md) -- [ARCHITECTURE](ARCHITECTURE.md) ## How to diff --git a/docs/general/rendering.md b/docs/general/rendering.md index b75c31938..21120a5a0 100644 --- a/docs/general/rendering.md +++ b/docs/general/rendering.md @@ -48,26 +48,11 @@ render json: @posts, serializer: CollectionSerializer, each_serializer: PostPrev ## Serializing non-ActiveRecord objects -All serializable resources must pass the -[ActiveModel::Serializer::Lint::Tests](../../lib/active_model/serializer/lint.rb#L17). - -See the ActiveModelSerializers::Model for a base class that implements the full -API for a plain-old Ruby object (PORO). +See [README](../../README.md#what-does-a-serializable-resource-look-like) ## SerializableResource options -The `options` hash passed to `render` or `ActiveModelSerializers::SerializableResource.new(resource, options)` -are partitioned into `serializer_opts` and `adapter_opts`. `adapter_opts` are passed to new Adapters; -`serializer_opts` are passed to new Serializers. - -The `adapter_opts` are specified in [ActiveModelSerializers::SerializableResource::ADAPTER_OPTIONS](../../lib/active_model_serializers/serializable_resource.rb#L5). -The `serializer_opts` are the remaining options. - -(In Rails, the `options` are also passed to the `as_json(options)` or `to_json(options)` -methods on the resource serialization by the Rails JSON renderer. They are, therefore, important -to know about, but not part of ActiveModelSerializers.) - -See [ARCHITECTURE](../ARCHITECTURE.md) for more information. +See [README](../../README.md#activemodelserializersserializableresource) ### adapter_opts From 80af763d2eb23fc0544966dca3494ec313cffaec Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Mon, 21 Nov 2016 09:31:11 -0600 Subject: [PATCH 06/43] Make test attributes explicit - Organize test poros with associations and by serializer - Freeze derived attributes/associations against mutation - Cleanup PORO fixtures --- lib/active_model_serializers.rb | 8 + lib/active_model_serializers/model.rb | 54 ++-- .../adapter_selector_test.rb | 4 +- .../action_controller/json_api/fields_test.rb | 21 +- .../json_api/transform_test.rb | 14 +- .../namespace_lookup_test.rb | 18 +- test/action_controller/serialization_test.rb | 2 +- test/adapter/json_api/fields_test.rb | 14 +- .../include_data_if_sideloaded_test.rb | 4 +- test/adapter/json_api/linked_test.rb | 6 +- test/adapter/json_api/links_test.rb | 2 +- test/adapter/json_api/transform_test.rb | 14 +- test/cache_test.rb | 38 ++- test/collection_serializer_test.rb | 33 +- test/fixtures/active_record.rb | 55 +++- test/fixtures/poro.rb | 290 +++++++----------- test/serializers/associations_test.rb | 25 +- test/serializers/attribute_test.rb | 6 +- test/serializers/options_test.rb | 23 +- .../serializer_for_with_namespace_test.rb | 11 +- 20 files changed, 372 insertions(+), 270 deletions(-) diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index b55dae35a..18cdd9f70 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -38,6 +38,14 @@ def self.default_include_directive @default_include_directive ||= JSONAPI::IncludeDirective.new(config.default_includes, allow_wildcard: true) end + def self.silence_warnings + original_verbose = $VERBOSE + $VERBOSE = nil + yield + ensure + $VERBOSE = original_verbose + end + require 'active_model/serializer/version' require 'active_model/serializer' require 'active_model/serializable_resource' diff --git a/lib/active_model_serializers/model.rb b/lib/active_model_serializers/model.rb index 2abe33344..0bdeda21b 100644 --- a/lib/active_model_serializers/model.rb +++ b/lib/active_model_serializers/model.rb @@ -3,42 +3,58 @@ # serializable non-activerecord objects. module ActiveModelSerializers class Model - include ActiveModel::Model include ActiveModel::Serializers::JSON + include ActiveModel::Model + + class_attribute :attribute_names + # Initialize +attribute_names+ for all subclasses. The array is usually + # mutated in the +attributes+ method, but can be set directly, as well. + self.attribute_names = [] def self.attributes(*names) - attr_accessor(*names) + self.attribute_names |= names.map(&:to_sym) + # Silence redefinition of methods warnings + ActiveModelSerializers.silence_warnings do + attr_accessor(*names) + end end - attr_reader :attributes, :errors + attr_reader :errors + # NOTE that +updated_at+ isn't included in +attribute_names+, + # which means it won't show up in +attributes+ unless a subclass has + # either attributes :updated_at which will redefine the methods + # or attribute_names << :updated_at. + attr_writer :updated_at + # NOTE that +id+ will always be in +attributes+. + attributes :id def initialize(attributes = {}) - @attributes = attributes && attributes.symbolize_keys @errors = ActiveModel::Errors.new(self) super end - # Defaults to the downcased model name. - def id - attributes.fetch(:id) { self.class.name.downcase } + # The the fields in +attribute_names+ determines the returned hash. + # +attributes+ are returned frozen to prevent any expectations that mutation affects + # the actual values in the model. + def attributes + attribute_names.each_with_object({}) do |attribute_name, result| + result[attribute_name] = public_send(attribute_name).freeze + end.with_indifferent_access.freeze end - # Defaults to the downcased model name and updated_at + # To customize model behavior, this method must be redefined. However, + # there are other ways of setting the +cache_key+ a serializer uses. def cache_key - attributes.fetch(:cache_key) { "#{self.class.name.downcase}/#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}" } + ActiveSupport::Cache.expand_cache_key([ + self.class.model_name.name.downcase, + "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}" + ].compact) end - # Defaults to the time the serializer file was modified. + # When no set, defaults to the time the file was modified. + # See NOTE by attr_writer :updated_at def updated_at - attributes.fetch(:updated_at) { File.mtime(__FILE__) } - end - - def read_attribute_for_serialization(key) - if key == :id || key == 'id' - attributes.fetch(key) { id } - else - attributes[key] - end + defined?(@updated_at) ? @updated_at : File.mtime(__FILE__) end # The following methods are needed to be minimally implemented for ActiveModel::Errors diff --git a/test/action_controller/adapter_selector_test.rb b/test/action_controller/adapter_selector_test.rb index 2746943f9..6f22aae25 100644 --- a/test/action_controller/adapter_selector_test.rb +++ b/test/action_controller/adapter_selector_test.rb @@ -15,7 +15,7 @@ def render_using_adapter_override end def render_skipping_adapter - @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') + @profile = Profile.new(id: 'render_skipping_adapter_id', name: 'Name 1', description: 'Description 1', comments: 'Comments 1') render json: @profile, adapter: false end end @@ -46,7 +46,7 @@ def test_render_using_adapter_override def test_render_skipping_adapter get :render_skipping_adapter - assert_equal '{"name":"Name 1","description":"Description 1","comments":"Comments 1"}', response.body + assert_equal '{"id":"render_skipping_adapter_id","name":"Name 1","description":"Description 1"}', response.body end end end diff --git a/test/action_controller/json_api/fields_test.rb b/test/action_controller/json_api/fields_test.rb index 4bf08c7e9..af87ad39a 100644 --- a/test/action_controller/json_api/fields_test.rb +++ b/test/action_controller/json_api/fields_test.rb @@ -5,7 +5,16 @@ module Serialization class JsonApi class FieldsTest < ActionController::TestCase class FieldsTestController < ActionController::Base - class PostSerializer < ActiveModel::Serializer + class AuthorWithName < Author + attributes :first_name, :last_name + end + class AuthorWithNameSerializer < AuthorSerializer + type 'authors' + end + class PostWithPublishAt < Post + attributes :publish_at + end + class PostWithPublishAtSerializer < ActiveModel::Serializer type 'posts' attributes :title, :body, :publish_at belongs_to :author @@ -14,19 +23,19 @@ class PostSerializer < ActiveModel::Serializer def setup_post ActionController::Base.cache_store.clear - @author = Author.new(id: 1, first_name: 'Bob', last_name: 'Jones') + @author = AuthorWithName.new(id: 1, first_name: 'Bob', last_name: 'Jones') @comment1 = Comment.new(id: 7, body: 'cool', author: @author) @comment2 = Comment.new(id: 12, body: 'awesome', author: @author) - @post = Post.new(id: 1337, title: 'Title 1', body: 'Body 1', - author: @author, comments: [@comment1, @comment2], - publish_at: '2020-03-16T03:55:25.291Z') + @post = PostWithPublishAt.new(id: 1337, title: 'Title 1', body: 'Body 1', + author: @author, comments: [@comment1, @comment2], + publish_at: '2020-03-16T03:55:25.291Z') @comment1.post = @post @comment2.post = @post end def render_fields_works_on_relationships setup_post - render json: @post, serializer: PostSerializer, adapter: :json_api, fields: { posts: [:author] } + render json: @post, serializer: PostWithPublishAtSerializer, adapter: :json_api, fields: { posts: [:author] } end end diff --git a/test/action_controller/json_api/transform_test.rb b/test/action_controller/json_api/transform_test.rb index 492b606be..69212f324 100644 --- a/test/action_controller/json_api/transform_test.rb +++ b/test/action_controller/json_api/transform_test.rb @@ -5,9 +5,17 @@ module Serialization class JsonApi class KeyTransformTest < ActionController::TestCase class KeyTransformTestController < ActionController::Base - class Post < ::Model; end - class Author < ::Model; end - class TopComment < ::Model; end + class Post < ::Model + attributes :title, :body, :publish_at + associations :author, :top_comments + end + class Author < ::Model + attributes :first_name, :last_name + end + class TopComment < ::Model + attributes :body + associations :author, :post + end class PostSerializer < ActiveModel::Serializer type 'posts' attributes :title, :body, :publish_at diff --git a/test/action_controller/namespace_lookup_test.rb b/test/action_controller/namespace_lookup_test.rb index 3203fd0b1..f40cca11d 100644 --- a/test/action_controller/namespace_lookup_test.rb +++ b/test/action_controller/namespace_lookup_test.rb @@ -3,10 +3,16 @@ module ActionController module Serialization class NamespaceLookupTest < ActionController::TestCase - class Book < ::Model; end - class Page < ::Model; end - class Chapter < ::Model; end - class Writer < ::Model; end + class Book < ::Model + attributes :title, :body + associations :writer, :chapters + end + class Chapter < ::Model + attributes :title + end + class Writer < ::Model + attributes :name + end module Api module V2 @@ -93,7 +99,7 @@ def explicit_namespace_as_symbol end def invalid_namespace - book = Book.new(title: 'New Post', body: 'Body') + book = Book.new(id: 'invalid_namespace_book_id', title: 'New Post', body: 'Body') render json: book, namespace: :api_v2 end @@ -205,7 +211,7 @@ def namespace_set_by_request_headers assert_serializer ActiveModel::Serializer::Null - expected = { 'title' => 'New Post', 'body' => 'Body' } + expected = { 'id' => 'invalid_namespace_book_id', 'title' => 'New Post', 'body' => 'Body' } actual = JSON.parse(@response.body) assert_equal expected, actual diff --git a/test/action_controller/serialization_test.rb b/test/action_controller/serialization_test.rb index b5900e1d2..e650cbfdb 100644 --- a/test/action_controller/serialization_test.rb +++ b/test/action_controller/serialization_test.rb @@ -135,7 +135,7 @@ def render_fragment_changed_object_with_relationship like = Like.new(id: 1, likeable: comment, time: 3.days.ago) generate_cached_serializer(like) - like.likable = comment2 + like.likeable = comment2 like.time = Time.zone.now.to_s render json: like diff --git a/test/adapter/json_api/fields_test.rb b/test/adapter/json_api/fields_test.rb index 8aea4a1de..852283187 100644 --- a/test/adapter/json_api/fields_test.rb +++ b/test/adapter/json_api/fields_test.rb @@ -4,9 +4,17 @@ module ActiveModelSerializers module Adapter class JsonApi class FieldsTest < ActiveSupport::TestCase - class Post < ::Model; end - class Author < ::Model; end - class Comment < ::Model; end + class Post < ::Model + attributes :title, :body + associations :author, :comments + end + class Author < ::Model + attributes :name, :birthday + end + class Comment < ::Model + attributes :body + associations :author, :post + end class PostSerializer < ActiveModel::Serializer type 'posts' diff --git a/test/adapter/json_api/include_data_if_sideloaded_test.rb b/test/adapter/json_api/include_data_if_sideloaded_test.rb index 1c97191d3..728eae13e 100644 --- a/test/adapter/json_api/include_data_if_sideloaded_test.rb +++ b/test/adapter/json_api/include_data_if_sideloaded_test.rb @@ -5,7 +5,9 @@ class Serializer module Adapter class JsonApi class IncludeParamTest < ActiveSupport::TestCase - IncludeParamAuthor = Class.new(::Model) + IncludeParamAuthor = Class.new(::Model) do + associations :tags, :posts + end class CustomCommentLoader def all diff --git a/test/adapter/json_api/linked_test.rb b/test/adapter/json_api/linked_test.rb index 949bcf60a..0d9c69b6b 100644 --- a/test/adapter/json_api/linked_test.rb +++ b/test/adapter/json_api/linked_test.rb @@ -1,6 +1,6 @@ require 'test_helper' -class NestedPost < ::Model; end +class NestedPost < ::Model; associations :nested_posts end class NestedPostSerializer < ActiveModel::Serializer has_many :nested_posts end @@ -301,8 +301,8 @@ def test_nil_link_with_specified_serializer end class NoDuplicatesTest < ActiveSupport::TestCase - class Post < ::Model; end - class Author < ::Model; end + class Post < ::Model; associations :author end + class Author < ::Model; associations :posts, :roles, :bio end class PostSerializer < ActiveModel::Serializer type 'posts' diff --git a/test/adapter/json_api/links_test.rb b/test/adapter/json_api/links_test.rb index 3534b03c5..ffbfa303e 100644 --- a/test/adapter/json_api/links_test.rb +++ b/test/adapter/json_api/links_test.rb @@ -4,7 +4,7 @@ module ActiveModelSerializers module Adapter class JsonApi class LinksTest < ActiveSupport::TestCase - class LinkAuthor < ::Model; end + class LinkAuthor < ::Model; associations :posts end class LinkAuthorSerializer < ActiveModel::Serializer link :self do href "http://example.com/link_author/#{object.id}" diff --git a/test/adapter/json_api/transform_test.rb b/test/adapter/json_api/transform_test.rb index 47488d295..887ec835f 100644 --- a/test/adapter/json_api/transform_test.rb +++ b/test/adapter/json_api/transform_test.rb @@ -4,9 +4,17 @@ module ActiveModelSerializers module Adapter class JsonApi class KeyCaseTest < ActiveSupport::TestCase - class Post < ::Model; end - class Author < ::Model; end - class Comment < ::Model; end + class Post < ::Model + attributes :title, :body, :publish_at + associations :author, :comments + end + class Author < ::Model + attributes :first_name, :last_name + end + class Comment < ::Model + attributes :body + associations :author, :post + end class PostSerializer < ActiveModel::Serializer type 'posts' diff --git a/test/cache_test.rb b/test/cache_test.rb index 445273786..b2cb27eb4 100644 --- a/test/cache_test.rb +++ b/test/cache_test.rb @@ -34,6 +34,7 @@ class UncachedAuthor < Author end class Article < ::Model + attributes :title # To confirm error is raised when cache_key is not set and cache_key option not passed to cache undef_method :cache_key end @@ -48,6 +49,16 @@ class InheritedRoleSerializer < RoleSerializer attribute :special_attribute end + class Comment < ::Model + attributes :body + associations :post, :author + + # Uses a custom non-time-based cache key + def cache_key + "comment/#{id}" + end + end + setup do cache_store.clear @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @@ -271,7 +282,7 @@ def test_a_serializer_rendered_by_two_adapter_returns_differently_fetch_attribut ended_at: nil, updated_at: alert.updated_at, created_at: alert.created_at - } + }.with_indifferent_access expected_cached_jsonapi_attributes = { id: '1', type: 'alerts', @@ -283,15 +294,15 @@ def test_a_serializer_rendered_by_two_adapter_returns_differently_fetch_attribut updated_at: alert.updated_at, created_at: alert.created_at } - } + }.with_indifferent_access # Assert attributes are serialized correctly serializable_alert = serializable(alert, serializer: AlertSerializer, adapter: :attributes) - attributes_serialization = serializable_alert.as_json + attributes_serialization = serializable_alert.as_json.with_indifferent_access assert_equal expected_fetch_attributes, alert.attributes assert_equal alert.attributes, attributes_serialization attributes_cache_key = serializable_alert.adapter.serializer.cache_key(serializable_alert.adapter) - assert_equal attributes_serialization, cache_store.fetch(attributes_cache_key) + assert_equal attributes_serialization, cache_store.fetch(attributes_cache_key).with_indifferent_access serializable_alert = serializable(alert, serializer: AlertSerializer, adapter: :json_api) jsonapi_cache_key = serializable_alert.adapter.serializer.cache_key(serializable_alert.adapter) @@ -303,7 +314,7 @@ def test_a_serializer_rendered_by_two_adapter_returns_differently_fetch_attribut serializable_alert = serializable(alert, serializer: UncachedAlertSerializer, adapter: :json_api) assert_equal serializable_alert.as_json, jsonapi_serialization - cached_serialization = cache_store.fetch(jsonapi_cache_key) + cached_serialization = cache_store.fetch(jsonapi_cache_key).with_indifferent_access assert_equal expected_cached_jsonapi_attributes, cached_serialization ensure Object.send(:remove_const, :Alert) @@ -329,11 +340,15 @@ def test_object_cache_keys actual = ActiveModel::Serializer.object_cache_keys(serializable.adapter.serializer, serializable.adapter, include_directive) assert_equal 3, actual.size - assert actual.any? { |key| key == "comment/1/#{serializable.adapter.cache_key}" } - assert actual.any? { |key| key =~ %r{post/post-\d+} } - assert actual.any? { |key| key =~ %r{author/author-\d+} } + expected_key = "comment/1/#{serializable.adapter.cache_key}" + assert actual.any? { |key| key == expected_key }, "actual '#{actual}' should include #{expected_key}" + expected_key = %r{post/post-\d+} + assert actual.any? { |key| key =~ expected_key }, "actual '#{actual}' should match '#{expected_key}'" + expected_key = %r{author/author-\d+} + assert actual.any? { |key| key =~ expected_key }, "actual '#{actual}' should match '#{expected_key}'" end + # rubocop:disable Metrics/AbcSize def test_fetch_attributes_from_cache serializers = ActiveModel::Serializer::CollectionSerializer.new([@comment, @comment]) @@ -344,10 +359,10 @@ def test_fetch_attributes_from_cache adapter_options = {} adapter_instance = ActiveModelSerializers::Adapter::Attributes.new(serializers, adapter_options) serializers.serializable_hash(adapter_options, options, adapter_instance) - cached_attributes = adapter_options.fetch(:cached_attributes) + cached_attributes = adapter_options.fetch(:cached_attributes).with_indifferent_access include_directive = ActiveModelSerializers.default_include_directive - manual_cached_attributes = ActiveModel::Serializer.cache_read_multi(serializers, adapter_instance, include_directive) + manual_cached_attributes = ActiveModel::Serializer.cache_read_multi(serializers, adapter_instance, include_directive).with_indifferent_access assert_equal manual_cached_attributes, cached_attributes assert_equal cached_attributes["#{@comment.cache_key}/#{adapter_instance.cache_key}"], Comment.new(id: 1, body: 'ZOMG A COMMENT').attributes @@ -358,6 +373,7 @@ def test_fetch_attributes_from_cache assert_equal cached_attributes["#{writer_cache_key}/#{adapter_instance.cache_key}"], Author.new(id: 'author', name: 'Joao M. D. Moura').attributes end end + # rubocop:enable Metrics/AbcSize def test_cache_read_multi_with_fragment_cache_enabled post_serializer = Class.new(ActiveModel::Serializer) do @@ -516,7 +532,7 @@ def test_fragment_fetch_with_virtual_attributes role_hash = role_serializer.fetch_attributes_fragment(adapter_instance) assert_equal(role_hash, expected_result) - role.attributes[:id] = 'this has been updated' + role.id = 'this has been updated' role.name = 'this was cached' role_hash = role_serializer.fetch_attributes_fragment(adapter_instance) diff --git a/test/collection_serializer_test.rb b/test/collection_serializer_test.rb index 7b2a33d22..cdbebb158 100644 --- a/test/collection_serializer_test.rb +++ b/test/collection_serializer_test.rb @@ -3,14 +3,27 @@ module ActiveModel class Serializer class CollectionSerializerTest < ActiveSupport::TestCase + class SingularModel < ::Model; end + class SingularModelSerializer < ActiveModel::Serializer + end + class HasManyModel < ::Model + associations :singular_models + end + class HasManyModelSerializer < ActiveModel::Serializer + has_many :singular_models + + def custom_options + instance_options + end + end class MessagesSerializer < ActiveModel::Serializer type 'messages' end def setup - @comment = Comment.new - @post = Post.new - @resource = build_named_collection @comment, @post + @singular_model = SingularModel.new + @has_many_model = HasManyModel.new + @resource = build_named_collection @singular_model, @has_many_model @serializer = collection_serializer.new(@resource, some: :options) end @@ -34,29 +47,29 @@ def test_respond_to_each def test_each_object_should_be_serialized_with_appropriate_serializer serializers = @serializer.to_a - assert_kind_of CommentSerializer, serializers.first - assert_kind_of Comment, serializers.first.object + assert_kind_of SingularModelSerializer, serializers.first + assert_kind_of SingularModel, serializers.first.object - assert_kind_of PostSerializer, serializers.last - assert_kind_of Post, serializers.last.object + assert_kind_of HasManyModelSerializer, serializers.last + assert_kind_of HasManyModel, serializers.last.object assert_equal :options, serializers.last.custom_options[:some] end def test_serializer_option_not_passed_to_each_serializer - serializers = collection_serializer.new([@post], serializer: PostSerializer).to_a + serializers = collection_serializer.new([@has_many_model], serializer: HasManyModelSerializer).to_a refute serializers.first.custom_options.key?(:serializer) end def test_root_default - @serializer = collection_serializer.new([@comment, @post]) + @serializer = collection_serializer.new([@singular_model, @has_many_model]) assert_nil @serializer.root end def test_root expected = 'custom_root' - @serializer = collection_serializer.new([@comment, @post], root: expected) + @serializer = collection_serializer.new([@singular_model, @has_many_model], root: expected) assert_equal expected, @serializer.root end diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb index 77ac030d6..9dc3830da 100644 --- a/test/fixtures/active_record.rb +++ b/test/fixtures/active_record.rb @@ -47,16 +47,6 @@ class Post < ActiveRecord::Base has_many :comments belongs_to :author end - - class Comment < ActiveRecord::Base - belongs_to :post - belongs_to :author - end - - class Author < ActiveRecord::Base - has_many :posts - end - class PostSerializer < ActiveModel::Serializer attributes :id, :title, :body @@ -64,15 +54,60 @@ class PostSerializer < ActiveModel::Serializer belongs_to :author end + class Comment < ActiveRecord::Base + belongs_to :post + belongs_to :author + end class CommentSerializer < ActiveModel::Serializer attributes :id, :contents belongs_to :author end + class Author < ActiveRecord::Base + has_many :posts + end class AuthorSerializer < ActiveModel::Serializer attributes :id, :name has_many :posts end end + +class Employee < ActiveRecord::Base + has_many :pictures, as: :imageable + has_many :object_tags, as: :taggable +end + +class PolymorphicSimpleSerializer < ActiveModel::Serializer + attributes :id +end + +class ObjectTag < ActiveRecord::Base + belongs_to :poly_tag + belongs_to :taggable, polymorphic: true +end +class PolymorphicObjectTagSerializer < ActiveModel::Serializer + attributes :id + has_many :taggable, serializer: PolymorphicSimpleSerializer, polymorphic: true +end + +class PolyTag < ActiveRecord::Base + has_many :object_tags +end +class PolymorphicTagSerializer < ActiveModel::Serializer + attributes :id, :phrase + has_many :object_tags, serializer: PolymorphicObjectTagSerializer +end + +class Picture < ActiveRecord::Base + belongs_to :imageable, polymorphic: true + has_many :object_tags, as: :taggable +end +class PolymorphicHasManySerializer < ActiveModel::Serializer + attributes :id, :name +end +class PolymorphicBelongsToSerializer < ActiveModel::Serializer + attributes :id, :title + has_one :imageable, serializer: PolymorphicHasManySerializer, polymorphic: true +end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index b7b82dff9..3c804ccca 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -1,25 +1,27 @@ -verbose = $VERBOSE -$VERBOSE = nil class Model < ActiveModelSerializers::Model FILE_DIGEST = Digest::MD5.hexdigest(File.open(__FILE__).read) - ### Helper methods, not required to be serializable + # Defaults to the downcased model name. + def id + @id ||= self.class.model_name.name.downcase + end + + # At this time, just for organization of intent + class_attribute :association_names + self.association_names = [] - # Convenience when not adding @attributes readers and writers - def method_missing(meth, *args) - if meth.to_s =~ /^(.*)=$/ - attributes[Regexp.last_match(1).to_sym] = args[0] - elsif attributes.key?(meth) - attributes[meth] - else - super + def self.associations(*names) + self.association_names |= names.map(&:to_sym) + # Silence redefinition of methods warnings + ActiveModelSerializers.silence_warnings do + attr_accessor(*names) end end - # required for ActiveModel::AttributeAssignment#_assign_attribute - # in Rails 5 - def respond_to_missing?(method_name, _include_private = false) - attributes.key?(method_name.to_s.tr('=', '').to_sym) || super + def associations + association_names.each_with_object({}) do |association_name, result| + result[association_name] = public_send(association_name).freeze + end.with_indifferent_access.freeze end end @@ -30,67 +32,59 @@ def respond_to_missing?(method_name, _include_private = false) # model = ModelWithErrors.new # model.validate! # => ["cannot be nil"] # model.errors.full_messages # => ["name cannot be nil"] -class ModelWithErrors < ::ActiveModelSerializers::Model +class ModelWithErrors < Model attributes :name end class Profile < Model + attributes :name, :description + associations :comments end - class ProfileSerializer < ActiveModel::Serializer attributes :name, :description - - # TODO: is this used anywhere? - def arguments_passed_in? - instance_options[:my_options] == :accessible - end end - class ProfilePreviewSerializer < ActiveModel::Serializer attributes :name end -class Post < Model; end -class Like < Model; end -class Author < Model; end -class Bio < Model; end -class Blog < Model; end -class Role < Model; end -class User < Model; end -class Location < Model; end -class Place < Model; end -class Tag < Model; end -class VirtualValue < Model; end -class Comment < Model - # Uses a custom non-time-based cache key - def cache_key - "#{self.class.name.downcase}/#{id}" - end +class Author < Model + attributes :name + associations :posts, :bio, :roles, :comments end +class AuthorSerializer < ActiveModel::Serializer + cache key: 'writer', skip_digest: true + attribute :id + attribute :name -class Employee < ActiveRecord::Base - has_many :pictures, as: :imageable - has_many :object_tags, as: :taggable + has_many :posts + has_many :roles + has_one :bio end - -class ObjectTag < ActiveRecord::Base - belongs_to :poly_tag - belongs_to :taggable, polymorphic: true +class AuthorPreviewSerializer < ActiveModel::Serializer + attributes :id + has_many :posts end -class Picture < ActiveRecord::Base - belongs_to :imageable, polymorphic: true - has_many :object_tags, as: :taggable +class Comment < Model + attributes :body, :date + associations :post, :author, :likes end - -class PolyTag < ActiveRecord::Base - has_many :object_tags +class CommentSerializer < ActiveModel::Serializer + cache expires_in: 1.day, skip_digest: true + attributes :id, :body + belongs_to :post + belongs_to :author end +class CommentPreviewSerializer < ActiveModel::Serializer + attributes :id -module Spam - class UnrelatedLink < Model; end + belongs_to :post end +class Post < Model + attributes :title, :body + associations :author, :comments, :blog, :tags, :related +end class PostSerializer < ActiveModel::Serializer cache key: 'post', expires_in: 0.1, skip_digest: true attributes :id, :title, :body @@ -102,75 +96,32 @@ class PostSerializer < ActiveModel::Serializer def blog Blog.new(id: 999, name: 'Custom blog') end - - # TODO: is this used anywhere? - def custom_options - instance_options - end end - class SpammyPostSerializer < ActiveModel::Serializer attributes :id has_many :related end +class PostPreviewSerializer < ActiveModel::Serializer + attributes :title, :body, :id -class CommentSerializer < ActiveModel::Serializer - cache expires_in: 1.day, skip_digest: true - attributes :id, :body - - belongs_to :post - belongs_to :author - - def custom_options - instance_options - end -end - -class AuthorSerializer < ActiveModel::Serializer - cache key: 'writer', skip_digest: true - attribute :id - attribute :name - - has_many :posts - has_many :roles - has_one :bio + has_many :comments, serializer: ::CommentPreviewSerializer + belongs_to :author, serializer: ::AuthorPreviewSerializer end - -class RoleSerializer < ActiveModel::Serializer - cache only: [:name, :slug], skip_digest: true - attributes :id, :name, :description - attribute :friendly_id, key: :slug - - def friendly_id - "#{object.name}-#{object.id}" - end - - belongs_to :author +class PostWithTagsSerializer < ActiveModel::Serializer + attributes :id + has_many :tags end - -class LikeSerializer < ActiveModel::Serializer - attributes :id, :time - - belongs_to :likeable +class PostWithCustomKeysSerializer < ActiveModel::Serializer + attributes :id + has_many :comments, key: :reviews + belongs_to :author, key: :writer + has_one :blog, key: :site end -class LocationSerializer < ActiveModel::Serializer - cache only: [:address], skip_digest: true - attributes :id, :lat, :lng - - belongs_to :place, key: :address - - def place - 'Nowhere' - end +class Bio < Model + attributes :content, :rating + associations :author end - -class PlaceSerializer < ActiveModel::Serializer - attributes :id, :name - - has_many :locations -end - class BioSerializer < ActiveModel::Serializer cache except: [:content], skip_digest: true attributes :id, :content, :rating @@ -178,6 +129,10 @@ class BioSerializer < ActiveModel::Serializer belongs_to :author end +class Blog < Model + attributes :name, :type, :special_attribute + associations :writer, :articles +end class BlogSerializer < ActiveModel::Serializer cache key: 'blog' attributes :id, :name @@ -185,61 +140,80 @@ class BlogSerializer < ActiveModel::Serializer belongs_to :writer has_many :articles end - -class PaginatedSerializer < ActiveModel::Serializer::CollectionSerializer - def json_key - 'paginated' - end -end - class AlternateBlogSerializer < ActiveModel::Serializer attribute :id attribute :name, key: :title end - class CustomBlogSerializer < ActiveModel::Serializer attribute :id attribute :special_attribute - has_many :articles end -class CommentPreviewSerializer < ActiveModel::Serializer - attributes :id - - belongs_to :post +class Role < Model + attributes :name, :description, :special_attribute + associations :author end +class RoleSerializer < ActiveModel::Serializer + cache only: [:name, :slug], skip_digest: true + attributes :id, :name, :description + attribute :friendly_id, key: :slug + belongs_to :author -class AuthorPreviewSerializer < ActiveModel::Serializer - attributes :id + def friendly_id + "#{object.name}-#{object.id}" + end +end - has_many :posts +class Location < Model + attributes :lat, :lng + associations :place end +class LocationSerializer < ActiveModel::Serializer + cache only: [:address], skip_digest: true + attributes :id, :lat, :lng -class PostPreviewSerializer < ActiveModel::Serializer - attributes :title, :body, :id + belongs_to :place, key: :address - has_many :comments, serializer: CommentPreviewSerializer - belongs_to :author, serializer: AuthorPreviewSerializer + def place + 'Nowhere' + end end -class PostWithTagsSerializer < ActiveModel::Serializer - attributes :id +class Place < Model + attributes :name + associations :locations +end +class PlaceSerializer < ActiveModel::Serializer + attributes :id, :name + has_many :locations +end - has_many :tags +class Like < Model + attributes :time + associations :likeable +end +class LikeSerializer < ActiveModel::Serializer + attributes :id, :time + belongs_to :likeable end -class PostWithCustomKeysSerializer < ActiveModel::Serializer - attributes :id +module Spam + class UnrelatedLink < Model + end + class UnrelatedLinkSerializer < ActiveModel::Serializer + cache only: [:id] + attributes :id + end +end - has_many :comments, key: :reviews - belongs_to :author, key: :writer - has_one :blog, key: :site +class Tag < Model + attributes :name end +class VirtualValue < Model; end class VirtualValueSerializer < ActiveModel::Serializer attributes :id - has_many :reviews, virtual_value: [{ type: 'reviews', id: '1' }, { type: 'reviews', id: '2' }] has_one :maker, virtual_value: { type: 'makers', id: '1' } @@ -251,36 +225,8 @@ def maker end end -class PolymorphicHasManySerializer < ActiveModel::Serializer - attributes :id, :name -end - -class PolymorphicBelongsToSerializer < ActiveModel::Serializer - attributes :id, :title - - has_one :imageable, serializer: PolymorphicHasManySerializer, polymorphic: true -end - -class PolymorphicSimpleSerializer < ActiveModel::Serializer - attributes :id -end - -class PolymorphicObjectTagSerializer < ActiveModel::Serializer - attributes :id - - has_many :taggable, serializer: PolymorphicSimpleSerializer, polymorphic: true -end - -class PolymorphicTagSerializer < ActiveModel::Serializer - attributes :id, :phrase - - has_many :object_tags, serializer: PolymorphicObjectTagSerializer -end - -module Spam - class UnrelatedLinkSerializer < ActiveModel::Serializer - cache only: [:id] - attributes :id +class PaginatedSerializer < ActiveModel::Serializer::CollectionSerializer + def json_key + 'paginated' end end -$VERBOSE = verbose diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb index ee4703858..6d6447c35 100644 --- a/test/serializers/associations_test.rb +++ b/test/serializers/associations_test.rb @@ -8,7 +8,7 @@ def setup @author.roles = [] @blog = Blog.new(name: 'AMS Blog') @post = Post.new(title: 'New Post', body: 'Body') - @tag = Tag.new(name: '#hashtagged') + @tag = Tag.new(id: 'tagid', name: '#hashtagged') @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @post.comments = [@comment] @post.tags = [@tag] @@ -53,7 +53,7 @@ def test_has_many_with_no_serializer assert_equal :tags, key assert_nil serializer - assert_equal [{ name: '#hashtagged' }].to_json, options[:virtual_value].to_json + assert_equal [{ id: 'tagid', name: '#hashtagged' }].to_json, options[:virtual_value].to_json end end @@ -62,7 +62,13 @@ def test_serializer_options_are_passed_into_associations_serializers .associations .detect { |assoc| assoc.key == :comments } - assert association.serializer.first.custom_options[:custom_options] + comment_serializer = association.serializer.first + class << comment_serializer + def custom_options + instance_options + end + end + assert comment_serializer.custom_options.fetch(:custom_options) end def test_belongs_to @@ -159,7 +165,9 @@ def test_virtual_attribute_block class NamespacedResourcesTest < ActiveSupport::TestCase class ResourceNamespace - class Post < ::Model; end + class Post < ::Model + associations :comments, :author, :description + end class Comment < ::Model; end class Author < ::Model; end class Description < ::Model; end @@ -200,7 +208,9 @@ def test_associations_namespaced_resources end class NestedSerializersTest < ActiveSupport::TestCase - class Post < ::Model; end + class Post < ::Model + associations :comments, :author, :description + end class Comment < ::Model; end class Author < ::Model; end class Description < ::Model; end @@ -240,7 +250,10 @@ def test_associations_namespaced_resources # rubocop:disable Metrics/AbcSize def test_conditional_associations - model = ::Model.new(true: true, false: false) + model = Class.new(::Model) do + attributes :true, :false + associations :association + end.new(true: true, false: false) scenarios = [ { options: { if: :true }, included: true }, diff --git a/test/serializers/attribute_test.rb b/test/serializers/attribute_test.rb index c359d2f93..608898c3e 100644 --- a/test/serializers/attribute_test.rb +++ b/test/serializers/attribute_test.rb @@ -81,7 +81,7 @@ def id assert_equal('custom', hash[:blog][:id]) end - class PostWithVirtualAttribute < ::Model; end + class PostWithVirtualAttribute < ::Model; attributes :first_name, :last_name end class PostWithVirtualAttributeSerializer < ActiveModel::Serializer attribute :name do "#{object.first_name} #{object.last_name}" @@ -98,7 +98,9 @@ def test_virtual_attribute_block # rubocop:disable Metrics/AbcSize def test_conditional_associations - model = ::Model.new(true: true, false: false) + model = Class.new(::Model) do + attributes :true, :false, :attribute + end.new(true: true, false: false) scenarios = [ { options: { if: :true }, included: true }, diff --git a/test/serializers/options_test.rb b/test/serializers/options_test.rb index 092714ab6..009388e35 100644 --- a/test/serializers/options_test.rb +++ b/test/serializers/options_test.rb @@ -3,18 +3,29 @@ module ActiveModel class Serializer class OptionsTest < ActiveSupport::TestCase - def setup - @profile = Profile.new(name: 'Name 1', description: 'Description 1') + class ModelWithOptions < ActiveModelSerializers::Model + attributes :name, :description + end + class ModelWithOptionsSerializer < ActiveModel::Serializer + attributes :name, :description + + def arguments_passed_in? + instance_options[:my_options] == :accessible + end + end + + setup do + @model_with_options = ModelWithOptions.new(name: 'Name 1', description: 'Description 1') end def test_options_are_accessible - @profile_serializer = ProfileSerializer.new(@profile, my_options: :accessible) - assert @profile_serializer.arguments_passed_in? + model_with_options_serializer = ModelWithOptionsSerializer.new(@model_with_options, my_options: :accessible) + assert model_with_options_serializer.arguments_passed_in? end def test_no_option_is_passed_in - @profile_serializer = ProfileSerializer.new(@profile) - refute @profile_serializer.arguments_passed_in? + model_with_options_serializer = ModelWithOptionsSerializer.new(@model_with_options) + refute model_with_options_serializer.arguments_passed_in? end end end diff --git a/test/serializers/serializer_for_with_namespace_test.rb b/test/serializers/serializer_for_with_namespace_test.rb index 5a8a9ed54..5c6e3e5e9 100644 --- a/test/serializers/serializer_for_with_namespace_test.rb +++ b/test/serializers/serializer_for_with_namespace_test.rb @@ -3,9 +3,12 @@ module ActiveModel class Serializer class SerializerForWithNamespaceTest < ActiveSupport::TestCase - class Book < ::Model; end - class Page < ::Model; end - class Publisher < ::Model; end + class Book < ::Model + attributes :title, :author_name + associations :publisher, :pages + end + class Page < ::Model; attributes :number, :text end + class Publisher < ::Model; attributes :name end module Api module V3 @@ -18,8 +21,6 @@ class BookSerializer < ActiveModel::Serializer class PageSerializer < ActiveModel::Serializer attributes :number, :text - - belongs_to :book end class PublisherSerializer < ActiveModel::Serializer From f8ca912de8530f3f88876e7302dd87b79d2fabbd Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Wed, 31 Aug 2016 21:33:06 -0500 Subject: [PATCH 07/43] Add failing test for AMS::Model accessor vs. attributes mutation --- test/active_model_serializers/model_test.rb | 49 ++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/test/active_model_serializers/model_test.rb b/test/active_model_serializers/model_test.rb index abebb1d26..c2f316479 100644 --- a/test/active_model_serializers/model_test.rb +++ b/test/active_model_serializers/model_test.rb @@ -4,7 +4,7 @@ module ActiveModelSerializers class ModelTest < ActiveSupport::TestCase include ActiveModel::Serializer::Lint::Tests - def setup + setup do @resource = ActiveModelSerializers::Model.new end @@ -18,5 +18,52 @@ def test_initialization_with_string_keys assert_equal model_instance.read_attribute_for_serialization(:key), value end + + def test_attributes_can_be_read_for_serialization + klass = Class.new(ActiveModelSerializers::Model) do + attributes :one, :two, :three + end + original_attributes = { one: 1, two: 2, three: 3 } + instance = klass.new(original_attributes) + + # Initial value + expected_attributes = { id: nil, one: 1, two: 2, three: 3 }.with_indifferent_access + assert_equal expected_attributes, instance.attributes + assert_equal 1, instance.one + assert_equal 1, instance.read_attribute_for_serialization(:one) + + # Change via accessor + instance.one = :not_one + + expected_attributes = { id: nil, one: :not_one, two: 2, three: 3 }.with_indifferent_access + assert_equal expected_attributes, instance.attributes + assert_equal :not_one, instance.one + assert_equal :not_one, instance.read_attribute_for_serialization(:one) + end + + def test_id_attribute_can_be_read_for_serialization + klass = Class.new(ActiveModelSerializers::Model) do + attributes :id, :one, :two, :three + end + self.class.const_set(:SomeTestModel, klass) + original_attributes = { id: :ego, one: 1, two: 2, three: 3 } + instance = klass.new(original_attributes) + + # Initial value + expected_attributes = { id: :ego, one: 1, two: 2, three: 3 }.with_indifferent_access + assert_equal expected_attributes, instance.attributes + assert_equal 1, instance.one + assert_equal 1, instance.read_attribute_for_serialization(:one) + + # Change via accessor + instance.id = :superego + + expected_attributes = { id: :superego, one: 1, two: 2, three: 3 }.with_indifferent_access + assert_equal expected_attributes, instance.attributes + assert_equal :superego, instance.id + assert_equal :superego, instance.read_attribute_for_serialization(:id) + ensure + self.class.send(:remove_const, :SomeTestModel) + end end end From 3b848452414bbf4815f3e255622e1ef75c1db6e8 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Sun, 4 Dec 2016 15:57:24 -0600 Subject: [PATCH 08/43] Add CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66289ace9..f8574b9a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,12 @@ Features: Fixes: +- [#1984](https://github.com/rails-api/active_model_serializers/pull/1984) Mutation of ActiveModelSerializers::Model now changes the attributes. (@bf4) + Misc: +- [#1984](https://github.com/rails-api/active_model_serializers/pull/1984) Make test attributes explicit. Test models have 'associations' support. (@bf4) + ### [v0.10.3 (2016-11-21)](https://github.com/rails-api/active_model_serializers/compare/v0.10.2...v0.10.3) Fixes: From 0422a1e7724023aa4c54b7fee683660641d7d523 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Wed, 7 Dec 2016 10:49:38 -0500 Subject: [PATCH 09/43] Swap out KeyTransform for CaseTransform (#1993) * delete KeyTransform, use CaseTransform * added changelog --- CHANGELOG.md | 1 + active_model_serializers.gemspec | 1 + lib/active_model_serializers/adapter/base.rb | 4 +- .../adapter/json_api/deserialization.rb | 2 +- lib/active_model_serializers/key_transform.rb | 74 ----- .../key_transform_test.rb | 297 ------------------ test/benchmark/bm_transform.rb | 10 +- 7 files changed, 10 insertions(+), 379 deletions(-) delete mode 100644 lib/active_model_serializers/key_transform.rb delete mode 100644 test/active_model_serializers/key_transform_test.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index f8574b9a3..90f75ac1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Fixes: Misc: - [#1984](https://github.com/rails-api/active_model_serializers/pull/1984) Make test attributes explicit. Test models have 'associations' support. (@bf4) +- [#1993](https://github.com/rails-api/active_model_serializers/pull/1993) Swap out KeyTransform for CaseTransform gem for the possibility of native extension use (@NullVoxPopuli) ### [v0.10.3 (2016-11-21)](https://github.com/rails-api/active_model_serializers/compare/v0.10.2...v0.10.3) diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index 89327bc63..3581ce408 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -43,6 +43,7 @@ Gem::Specification.new do |spec| # 'thread_safe' spec.add_runtime_dependency 'jsonapi', '0.1.1.beta2' + spec.add_runtime_dependency 'case_transform', '>= 0.2' spec.add_development_dependency 'activerecord', rails_versions # arel diff --git a/lib/active_model_serializers/adapter/base.rb b/lib/active_model_serializers/adapter/base.rb index 7b60db70b..851583285 100644 --- a/lib/active_model_serializers/adapter/base.rb +++ b/lib/active_model_serializers/adapter/base.rb @@ -1,4 +1,4 @@ -require 'active_model_serializers/key_transform' +require 'case_transform' module ActiveModelSerializers module Adapter @@ -31,7 +31,7 @@ def self.transform(options) # @param options [Object] serializable resource options # @return [Symbol] the default transform for the adapter def self.transform_key_casing!(value, options) - KeyTransform.send(transform(options), value) + CaseTransform.send(transform(options), value) end def self.cache_key diff --git a/lib/active_model_serializers/adapter/json_api/deserialization.rb b/lib/active_model_serializers/adapter/json_api/deserialization.rb index 2e0e531dd..b79125ac4 100644 --- a/lib/active_model_serializers/adapter/json_api/deserialization.rb +++ b/lib/active_model_serializers/adapter/json_api/deserialization.rb @@ -205,7 +205,7 @@ def parse_relationships(relationships, options) # @api private def transform_keys(hash, options) transform = options[:key_transform] || :underscore - KeyTransform.send(transform, hash) + CaseTransform.send(transform, hash) end end end diff --git a/lib/active_model_serializers/key_transform.rb b/lib/active_model_serializers/key_transform.rb deleted file mode 100644 index d0e648e59..000000000 --- a/lib/active_model_serializers/key_transform.rb +++ /dev/null @@ -1,74 +0,0 @@ -require 'active_support/core_ext/hash/keys' - -module ActiveModelSerializers - module KeyTransform - module_function - - # Transforms values to UpperCamelCase or PascalCase. - # - # @example: - # "some_key" => "SomeKey", - # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize} - def camel(value) - case value - when Array then value.map { |item| camel(item) } - when Hash then value.deep_transform_keys! { |key| camel(key) } - when Symbol then camel(value.to_s).to_sym - when String then value.underscore.camelize - else value - end - end - - # Transforms values to camelCase. - # - # @example: - # "some_key" => "someKey", - # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L66-L76 ActiveSupport::Inflector.camelize} - def camel_lower(value) - case value - when Array then value.map { |item| camel_lower(item) } - when Hash then value.deep_transform_keys! { |key| camel_lower(key) } - when Symbol then camel_lower(value.to_s).to_sym - when String then value.underscore.camelize(:lower) - else value - end - end - - # Transforms values to dashed-case. - # This is the default case for the JsonApi adapter. - # - # @example: - # "some_key" => "some-key", - # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L185-L187 ActiveSupport::Inflector.dasherize} - def dash(value) - case value - when Array then value.map { |item| dash(item) } - when Hash then value.deep_transform_keys! { |key| dash(key) } - when Symbol then dash(value.to_s).to_sym - when String then value.underscore.dasherize - else value - end - end - - # Transforms values to underscore_case. - # This is the default case for deserialization in the JsonApi adapter. - # - # @example: - # "some-key" => "some_key", - # @see {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb#L89-L98 ActiveSupport::Inflector.underscore} - def underscore(value) - case value - when Array then value.map { |item| underscore(item) } - when Hash then value.deep_transform_keys! { |key| underscore(key) } - when Symbol then underscore(value.to_s).to_sym - when String then value.underscore - else value - end - end - - # Returns the value unaltered - def unaltered(value) - value - end - end -end diff --git a/test/active_model_serializers/key_transform_test.rb b/test/active_model_serializers/key_transform_test.rb deleted file mode 100644 index b4ff4d311..000000000 --- a/test/active_model_serializers/key_transform_test.rb +++ /dev/null @@ -1,297 +0,0 @@ -require 'test_helper' - -module ActiveModelSerializers - class KeyTransformTest < ActiveSupport::TestCase - def test_camel - obj = Object.new - scenarios = [ - { - value: { :"some-key" => 'value' }, - expected: { SomeKey: 'value' } - }, - { - value: { someKey: 'value' }, - expected: { SomeKey: 'value' } - }, - { - value: { some_key: 'value' }, - expected: { SomeKey: 'value' } - }, - { - value: { 'some-key' => 'value' }, - expected: { 'SomeKey' => 'value' } - }, - { - value: { 'someKey' => 'value' }, - expected: { 'SomeKey' => 'value' } - }, - { - value: { 'some_key' => 'value' }, - expected: { 'SomeKey' => 'value' } - }, - { - value: :"some-value", - expected: :SomeValue - }, - { - value: :some_value, - expected: :SomeValue - }, - { - value: :someValue, - expected: :SomeValue - }, - { - value: 'some-value', - expected: 'SomeValue' - }, - { - value: 'someValue', - expected: 'SomeValue' - }, - { - value: 'some_value', - expected: 'SomeValue' - }, - { - value: obj, - expected: obj - }, - { - value: nil, - expected: nil - }, - { - value: [ - { some_value: 'value' } - ], - expected: [ - { SomeValue: 'value' } - ] - } - ] - scenarios.each do |s| - result = ActiveModelSerializers::KeyTransform.camel(s[:value]) - assert_equal s[:expected], result - end - end - - def test_camel_lower - obj = Object.new - scenarios = [ - { - value: { :"some-key" => 'value' }, - expected: { someKey: 'value' } - }, - { - value: { SomeKey: 'value' }, - expected: { someKey: 'value' } - }, - { - value: { some_key: 'value' }, - expected: { someKey: 'value' } - }, - { - value: { 'some-key' => 'value' }, - expected: { 'someKey' => 'value' } - }, - { - value: { 'SomeKey' => 'value' }, - expected: { 'someKey' => 'value' } - }, - { - value: { 'some_key' => 'value' }, - expected: { 'someKey' => 'value' } - }, - { - value: :"some-value", - expected: :someValue - }, - { - value: :SomeValue, - expected: :someValue - }, - { - value: :some_value, - expected: :someValue - }, - { - value: 'some-value', - expected: 'someValue' - }, - { - value: 'SomeValue', - expected: 'someValue' - }, - { - value: 'some_value', - expected: 'someValue' - }, - { - value: obj, - expected: obj - }, - { - value: nil, - expected: nil - }, - { - value: [ - { some_value: 'value' } - ], - expected: [ - { someValue: 'value' } - ] - } - ] - scenarios.each do |s| - result = ActiveModelSerializers::KeyTransform.camel_lower(s[:value]) - assert_equal s[:expected], result - end - end - - def test_dash - obj = Object.new - scenarios = [ - { - value: { some_key: 'value' }, - expected: { :"some-key" => 'value' } - }, - { - value: { 'some_key' => 'value' }, - expected: { 'some-key' => 'value' } - }, - { - value: { SomeKey: 'value' }, - expected: { :"some-key" => 'value' } - }, - { - value: { 'SomeKey' => 'value' }, - expected: { 'some-key' => 'value' } - }, - { - value: { someKey: 'value' }, - expected: { :"some-key" => 'value' } - }, - { - value: { 'someKey' => 'value' }, - expected: { 'some-key' => 'value' } - }, - { - value: :some_value, - expected: :"some-value" - }, - { - value: :SomeValue, - expected: :"some-value" - }, - { - value: 'SomeValue', - expected: 'some-value' - }, - { - value: :someValue, - expected: :"some-value" - }, - { - value: 'someValue', - expected: 'some-value' - }, - { - value: obj, - expected: obj - }, - { - value: nil, - expected: nil - }, - { - value: [ - { 'some_value' => 'value' } - ], - expected: [ - { 'some-value' => 'value' } - ] - } - ] - scenarios.each do |s| - result = ActiveModelSerializers::KeyTransform.dash(s[:value]) - assert_equal s[:expected], result - end - end - - def test_underscore - obj = Object.new - scenarios = [ - { - value: { :"some-key" => 'value' }, - expected: { some_key: 'value' } - }, - { - value: { 'some-key' => 'value' }, - expected: { 'some_key' => 'value' } - }, - { - value: { SomeKey: 'value' }, - expected: { some_key: 'value' } - }, - { - value: { 'SomeKey' => 'value' }, - expected: { 'some_key' => 'value' } - }, - { - value: { someKey: 'value' }, - expected: { some_key: 'value' } - }, - { - value: { 'someKey' => 'value' }, - expected: { 'some_key' => 'value' } - }, - { - value: :"some-value", - expected: :some_value - }, - { - value: :SomeValue, - expected: :some_value - }, - { - value: :someValue, - expected: :some_value - }, - { - value: 'some-value', - expected: 'some_value' - }, - { - value: 'SomeValue', - expected: 'some_value' - }, - { - value: 'someValue', - expected: 'some_value' - }, - { - value: obj, - expected: obj - }, - { - value: nil, - expected: nil - }, - { - value: [ - { 'some-value' => 'value' } - ], - expected: [ - { 'some_value' => 'value' } - ] - } - ] - scenarios.each do |s| - result = ActiveModelSerializers::KeyTransform.underscore(s[:value]) - assert_equal s[:expected], result - end - end - end -end diff --git a/test/benchmark/bm_transform.rb b/test/benchmark/bm_transform.rb index 8af5298e9..97c655c01 100644 --- a/test/benchmark/bm_transform.rb +++ b/test/benchmark/bm_transform.rb @@ -25,21 +25,21 @@ serialization = adapter.as_json Benchmark.ams('camel', time: time, disable_gc: disable_gc) do - ActiveModelSerializers::KeyTransform.camel(serialization) + CaseTransform.camel(serialization) end Benchmark.ams('camel_lower', time: time, disable_gc: disable_gc) do - ActiveModelSerializers::KeyTransform.camel_lower(serialization) + CaseTransform.camel_lower(serialization) end Benchmark.ams('dash', time: time, disable_gc: disable_gc) do - ActiveModelSerializers::KeyTransform.dash(serialization) + CaseTransform.dash(serialization) end Benchmark.ams('unaltered', time: time, disable_gc: disable_gc) do - ActiveModelSerializers::KeyTransform.unaltered(serialization) + CaseTransform.unaltered(serialization) end Benchmark.ams('underscore', time: time, disable_gc: disable_gc) do - ActiveModelSerializers::KeyTransform.underscore(serialization) + CaseTransform.underscore(serialization) end From 05430fb2331722d08720aacdb573e80bb68309b1 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Sun, 11 Dec 2016 23:41:20 -0600 Subject: [PATCH 10/43] Fix typos ``` go get -u github.com/client9/misspell/cmd/misspell misspell -w -q -error -source=text {app,config,lib,test}/**/* ``` > workers = flag.Int("j", 0, "Number of workers, 0 = number of CPUs") > writeit = flag.Bool("w", false, "Overwrite file with corrections (default is just to display)") > quietFlag = flag.Bool("q", false, "Do not emit misspelling output") > outFlag = flag.String("o", "stdout", "output file or [stderr|stdout|]") > format = flag.String("f", "", "'csv', 'sqlite3' or custom Golang template for output") > ignores = flag.String("i", "", "ignore the following corrections, comma separated") > locale = flag.String("locale", "", "Correct spellings using locale perferances for US or UK. Default is to use a neutral variety of English. Setting locale to US will correct the British spelling of 'colour' to 'color'") > mode = flag.String("source", "auto", "Source mode: auto=guess, go=golang source, text=plain or markdown-like text") > debugFlag = flag.Bool("debug", false, "Debug matching, very slow") > exitError = flag.Bool("error", false, "Exit with 2 if misspelling found") --- lib/active_model/serializer/concerns/attributes.rb | 2 +- test/action_controller/json_api/errors_test.rb | 4 ++-- test/action_controller/serialization_test.rb | 2 +- test/active_model_serializers/json_pointer_test.rb | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/active_model/serializer/concerns/attributes.rb b/lib/active_model/serializer/concerns/attributes.rb index d1968d77e..6ee2732fd 100644 --- a/lib/active_model/serializer/concerns/attributes.rb +++ b/lib/active_model/serializer/concerns/attributes.rb @@ -66,7 +66,7 @@ def _attributes end # @api private - # maps attribute value to explict key name + # maps attribute value to explicit key name # @see Serializer::attribute # @see FragmentCache#fragment_serializer def _attributes_keys diff --git a/test/action_controller/json_api/errors_test.rb b/test/action_controller/json_api/errors_test.rb index dd1249f20..6da3c9ada 100644 --- a/test/action_controller/json_api/errors_test.rb +++ b/test/action_controller/json_api/errors_test.rb @@ -14,10 +14,10 @@ def test_active_model_with_multiple_errors { source: { pointer: '/data/attributes/id' }, detail: 'must be a uuid' } ] }.to_json - assert_equal json_reponse_body.to_json, expected_errors_object + assert_equal json_response_body.to_json, expected_errors_object end - def json_reponse_body + def json_response_body JSON.load(@response.body) end diff --git a/test/action_controller/serialization_test.rb b/test/action_controller/serialization_test.rb index e650cbfdb..dfd72b42e 100644 --- a/test/action_controller/serialization_test.rb +++ b/test/action_controller/serialization_test.rb @@ -456,7 +456,7 @@ def use_adapter? end end - def test_render_event_is_emmited + def test_render_event_is_emitted subscriber = ::ActiveSupport::Notifications.subscribe('render.active_model_serializers') do |name| @name = name end diff --git a/test/active_model_serializers/json_pointer_test.rb b/test/active_model_serializers/json_pointer_test.rb index 0c8cf58fc..60619ee6e 100644 --- a/test/active_model_serializers/json_pointer_test.rb +++ b/test/active_model_serializers/json_pointer_test.rb @@ -13,7 +13,7 @@ def test_primary_data_pointer assert_equal '/data', pointer end - def test_unkown_data_pointer + def test_unknown_data_pointer assert_raises(TypeError) do ActiveModelSerializers::JsonPointer.new(:unknown) end From 0976bdc4d0ed73b1465dcf6369e95afaca2aaf2d Mon Sep 17 00:00:00 2001 From: Bernardo Farah Date: Mon, 12 Dec 2016 09:00:27 -0800 Subject: [PATCH 11/43] Link to 0.10.3 tag instead of `master` branch It may be less confusing for new users if the docs link them to the current stable release. It could reduce issues like [this one](https://github.com/rails-api/active_model_serializers/issues/1988) --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0400bb33e..386ff66fc 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,14 @@ If you'd like to chat, we have a [community slack](http://amserializers.herokuap Thanks! ## Documentation + +If you're reading this at https://github.com/rails-api/active_model_serializers you are +reading documentation for our `master`, which may include features that have not +been released yet. Please see below for the documentation relevant to you. + - [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master) - - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/active_model_serializers/0.10.2) +- [0.10.3 (latest release) Documentation](https://github.com/rails-api/active_model_serializers/tree/v0.10.3) + - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/active_model_serializers/0.10.3) - [Guides](docs) - [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable) - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable) From 9eacf9f3b7227736d98289783755325269a678d8 Mon Sep 17 00:00:00 2001 From: Pierre-Alexandre Kofron Date: Fri, 16 Dec 2016 18:00:49 +0100 Subject: [PATCH 12/43] Update jsonapi runtime dependency to 0.1.1.beta6 --- active_model_serializers.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index 3581ce408..a1fc0107a 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -42,7 +42,7 @@ Gem::Specification.new do |spec| # 'minitest' # 'thread_safe' - spec.add_runtime_dependency 'jsonapi', '0.1.1.beta2' + spec.add_runtime_dependency 'jsonapi', '0.1.1.beta6' spec.add_runtime_dependency 'case_transform', '>= 0.2' spec.add_development_dependency 'activerecord', rails_versions From c1fc0e437159ac0ec893fd7c254630fb49524adc Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 23 Dec 2016 11:14:59 -0600 Subject: [PATCH 13/43] Handle different messages from different versions of JSON gem --- test/active_model_serializers/test/schema_test.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/active_model_serializers/test/schema_test.rb b/test/active_model_serializers/test/schema_test.rb index 105ac575d..d217d0064 100644 --- a/test/active_model_serializers/test/schema_test.rb +++ b/test/active_model_serializers/test/schema_test.rb @@ -115,7 +115,8 @@ def test_with_a_non_existent_file end def test_that_raises_with_a_invalid_json_body - message = 'A JSON text must at least contain two octets!' + # message changes from JSON gem 2.0.2 to 2.2.0 + message = /A JSON text must at least contain two octets!|an unexpected token at ''/ get :invalid_json_body @@ -123,7 +124,7 @@ def test_that_raises_with_a_invalid_json_body assert_response_schema('custom/show.json') end - assert_equal(message, error.message) + assert_match(message, error.message) end end end From f246741cc584fc0c83bd851b48e438708567e217 Mon Sep 17 00:00:00 2001 From: Ankit Shah Date: Sat, 24 Dec 2016 21:34:07 -0500 Subject: [PATCH 14/43] Updated isolated tests to assert correct behavior. (#2010) * Updated isolated tests to assert correct behavior. * Added check to get unsafe params if rails version is great than 5 --- ...register_jsonapi_renderer_test_isolated.rb | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb b/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb index 9ba79e229..616d42b06 100644 --- a/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +++ b/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb @@ -12,7 +12,9 @@ class << self end def render_with_jsonapi_renderer - author = Author.new(params[:data][:attributes]) + unlocked_params = Rails::VERSION::MAJOR >= 5 ? params.to_unsafe_h : params + attributes = unlocked_params[:data].present? ? unlocked_params[:data][:attributes] : {} + author = Author.new(attributes) render jsonapi: author end @@ -59,18 +61,12 @@ def test_jsonapi_parser_not_registered end def test_jsonapi_renderer_not_registered - expected = { - 'data' => { - 'attributes' => { - 'name' => 'Johnny Rico' - }, - 'type' => 'users' - } - } payload = '{"data": {"attributes": {"name": "Johnny Rico"}, "type": "authors"}}' headers = { 'CONTENT_TYPE' => 'application/vnd.api+json' } post '/render_with_jsonapi_renderer', params: payload, headers: headers - assert expected, response.body + assert_equal 500, response.status + assert_equal '', response.body + assert response.request.env['action_dispatch.exception'].is_a?(ActionView::MissingTemplate) if response.request.present? end def test_jsonapi_parser @@ -113,16 +109,21 @@ def test_jsonapi_parser_registered def test_jsonapi_renderer_registered expected = { 'data' => { - 'attributes' => { - 'name' => 'Johnny Rico' - }, - 'type' => 'users' + 'id' => 'author', + 'type' => 'authors', + 'attributes' => { 'name' => 'Johnny Rico' }, + 'relationships' => { + 'posts' => { 'data' => nil }, + 'roles' => { 'data' => nil }, + 'bio' => { 'data' => nil } + } } } + payload = '{"data": {"attributes": {"name": "Johnny Rico"}, "type": "authors"}}' headers = { 'CONTENT_TYPE' => 'application/vnd.api+json' } post '/render_with_jsonapi_renderer', params: payload, headers: headers - assert expected, response.body + assert_equal expected.to_json, response.body end def test_jsonapi_parser From 4394f76b866832abc01e5c1e175c0a226ebc05c2 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Sat, 24 Dec 2016 21:04:55 -0600 Subject: [PATCH 15/43] Cleanup assertions in isolated jsonapi renderer tests a bit --- ...register_jsonapi_renderer_test_isolated.rb | 49 +++++++++++++------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb b/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb index 616d42b06..37452955f 100644 --- a/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +++ b/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb @@ -12,8 +12,16 @@ class << self end def render_with_jsonapi_renderer - unlocked_params = Rails::VERSION::MAJOR >= 5 ? params.to_unsafe_h : params - attributes = unlocked_params[:data].present? ? unlocked_params[:data][:attributes] : {} + permitted_params = params.permit(data: [:id, :type, attributes: [:name]]) + permitted_params = permitted_params.to_h.with_indifferent_access + attributes = + if permitted_params[:data] + permitted_params[:data][:attributes].merge(id: permitted_params[:data][:id]) + else + # Rails returns empty params when no mime type can be negotiated. + # (Until https://github.com/rails/rails/pull/26632 is reviewed.) + permitted_params + end author = Author.new(attributes) render jsonapi: author end @@ -34,6 +42,17 @@ def assert_parses(expected, actual, headers = {}) assert_equal(expected, TestController.last_request_parameters) end + def define_author_model_and_serializer + TestController.const_set(:Author, Class.new(ActiveModelSerializers::Model) do + attributes :name + end) + TestController.const_set(:AuthorSerializer, Class.new(ActiveModel::Serializer) do + type 'users' + attribute :id + attribute :name + end) + end + class WithoutRenderer < JsonApiRendererTest setup do require 'rails' @@ -49,6 +68,7 @@ class WithoutRenderer < JsonApiRendererTest match ':action', to: TestController, via: [:get, :post] end end + define_author_model_and_serializer end def test_jsonapi_parser_not_registered @@ -61,12 +81,12 @@ def test_jsonapi_parser_not_registered end def test_jsonapi_renderer_not_registered - payload = '{"data": {"attributes": {"name": "Johnny Rico"}, "type": "authors"}}' + payload = '{"data": {"attributes": {"name": "Johnny Rico"}, "type": "users", "id": "36c9c04e-86b1-4636-a5b0-8616672d1765"}}' headers = { 'CONTENT_TYPE' => 'application/vnd.api+json' } post '/render_with_jsonapi_renderer', params: payload, headers: headers - assert_equal 500, response.status assert_equal '', response.body - assert response.request.env['action_dispatch.exception'].is_a?(ActionView::MissingTemplate) if response.request.present? + assert_equal 500, response.status + assert_equal ActionView::MissingTemplate, request.env['action_dispatch.exception'].class end def test_jsonapi_parser @@ -94,6 +114,7 @@ class WithRenderer < JsonApiRendererTest match ':action', to: TestController, via: [:get, :post] end end + define_author_model_and_serializer end def test_jsonapi_parser_registered @@ -109,18 +130,13 @@ def test_jsonapi_parser_registered def test_jsonapi_renderer_registered expected = { 'data' => { - 'id' => 'author', - 'type' => 'authors', - 'attributes' => { 'name' => 'Johnny Rico' }, - 'relationships' => { - 'posts' => { 'data' => nil }, - 'roles' => { 'data' => nil }, - 'bio' => { 'data' => nil } - } + 'id' => '36c9c04e-86b1-4636-a5b0-8616672d1765', + 'type' => 'users', + 'attributes' => { 'name' => 'Johnny Rico' } } } - payload = '{"data": {"attributes": {"name": "Johnny Rico"}, "type": "authors"}}' + payload = '{"data": {"attributes": {"name": "Johnny Rico"}, "type": "users", "id": "36c9c04e-86b1-4636-a5b0-8616672d1765"}}' headers = { 'CONTENT_TYPE' => 'application/vnd.api+json' } post '/render_with_jsonapi_renderer', params: payload, headers: headers assert_equal expected.to_json, response.body @@ -133,10 +149,11 @@ def test_jsonapi_parser 'attributes' => { 'name' => 'John Doe' }, - 'type' => 'users' + 'type' => 'users', + 'id' => '36c9c04e-86b1-4636-a5b0-8616672d1765' } }, - '{"data": {"attributes": {"name": "John Doe"}, "type": "users"}}', + '{"data": {"attributes": {"name": "John Doe"}, "type": "users", "id": "36c9c04e-86b1-4636-a5b0-8616672d1765"}}', 'CONTENT_TYPE' => 'application/vnd.api+json' ) end From 6cf84c11e05d4fd2883cdd9920bf7a05e2a3fd8f Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Sat, 24 Dec 2016 21:29:46 -0600 Subject: [PATCH 16/43] Less strict exception matching --- test/active_model_serializers/test/schema_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/active_model_serializers/test/schema_test.rb b/test/active_model_serializers/test/schema_test.rb index d217d0064..0fe497d78 100644 --- a/test/active_model_serializers/test/schema_test.rb +++ b/test/active_model_serializers/test/schema_test.rb @@ -116,7 +116,7 @@ def test_with_a_non_existent_file def test_that_raises_with_a_invalid_json_body # message changes from JSON gem 2.0.2 to 2.2.0 - message = /A JSON text must at least contain two octets!|an unexpected token at ''/ + message = /A JSON text must at least contain two octets!|unexpected token at ''/ get :invalid_json_body From 91128fadb8e03dafd0b3bd4bd9f3f2f3dbdd6fa1 Mon Sep 17 00:00:00 2001 From: Yohan Robert Date: Fri, 30 Dec 2016 20:56:20 +0100 Subject: [PATCH 17/43] [skip ci] Fix relationship link documentation --- CHANGELOG.md | 1 + docs/howto/add_relationship_links.md | 45 +++++++++++++++------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f75ac1b..b68da1d6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Fixes: Misc: +- [#1981](https://github.com/rails-api/active_model_serializers/pull/1981) Fix relationship link documentation. (@groyoh) - [#1984](https://github.com/rails-api/active_model_serializers/pull/1984) Make test attributes explicit. Test models have 'associations' support. (@bf4) - [#1993](https://github.com/rails-api/active_model_serializers/pull/1993) Swap out KeyTransform for CaseTransform gem for the possibility of native extension use (@NullVoxPopuli) diff --git a/docs/howto/add_relationship_links.md b/docs/howto/add_relationship_links.md index a27f7e2da..ba8f7f8ad 100644 --- a/docs/howto/add_relationship_links.md +++ b/docs/howto/add_relationship_links.md @@ -15,40 +15,42 @@ class Api::V1::UsersController < ApplicationController serializer: Api::V1::UserSerializer, include: [] end +end ``` Bear in mind though that ActiveModelSerializers are [framework-agnostic](outside_controller_use.md), Rails is just a common example here. ### Links as an attribute of a resource -**This is applicable to JSONAPI, JSON and Attributes adapters** +**This is applicable to JSON and Attributes adapters** You can define an attribute in the resource, named `links`. ```ruby class Api::V1::UserSerializer < ActiveModel::Serializer - attributes :id, :name, :links + include Rails.application.routes.url_helpers + + attributes :id, :name - def links + attribute :links do + id = object.id { - self: api_v1_user_path(object.id), - microposts: api_v1_microposts_path(user_id: object.id) + self: api_v1_user_path(id), + microposts: api_v1_microposts_path(user_id: id) } end end ``` -This will result in (example is in JSONAPI adapter): +Using the `JSON` adapter, this will result in: + ```json { - "data": { + "user": { "id": "1", - "type": "users", - "attributes": { - "name": "Example User", - "links": { - "self": "/api/v1/users/1", - "microposts": "/api/v1/microposts?user_id=1" - } + "name": "John", + "links": { + "self": "/api/v1/users/1", + "microposts": "/api/v1/microposts?user_id=1" } } } @@ -58,7 +60,7 @@ This will result in (example is in JSONAPI adapter): ### Links as a property of the resource definiton **This is only applicable to JSONAPI adapter** -You can use the `links` class method to define the links you need in the resource's primary data. +You can use the `link` class method to define the links you need in the resource's primary data. ```ruby class Api::V1::UserSerializer < ActiveModel::Serializer @@ -69,7 +71,8 @@ class Api::V1::UserSerializer < ActiveModel::Serializer end ``` -This will result in (example is in JSONAPI adapter): +Using the `JSONAPI` adapter, this will result in: + ```json { "data": { @@ -104,12 +107,12 @@ class Api::V1::UserSerializer < ActiveModel::Serializer has_many :microposts, serializer: Api::V1::MicropostSerializer do link(:related) { api_v1_microposts_path(user_id: object.id) } - end - #this is needed to avoid n+1, gem core devs are working to remove this necessity - #more on: https://github.com/rails-api/active_model_serializers/issues/1325 - def microposts - object.microposts.loaded ? object.microposts : object.microposts.none + microposts = object.microposts + # The following code is needed to avoid n+1 queries. + # Core devs are working to remove this necessity. + # See: https://github.com/rails-api/active_model_serializers/issues/1325 + microposts.loaded? ? microposts : microposts.none end end ``` From 655c721d0d18988be795d767797cdcddc5369a87 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 6 Jan 2017 16:21:04 -0600 Subject: [PATCH 18/43] Bump to v0.10.4 Conflicts: CHANGELOG.md --- CHANGELOG.md | 12 +++++++++--- README.md | 4 ++-- lib/active_model/serializer/version.rb | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90f75ac1b..6eb41c290 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,12 @@ ## 0.10.x -### [master (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.3...master) +### [master (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.4...master) Breaking changes: Features: -- [#1982](https://github.com/rails-api/active_model_serializers/pull/1982) Add ActiveModelSerializers::Model.attributes to configure PORO attributes (@bf4). +- [#1982](https://github.com/rails-api/active_model_serializers/pull/1982) Add ActiveModelSerializers::Model.attributes to configure PORO attributes. (@bf4) Fixes: @@ -15,7 +15,13 @@ Fixes: Misc: - [#1984](https://github.com/rails-api/active_model_serializers/pull/1984) Make test attributes explicit. Test models have 'associations' support. (@bf4) -- [#1993](https://github.com/rails-api/active_model_serializers/pull/1993) Swap out KeyTransform for CaseTransform gem for the possibility of native extension use (@NullVoxPopuli) + +### [v0.10.4 (2017-01-06)](https://github.com/rails-api/active_model_serializers/compare/v0.10.3...v0.10.4) + +Misc: + +- [#2005](https://github.com/rails-api/active_model_serializers/pull/2005) Update jsonapi runtime dependency to 0.1.1.beta6, support Ruby 2.4. (@kofronpi) +- [#1993](https://github.com/rails-api/active_model_serializers/pull/1993) Swap out KeyTransform for CaseTransform gem for the possibility of native extension use. (@NullVoxPopuli) ### [v0.10.3 (2016-11-21)](https://github.com/rails-api/active_model_serializers/compare/v0.10.2...v0.10.3) diff --git a/README.md b/README.md index 386ff66fc..59a8c854f 100644 --- a/README.md +++ b/README.md @@ -90,8 +90,8 @@ reading documentation for our `master`, which may include features that have not been released yet. Please see below for the documentation relevant to you. - [0.10 (master) Documentation](https://github.com/rails-api/active_model_serializers/tree/master) -- [0.10.3 (latest release) Documentation](https://github.com/rails-api/active_model_serializers/tree/v0.10.3) - - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/active_model_serializers/0.10.3) +- [0.10.4 (latest release) Documentation](https://github.com/rails-api/active_model_serializers/tree/v0.10.4) + - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/gems/active_model_serializers/0.10.4) - [Guides](docs) - [0.9 (0-9-stable) Documentation](https://github.com/rails-api/active_model_serializers/tree/0-9-stable) - [![API Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/rails-api/active_model_serializers/0-9-stable) diff --git a/lib/active_model/serializer/version.rb b/lib/active_model/serializer/version.rb index 5c3a99fe2..b72d23e82 100644 --- a/lib/active_model/serializer/version.rb +++ b/lib/active_model/serializer/version.rb @@ -1,5 +1,5 @@ module ActiveModel class Serializer - VERSION = '0.10.3'.freeze + VERSION = '0.10.4'.freeze end end From 40489fa8a2c033698e8338e94dbb9466de7b3e4b Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 6 Jan 2017 12:38:23 -0600 Subject: [PATCH 19/43] Fix method redefined warning --- test/generators/serializer_generator_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/generators/serializer_generator_test.rb b/test/generators/serializer_generator_test.rb index 57625fc75..eef4a41e1 100644 --- a/test/generators/serializer_generator_test.rb +++ b/test/generators/serializer_generator_test.rb @@ -67,6 +67,7 @@ def stub_safe_constantize(expected:) yield ensure String.class_eval do + undef_method :safe_constantize alias_method :safe_constantize, :old undef_method :old end From ced317d827886fdcd423e5ea400b66caa9e9b0e5 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 6 Jan 2017 13:18:51 -0600 Subject: [PATCH 20/43] Fix thor warning to set type: :boolean, was setting `{ banner: "" }` ``` require "rails/generators/rails/model/model_generator" module Rails module Generators class ResourceGenerator < ModelGenerator # :nodoc: include ResourceHelpers hook_for :resource_controller, required: true do |controller| invoke controller, [ controller_name, options[:actions] ] end class_option :actions, type: :array, banner: "ACTION ACTION", default: [], desc: "Actions for the resource controller" hook_for :resource_route, required: true end end end ``` ``` # .bundle/ruby/2.2.0/bundler/gems/rails-4c5f1bc9d45e/railties/lib/rails/generators/base.rb # Invoke a generator based on the value supplied by the user to the # given option named "name". A class option is created when this method # is invoked and you can set a hash to customize it. # # ==== Examples # # module Rails::Generators # class ControllerGenerator < Base # hook_for :test_framework, aliases: "-t" # end # end # # The example above will create a test framework option and will invoke # a generator based on the user supplied value. # # For example, if the user invoke the controller generator as: # # rails generate controller Account --test-framework=test_unit # # The controller generator will then try to invoke the following generators: # # "rails:test_unit", "test_unit:controller", "test_unit" # # Notice that "rails:generators:test_unit" could be loaded as well, what # Rails looks for is the first and last parts of the namespace. This is what # allows any test framework to hook into Rails as long as it provides any # of the hooks above. # # ==== Options # # The first and last part used to find the generator to be invoked are # guessed based on class invokes hook_for, as noticed in the example above. # This can be customized with two options: :in and :as. # # Let's suppose you are creating a generator that needs to invoke the # controller generator from test unit. Your first attempt is: # # class AwesomeGenerator < Rails::Generators::Base # hook_for :test_framework # end # # The lookup in this case for test_unit as input is: # # "test_unit:awesome", "test_unit" # # Which is not the desired lookup. You can change it by providing the # :as option: # # class AwesomeGenerator < Rails::Generators::Base # hook_for :test_framework, as: :controller # end # # And now it will look up at: # # "test_unit:controller", "test_unit" # # Similarly, if you want it to also look up in the rails namespace, you # just need to provide the :in value: # # class AwesomeGenerator < Rails::Generators::Base # hook_for :test_framework, in: :rails, as: :controller # end # # And the lookup is exactly the same as previously: # # "rails:test_unit", "test_unit:controller", "test_unit" # # ==== Switches # # All hooks come with switches for user interface. If you do not want # to use any test framework, you can do: # # rails generate controller Account --skip-test-framework # # Or similarly: # # rails generate controller Account --no-test-framework # # ==== Boolean hooks # # In some cases, you may want to provide a boolean hook. For example, webrat # developers might want to have webrat available on controller generator. # This can be achieved as: # # Rails::Generators::ControllerGenerator.hook_for :webrat, type: :boolean # # Then, if you want webrat to be invoked, just supply: # # rails generate controller Account --webrat # # The hooks lookup is similar as above: # # "rails:generators:webrat", "webrat:generators:controller", "webrat" # # ==== Custom invocations # # You can also supply a block to hook_for to customize how the hook is # going to be invoked. The block receives two arguments, an instance # of the current class and the class to be invoked. # # For example, in the resource generator, the controller should be invoked # with a pluralized class name. But by default it is invoked with the same # name as the resource generator, which is singular. To change this, we # can give a block to customize how the controller can be invoked. # # hook_for :resource_controller do |instance, controller| # instance.invoke controller, [ instance.name.pluralize ] # end # def self.hook_for(*names, &block) options = names.extract_options! in_base = options.delete(:in) || base_name as_hook = options.delete(:as) || generator_name names.each do |name| unless class_options.key?(name) defaults = if options[:type] == :boolean {} elsif [true, false].include?(default_value_for_option(name, options)) { banner: "" } else { desc: "#{name.to_s.humanize} to be invoked", banner: "NAME" } end class_option(name, defaults.merge!(options)) end hooks[name] = [ in_base, as_hook ] invoke_from_option(name, options, &block) end end ``` ``` # .bundle/ruby/2.2.0/gems/thor-0.19.4/lib/thor/parser/option.rb:113:in `validate!' # parse :foo => true # #=> Option foo with default value true and type boolean # # The valid types are :boolean, :numeric, :hash, :array and :string. If none # is given a default type is assumed. This default type accepts arguments as # string (--foo=value) or booleans (just --foo). # # By default all options are optional, unless :required is given. def validate_default_type! default_type = case @default when nil return when TrueClass, FalseClass required? ? :string : :boolean when Numeric :numeric when Symbol :string when Hash, Array, String @default.class.name.downcase.to_sym end # TODO: This should raise an ArgumentError in a future version of Thor if default_type != @type warn "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" end end ``` --- lib/generators/rails/resource_override.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/generators/rails/resource_override.rb b/lib/generators/rails/resource_override.rb index ebcba8df3..5177a6369 100644 --- a/lib/generators/rails/resource_override.rb +++ b/lib/generators/rails/resource_override.rb @@ -4,7 +4,7 @@ module Rails module Generators class ResourceGenerator - hook_for :serializer, default: true, boolean: true + hook_for :serializer, default: true, type: :boolean end end end From b620c275e5b0787ca5f6da97dd64b24f588e831d Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 6 Jan 2017 12:39:07 -0600 Subject: [PATCH 21/43] Silence Grape warnings --- test/grape_test.rb | 22 ++++++++++++++++++++-- test/test_helper.rb | 12 ++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/test/grape_test.rb b/test/grape_test.rb index b026021d8..4851e57a7 100644 --- a/test/grape_test.rb +++ b/test/grape_test.rb @@ -1,5 +1,7 @@ require 'test_helper' -require 'grape' +TestHelper.silence_warnings do + require 'grape' +end require 'grape/active_model_serializers' require 'kaminari' require 'kaminari/hooks' @@ -53,7 +55,15 @@ def self.collection class GrapeTest < Grape::API format :json - include Grape::ActiveModelSerializers + TestHelper.silence_warnings do + include Grape::ActiveModelSerializers + end + + def self.resources(*) + TestHelper.silence_warnings do + super + end + end resources :grape do get '/render' do @@ -93,6 +103,14 @@ def app Grape::Middleware::Globals.new(GrapeTest.new) end + extend Minitest::Assertions + def self.run_one_method(*) + _, stderr = capture_io do + super + end + fail Minitest::Assertion, stderr if stderr !~ /grape/ + end + def test_formatter_returns_json get '/grape/render' diff --git a/test/test_helper.rb b/test/test_helper.rb index e96c4840f..294fa33c4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -40,6 +40,18 @@ def serialization_options(options) require 'minitest/autorun' Minitest.backtrace_filter = Minitest::BacktraceFilter.new +module TestHelper + module_function + + def silence_warnings + original_verbose = $VERBOSE + $VERBOSE = nil + yield + ensure + $VERBOSE = original_verbose + end +end + require 'support/rails_app' # require "rails/test_help" From 6acb4055c9ec68257b192ce4a3868383f2d6ffe4 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 6 Jan 2017 12:38:11 -0600 Subject: [PATCH 22/43] Fix MT6 assert_nil warnings --- .../serialization_scope_name_test.rb | 5 ++++- .../railtie_test_isolated.rb | 5 ++++- .../json_api/include_data_if_sideloaded_test.rb | 7 ++++++- test/cache_test.rb | 8 ++++---- .../caching_configuration_test_isolated.rb | 12 ++++++------ test/serializers/serializer_for_test.rb | 12 ++++++------ 6 files changed, 30 insertions(+), 19 deletions(-) diff --git a/test/action_controller/serialization_scope_name_test.rb b/test/action_controller/serialization_scope_name_test.rb index 33ac88f4f..d4db3b63d 100644 --- a/test/action_controller/serialization_scope_name_test.rb +++ b/test/action_controller/serialization_scope_name_test.rb @@ -75,7 +75,10 @@ def test_default_serialization_scope end def test_default_serialization_scope_object - assert_equal @controller.current_user, @controller.serialization_scope + expected = @controller.current_user + actual = @controller.serialization_scope + assert_nil expected + assert_nil actual end def test_default_scope_non_admin diff --git a/test/active_model_serializers/railtie_test_isolated.rb b/test/active_model_serializers/railtie_test_isolated.rb index 21f6c178e..f0d1c7e5a 100644 --- a/test/active_model_serializers/railtie_test_isolated.rb +++ b/test/active_model_serializers/railtie_test_isolated.rb @@ -32,7 +32,10 @@ class WithRails < RailtieTest test 'it is configured for caching' do assert_equal ActionController::Base.cache_store, ActiveModelSerializers.config.cache_store - assert_equal Rails.configuration.action_controller.perform_caching, ActiveModelSerializers.config.perform_caching + expected = Rails.configuration.action_controller.perform_caching + actual = ActiveModelSerializers.config.perform_caching + assert_nil expected + assert_nil actual end end diff --git a/test/adapter/json_api/include_data_if_sideloaded_test.rb b/test/adapter/json_api/include_data_if_sideloaded_test.rb index 728eae13e..aabc3bc5a 100644 --- a/test/adapter/json_api/include_data_if_sideloaded_test.rb +++ b/test/adapter/json_api/include_data_if_sideloaded_test.rb @@ -159,7 +159,12 @@ def result(opts) def assert_relationship(relationship_name, expected, opts = {}) hash = result(opts) - assert_equal(expected, hash[:data][:relationships][relationship_name]) + actual = hash[:data][:relationships][relationship_name] + if expected.nil? + assert_nil(actual) + else + assert_equal(expected, actual) + end end end end diff --git a/test/cache_test.rb b/test/cache_test.rb index b2cb27eb4..06043fb7f 100644 --- a/test/cache_test.rb +++ b/test/cache_test.rb @@ -128,7 +128,7 @@ def test_cache_definition def test_cache_key_definition assert_equal('post', @post_serializer.class._cache_key) assert_equal('writer', @author_serializer.class._cache_key) - assert_equal(nil, @comment_serializer.class._cache_key) + assert_nil(@comment_serializer.class._cache_key) end def test_cache_key_interpolation_with_updated_at_when_cache_key_is_not_defined_on_object @@ -171,7 +171,7 @@ def test_error_is_raised_if_cache_key_is_not_defined_on_object_or_passed_as_cach def test_cache_options_definition assert_equal({ expires_in: 0.1, skip_digest: true }, @post_serializer.class._cache_options) - assert_equal(nil, @blog_serializer.class._cache_options) + assert_nil(@blog_serializer.class._cache_options) assert_equal({ expires_in: 1.day, skip_digest: true }, @comment_serializer.class._cache_options) end @@ -182,8 +182,8 @@ def test_fragment_cache_definition def test_associations_separately_cache cache_store.clear - assert_equal(nil, cache_store.fetch(@post.cache_key)) - assert_equal(nil, cache_store.fetch(@comment.cache_key)) + assert_nil(cache_store.fetch(@post.cache_key)) + assert_nil(cache_store.fetch(@comment.cache_key)) Timecop.freeze(Time.current) do render_object_with_cache(@post) diff --git a/test/serializers/caching_configuration_test_isolated.rb b/test/serializers/caching_configuration_test_isolated.rb index 82e497b23..b5698dd1a 100644 --- a/test/serializers/caching_configuration_test_isolated.rb +++ b/test/serializers/caching_configuration_test_isolated.rb @@ -69,9 +69,9 @@ class PerformCachingTrue < CachingConfigurationTest end test 'the non-cached serializer cache_store is nil' do - assert_equal nil, @non_cached_serializer._cache - assert_equal nil, @non_cached_serializer.cache_store - assert_equal nil, @non_cached_serializer._cache + assert_nil @non_cached_serializer._cache + assert_nil @non_cached_serializer.cache_store + assert_nil @non_cached_serializer._cache end test 'the non-cached serializer does not have cache_enabled?' do @@ -136,9 +136,9 @@ class PerformCachingFalse < CachingConfigurationTest end test 'the non-cached serializer cache_store is nil' do - assert_equal nil, @non_cached_serializer._cache - assert_equal nil, @non_cached_serializer.cache_store - assert_equal nil, @non_cached_serializer._cache + assert_nil @non_cached_serializer._cache + assert_nil @non_cached_serializer.cache_store + assert_nil @non_cached_serializer._cache end test 'the non-cached serializer does not have cache_enabled?' do diff --git a/test/serializers/serializer_for_test.rb b/test/serializers/serializer_for_test.rb index b7607cfea..9f6917081 100644 --- a/test/serializers/serializer_for_test.rb +++ b/test/serializers/serializer_for_test.rb @@ -59,7 +59,7 @@ def setup def test_serializer_for_non_ams_serializer serializer = ActiveModel::Serializer.serializer_for(@tweet) - assert_equal nil, serializer + assert_nil serializer end def test_serializer_for_existing_serializer @@ -71,12 +71,12 @@ def test_serializer_for_existing_serializer_with_lookup_disabled serializer = with_serializer_lookup_disabled do ActiveModel::Serializer.serializer_for(@profile) end - assert_equal nil, serializer + assert_nil serializer end def test_serializer_for_not_existing_serializer serializer = ActiveModel::Serializer.serializer_for(@model) - assert_equal nil, serializer + assert_nil serializer end def test_serializer_inherited_serializer @@ -88,7 +88,7 @@ def test_serializer_inherited_serializer_with_lookup_disabled serializer = with_serializer_lookup_disabled do ActiveModel::Serializer.serializer_for(@my_profile) end - assert_equal nil, serializer + assert_nil serializer end def test_serializer_custom_serializer @@ -114,7 +114,7 @@ def test_serializer_for_namespaced_resource_with_lookup_disabled serializer = with_serializer_lookup_disabled do ActiveModel::Serializer.serializer_for(post) end - assert_equal nil, serializer + assert_nil serializer end def test_serializer_for_nested_resource @@ -128,7 +128,7 @@ def test_serializer_for_nested_resource_with_lookup_disabled serializer = with_serializer_lookup_disabled do ResourceNamespace::PostSerializer.serializer_for(comment) end - assert_equal nil, serializer + assert_nil serializer end end end From 4dfbe2747b9060ee8657bd6e187a79c374cae6cc Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 6 Jan 2017 22:49:50 -0600 Subject: [PATCH 23/43] Fix test bug found by assert_nil --- .../include_data_if_sideloaded_test.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/adapter/json_api/include_data_if_sideloaded_test.rb b/test/adapter/json_api/include_data_if_sideloaded_test.rb index aabc3bc5a..b5c818693 100644 --- a/test/adapter/json_api/include_data_if_sideloaded_test.rb +++ b/test/adapter/json_api/include_data_if_sideloaded_test.rb @@ -146,25 +146,25 @@ def test_block_relationship end def test_node_not_included_when_no_link - expected = nil - assert_relationship(:unlinked_tags, expected) + expected = { meta: {} } + assert_relationship(:unlinked_tags, expected, key_transform: :unaltered) end private + def assert_relationship(relationship_name, expected, opts = {}) + actual = relationship_data(relationship_name, opts) + assert_equal(expected, actual) + end + def result(opts) opts = { adapter: :json_api }.merge(opts) serializable(@author, opts).serializable_hash end - def assert_relationship(relationship_name, expected, opts = {}) + def relationship_data(relationship_name, opts = {}) hash = result(opts) - actual = hash[:data][:relationships][relationship_name] - if expected.nil? - assert_nil(actual) - else - assert_equal(expected, actual) - end + hash[:data][:relationships][relationship_name] end end end From c52af54b4e01cd3742e768d407e794348265c365 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Fri, 6 Jan 2017 23:09:52 -0600 Subject: [PATCH 24/43] Improve tests found by assert_nil --- .../serialization_scope_name_test.rb | 15 ++++++++----- .../railtie_test_isolated.rb | 22 ++++++++++--------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/test/action_controller/serialization_scope_name_test.rb b/test/action_controller/serialization_scope_name_test.rb index d4db3b63d..3d767d049 100644 --- a/test/action_controller/serialization_scope_name_test.rb +++ b/test/action_controller/serialization_scope_name_test.rb @@ -33,7 +33,8 @@ def json_key end end class PostTestController < ActionController::Base - attr_accessor :current_user + attr_writer :current_user + def render_post_by_non_admin self.current_user = User.new(id: 3, name: 'Pete', admin: false) render json: new_post, serializer: serializer, adapter: :json @@ -44,6 +45,10 @@ def render_post_by_admin render json: new_post, serializer: serializer, adapter: :json end + def current_user + defined?(@current_user) ? @current_user : :current_user_not_set + end + private def new_post @@ -75,10 +80,8 @@ def test_default_serialization_scope end def test_default_serialization_scope_object - expected = @controller.current_user - actual = @controller.serialization_scope - assert_nil expected - assert_nil actual + assert_equal :current_user_not_set, @controller.current_user + assert_equal :current_user_not_set, @controller.serialization_scope end def test_default_scope_non_admin @@ -128,7 +131,7 @@ def test_defined_serialization_scope end def test_defined_serialization_scope_object - assert_equal @controller.view_context.class, @controller.serialization_scope.class + assert_equal @controller.view_context.controller, @controller.serialization_scope.controller end def test_serialization_scope_non_admin diff --git a/test/active_model_serializers/railtie_test_isolated.rb b/test/active_model_serializers/railtie_test_isolated.rb index f0d1c7e5a..1044fc8b9 100644 --- a/test/active_model_serializers/railtie_test_isolated.rb +++ b/test/active_model_serializers/railtie_test_isolated.rb @@ -4,11 +4,13 @@ class RailtieTest < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation - class WithRails < RailtieTest + class WithRailsRequiredFirst < RailtieTest setup do require 'rails' require 'active_model_serializers' - make_basic_app + make_basic_app do |app| + app.config.action_controller.perform_caching = true + end end test 'mixes ActionController::Serialization into ActionController::Base' do @@ -32,17 +34,17 @@ class WithRails < RailtieTest test 'it is configured for caching' do assert_equal ActionController::Base.cache_store, ActiveModelSerializers.config.cache_store - expected = Rails.configuration.action_controller.perform_caching - actual = ActiveModelSerializers.config.perform_caching - assert_nil expected - assert_nil actual + assert_equal true, Rails.configuration.action_controller.perform_caching + assert_equal true, ActiveModelSerializers.config.perform_caching end end - class WithoutRails < RailtieTest + class WithoutRailsRequiredFirst < RailtieTest setup do require 'active_model_serializers' - make_basic_app + make_basic_app do |app| + app.config.action_controller.perform_caching = true + end end test 'does not mix ActionController::Serialization into ActionController::Base' do @@ -59,8 +61,8 @@ class WithoutRails < RailtieTest test 'it is not configured for caching' do refute_nil ActionController::Base.cache_store assert_nil ActiveModelSerializers.config.cache_store - refute Rails.configuration.action_controller.perform_caching - refute ActiveModelSerializers.config.perform_caching + assert_equal true, Rails.configuration.action_controller.perform_caching + assert_nil ActiveModelSerializers.config.perform_caching end end end From 93ca27fe444c3a940890cb7a4dedbd8369f481f3 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Tue, 10 Jan 2017 02:28:50 -0600 Subject: [PATCH 25/43] Merge 0-10-stable into master (to fix breaking change). (#2023) * Merge pull request #1990 from mxie/mx-result-typo Fix typos and capitalization in Relationship Links docs [ci skip] * Merge pull request #1992 from ojiry/bump_ruby_versions Run tests by Ruby 2.2.6 and 2.3.3 * Merge pull request #1994 from bf4/promote_architecture Promote important architecture description that answers a lot of questions we get Conflicts: docs/ARCHITECTURE.md * Merge pull request #1999 from bf4/typos Fix typos [ci skip] * Merge pull request #2000 from berfarah/patch-1 Link to 0.10.3 tag instead of `master` branch * Merge pull request #2007 from bf4/check_ci Test was failing due to change in JSON exception message when parsing empty string * Swap out KeyTransform for CaseTransform (#1993) * delete KeyTransform, use CaseTransform * added changelog Conflicts: CHANGELOG.md * Merge pull request #2005 from kofronpi/support-ruby-2.4 Update jsonapi runtime dependency to 0.1.1.beta6 * Bump to v0.10.4 * Merge pull request #2018 from rails-api/bump_version Bump to v0.10.4 [ci skip] Conflicts: CHANGELOG.md * Merge pull request #2019 from bf4/fix_method_redefined_warning Fix AMS warnings * Merge pull request #2020 from bf4/silence_grape_warnings Silence Grape warnings * Merge pull request #2017 from bf4/remove_warnings Fix mt6 assert_nil warnings * Updated isolated tests to assert correct behavior. (#2010) * Updated isolated tests to assert correct behavior. * Added check to get unsafe params if rails version is great than 5 * Merge pull request #2012 from bf4/cleanup_isolated_jsonapi_renderer_tests_a_bit Cleanup assertions in isolated jsonapi renderer tests a bit * Add Model#attributes helper; make test attributes explicit * Fix model attributes accessors * Fix typos * Randomize testing of compatibility layer against regressions * Test bugfix * Add CHANGELOG * Merge pull request #1981 from groyoh/link_doc Fix relationship links doc Conflicts: CHANGELOG.md --- CHANGELOG.md | 6 +- docs/howto/serialize_poro.md | 21 ++- lib/active_model_serializers/model.rb | 123 ++++++++++++++---- .../adapter_selector_test.rb | 9 ++ .../namespace_lookup_test.rb | 6 +- test/active_model_serializers/model_test.rb | 89 +++++++++++-- ...register_jsonapi_renderer_test_isolated.rb | 2 +- test/adapter/attributes_test.rb | 7 +- test/adapter/json/has_many_test.rb | 12 +- test/adapter/json_api/has_many_test.rb | 12 +- .../include_data_if_sideloaded_test.rb | 10 ++ test/cache_test.rb | 90 ++++++++++--- test/fixtures/poro.rb | 19 +-- test/serializers/associations_test.rb | 12 +- 14 files changed, 335 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfc0d0792..b025a61bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,16 +6,16 @@ Breaking changes: Features: -- [#1982](https://github.com/rails-api/active_model_serializers/pull/1982) Add ActiveModelSerializers::Model.attributes to configure PORO attributes. (@bf4) +- [#2021](https://github.com/rails-api/active_model_serializers/pull/2021) ActiveModelSerializers::Model#attributes. Originally in [#1982](https://github.com/rails-api/active_model_serializers/pull/1982). (@bf4) Fixes: -- [#1984](https://github.com/rails-api/active_model_serializers/pull/1984) Mutation of ActiveModelSerializers::Model now changes the attributes. (@bf4) +- [#2022](https://github.com/rails-api/active_model_serializers/pull/2022) Mutation of ActiveModelSerializers::Model now changes the attributes. Originally in [#1984](https://github.com/rails-api/active_model_serializers/pull/1984). (@bf4) Misc: +- [#2021](https://github.com/rails-api/active_model_serializers/pull/2021) Make test attributes explicit. Tests have Model#associations. (@bf4) - [#1981](https://github.com/rails-api/active_model_serializers/pull/1981) Fix relationship link documentation. (@groyoh) -- [#1984](https://github.com/rails-api/active_model_serializers/pull/1984) Make test attributes explicit. Test models have 'associations' support. (@bf4) ### [v0.10.4 (2017-01-06)](https://github.com/rails-api/active_model_serializers/compare/v0.10.3...v0.10.4) diff --git a/docs/howto/serialize_poro.md b/docs/howto/serialize_poro.md index fa9c9bf84..20091c52a 100644 --- a/docs/howto/serialize_poro.md +++ b/docs/howto/serialize_poro.md @@ -2,13 +2,16 @@ # How to serialize a Plain-Old Ruby Object (PORO) -When you are first getting started with ActiveModelSerializers, it may seem only `ActiveRecord::Base` objects can be serializable, but pretty much any object can be serializable with ActiveModelSerializers. Here is an example of a PORO that is serializable: +When you are first getting started with ActiveModelSerializers, it may seem only `ActiveRecord::Base` objects can be serializable, +but pretty much any object can be serializable with ActiveModelSerializers. +Here is an example of a PORO that is serializable in most situations: + ```ruby # my_model.rb class MyModel alias :read_attribute_for_serialization :send attr_accessor :id, :name, :level - + def initialize(attributes) @id = attributes[:id] @name = attributes[:name] @@ -21,7 +24,15 @@ class MyModel end ``` -Fortunately, ActiveModelSerializers provides a [`ActiveModelSerializers::Model`](https://github.com/rails-api/active_model_serializers/blob/master/lib/active_model_serializers/model.rb) which you can use in production code that will make your PORO a lot cleaner. The above code now becomes: +The [ActiveModel::Serializer::Lint::Tests](../../lib/active_model/serializer/lint.rb) +define and validate which methods ActiveModelSerializers expects to be implemented. + +An implementation of the complete spec is included either for use or as reference: +[`ActiveModelSerializers::Model`](../../lib/active_model_serializers/model.rb). +You can use in production code that will make your PORO a lot cleaner. + +The above code now becomes: + ```ruby # my_model.rb class MyModel < ActiveModelSerializers::Model @@ -29,4 +40,6 @@ class MyModel < ActiveModelSerializers::Model end ``` -The default serializer would be `MyModelSerializer`. \ No newline at end of file +The default serializer would be `MyModelSerializer`. + +For more information, see [README: What does a 'serializable resource' look like?](../../README.md#what-does-a-serializable-resource-look-like). diff --git a/lib/active_model_serializers/model.rb b/lib/active_model_serializers/model.rb index 0bdeda21b..b61661bc1 100644 --- a/lib/active_model_serializers/model.rb +++ b/lib/active_model_serializers/model.rb @@ -1,16 +1,40 @@ -# ActiveModelSerializers::Model is a convenient -# serializable class to inherit from when making -# serializable non-activerecord objects. +# ActiveModelSerializers::Model is a convenient superclass for making your models +# from Plain-Old Ruby Objects (PORO). It also serves as a reference implementation +# that satisfies ActiveModel::Serializer::Lint::Tests. module ActiveModelSerializers class Model include ActiveModel::Serializers::JSON include ActiveModel::Model - class_attribute :attribute_names + # Declare names of attributes to be included in +sttributes+ hash. + # Is only available as a class-method since the ActiveModel::Serialization mixin in Rails + # uses an +attribute_names+ local variable, which may conflict if we were to add instance methods here. + # + # @overload attribute_names + # @return [Array] + class_attribute :attribute_names, instance_writer: false, instance_reader: false # Initialize +attribute_names+ for all subclasses. The array is usually # mutated in the +attributes+ method, but can be set directly, as well. self.attribute_names = [] + # Easily declare instance attributes with setters and getters for each. + # + # All attributes to initialize an instance must have setters. + # However, the hash turned by +attributes+ instance method will ALWAYS + # be the value of the initial attributes, regardless of what accessors are defined. + # The only way to change the change the attributes after initialization is + # to mutate the +attributes+ directly. + # Accessor methods do NOT mutate the attributes. (This is a bug). + # + # @note For now, the Model only supports the notion of 'attributes'. + # In the tests, there is a special Model that also supports 'associations'. This is + # important so that we can add accessors for values that should not appear in the + # attributes hash when modeling associations. It is not yet clear if it + # makes sense for a PORO to have associations outside of the tests. + # + # @overload attributes(names) + # @param names [Array] + # @param name [String, Symbol] def self.attributes(*names) self.attribute_names |= names.map(&:to_sym) # Silence redefinition of methods warnings @@ -19,31 +43,90 @@ def self.attributes(*names) end end + # Opt-in to breaking change + def self.derive_attributes_from_names_and_fix_accessors + unless included_modules.include?(DeriveAttributesFromNamesAndFixAccessors) + prepend(DeriveAttributesFromNamesAndFixAccessors) + end + end + + module DeriveAttributesFromNamesAndFixAccessors + def self.included(base) + # NOTE that +id+ will always be in +attributes+. + base.attributes :id + end + + # Override the initialize method so that attributes aren't processed. + # + # @param attributes [Hash] + def initialize(attributes = {}) + @errors = ActiveModel::Errors.new(self) + super + end + + # Override the +attributes+ method so that the hash is derived from +attribute_names+. + # + # The the fields in +attribute_names+ determines the returned hash. + # +attributes+ are returned frozen to prevent any expectations that mutation affects + # the actual values in the model. + def attributes + self.class.attribute_names.each_with_object({}) do |attribute_name, result| + result[attribute_name] = public_send(attribute_name).freeze + end.with_indifferent_access.freeze + end + end + + # Support for validation and other ActiveModel::Errors + # @return [ActiveModel::Errors] attr_reader :errors - # NOTE that +updated_at+ isn't included in +attribute_names+, - # which means it won't show up in +attributes+ unless a subclass has - # either attributes :updated_at which will redefine the methods - # or attribute_names << :updated_at. + + # (see #updated_at) attr_writer :updated_at - # NOTE that +id+ will always be in +attributes+. - attributes :id + # The only way to change the attributes of an instance is to directly mutate the attributes. + # @example + # + # model.attributes[:foo] = :bar + # @return [Hash] + attr_reader :attributes + + # @param attributes [Hash] def initialize(attributes = {}) + attributes ||= {} # protect against nil + @attributes = attributes.symbolize_keys.with_indifferent_access @errors = ActiveModel::Errors.new(self) super end - # The the fields in +attribute_names+ determines the returned hash. - # +attributes+ are returned frozen to prevent any expectations that mutation affects - # the actual values in the model. - def attributes - attribute_names.each_with_object({}) do |attribute_name, result| - result[attribute_name] = public_send(attribute_name).freeze - end.with_indifferent_access.freeze + # Defaults to the downcased model name. + # This probably isn't a good default, since it's not a unique instance identifier, + # but that's what is currently implemented \_('-')_/. + # + # @note Though +id+ is defined, it will only show up + # in +attributes+ when it is passed in to the initializer or added to +attributes+, + # such as attributes[:id] = 5. + # @return [String, Numeric, Symbol] + def id + attributes.fetch(:id) do + defined?(@id) ? @id : self.class.model_name.name && self.class.model_name.name.downcase + end + end + + # When not set, defaults to the time the file was modified. + # + # @note Though +updated_at+ and +updated_at=+ are defined, it will only show up + # in +attributes+ when it is passed in to the initializer or added to +attributes+, + # such as attributes[:updated_at] = Time.current. + # @return [String, Numeric, Time] + def updated_at + attributes.fetch(:updated_at) do + defined?(@updated_at) ? @updated_at : File.mtime(__FILE__) + end end # To customize model behavior, this method must be redefined. However, # there are other ways of setting the +cache_key+ a serializer uses. + # @return [String] def cache_key ActiveSupport::Cache.expand_cache_key([ self.class.model_name.name.downcase, @@ -51,12 +134,6 @@ def cache_key ].compact) end - # When no set, defaults to the time the file was modified. - # See NOTE by attr_writer :updated_at - def updated_at - defined?(@updated_at) ? @updated_at : File.mtime(__FILE__) - end - # The following methods are needed to be minimally implemented for ActiveModel::Errors # :nocov: def self.human_attribute_name(attr, _options = {}) diff --git a/test/action_controller/adapter_selector_test.rb b/test/action_controller/adapter_selector_test.rb index 6f22aae25..db93573b4 100644 --- a/test/action_controller/adapter_selector_test.rb +++ b/test/action_controller/adapter_selector_test.rb @@ -3,6 +3,15 @@ module ActionController module Serialization class AdapterSelectorTest < ActionController::TestCase + class Profile < Model + attributes :id, :name, :description + associations :comments + end + class ProfileSerializer < ActiveModel::Serializer + type 'profiles' + attributes :name, :description + end + class AdapterSelectorTestController < ActionController::Base def render_using_default_adapter @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') diff --git a/test/action_controller/namespace_lookup_test.rb b/test/action_controller/namespace_lookup_test.rb index f40cca11d..b5c8f496d 100644 --- a/test/action_controller/namespace_lookup_test.rb +++ b/test/action_controller/namespace_lookup_test.rb @@ -4,7 +4,7 @@ module ActionController module Serialization class NamespaceLookupTest < ActionController::TestCase class Book < ::Model - attributes :title, :body + attributes :id, :title, :body associations :writer, :chapters end class Chapter < ::Model @@ -86,7 +86,7 @@ def explicit_namespace_as_string book = Book.new(title: 'New Post', body: 'Body') # because this is a string, ruby can't auto-lookup the constant, so otherwise - # the looku things we mean ::Api::V2 + # the lookup thinks we mean ::Api::V2 render json: book, namespace: 'ActionController::Serialization::NamespaceLookupTest::Api::V2' end @@ -94,7 +94,7 @@ def explicit_namespace_as_symbol book = Book.new(title: 'New Post', body: 'Body') # because this is a string, ruby can't auto-lookup the constant, so otherwise - # the looku things we mean ::Api::V2 + # the lookup thinks we mean ::Api::V2 render json: book, namespace: :'ActionController::Serialization::NamespaceLookupTest::Api::V2' end diff --git a/test/active_model_serializers/model_test.rb b/test/active_model_serializers/model_test.rb index c2f316479..6a8a29afb 100644 --- a/test/active_model_serializers/model_test.rb +++ b/test/active_model_serializers/model_test.rb @@ -24,21 +24,56 @@ def test_attributes_can_be_read_for_serialization attributes :one, :two, :three end original_attributes = { one: 1, two: 2, three: 3 } - instance = klass.new(original_attributes) + original_instance = klass.new(original_attributes) # Initial value - expected_attributes = { id: nil, one: 1, two: 2, three: 3 }.with_indifferent_access + instance = original_instance + expected_attributes = { one: 1, two: 2, three: 3 }.with_indifferent_access assert_equal expected_attributes, instance.attributes assert_equal 1, instance.one assert_equal 1, instance.read_attribute_for_serialization(:one) - # Change via accessor + # FIXME: Change via accessor has no effect on attributes. + instance = original_instance.dup instance.one = :not_one + assert_equal expected_attributes, instance.attributes + assert_equal :not_one, instance.one + assert_equal :not_one, instance.read_attribute_for_serialization(:one) + + # FIXME: Change via mutating attributes + instance = original_instance.dup + instance.attributes[:one] = :not_one + expected_attributes = { one: :not_one, two: 2, three: 3 }.with_indifferent_access + assert_equal expected_attributes, instance.attributes + assert_equal 1, instance.one + assert_equal 1, instance.read_attribute_for_serialization(:one) + end + + def test_attributes_can_be_read_for_serialization_with_attributes_accessors_fix + klass = Class.new(ActiveModelSerializers::Model) do + derive_attributes_from_names_and_fix_accessors + attributes :one, :two, :three + end + original_attributes = { one: 1, two: 2, three: 3 } + original_instance = klass.new(original_attributes) + + # Initial value + instance = original_instance + expected_attributes = { one: 1, two: 2, three: 3 }.with_indifferent_access + assert_equal expected_attributes, instance.attributes + assert_equal 1, instance.one + assert_equal 1, instance.read_attribute_for_serialization(:one) - expected_attributes = { id: nil, one: :not_one, two: 2, three: 3 }.with_indifferent_access + expected_attributes = { one: :not_one, two: 2, three: 3 }.with_indifferent_access + # Change via accessor + instance = original_instance.dup + instance.one = :not_one assert_equal expected_attributes, instance.attributes assert_equal :not_one, instance.one assert_equal :not_one, instance.read_attribute_for_serialization(:one) + + # Attributes frozen + assert instance.attributes.frozen? end def test_id_attribute_can_be_read_for_serialization @@ -47,21 +82,59 @@ def test_id_attribute_can_be_read_for_serialization end self.class.const_set(:SomeTestModel, klass) original_attributes = { id: :ego, one: 1, two: 2, three: 3 } - instance = klass.new(original_attributes) + original_instance = klass.new(original_attributes) # Initial value + instance = original_instance.dup expected_attributes = { id: :ego, one: 1, two: 2, three: 3 }.with_indifferent_access assert_equal expected_attributes, instance.attributes - assert_equal 1, instance.one - assert_equal 1, instance.read_attribute_for_serialization(:one) + assert_equal :ego, instance.id + assert_equal :ego, instance.read_attribute_for_serialization(:id) - # Change via accessor + # FIXME: Change via accessor has no effect on attributes. + instance = original_instance.dup instance.id = :superego + assert_equal expected_attributes, instance.attributes + assert_equal :superego, instance.id + assert_equal :superego, instance.read_attribute_for_serialization(:id) + # FIXME: Change via mutating attributes + instance = original_instance.dup + instance.attributes[:id] = :superego expected_attributes = { id: :superego, one: 1, two: 2, three: 3 }.with_indifferent_access assert_equal expected_attributes, instance.attributes + assert_equal :ego, instance.id + assert_equal :ego, instance.read_attribute_for_serialization(:id) + ensure + self.class.send(:remove_const, :SomeTestModel) + end + + def test_id_attribute_can_be_read_for_serialization_with_attributes_accessors_fix + klass = Class.new(ActiveModelSerializers::Model) do + derive_attributes_from_names_and_fix_accessors + attributes :id, :one, :two, :three + end + self.class.const_set(:SomeTestModel, klass) + original_attributes = { id: :ego, one: 1, two: 2, three: 3 } + original_instance = klass.new(original_attributes) + + # Initial value + instance = original_instance.dup + expected_attributes = { id: :ego, one: 1, two: 2, three: 3 }.with_indifferent_access + assert_equal expected_attributes, instance.attributes + assert_equal :ego, instance.id + assert_equal :ego, instance.read_attribute_for_serialization(:id) + + expected_attributes = { id: :superego, one: 1, two: 2, three: 3 }.with_indifferent_access + # Change via accessor + instance = original_instance.dup + instance.id = :superego + assert_equal expected_attributes, instance.attributes assert_equal :superego, instance.id assert_equal :superego, instance.read_attribute_for_serialization(:id) + + # Attributes frozen + assert instance.attributes.frozen? ensure self.class.send(:remove_const, :SomeTestModel) end diff --git a/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb b/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb index 37452955f..30542408f 100644 --- a/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +++ b/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb @@ -44,7 +44,7 @@ def assert_parses(expected, actual, headers = {}) def define_author_model_and_serializer TestController.const_set(:Author, Class.new(ActiveModelSerializers::Model) do - attributes :name + attributes :id, :name end) TestController.const_set(:AuthorSerializer, Class.new(ActiveModel::Serializer) do type 'users' diff --git a/test/adapter/attributes_test.rb b/test/adapter/attributes_test.rb index dee0c7753..e60019f50 100644 --- a/test/adapter/attributes_test.rb +++ b/test/adapter/attributes_test.rb @@ -3,11 +3,8 @@ module ActiveModelSerializers module Adapter class AttributesTest < ActiveSupport::TestCase - class Person - include ActiveModel::Model - include ActiveModel::Serialization - - attr_accessor :first_name, :last_name + class Person < ActiveModelSerializers::Model + attributes :first_name, :last_name end class PersonSerializer < ActiveModel::Serializer diff --git a/test/adapter/json/has_many_test.rb b/test/adapter/json/has_many_test.rb index 3f6fa546e..feeec93c3 100644 --- a/test/adapter/json/has_many_test.rb +++ b/test/adapter/json/has_many_test.rb @@ -4,6 +4,10 @@ module ActiveModelSerializers module Adapter class Json class HasManyTestTest < ActiveSupport::TestCase + class ModelWithoutSerializer < ::Model + attributes :id, :name + end + def setup ActionController::Base.cache_store.clear @author = Author.new(id: 1, name: 'Steve K.') @@ -16,7 +20,7 @@ def setup @second_comment.post = @post @blog = Blog.new(id: 1, name: 'My Blog!!') @post.blog = @blog - @tag = Tag.new(id: 1, name: '#hash_tag') + @tag = ModelWithoutSerializer.new(id: 1, name: '#hash_tag') @post.tags = [@tag] end @@ -30,7 +34,11 @@ def test_has_many end def test_has_many_with_no_serializer - serializer = PostWithTagsSerializer.new(@post) + post_serializer_class = Class.new(ActiveModel::Serializer) do + attributes :id + has_many :tags + end + serializer = post_serializer_class.new(@post) adapter = ActiveModelSerializers::Adapter::Json.new(serializer) assert_equal({ id: 42, diff --git a/test/adapter/json_api/has_many_test.rb b/test/adapter/json_api/has_many_test.rb index 9da73af98..a9fa9ac92 100644 --- a/test/adapter/json_api/has_many_test.rb +++ b/test/adapter/json_api/has_many_test.rb @@ -4,6 +4,10 @@ module ActiveModelSerializers module Adapter class JsonApi class HasManyTest < ActiveSupport::TestCase + class ModelWithoutSerializer < ::Model + attributes :id, :name + end + def setup ActionController::Base.cache_store.clear @author = Author.new(id: 1, name: 'Steve K.') @@ -26,7 +30,7 @@ def setup @blog.articles = [@post] @post.blog = @blog @post_without_comments.blog = nil - @tag = Tag.new(id: 1, name: '#hash_tag') + @tag = ModelWithoutSerializer.new(id: 1, name: '#hash_tag') @post.tags = [@tag] @serializer = PostSerializer.new(@post) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer) @@ -129,7 +133,11 @@ def test_include_type_for_association_when_different_than_name end def test_has_many_with_no_serializer - serializer = PostWithTagsSerializer.new(@post) + post_serializer_class = Class.new(ActiveModel::Serializer) do + attributes :id + has_many :tags + end + serializer = post_serializer_class.new(@post) adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer) assert_equal({ diff --git a/test/adapter/json_api/include_data_if_sideloaded_test.rb b/test/adapter/json_api/include_data_if_sideloaded_test.rb index b5c818693..c0da94886 100644 --- a/test/adapter/json_api/include_data_if_sideloaded_test.rb +++ b/test/adapter/json_api/include_data_if_sideloaded_test.rb @@ -14,11 +14,21 @@ def all [{ foo: 'bar' }] end end + class Tag < ::Model + attributes :id, :name + end class TagSerializer < ActiveModel::Serializer + type 'tags' attributes :id, :name end + class PostWithTagsSerializer < ActiveModel::Serializer + type 'posts' + attributes :id + has_many :tags + end + class IncludeParamAuthorSerializer < ActiveModel::Serializer class_attribute :comment_loader diff --git a/test/cache_test.rb b/test/cache_test.rb index 06043fb7f..f09589314 100644 --- a/test/cache_test.rb +++ b/test/cache_test.rb @@ -4,6 +4,20 @@ module ActiveModelSerializers class CacheTest < ActiveSupport::TestCase + class Article < ::Model + attributes :title + # To confirm error is raised when cache_key is not set and cache_key option not passed to cache + undef_method :cache_key + end + class ArticleSerializer < ActiveModel::Serializer + cache only: [:place], skip_digest: true + attributes :title + end + + class Author < ::Model + attributes :id, :name + associations :posts, :bio, :roles + end # Instead of a primitive cache key (i.e. a string), this class # returns a list of objects that require to be expanded themselves. class AuthorWithExpandableCacheElements < Author @@ -27,30 +41,32 @@ def cache_key ] end end - class UncachedAuthor < Author # To confirm cache_key is set using updated_at and cache_key option passed to cache undef_method :cache_key end + class AuthorSerializer < ActiveModel::Serializer + cache key: 'writer', skip_digest: true + attributes :id, :name - class Article < ::Model - attributes :title - # To confirm error is raised when cache_key is not set and cache_key option not passed to cache - undef_method :cache_key + has_many :posts + has_many :roles + has_one :bio end - class ArticleSerializer < ActiveModel::Serializer - cache only: [:place], skip_digest: true - attributes :title + class Blog < ::Model + attributes :name + associations :writer end + class BlogSerializer < ActiveModel::Serializer + cache key: 'blog' + attributes :id, :name - class InheritedRoleSerializer < RoleSerializer - cache key: 'inherited_role', only: [:name, :special_attribute] - attribute :special_attribute + belongs_to :writer end class Comment < ::Model - attributes :body + attributes :id, :body associations :post, :author # Uses a custom non-time-based cache key @@ -58,14 +74,52 @@ def cache_key "comment/#{id}" end end + class CommentSerializer < ActiveModel::Serializer + cache expires_in: 1.day, skip_digest: true + attributes :id, :body + belongs_to :post + belongs_to :author + end + + class Post < ::Model + attributes :id, :title, :body + associations :author, :comments, :blog + end + class PostSerializer < ActiveModel::Serializer + cache key: 'post', expires_in: 0.1, skip_digest: true + attributes :id, :title, :body + + has_many :comments + belongs_to :blog + belongs_to :author + end + + class Role < ::Model + attributes :name, :description, :special_attribute + associations :author + end + class RoleSerializer < ActiveModel::Serializer + cache only: [:name, :slug], skip_digest: true + attributes :id, :name, :description + attribute :friendly_id, key: :slug + belongs_to :author + + def friendly_id + "#{object.name}-#{object.id}" + end + end + class InheritedRoleSerializer < RoleSerializer + cache key: 'inherited_role', only: [:name, :special_attribute] + attribute :special_attribute + end setup do cache_store.clear @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') - @post = Post.new(title: 'New Post', body: 'Body') + @post = Post.new(id: 'post', title: 'New Post', body: 'Body') @bio = Bio.new(id: 1, content: 'AMS Contributor') - @author = Author.new(name: 'Joao M. D. Moura') - @blog = Blog.new(id: 999, name: 'Custom blog', writer: @author, articles: []) + @author = Author.new(id: 'author', name: 'Joao M. D. Moura') + @blog = Blog.new(id: 999, name: 'Custom blog', writer: @author) @role = Role.new(name: 'Great Author') @location = Location.new(lat: '-23.550520', lng: '-46.633309') @place = Place.new(name: 'Amazing Place') @@ -325,12 +379,14 @@ def test_a_serializer_rendered_by_two_adapter_returns_differently_fetch_attribut def test_uses_file_digest_in_cache_key render_object_with_cache(@blog) - key = "#{@blog.cache_key}/#{adapter.cache_key}/#{::Model::FILE_DIGEST}" + file_digest = Digest::MD5.hexdigest(File.open(__FILE__).read) + key = "#{@blog.cache_key}/#{adapter.cache_key}/#{file_digest}" assert_equal(@blog_serializer.attributes, cache_store.fetch(key)) end def test_cache_digest_definition - assert_equal(::Model::FILE_DIGEST, @post_serializer.class._cache_digest) + file_digest = Digest::MD5.hexdigest(File.open(__FILE__).read) + assert_equal(file_digest, @post_serializer.class._cache_digest) end def test_object_cache_keys diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 3c804ccca..6245ad23d 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -1,10 +1,7 @@ class Model < ActiveModelSerializers::Model - FILE_DIGEST = Digest::MD5.hexdigest(File.open(__FILE__).read) + rand(2).zero? && derive_attributes_from_names_and_fix_accessors - # Defaults to the downcased model name. - def id - @id ||= self.class.model_name.name.downcase - end + attr_writer :id # At this time, just for organization of intent class_attribute :association_names @@ -23,6 +20,10 @@ def associations result[association_name] = public_send(association_name).freeze end.with_indifferent_access.freeze end + + def attributes + super.except(*association_names) + end end # see @@ -107,10 +108,6 @@ class PostPreviewSerializer < ActiveModel::Serializer has_many :comments, serializer: ::CommentPreviewSerializer belongs_to :author, serializer: ::AuthorPreviewSerializer end -class PostWithTagsSerializer < ActiveModel::Serializer - attributes :id - has_many :tags -end class PostWithCustomKeysSerializer < ActiveModel::Serializer attributes :id has_many :comments, key: :reviews @@ -207,10 +204,6 @@ class UnrelatedLinkSerializer < ActiveModel::Serializer end end -class Tag < Model - attributes :name -end - class VirtualValue < Model; end class VirtualValueSerializer < ActiveModel::Serializer attributes :id diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb index 6d6447c35..90d213dca 100644 --- a/test/serializers/associations_test.rb +++ b/test/serializers/associations_test.rb @@ -2,13 +2,17 @@ module ActiveModel class Serializer class AssociationsTest < ActiveSupport::TestCase + class ModelWithoutSerializer < ::Model + attributes :id, :name + end + def setup @author = Author.new(name: 'Steve K.') @author.bio = nil @author.roles = [] @blog = Blog.new(name: 'AMS Blog') @post = Post.new(title: 'New Post', body: 'Body') - @tag = Tag.new(id: 'tagid', name: '#hashtagged') + @tag = ModelWithoutSerializer.new(id: 'tagid', name: '#hashtagged') @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @post.comments = [@comment] @post.tags = [@tag] @@ -46,7 +50,11 @@ def test_has_many_and_has_one end def test_has_many_with_no_serializer - PostWithTagsSerializer.new(@post).associations.each do |association| + post_serializer_class = Class.new(ActiveModel::Serializer) do + attributes :id + has_many :tags + end + post_serializer_class.new(@post).associations.each do |association| key = association.key serializer = association.serializer options = association.options From 68f8ebedf42d3000112322760b4f0a99c09b343e Mon Sep 17 00:00:00 2001 From: MSathieu Date: Tue, 17 Jan 2017 17:43:22 +0100 Subject: [PATCH 26/43] Update logging.md --- docs/general/logging.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/general/logging.md b/docs/general/logging.md index 306bfd506..9fba8c28b 100644 --- a/docs/general/logging.md +++ b/docs/general/logging.md @@ -12,3 +12,9 @@ You may customize the logger in an initializer, for example: ```ruby ActiveModelSerializers.logger = Logger.new(STDOUT) ``` + +You can also disable the logger, just put this in `config/application.rb`: + +```ruby +ActiveSupport::Notifications.unsubscribe(ActiveModelSerializers::Logging::RENDER_EVENT) +``` From 3d44bfcf2880f7b2a5d2ad0f4262397bfbfef01a Mon Sep 17 00:00:00 2001 From: MSathieu Date: Tue, 17 Jan 2017 18:21:21 +0100 Subject: [PATCH 27/43] Update logging.md --- docs/general/logging.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/general/logging.md b/docs/general/logging.md index 9fba8c28b..1a2de8000 100644 --- a/docs/general/logging.md +++ b/docs/general/logging.md @@ -13,7 +13,7 @@ You may customize the logger in an initializer, for example: ActiveModelSerializers.logger = Logger.new(STDOUT) ``` -You can also disable the logger, just put this in `config/application.rb`: +You can also disable the logger, just put this in `config/initializers/active_model_serializers.rb`: ```ruby ActiveSupport::Notifications.unsubscribe(ActiveModelSerializers::Logging::RENDER_EVENT) From ab98c4a664f26077e5b3c90ea6bcbe129ec2d0b9 Mon Sep 17 00:00:00 2001 From: MSathieu Date: Sun, 22 Jan 2017 13:14:19 +0100 Subject: [PATCH 28/43] Update logging.md --- docs/general/logging.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/general/logging.md b/docs/general/logging.md index 1a2de8000..321bf5d8b 100644 --- a/docs/general/logging.md +++ b/docs/general/logging.md @@ -16,5 +16,6 @@ ActiveModelSerializers.logger = Logger.new(STDOUT) You can also disable the logger, just put this in `config/initializers/active_model_serializers.rb`: ```ruby +require 'active_model_serializers' ActiveSupport::Notifications.unsubscribe(ActiveModelSerializers::Logging::RENDER_EVENT) ``` From bd50ae9ada686f179e8f06a497bfe948a7201e53 Mon Sep 17 00:00:00 2001 From: MSathieu Date: Mon, 23 Jan 2017 07:24:41 +0100 Subject: [PATCH 29/43] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b025a61bb..9526d3095 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Misc: - [#2021](https://github.com/rails-api/active_model_serializers/pull/2021) Make test attributes explicit. Tests have Model#associations. (@bf4) - [#1981](https://github.com/rails-api/active_model_serializers/pull/1981) Fix relationship link documentation. (@groyoh) +- [#2035](https://github.com/rails-api/active_model_serializers/pull/2035) Document how to disable the logger. (@MSathieu) ### [v0.10.4 (2017-01-06)](https://github.com/rails-api/active_model_serializers/compare/v0.10.3...v0.10.4) From d6b1b1c81f4cbad4d17b63ac0ced83e0d4c185cd Mon Sep 17 00:00:00 2001 From: Igor Zubkov Date: Tue, 24 Jan 2017 13:27:20 +0200 Subject: [PATCH 30/43] Fix typo --- docs/howto/add_pagination_links.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/add_pagination_links.md b/docs/howto/add_pagination_links.md index 7c486fbd2..eec974cff 100644 --- a/docs/howto/add_pagination_links.md +++ b/docs/howto/add_pagination_links.md @@ -117,7 +117,7 @@ ex. You can also achieve the same result if you have a helper method that adds the pagination info in the meta tag. For instance, in your action specify a custom serializer. ```ruby -render json: @posts, each_serializer: PostPreviewSerializer, meta: meta_attributes(@post) +render json: @posts, each_serializer: PostPreviewSerializer, meta: meta_attributes(@posts) ``` ```ruby From 3c6eb57ee945bcde7857f575ea3895e19302269f Mon Sep 17 00:00:00 2001 From: Igor Zubkov Date: Tue, 24 Jan 2017 13:28:50 +0200 Subject: [PATCH 31/43] Replace object with collection. Replace resource with collection. --- docs/howto/add_pagination_links.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/howto/add_pagination_links.md b/docs/howto/add_pagination_links.md index eec974cff..69d290c2f 100644 --- a/docs/howto/add_pagination_links.md +++ b/docs/howto/add_pagination_links.md @@ -77,13 +77,13 @@ If you are using `JSON` adapter, pagination links will not be included automatic Add this method to your base API controller. ```ruby -def pagination_dict(object) +def pagination_dict(collection) { - current_page: object.current_page, - next_page: object.next_page, - prev_page: object.prev_page, # use object.previous_page when using will_paginate - total_pages: object.total_pages, - total_count: object.total_count + current_page: collection.current_page, + next_page: collection.next_page, + prev_page: collection.prev_page, # use collection.previous_page when using will_paginate + total_pages: collection.total_pages, + total_count: collection.total_count } end ``` @@ -122,13 +122,13 @@ render json: @posts, each_serializer: PostPreviewSerializer, meta: meta_attribut ```ruby #expects pagination! -def meta_attributes(resource, extra_meta = {}) +def meta_attributes(collection, extra_meta = {}) { - current_page: resource.current_page, - next_page: resource.next_page, - prev_page: resource.prev_page, # use resource.previous_page when using will_paginate - total_pages: resource.total_pages, - total_count: resource.total_count + current_page: collection.current_page, + next_page: collection.next_page, + prev_page: collection.prev_page, # use collection.previous_page when using will_paginate + total_pages: collection.total_pages, + total_count: collection.total_count }.merge(extra_meta) end ``` From 775ad66ffd1bbe0806ed7a7eb5831858da273cb0 Mon Sep 17 00:00:00 2001 From: Igor Zubkov Date: Tue, 24 Jan 2017 15:12:25 +0200 Subject: [PATCH 32/43] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b025a61bb..ad6232e2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ Misc: - [#2021](https://github.com/rails-api/active_model_serializers/pull/2021) Make test attributes explicit. Tests have Model#associations. (@bf4) - [#1981](https://github.com/rails-api/active_model_serializers/pull/1981) Fix relationship link documentation. (@groyoh) +- [#2039](https://github.com/rails-api/active_model_serializers/pull/2039) Documentation fixes. (@biow0lf) ### [v0.10.4 (2017-01-06)](https://github.com/rails-api/active_model_serializers/compare/v0.10.3...v0.10.4) From 9ccdb15610a200870135635fd346e895916a0096 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Thu, 26 Jan 2017 13:52:46 -0600 Subject: [PATCH 33/43] Fix JRuby build on CI, with a suggestion from Travis CI support (#2040) * Fix JRuby build on CI, with a suggestion from Travis CI support per https://github.com/hanami/helpers/pull/97/commits/13f30e287c315f93141c2da005d4f08dec0d16dc per https://twitter.com/jodosha/status/823522145745731586 --- .travis.yml | 10 +++++++--- appveyor.yml | 12 +++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0cd358e43..9aff1edcb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,16 @@ rvm: - 2.2.6 - 2.3.3 - ruby-head - - jruby-9.0.4.0 + - jruby-9.1.5.0 # is precompiled per http://rubies.travis-ci.org/ - jruby-head jdk: - oraclejdk8 +before_install: + - gem update --system + - rvm @global do gem uninstall bundler -a -x + - rvm @global do gem install bundler -v 1.13.7 install: bundle install --path=vendor/bundle --retry=3 --jobs=3 cache: directories: @@ -35,13 +39,13 @@ matrix: exclude: - rvm: 2.1 env: RAILS_VERSION=master - - rvm: jruby-9.0.4.0 + - rvm: jruby-9.1.5.0 env: RAILS_VERSION=master - rvm: jruby-head env: RAILS_VERSION=master - rvm: 2.1 env: RAILS_VERSION=5.0 - - rvm: jruby-9.0.4.0 + - rvm: jruby-9.1.5.0 env: RAILS_VERSION=5.0 - rvm: jruby-head env: RAILS_VERSION=5.0 diff --git a/appveyor.yml b/appveyor.yml index 9cd4fd0da..7ecfa13ad 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: '{build}' +version: 1.0.{build}-{branch} skip_tags: true @@ -7,17 +7,23 @@ environment: matrix: - ruby_version: "Ruby21" - ruby_version: "Ruby21-x64" - - ruby_version: "jruby-9.0.0.0" cache: - vendor/bundle install: - SET PATH=C:\%ruby_version%\bin;%PATH% - - gem install bundler + - gem update --system + - gem uninstall bundler -a -x + - gem install bundler -v 1.13.7 - bundle env - bundle install --path=vendor/bundle --retry=3 --jobs=3 +before_test: + - ruby -v + - gem -v + - bundle -v + test_script: - bundle exec rake ci From 28c1b5bef639f592e0359ed38e9ee5c3cf0091fa Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Thu, 19 Jan 2017 16:07:47 -0600 Subject: [PATCH 34/43] Document Model delcared attributes --- docs/howto/serialize_poro.md | 28 +++++++++++++++++++++++++++ lib/active_model_serializers/model.rb | 8 -------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/docs/howto/serialize_poro.md b/docs/howto/serialize_poro.md index 20091c52a..3b98267c7 100644 --- a/docs/howto/serialize_poro.md +++ b/docs/howto/serialize_poro.md @@ -42,4 +42,32 @@ end The default serializer would be `MyModelSerializer`. +*IMPORTANT*: There is a surprising behavior (bug) in the current implementation of ActiveModelSerializers::Model that +prevents an accessor from modifying attributes on the instance. The fix for this bug +is a breaking change, so we have made an opt-in configuration. + +New applications should set: + +```ruby +ActiveModelSerializers::Model.derive_attributes_from_names_and_fix_accessors +``` + +Existing applications can use the fix *and* avoid breaking changes +by making a superclass for new models. For example: + +```ruby +class SerializablePoro < ActiveModelSerializers::Model + derive_attributes_from_names_and_fix_accessors +end +``` + +So that `MyModel` above would inherit from `SerializablePoro`. + +`derive_attributes_from_names_and_fix_accessors` prepends the `DeriveAttributesFromNamesAndFixAccessors` +module and does the following: + +- `id` will *always* be in the attributes. (This is until we separate out the caching requirement for POROs.) +- Overwrites the `attributes` method to that it only returns declared attributes. + `attributes` will now be a frozen hash with indifferent access. + For more information, see [README: What does a 'serializable resource' look like?](../../README.md#what-does-a-serializable-resource-look-like). diff --git a/lib/active_model_serializers/model.rb b/lib/active_model_serializers/model.rb index b61661bc1..7e05ffe65 100644 --- a/lib/active_model_serializers/model.rb +++ b/lib/active_model_serializers/model.rb @@ -56,14 +56,6 @@ def self.included(base) base.attributes :id end - # Override the initialize method so that attributes aren't processed. - # - # @param attributes [Hash] - def initialize(attributes = {}) - @errors = ActiveModel::Errors.new(self) - super - end - # Override the +attributes+ method so that the hash is derived from +attribute_names+. # # The the fields in +attribute_names+ determines the returned hash. From 7b9d71e99b1d78936d1e5de542b651efd4d882bf Mon Sep 17 00:00:00 2001 From: Leonel Galan Date: Mon, 6 Feb 2017 14:58:36 -0500 Subject: [PATCH 35/43] Fixes bug in Test::Schema when using filter_parameters --- lib/active_model_serializers/test/schema.rb | 4 ++-- test/active_model_serializers/test/schema_test.rb | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/active_model_serializers/test/schema.rb b/lib/active_model_serializers/test/schema.rb index 1f4dccc0c..a00015869 100644 --- a/lib/active_model_serializers/test/schema.rb +++ b/lib/active_model_serializers/test/schema.rb @@ -60,11 +60,11 @@ def call attr_reader :document_store def controller_path - request.filtered_parameters[:controller] + request.filtered_parameters.with_indifferent_access[:controller] end def action - request.filtered_parameters[:action] + request.filtered_parameters.with_indifferent_access[:action] end def schema_directory diff --git a/test/active_model_serializers/test/schema_test.rb b/test/active_model_serializers/test/schema_test.rb index 105ac575d..2a465114d 100644 --- a/test/active_model_serializers/test/schema_test.rb +++ b/test/active_model_serializers/test/schema_test.rb @@ -1,5 +1,7 @@ require 'test_helper' +Rails.application.config.filter_parameters += [:password] + module ActiveModelSerializers module Test class SchemaTest < ActionController::TestCase From e7c79b1f4983019c7eee38fd8546483a2e770a5d Mon Sep 17 00:00:00 2001 From: Leonel Galan Date: Mon, 6 Feb 2017 15:52:53 -0500 Subject: [PATCH 36/43] Move Rails.application.config into configure block for test rails app. --- test/active_model_serializers/test/schema_test.rb | 2 -- test/support/rails_app.rb | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/active_model_serializers/test/schema_test.rb b/test/active_model_serializers/test/schema_test.rb index 2a465114d..105ac575d 100644 --- a/test/active_model_serializers/test/schema_test.rb +++ b/test/active_model_serializers/test/schema_test.rb @@ -1,7 +1,5 @@ require 'test_helper' -Rails.application.config.filter_parameters += [:password] - module ActiveModelSerializers module Test class SchemaTest < ActionController::TestCase diff --git a/test/support/rails_app.rb b/test/support/rails_app.rb index 0bbae1fe5..43324b78c 100644 --- a/test/support/rails_app.rb +++ b/test/support/rails_app.rb @@ -6,6 +6,8 @@ module ActiveModelSerializers config.active_support.test_order = :random config.action_controller.perform_caching = true config.action_controller.cache_store = :memory_store + + config.filter_parameters += [:password] end app.routes.default_url_options = { host: 'example.com' } From a9d533d916e64c8fb585a3b19c2cbbc8405a6f05 Mon Sep 17 00:00:00 2001 From: Nick Ottrando Date: Wed, 8 Feb 2017 13:06:31 -0800 Subject: [PATCH 37/43] Update outside_controller_use.md (#2047) * Update outside_controller_use.md Provide example for options parameter when serializing a resource. --- docs/howto/outside_controller_use.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/howto/outside_controller_use.md b/docs/howto/outside_controller_use.md index 61d0620e8..cb6d9b5ef 100644 --- a/docs/howto/outside_controller_use.md +++ b/docs/howto/outside_controller_use.md @@ -10,8 +10,8 @@ In ActiveModelSerializers versions 0.10 or later, serializing resources outside # Create our resource post = Post.create(title: "Sample post", body: "I love Active Model Serializers!") -# Optional options parameters -options = {} +# Optional options parameters for both the serializer and instance +options = {serializer: PostDetailedSerializer, username: 'sample user'} # Create a serializable resource instance serializable_resource = ActiveModelSerializers::SerializableResource.new(post, options) @@ -20,6 +20,7 @@ serializable_resource = ActiveModelSerializers::SerializableResource.new(post, o model_json = serializable_resource.as_json ``` The object that is passed to `ActiveModelSerializers::SerializableResource.new` can be a single resource or a collection. +The additional options are the same options that are passed [through controllers](../general/rendering.md#explicit-serializer). ### Looking up the Serializer for a Resource From 006956e56bfc96d5c8abe49f29f9bbaca1e578b3 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Wed, 15 Feb 2017 20:30:28 -0600 Subject: [PATCH 38/43] ActiveModel::Model handles the ActiveModel::Errors API As pointed out in https://github.com/rails-api/active_model_serializers/issues/2049 ActiveModel::Model already extends ActiveModel::Translation which implements human_attribute_name and lookup_ancestors --- lib/active_model_serializers/model.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/active_model_serializers/model.rb b/lib/active_model_serializers/model.rb index 7e05ffe65..e3c86e98b 100644 --- a/lib/active_model_serializers/model.rb +++ b/lib/active_model_serializers/model.rb @@ -125,16 +125,5 @@ def cache_key "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}" ].compact) end - - # The following methods are needed to be minimally implemented for ActiveModel::Errors - # :nocov: - def self.human_attribute_name(attr, _options = {}) - attr - end - - def self.lookup_ancestors - [self] - end - # :nocov: end end From a081e4ffc4779f4a552cfb354e694cd9645e6704 Mon Sep 17 00:00:00 2001 From: Jared Beck Date: Fri, 17 Feb 2017 13:39:21 -0500 Subject: [PATCH 39/43] jsonapi is deprecated, just use jsonapi-renderer From the author of jsonapi: > .. The jsonapi gem was previously just a bundle of jsonapi-serializer and jsonapi-renderer, and AMS is using only a helper class of jsonapi-renderer (namely JSONAPI::IncludeDirective). The AMS dependency was previously not properly pinned to a specific version, which I saw as a risk for many users, so I avoided updating this gem. Moreover, the name jsonapi being somewhat too generic for what this gem evolved into (namely jsonapi-rb, which bundles jsonapi-renderer and jsonapi-parser, along with serializers and deserializers, with tight integrations with various frameworks), I decided to stay away from it for fairness. > TL;DR: Yes, people should use jsonapi-parser and jsonapi-renderer directly (or give a try to jsonapi-rb, depending on their needs). We should also update jsonapi-renderer to the latest, currently 0.1.2, but I thought that should be a separate PR. --- CHANGELOG.md | 2 ++ active_model_serializers.gemspec | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a669bed2..3908d73d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Fixes: Misc: +- [#2055](https://github.com/rails-api/active_model_serializers/pull/2055) + Replace deprecated dependency jsonapi with jsonapi-renderer. (@jaredbeck) - [#2021](https://github.com/rails-api/active_model_serializers/pull/2021) Make test attributes explicit. Tests have Model#associations. (@bf4) - [#1981](https://github.com/rails-api/active_model_serializers/pull/1981) Fix relationship link documentation. (@groyoh) - [#2035](https://github.com/rails-api/active_model_serializers/pull/2035) Document how to disable the logger. (@MSathieu) diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index a1fc0107a..b81be0e10 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -42,7 +42,8 @@ Gem::Specification.new do |spec| # 'minitest' # 'thread_safe' - spec.add_runtime_dependency 'jsonapi', '0.1.1.beta6' + # TODO: Latest jsonapi-renderer is 0.1.2 + spec.add_runtime_dependency 'jsonapi-renderer', '0.1.1.beta1' spec.add_runtime_dependency 'case_transform', '>= 0.2' spec.add_development_dependency 'activerecord', rails_versions From 1005aa60a95aa1af443b8f86dbefc145fccfb316 Mon Sep 17 00:00:00 2001 From: Jared Beck Date: Mon, 20 Feb 2017 01:50:02 -0500 Subject: [PATCH 40/43] Update version constraint for jsonapi-renderer Currently (2017-02-20) the latest version is 0.1.2. Why not use a version constraint like '~> 0.1.1'? Because we know of no reason why 0.1.1.beta1 cannot still be used. That said, we have done no research looking for such a reason. --- CHANGELOG.md | 3 +++ active_model_serializers.gemspec | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3908d73d1..f5644549e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ Breaking changes: Features: - [#2021](https://github.com/rails-api/active_model_serializers/pull/2021) ActiveModelSerializers::Model#attributes. Originally in [#1982](https://github.com/rails-api/active_model_serializers/pull/1982). (@bf4) +- [#2057](https://github.com/rails-api/active_model_serializers/pull/2057) + Update version constraint for jsonapi-renderer to `['>= 0.1.1.beta1', '< 0.2']` + (@jaredbeck) Fixes: diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index b81be0e10..108f166a4 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -42,8 +42,7 @@ Gem::Specification.new do |spec| # 'minitest' # 'thread_safe' - # TODO: Latest jsonapi-renderer is 0.1.2 - spec.add_runtime_dependency 'jsonapi-renderer', '0.1.1.beta1' + spec.add_runtime_dependency 'jsonapi-renderer', ['>= 0.1.1.beta1', '< 0.2'] spec.add_runtime_dependency 'case_transform', '>= 0.2' spec.add_development_dependency 'activerecord', rails_versions From 19f8ada4afce77430c59bab17e0388a6ec8079f7 Mon Sep 17 00:00:00 2001 From: Akiicat Date: Tue, 28 Feb 2017 05:23:19 +0800 Subject: [PATCH 41/43] Update serializers.md --- docs/general/serializers.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/general/serializers.md b/docs/general/serializers.md index 9925744fa..bb6e21be2 100644 --- a/docs/general/serializers.md +++ b/docs/general/serializers.md @@ -225,10 +225,10 @@ With the `:json` adapter, the previous serializer would be rendered as: link :self do href "https://example.com/link_author/#{object.id}" end -link :author { link_author_url(object) } -link :link_authors { link_authors_url } +link(:author) { link_author_url(object) } +link(:link_authors) { link_authors_url } link :other, 'https://example.com/resource' -link :posts { link_author_posts_url(object) } +link(:posts) { link_author_posts_url(object) } ``` #### #object From d48aaefdb250492a2f7607fcb601c449fc0441fc Mon Sep 17 00:00:00 2001 From: Hitabis GmbH Date: Tue, 7 Mar 2017 09:36:05 +0100 Subject: [PATCH 42/43] Remove typo from upgrade from 0.8 to 0.10 docs Typo ActiveMode::Serializer was changed to ActiveModel::Serializer --- docs/howto/upgrade_from_0_8_to_0_10.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/upgrade_from_0_8_to_0_10.md b/docs/howto/upgrade_from_0_8_to_0_10.md index 12303d144..ea51e81c2 100644 --- a/docs/howto/upgrade_from_0_8_to_0_10.md +++ b/docs/howto/upgrade_from_0_8_to_0_10.md @@ -107,7 +107,7 @@ end ``` Add this class to your app however you see fit. This is the class that your existing serializers -that inherit from `ActiveMode::Serializer` should inherit from. +that inherit from `ActiveModel::Serializer` should inherit from. ### 3. Add `ActiveModel::V08::CollectionSerializer` ```ruby From 28b8e3dd175c5bc2a888746c87034c9a26605828 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Tue, 7 Mar 2017 15:41:20 -0600 Subject: [PATCH 43/43] Bump to v0.10.5 --- CHANGELOG.md | 12 +++++++++++- lib/active_model/serializer/version.rb | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5644549e..0f6ea9535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ ## 0.10.x -### [master (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.4...master) +### [master (unreleased)](https://github.com/rails-api/active_model_serializers/compare/v0.10.5...master) + +Breaking changes: + +Features: + +Fixes: + +Misc: + +### [v0.10.5 (2017-03-07)](https://github.com/rails-api/active_model_serializers/compare/v0.10.4...v0.10.5) Breaking changes: diff --git a/lib/active_model/serializer/version.rb b/lib/active_model/serializer/version.rb index b72d23e82..209437da9 100644 --- a/lib/active_model/serializer/version.rb +++ b/lib/active_model/serializer/version.rb @@ -1,5 +1,5 @@ module ActiveModel class Serializer - VERSION = '0.10.4'.freeze + VERSION = '0.10.5'.freeze end end