-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Conversation
end | ||
|
||
def _assign_attribute(k, v) | ||
fail UnknownAttributeError.new(self, k) unless respond_to?("#{k}=") |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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: []) |
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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
@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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
0df26d0
to
be534ef
Compare
LGTM, ready for merge? |
@NullVoxPopuli I want to finish cleaning up some of the test poro attributes -> associations |
f00d678
to
6ae54ee
Compare
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') |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bug ref #1857 (comment)
cc @richmolj |
There was a problem hiding this 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) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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'.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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__) |
There was a problem hiding this comment.
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__)
.
There was a problem hiding this comment.
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
1e6f7cd
to
8dc1124
Compare
8dc1124
to
37f85b3
Compare
def attributes | ||
attribute_names.each_with_object({}) do |attribute_name, result| | ||
result[attribute_name] = public_send(attribute_name) | ||
end.with_indifferent_access |
There was a problem hiding this comment.
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')}" |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
@groyoh Just looks for consensus since there are some important changes in here. |
d604d13
to
539683f
Compare
@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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/no/not/
?
Isn't there too many changes here? |
instance_options | ||
end | ||
end | ||
assert comment_serializer.custom_options |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]
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 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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo The the
d4eba71
to
f36943a
Compare
- Organize test poros with associations and by serializer - Freeze derived attributes/associations against mutation - Cleanup PORO fixtures
f36943a
to
3b84845
Compare
@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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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
I'm mostly 👍 |
Follows #1982