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 the output of ActiveRecord::Core#inspect
configurable.
#49765
Conversation
a419cda
to
6c384a2
Compare
@@ -132,6 +132,8 @@ class SymbolIgnoredDeveloper < ActiveRecord::Base | |||
class AuditLog < ActiveRecord::Base | |||
belongs_to :developer, validate: true | |||
belongs_to :unvalidated_developer, class_name: "Developer" | |||
|
|||
self.attributes_to_inspect = [:id, :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.
Fixes AssociationsTest#test_inspect_does_not_reload_a_not_yet_loaded_target
@@ -51,9 +65,9 @@ def test_inspect_class_without_table | |||
assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect | |||
end | |||
|
|||
def test_inspect_relation_with_virtual_field | |||
relation = Topic.limit(1).select("1 as virtual_field") | |||
assert_match(/virtual_field: 1/, relation.inspect) |
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 decided to remove this test. Relation#inspect
calls inspect
on each of the records. It seems unnatural that virtual columns would be included in attributes_to_expect
so I didn't want to add that as a test case.
I think we could add a full_inspect
method to Relation
if we want to preserve this behaviour.
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 think you can rewrite this test in a way that makes sure virtual attributes are inside the loaded record and can be inspected. You could call full_inspect
in relation.first
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.
Would there be value in adding a global config, e.g. config.active_record.attributes_to_inspect
? If we opted for an :all
option, might be useful to be able to set this across all Active Records in order to maintain the existing behaviour.
I think the argument is less strong if we stick with #full_inspect
instead of an :all
option, although you could make the argument that users may always want inspect to include certain fields (e.g. timestamps).
activerecord/CHANGELOG.md
Outdated
Post.first.inspect #=> "#<(Post id: 1)>" | ||
``` | ||
|
||
The attributes to be included in the out of `inspect` can be configured with |
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 attributes to be included in the out of `inspect` can be configured with | |
The attributes to be included in the output of `inspect` can be configured with |
activerecord/test/cases/core_test.rb
Outdated
assert_match(/virtual_field: 1/, relation.inspect) | ||
def test_full_inspect_instance | ||
topic = topics(:first) | ||
assert_equal %(#<Topic id: 1, title: "The First Topic", author_name: "David", author_email_address: "david@loudthinking.com", written_on: "#{topic.written_on.to_fs(:inspect)}", bonus_time: "#{topic.bonus_time.to_fs(:inspect)}", last_read: "#{topic.last_read.to_fs(:inspect)}", content: "Have a nice day", important: nil, binary_content: nil, approved: false, replies_count: 1, unique_replies_count: 0, parent_id: nil, parent_title: nil, type: nil, group: nil, created_at: "#{topic.created_at.to_fs(:inspect)}", updated_at: "#{topic.updated_at.to_fs(:inspect)}">), topic.full_inspect |
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.
We could pull this out into a HEREDOC to make it easier to read?
3053798
to
e22000f
Compare
@@ -680,21 +683,18 @@ def connection_handler | |||
self.class.connection_handler | |||
end | |||
|
|||
# Returns the contents of the record as a nicely formatted string. | |||
# Returns the attributes specified by #attributes_for_inspect as a nicely formatted string. |
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.
# Returns the attributes specified by #attributes_for_inspect as a nicely formatted string. | |
# Returns the attributes specified by <tt>.attributes_for_inspect</tt> as a nicely formatted string. |
@@ -51,9 +65,9 @@ def test_inspect_class_without_table | |||
assert_equal "NonExistentTable(Table doesn't exist)", NonExistentTable.inspect | |||
end | |||
|
|||
def test_inspect_relation_with_virtual_field | |||
relation = Topic.limit(1).select("1 as virtual_field") | |||
assert_match(/virtual_field: 1/, relation.inspect) |
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 think you can rewrite this test in a way that makes sure virtual attributes are inside the loaded record and can be inspected. You could call full_inspect
in relation.first
e22000f
to
9289e5b
Compare
end | ||
end.join(", ") | ||
if attributes_for_inspect == :all | ||
inspect_with_attributes(attribute_names) |
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.
inspect_with_attributes(attribute_names) | |
full_inspect |
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.
Agree
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.
Argh, thanks. I think I left it like this when I was playing with a version that had inspect(:all)
instead of full_inspect
We also already have a |
It doesn't overlap. filter_attributes tell active Record to show BTW, maybe we should implement another improvement on this entire pipeline because it doesn't make sense to:
Just to show |
Ah sorry, my mistake. I thought it would bypass the parameter filter, but instead it creates a customer filter if |
9289e5b
to
add9776
Compare
Currently there doesn't seem to be a distinction between local/remote here, which the original issue mentions. I have accidentally printed much more than I intended in production and because of slow connections that took quite a while. IRB had its autocomplete disabled in production because of similar reasons. In development however I want to see everything. I know I can set the required config to do that, but it really should be the default in my opinion. |
I do agree with that. Let's automatically set it to Add a test to make sure it is set to |
848bfe7
to
a80564a
Compare
c3fbfc4
to
62e9693
Compare
I added the tests to |
828bf88
to
9563777
Compare
Okay, after playing with this on my app in the debugger, I added this functionality to This also pushed me to do a bit of refactoring as well. Instead of needing to call This is in a state I am pretty happy with now. |
9563777
to
0a328e0
Compare
initializer "active_record.attributes_for_inspect" do |app| | ||
ActiveSupport.on_load(:active_record) do | ||
if app.config.consider_all_requests_local | ||
ActiveRecord::Base.attributes_for_inspect = :all |
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 override any config the user set, no?
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 don't think so, since it is run in the :active_record
load hook, it will be set before Base
is inherited by anything. I tested in my app and it worked as expected. But I will add a test like you suggested in your other comment.
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 I mean is setting config.active_record.attributes_for_inspect = [:uuid]
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.
Ah sorry, wasn't thinking of that angle. Fixed it and added a test. Thanks!
require "cases/helper" | ||
require "active_support/testing/isolation" | ||
|
||
class RailtieTest < ActiveRecord::TestCase |
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 test should be in the railties framework folder, no here. We should create a full integration test, not an unit test.
Generate a Rails application, boot it in development, assert the config value is correct. There are other tests similar to that inside railties/test
.
The test that is missing is to not use :all
when used explicitly set config.active_record.attributes_for_inspect
to something else.
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 comment was out of date - I already move the tests to railties/test/application/configuration_test.rb
. But thanks for the suggestion about the missing test case, I added that.
da9d692
to
3b6c10b
Compare
By default, calling `inspect` on a record will yield a formatted string including just the `id`. ```ruby Post.first.inspect #=> "#<Post id: 1>" ``` The attributes to be included in the output of `inspect` can be configured with `ActiveRecord::Core#attributes_for_inspect`. ```ruby Post.attributes_for_inspect = [:id, :title] Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">" ``` With the `attributes_for_inspect` set to `:all`, `inspect` will list all the record's attributes. ```ruby Post.attributes_for_inspect = :all Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">" ```
3b6c10b
to
e50182a
Compare
Rails changed what it prints in `inspect` method (rails/rails#49765). Only `id` is printed by default.
Motivation / Background
Fixes #49707
This Pull Request has been created because we noticed that
ActiveRecord::Core#inspect
was taking >9s to run for some large objects in production. This is because the parameter filter is be executed for every attribute.Detail
This Pull Request introduces configuration for
ActiveRecord::Core#inspect
. By default,inspect
just returns the object with its id (eg#<Post id: 1>
. The attributes to include can be configured withPost.attributes_to_inspect=
. If you want to full output with all the attributes you can callPost.full_inspect
.Additional information
In my first draft I had a config to disable this, if you just always want to use
full_inspect
. But it seems easy enough just to doalias_method :inspect, :full_inspect
on the model, or inApplicationRecord
if you want to disable it globally. So the config didn't seem worth it to me.Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]