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
This commit is contained in:
Benjamin Fleischer 2017-01-10 02:28:50 -06:00 committed by GitHub
parent 2a6d373cb2
commit 93ca27fe44
14 changed files with 340 additions and 88 deletions

View File

@ -6,16 +6,16 @@ Breaking changes:
Features: 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: 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: 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) - [#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) ### [v0.10.4 (2017-01-06)](https://github.com/rails-api/active_model_serializers/compare/v0.10.3...v0.10.4)

View File

@ -2,7 +2,10 @@
# How to serialize a Plain-Old Ruby Object (PORO) # 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 ```ruby
# my_model.rb # my_model.rb
class MyModel class MyModel
@ -21,7 +24,15 @@ class MyModel
end 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 ```ruby
# my_model.rb # my_model.rb
class MyModel < ActiveModelSerializers::Model class MyModel < ActiveModelSerializers::Model
@ -30,3 +41,5 @@ end
``` ```
The default serializer would be `MyModelSerializer`. 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).

View File

@ -1,16 +1,40 @@
# ActiveModelSerializers::Model is a convenient # ActiveModelSerializers::Model is a convenient superclass for making your models
# serializable class to inherit from when making # from Plain-Old Ruby Objects (PORO). It also serves as a reference implementation
# serializable non-activerecord objects. # that satisfies ActiveModel::Serializer::Lint::Tests.
module ActiveModelSerializers module ActiveModelSerializers
class Model class Model
include ActiveModel::Serializers::JSON include ActiveModel::Serializers::JSON
include ActiveModel::Model 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<Symbol>]
class_attribute :attribute_names, instance_writer: false, instance_reader: false
# Initialize +attribute_names+ for all subclasses. The array is usually # Initialize +attribute_names+ for all subclasses. The array is usually
# mutated in the +attributes+ method, but can be set directly, as well. # mutated in the +attributes+ method, but can be set directly, as well.
self.attribute_names = [] 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<String, Symbol>]
# @param name [String, Symbol]
def self.attributes(*names) def self.attributes(*names)
self.attribute_names |= names.map(&:to_sym) self.attribute_names |= names.map(&:to_sym)
# Silence redefinition of methods warnings # Silence redefinition of methods warnings
@ -19,31 +43,90 @@ module ActiveModelSerializers
end end
end end
attr_reader :errors # Opt-in to breaking change
# NOTE that +updated_at+ isn't included in +attribute_names+, def self.derive_attributes_from_names_and_fix_accessors
# which means it won't show up in +attributes+ unless a subclass has unless included_modules.include?(DeriveAttributesFromNamesAndFixAccessors)
# either <tt>attributes :updated_at</tt> which will redefine the methods prepend(DeriveAttributesFromNamesAndFixAccessors)
# or <tt>attribute_names << :updated_at</tt>. end
attr_writer :updated_at end
# NOTE that +id+ will always be in +attributes+.
attributes :id
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
# (see #updated_at)
attr_writer :updated_at
# 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 = {}) def initialize(attributes = {})
attributes ||= {} # protect against nil
@attributes = attributes.symbolize_keys.with_indifferent_access
@errors = ActiveModel::Errors.new(self) @errors = ActiveModel::Errors.new(self)
super super
end end
# The the fields in +attribute_names+ determines the returned hash. # Defaults to the downcased model name.
# +attributes+ are returned frozen to prevent any expectations that mutation affects # This probably isn't a good default, since it's not a unique instance identifier,
# the actual values in the model. # but that's what is currently implemented \_('-')_/.
def attributes #
attribute_names.each_with_object({}) do |attribute_name, result| # @note Though +id+ is defined, it will only show up
result[attribute_name] = public_send(attribute_name).freeze # in +attributes+ when it is passed in to the initializer or added to +attributes+,
end.with_indifferent_access.freeze # such as <tt>attributes[:id] = 5</tt>.
# @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 <tt>attributes[:updated_at] = Time.current</tt>.
# @return [String, Numeric, Time]
def updated_at
attributes.fetch(:updated_at) do
defined?(@updated_at) ? @updated_at : File.mtime(__FILE__)
end
end end
# To customize model behavior, this method must be redefined. However, # To customize model behavior, this method must be redefined. However,
# there are other ways of setting the +cache_key+ a serializer uses. # there are other ways of setting the +cache_key+ a serializer uses.
# @return [String]
def cache_key def cache_key
ActiveSupport::Cache.expand_cache_key([ ActiveSupport::Cache.expand_cache_key([
self.class.model_name.name.downcase, self.class.model_name.name.downcase,
@ -51,12 +134,6 @@ module ActiveModelSerializers
].compact) ].compact)
end 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 # The following methods are needed to be minimally implemented for ActiveModel::Errors
# :nocov: # :nocov:
def self.human_attribute_name(attr, _options = {}) def self.human_attribute_name(attr, _options = {})

View File

@ -3,6 +3,15 @@ require 'test_helper'
module ActionController module ActionController
module Serialization module Serialization
class AdapterSelectorTest < ActionController::TestCase 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 class AdapterSelectorTestController < ActionController::Base
def render_using_default_adapter def render_using_default_adapter
@profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1') @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1')

View File

@ -4,7 +4,7 @@ module ActionController
module Serialization module Serialization
class NamespaceLookupTest < ActionController::TestCase class NamespaceLookupTest < ActionController::TestCase
class Book < ::Model class Book < ::Model
attributes :title, :body attributes :id, :title, :body
associations :writer, :chapters associations :writer, :chapters
end end
class Chapter < ::Model class Chapter < ::Model
@ -86,7 +86,7 @@ module ActionController
book = Book.new(title: 'New Post', body: 'Body') book = Book.new(title: 'New Post', body: 'Body')
# because this is a string, ruby can't auto-lookup the constant, so otherwise # 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' render json: book, namespace: 'ActionController::Serialization::NamespaceLookupTest::Api::V2'
end end
@ -94,7 +94,7 @@ module ActionController
book = Book.new(title: 'New Post', body: 'Body') book = Book.new(title: 'New Post', body: 'Body')
# because this is a string, ruby can't auto-lookup the constant, so otherwise # 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' render json: book, namespace: :'ActionController::Serialization::NamespaceLookupTest::Api::V2'
end end

View File

@ -24,21 +24,56 @@ module ActiveModelSerializers
attributes :one, :two, :three attributes :one, :two, :three
end end
original_attributes = { one: 1, two: 2, three: 3 } original_attributes = { one: 1, two: 2, three: 3 }
instance = klass.new(original_attributes) original_instance = klass.new(original_attributes)
# Initial value # 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 expected_attributes, instance.attributes
assert_equal 1, instance.one assert_equal 1, instance.one
assert_equal 1, instance.read_attribute_for_serialization(: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 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 expected_attributes, instance.attributes
assert_equal :not_one, instance.one assert_equal :not_one, instance.one
assert_equal :not_one, instance.read_attribute_for_serialization(: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 = { 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 end
def test_id_attribute_can_be_read_for_serialization def test_id_attribute_can_be_read_for_serialization
@ -47,21 +82,59 @@ module ActiveModelSerializers
end end
self.class.const_set(:SomeTestModel, klass) self.class.const_set(:SomeTestModel, klass)
original_attributes = { id: :ego, one: 1, two: 2, three: 3 } original_attributes = { id: :ego, one: 1, two: 2, three: 3 }
instance = klass.new(original_attributes) original_instance = klass.new(original_attributes)
# Initial value # Initial value
instance = original_instance.dup
expected_attributes = { id: :ego, one: 1, two: 2, three: 3 }.with_indifferent_access expected_attributes = { id: :ego, one: 1, two: 2, three: 3 }.with_indifferent_access
assert_equal expected_attributes, instance.attributes assert_equal expected_attributes, instance.attributes
assert_equal 1, instance.one assert_equal :ego, instance.id
assert_equal 1, instance.read_attribute_for_serialization(:one) 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 instance.id = :superego
expected_attributes = { id: :superego, one: 1, two: 2, three: 3 }.with_indifferent_access
assert_equal expected_attributes, instance.attributes assert_equal expected_attributes, instance.attributes
assert_equal :superego, instance.id assert_equal :superego, instance.id
assert_equal :superego, instance.read_attribute_for_serialization(: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 ensure
self.class.send(:remove_const, :SomeTestModel) self.class.send(:remove_const, :SomeTestModel)
end end

View File

@ -44,7 +44,7 @@ class JsonApiRendererTest < ActionDispatch::IntegrationTest
def define_author_model_and_serializer def define_author_model_and_serializer
TestController.const_set(:Author, Class.new(ActiveModelSerializers::Model) do TestController.const_set(:Author, Class.new(ActiveModelSerializers::Model) do
attributes :name attributes :id, :name
end) end)
TestController.const_set(:AuthorSerializer, Class.new(ActiveModel::Serializer) do TestController.const_set(:AuthorSerializer, Class.new(ActiveModel::Serializer) do
type 'users' type 'users'

View File

@ -3,11 +3,8 @@ require 'test_helper'
module ActiveModelSerializers module ActiveModelSerializers
module Adapter module Adapter
class AttributesTest < ActiveSupport::TestCase class AttributesTest < ActiveSupport::TestCase
class Person class Person < ActiveModelSerializers::Model
include ActiveModel::Model attributes :first_name, :last_name
include ActiveModel::Serialization
attr_accessor :first_name, :last_name
end end
class PersonSerializer < ActiveModel::Serializer class PersonSerializer < ActiveModel::Serializer

View File

@ -4,6 +4,10 @@ module ActiveModelSerializers
module Adapter module Adapter
class Json class Json
class HasManyTestTest < ActiveSupport::TestCase class HasManyTestTest < ActiveSupport::TestCase
class ModelWithoutSerializer < ::Model
attributes :id, :name
end
def setup def setup
ActionController::Base.cache_store.clear ActionController::Base.cache_store.clear
@author = Author.new(id: 1, name: 'Steve K.') @author = Author.new(id: 1, name: 'Steve K.')
@ -16,7 +20,7 @@ module ActiveModelSerializers
@second_comment.post = @post @second_comment.post = @post
@blog = Blog.new(id: 1, name: 'My Blog!!') @blog = Blog.new(id: 1, name: 'My Blog!!')
@post.blog = @blog @post.blog = @blog
@tag = Tag.new(id: 1, name: '#hash_tag') @tag = ModelWithoutSerializer.new(id: 1, name: '#hash_tag')
@post.tags = [@tag] @post.tags = [@tag]
end end
@ -30,7 +34,11 @@ module ActiveModelSerializers
end end
def test_has_many_with_no_serializer 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) adapter = ActiveModelSerializers::Adapter::Json.new(serializer)
assert_equal({ assert_equal({
id: 42, id: 42,

View File

@ -4,6 +4,10 @@ module ActiveModelSerializers
module Adapter module Adapter
class JsonApi class JsonApi
class HasManyTest < ActiveSupport::TestCase class HasManyTest < ActiveSupport::TestCase
class ModelWithoutSerializer < ::Model
attributes :id, :name
end
def setup def setup
ActionController::Base.cache_store.clear ActionController::Base.cache_store.clear
@author = Author.new(id: 1, name: 'Steve K.') @author = Author.new(id: 1, name: 'Steve K.')
@ -26,7 +30,7 @@ module ActiveModelSerializers
@blog.articles = [@post] @blog.articles = [@post]
@post.blog = @blog @post.blog = @blog
@post_without_comments.blog = nil @post_without_comments.blog = nil
@tag = Tag.new(id: 1, name: '#hash_tag') @tag = ModelWithoutSerializer.new(id: 1, name: '#hash_tag')
@post.tags = [@tag] @post.tags = [@tag]
@serializer = PostSerializer.new(@post) @serializer = PostSerializer.new(@post)
@adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer) @adapter = ActiveModelSerializers::Adapter::JsonApi.new(@serializer)
@ -129,7 +133,11 @@ module ActiveModelSerializers
end end
def test_has_many_with_no_serializer 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) adapter = ActiveModelSerializers::Adapter::JsonApi.new(serializer)
assert_equal({ assert_equal({

View File

@ -14,11 +14,21 @@ module ActiveModel
[{ foo: 'bar' }] [{ foo: 'bar' }]
end end
end end
class Tag < ::Model
attributes :id, :name
end
class TagSerializer < ActiveModel::Serializer class TagSerializer < ActiveModel::Serializer
type 'tags'
attributes :id, :name attributes :id, :name
end end
class PostWithTagsSerializer < ActiveModel::Serializer
type 'posts'
attributes :id
has_many :tags
end
class IncludeParamAuthorSerializer < ActiveModel::Serializer class IncludeParamAuthorSerializer < ActiveModel::Serializer
class_attribute :comment_loader class_attribute :comment_loader

View File

@ -4,6 +4,20 @@ require 'tempfile'
module ActiveModelSerializers module ActiveModelSerializers
class CacheTest < ActiveSupport::TestCase 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 # Instead of a primitive cache key (i.e. a string), this class
# returns a list of objects that require to be expanded themselves. # returns a list of objects that require to be expanded themselves.
class AuthorWithExpandableCacheElements < Author class AuthorWithExpandableCacheElements < Author
@ -27,30 +41,32 @@ module ActiveModelSerializers
] ]
end end
end end
class UncachedAuthor < Author class UncachedAuthor < Author
# To confirm cache_key is set using updated_at and cache_key option passed to cache # To confirm cache_key is set using updated_at and cache_key option passed to cache
undef_method :cache_key undef_method :cache_key
end end
class AuthorSerializer < ActiveModel::Serializer
cache key: 'writer', skip_digest: true
attributes :id, :name
class Article < ::Model has_many :posts
attributes :title has_many :roles
# To confirm error is raised when cache_key is not set and cache_key option not passed to cache has_one :bio
undef_method :cache_key
end end
class ArticleSerializer < ActiveModel::Serializer class Blog < ::Model
cache only: [:place], skip_digest: true attributes :name
attributes :title associations :writer
end end
class BlogSerializer < ActiveModel::Serializer
cache key: 'blog'
attributes :id, :name
class InheritedRoleSerializer < RoleSerializer belongs_to :writer
cache key: 'inherited_role', only: [:name, :special_attribute]
attribute :special_attribute
end end
class Comment < ::Model class Comment < ::Model
attributes :body attributes :id, :body
associations :post, :author associations :post, :author
# Uses a custom non-time-based cache key # Uses a custom non-time-based cache key
@ -58,14 +74,52 @@ module ActiveModelSerializers
"comment/#{id}" "comment/#{id}"
end end
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 setup do
cache_store.clear cache_store.clear
@comment = Comment.new(id: 1, body: 'ZOMG A COMMENT') @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') @bio = Bio.new(id: 1, content: 'AMS Contributor')
@author = Author.new(name: 'Joao M. D. Moura') @author = Author.new(id: 'author', name: 'Joao M. D. Moura')
@blog = Blog.new(id: 999, name: 'Custom blog', writer: @author, articles: []) @blog = Blog.new(id: 999, name: 'Custom blog', writer: @author)
@role = Role.new(name: 'Great Author') @role = Role.new(name: 'Great Author')
@location = Location.new(lat: '-23.550520', lng: '-46.633309') @location = Location.new(lat: '-23.550520', lng: '-46.633309')
@place = Place.new(name: 'Amazing Place') @place = Place.new(name: 'Amazing Place')
@ -325,12 +379,14 @@ module ActiveModelSerializers
def test_uses_file_digest_in_cache_key def test_uses_file_digest_in_cache_key
render_object_with_cache(@blog) 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)) assert_equal(@blog_serializer.attributes, cache_store.fetch(key))
end end
def test_cache_digest_definition 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 end
def test_object_cache_keys def test_object_cache_keys

19
test/fixtures/poro.rb vendored
View File

@ -1,10 +1,7 @@
class Model < ActiveModelSerializers::Model 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. attr_writer :id
def id
@id ||= self.class.model_name.name.downcase
end
# At this time, just for organization of intent # At this time, just for organization of intent
class_attribute :association_names class_attribute :association_names
@ -23,6 +20,10 @@ class Model < ActiveModelSerializers::Model
result[association_name] = public_send(association_name).freeze result[association_name] = public_send(association_name).freeze
end.with_indifferent_access.freeze end.with_indifferent_access.freeze
end end
def attributes
super.except(*association_names)
end
end end
# see # see
@ -107,10 +108,6 @@ class PostPreviewSerializer < ActiveModel::Serializer
has_many :comments, serializer: ::CommentPreviewSerializer has_many :comments, serializer: ::CommentPreviewSerializer
belongs_to :author, serializer: ::AuthorPreviewSerializer belongs_to :author, serializer: ::AuthorPreviewSerializer
end end
class PostWithTagsSerializer < ActiveModel::Serializer
attributes :id
has_many :tags
end
class PostWithCustomKeysSerializer < ActiveModel::Serializer class PostWithCustomKeysSerializer < ActiveModel::Serializer
attributes :id attributes :id
has_many :comments, key: :reviews has_many :comments, key: :reviews
@ -207,10 +204,6 @@ module Spam
end end
end end
class Tag < Model
attributes :name
end
class VirtualValue < Model; end class VirtualValue < Model; end
class VirtualValueSerializer < ActiveModel::Serializer class VirtualValueSerializer < ActiveModel::Serializer
attributes :id attributes :id

View File

@ -2,13 +2,17 @@ require 'test_helper'
module ActiveModel module ActiveModel
class Serializer class Serializer
class AssociationsTest < ActiveSupport::TestCase class AssociationsTest < ActiveSupport::TestCase
class ModelWithoutSerializer < ::Model
attributes :id, :name
end
def setup def setup
@author = Author.new(name: 'Steve K.') @author = Author.new(name: 'Steve K.')
@author.bio = nil @author.bio = nil
@author.roles = [] @author.roles = []
@blog = Blog.new(name: 'AMS Blog') @blog = Blog.new(name: 'AMS Blog')
@post = Post.new(title: 'New Post', body: 'Body') @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') @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
@post.comments = [@comment] @post.comments = [@comment]
@post.tags = [@tag] @post.tags = [@tag]
@ -46,7 +50,11 @@ module ActiveModel
end end
def test_has_many_with_no_serializer 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 key = association.key
serializer = association.serializer serializer = association.serializer
options = association.options options = association.options