Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make test attributes explicit #1984

Merged
merged 3 commits into from
Dec 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 8 additions & 0 deletions lib/active_model_serializers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
54 changes: 35 additions & 19 deletions lib/active_model_serializers/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this actually needed? Which method redefinition are you actually trying to silence?

Wouldn't

names.map!(&to_sym)
attr_accessor(names - self.attribute_name)
self.attribute_names |= names

work instead of messing up with $VERBOSE?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the attr_accessor. It still yields warnings when, e.g. redefining updated_at, which isn't an attribute on the model by default

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 <tt>attributes :updated_at</tt> which will redefine the methods
# or <tt>attribute_names << :updated_at</tt>.
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo The the

# +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__)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is safe to do @updated_at || File.mtime(__FILE__).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@beauby yeah, but give ruby warning undefined ivar

end

# The following methods are needed to be minimally implemented for ActiveModel::Errors
Expand Down
4 changes: 2 additions & 2 deletions test/action_controller/adapter_selector_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably a good thing that id, which is required, is now included

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

render json: @profile, adapter: false
end
end
Expand Down Expand Up @@ -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
Expand Down
21 changes: 15 additions & 6 deletions test/action_controller/json_api/fields_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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')
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

¯\_(ツ)_/¯ rubocop

@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

Expand Down
14 changes: 11 additions & 3 deletions test/action_controller/json_api/transform_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm certainly a fan of this being more explicit

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
Expand Down
18 changes: 12 additions & 6 deletions test/action_controller/namespace_lookup_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion test/action_controller/serialization_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, caught it because it raised an error, undefined attribute.. makes me wonder if it was being used...

like.time = Time.zone.now.to_s

render json: like
Expand Down
49 changes: 48 additions & 1 deletion test/active_model_serializers/model_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module ActiveModelSerializers
class ModelTest < ActiveSupport::TestCase
include ActiveModel::Serializer::Lint::Tests

def setup
setup do
@resource = ActiveModelSerializers::Model.new
end

Expand All @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should new tests be using test '...' do?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, I guess so. I guess I haven't completely switch to activesupport form. I mostly just wanted the stable interface vs. minitest

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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is fixed #1903

ref #1877

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug ref #1857 (comment)


def test_id_attribute_can_be_read_for_serialization
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no longer necessary now that the read_attribute_for_serialization method has been removed

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
14 changes: 11 additions & 3 deletions test/adapter/json_api/fields_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
4 changes: 3 additions & 1 deletion test/adapter/json_api/include_data_if_sideloaded_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions test/adapter/json_api/linked_test.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion test/adapter/json_api/links_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down
14 changes: 11 additions & 3 deletions test/adapter/json_api/transform_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Loading