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

Conversation

bf4
Copy link
Member

@bf4 bf4 commented Nov 21, 2016

Follows #1982

end

def _assign_attribute(k, v)
fail UnknownAttributeError.new(self, k) unless respond_to?("#{k}=")
Copy link
Member Author

Choose a reason for hiding this comment

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

TODO: check vs. attribute_names

end

# Raised when unknown attributes are supplied via mass assignment.
class UnknownAttributeError < NoMethodError
Copy link
Member Author

Choose a reason for hiding this comment

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

copied from Rails 5 ActiveModel::AttributeAssignment

@@ -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.

@@ -50,7 +49,7 @@ class LookupTestController < ActionController::Base

def implicit_namespaced_serializer
writer = Writer.new(name: 'Bob')
book = Book.new(title: 'New Post', body: 'Body', writer: writer, chapters: [])
book = Book.new(id: 'bookid', title: 'New Post', body: 'Body', writer: writer, chapters: [])
Copy link
Member Author

Choose a reason for hiding this comment

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

used?

@@ -205,7 +204,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', 'writer'=>nil, 'chapters'=>nil}
Copy link
Member Author

Choose a reason for hiding this comment

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

see #1985 which would aim to have the the PORO model attributes/associations map better to the Serializer attributes/associations

@mention-bot
Copy link

@bf4, thanks for your PR! By analyzing the history of the files in this pull request, we identified @NullVoxPopuli, @bolshakov and @kevintyll to be potential reviewers.

@@ -323,15 +328,21 @@ def test_cache_digest_definition
end

def test_object_cache_keys
class << @comment
def cache_key; "comment/#{id}"; end
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 hard-coded in test/poro/fixtures.rb but we're not using that anymore

class Comment < Model
  # Uses a custom non-time-based cache key
  def cache_key
    "#{self.class.name.downcase}/#{id}"
  end
end

include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
extend ActiveModel::Translation
Copy link
Member Author

Choose a reason for hiding this comment

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

code adapted from ActiveModel::Model which is has good logic in it, but doesn't still has a few cases we want to handle differently

Copy link
Contributor

Choose a reason for hiding this comment

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

what are those use cases?

@bf4 bf4 force-pushed the make_test_attributes_explicit branch 3 times, most recently from 0df26d0 to be534ef Compare November 21, 2016 19:32
@NullVoxPopuli
Copy link
Contributor

LGTM, ready for merge?

@bf4
Copy link
Member Author

bf4 commented Nov 21, 2016

@NullVoxPopuli I want to finish cleaning up some of the test poro attributes -> associations

@bf4 bf4 force-pushed the make_test_attributes_explicit branch 2 times, most recently from f00d678 to 6ae54ee Compare November 21, 2016 23:42
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

@@ -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...

else
super
end
# At this time, just for organization of intent
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 good to promote to parent class once we add some additional behavior to the attributes method

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)

@bf4
Copy link
Member Author

bf4 commented Nov 22, 2016

cc @richmolj

Copy link
Contributor

@beauby beauby left a comment

Choose a reason for hiding this comment

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

I'm lacking a bit of context on this one, but the code looks good. See a few minor remarks.


def self.attributes(*names)
attr_accessor(*names)
self.attribute_names = attribute_names | names.map(&:to_sym)
Copy link
Contributor

Choose a reason for hiding this comment

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

self.attribute-names |= names.map(&:to_sym)

@errors = ActiveModel::Errors.new(self)
super
end

# Defaults to the downcased model name.
def id
attributes.fetch(:id) { self.class.name.downcase }
@id ||= self.class.name && self.class.name.downcase
Copy link
Contributor

Choose a reason for hiding this comment

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

The id method should return a unique identifier for a resource, no? Not sure if providing such a default is more useful than dangerous.

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 I agree this is a somewhat problematic implementation. Also, I think only JSON API requires an 'id'.

Copy link
Contributor

Choose a reason for hiding this comment

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

In that case, I'd suggest trying to read the :id attribute, and if none available, fall back to object_id.

end

# Defaults to the downcased model name and updated_at
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.name && self.class.name.downcase,
Copy link
Contributor

Choose a reason for hiding this comment

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

How about defaulting to self.class.object_id for anonymous classes? That way you avoid colliding all anonymous classes in the cache.

Copy link
Member Author

Choose a reason for hiding this comment

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

I thought about that, or hash, but I'm not sure that's any better, as it's not really a unique identifier. We could hash the attributes, I guess. If we just used ActiveModel::Name then it would blow up on the anonymous class and we could rely on it's exception message.

Copy link
Contributor

Choose a reason for hiding this comment

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

self.class.object_id is a unique identifier for the class. For the the instances, I don't see any better solution than hashing the attributes, which would suck because it would either be complicated or fail for hashes. I'd vote for no caching for those models.

Copy link
Member Author

Choose a reason for hiding this comment

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

Should I just remove 'id' as required at the Model level?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, people who use JSON API should be aware of the first 5 lines of the spec.

end

# Defaults to the time the serializer file was modified.
def updated_at
attributes.fetch(:updated_at) { File.mtime(__FILE__) }
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

@bf4 bf4 force-pushed the make_test_attributes_explicit branch from 1e6f7cd to 8dc1124 Compare November 22, 2016 20:47
@bf4 bf4 force-pushed the make_test_attributes_explicit branch from 8dc1124 to 37f85b3 Compare November 22, 2016 21:47
def attributes
attribute_names.each_with_object({}) do |attribute_name, result|
result[attribute_name] = public_send(attribute_name)
end.with_indifferent_access
Copy link
Member Author

Choose a reason for hiding this comment

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

.freeze

def cache_key
ActiveSupport::Cache.expand_cache_key([
self.class.model_name.name.downcase,
"#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}"
Copy link
Member Author

Choose a reason for hiding this comment

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

This will blow up if updated_at isn't a 'Time', but that will only happen when users set updated_at, since the default is File.mtime

Copy link
Member Author

@bf4 bf4 Nov 23, 2016

Choose a reason for hiding this comment

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

note that cache_key isn't included in attribute_names and has no defined cache_key= setter.

@bf4
Copy link
Member Author

bf4 commented Nov 25, 2016

@groyoh Just looks for consensus since there are some important changes in here.

@bf4 bf4 mentioned this pull request Nov 27, 2016
@bf4 bf4 force-pushed the make_test_attributes_explicit branch 3 times, most recently from d604d13 to 539683f Compare November 27, 2016 05:20
@bf4
Copy link
Member Author

bf4 commented Nov 27, 2016

@rails-api/ams @rails-api/commit ready for final review. Should I break this up? Good enough?

def cache_key
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.
Copy link
Member

Choose a reason for hiding this comment

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

s/no/not/ ?

@groyoh
Copy link
Member

groyoh commented Nov 27, 2016

Isn't there too many changes here?

instance_options
end
end
assert comment_serializer.custom_options
Copy link
Member

Choose a reason for hiding this comment

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

Shoudn't that be assert comment_serializer.custom_options[:custom_options]? or the above method be instance_options[:custom_options]? You're currently testing assert {} I think.

Copy link
Member Author

Choose a reason for hiding this comment

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

thanks, you're right! I lost the custom_options[:custom_options]

@bf4
Copy link
Member Author

bf4 commented Nov 27, 2016

@groyoh

Isn't there too many changes here?

Yes, but I'm not sure how I'd like to break it up, since they go well together... part of the issue is that making the test attributes explicit, we need to decide when id or updated_at should be in the attributes hash, IIRC

I'm open to any suggestions how best to do it. That's why I'm not comfortable merging just yet.

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

Choose a reason for hiding this comment

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

use poro builder

else
attributes[key]
end
# 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

@bf4 bf4 force-pushed the make_test_attributes_explicit branch 2 times, most recently from d4eba71 to f36943a Compare December 5, 2016 01:30
bf4 added 3 commits December 4, 2016 19:33
- Organize test poros with associations and by serializer
- Freeze derived attributes/associations against mutation
- Cleanup PORO fixtures
@bf4 bf4 force-pushed the make_test_attributes_explicit branch from f36943a to 3b84845 Compare December 5, 2016 01:33
@bf4
Copy link
Member Author

bf4 commented Dec 5, 2016

@rails-api/ams @rails-api/commit ready for review again

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

@@ -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

@NullVoxPopuli
Copy link
Contributor

I'm mostly 👍

@bf4 bf4 merged commit 847f50a into rails-api:master Dec 6, 2016
@bf4 bf4 deleted the make_test_attributes_explicit branch December 6, 2016 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants