Introduce mattr_accessor default option #29294

Merged
merged 2 commits into from Jun 4, 2017

Conversation

Projects
None yet
5 participants
@gsamokovarov
Contributor

gsamokovarov commented May 31, 2017

Since David introduced a default: to the class_attribute macro, we decided to follow it in the mattr_accessor family of methods. This PR does not deprecate the current defaulting behaviour:

mattr_accessor(:settings) { {} }

I simply do not document it anymore. My fear is that it may be too disruptive and force plugin authors to change their code, while they can go without that, but if you still prefer to properly deprecate it, I can issue the warning.

@rails-bot

This comment has been minimized.

Show comment
Hide comment
@rails-bot

rails-bot May 31, 2017

r? @schneems

(@rails-bot has picked a reviewer for you, use r? to override)

r? @schneems

(@rails-bot has picked a reviewer for you, use r? to override)

- class_variable_set("@@#{sym}", yield) if block_given?
+
+ default ||= yield if block_given?
+ class_variable_set("@@#{sym}", default) if default

This comment has been minimized.

@kaspth

kaspth May 31, 2017

Member

false needs to be an acceptable default, so unless default.nil? to match class_attribute.

@kaspth

kaspth May 31, 2017

Member

false needs to be an acceptable default, so unless default.nil? to match class_attribute.

This comment has been minimized.

@gsamokovarov

gsamokovarov May 31, 2017

Contributor

Thanks for the catch!

@gsamokovarov

gsamokovarov May 31, 2017

Contributor

Thanks for the catch!

@gsamokovarov

This comment has been minimized.

Show comment
Hide comment
@gsamokovarov

gsamokovarov May 31, 2017

Contributor

The failure seems a sporadic AC one.

Contributor

gsamokovarov commented May 31, 2017

The failure seems a sporadic AC one.

@kaspth

Let's squash the commits down to two: one for the new implementation with kwargs and then another to switch the codebase to use :default.

- def mattr_reader(*syms)
- options = syms.extract_options!
+ def mattr_reader(*syms, instance_reader: nil, instance_accessor: nil, default: nil)
+ default ||= yield if block_given?

This comment has been minimized.

@kaspth

kaspth Jun 1, 2017

Member

This seems to have odd rules of precedence. Consider:

mattr_accessor(:hair_colors, default: false) { [ :brown ] } # default would be brown.
mattr_accessor(:hair_colors, default: true)  { [ :brown ] } # default would be true.

These aren't supposed to mix of course. But I'd like default to always take precedence since it's the newest option and more intention revealing of the two. So:

default = yield if block_given? && default.nil?

Then same in mattr_writer.

@kaspth

kaspth Jun 1, 2017

Member

This seems to have odd rules of precedence. Consider:

mattr_accessor(:hair_colors, default: false) { [ :brown ] } # default would be brown.
mattr_accessor(:hair_colors, default: true)  { [ :brown ] } # default would be true.

These aren't supposed to mix of course. But I'd like default to always take precedence since it's the newest option and more intention revealing of the two. So:

default = yield if block_given? && default.nil?

Then same in mattr_writer.

This comment has been minimized.

@kaspth

kaspth Jun 1, 2017

Member

Let's also move it to the end of the method, so the default handling isn't spread across the method. Same for the writer.

@kaspth

kaspth Jun 1, 2017

Member

Let's also move it to the end of the method, so the default handling isn't spread across the method. Same for the writer.

This comment has been minimized.

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

Moved it to the top, because of the loop. Won't happen often, but when we pass multiple attributes, we'll waste some time that way.

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

Moved it to the top, because of the loop. Won't happen often, but when we pass multiple attributes, we'll waste some time that way.

# end
#
# class Person
# include HairColors
# end
#
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
- def mattr_writer(*syms)
- options = syms.extract_options!
+ def mattr_writer(*syms, instance_writer: nil, instance_accessor: nil, default: nil)

This comment has been minimized.

@kaspth

kaspth Jun 1, 2017

Member

If we default instance_writer and instance_accessor to true here could we then trim the == false below?

@kaspth

kaspth Jun 1, 2017

Member

If we default instance_writer and instance_accessor to true here could we then trim the == false below?

This comment has been minimized.

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

Now seems like a good time to try it. Doubt someone used an explicit instance_writer: nil.

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

Now seems like a good time to try it. Doubt someone used an explicit instance_writer: nil.

railties/lib/rails/info.rb
- mattr_accessor :properties
- class << (@@properties = [])
+ mattr_accessor :properties, default: []
+ class << @@properties

This comment has been minimized.

@kaspth

kaspth Jun 1, 2017

Member

New line before this one.

@kaspth

kaspth Jun 1, 2017

Member

New line before this one.

activesupport/CHANGELOG.md
+
+ While the example above still works, the recommended way to set a default now is:
+
+ mattr_accessor :settings, default: {}

This comment has been minimized.

@kaspth

kaspth Jun 1, 2017

Member

Let's just mention the new way only and spell out the methods.

*   Add default option to module and class attribute accessors.

       mattr_accessor :settings, default: {}

    Works for `mattr_reader`, `mattr_writer`, `cattr_accessor`, `cattr_reader`, and `cattr_writer` as well.
@kaspth

kaspth Jun 1, 2017

Member

Let's just mention the new way only and spell out the methods.

*   Add default option to module and class attribute accessors.

       mattr_accessor :settings, default: {}

    Works for `mattr_reader`, `mattr_writer`, `cattr_accessor`, `cattr_reader`, and `cattr_writer` as well.
- def mattr_reader(*syms)
- options = syms.extract_options!
+ def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil)
+ default = yield if block_given? && default.nil?

This comment has been minimized.

@matthewd

matthewd Jun 2, 2017

Member

Moving the yield out of the loop is a behavioural change.. probably better avoided.

@matthewd

matthewd Jun 2, 2017

Member

Moving the yield out of the loop is a behavioural change.. probably better avoided.

This comment has been minimized.

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

I moved it back in.

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

I moved it back in.

class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{sym}
@@#{sym}
end
EOS
end
- class_variable_set("@@#{sym}", yield) if block_given?
+
+ default = yield if block_given? && default.nil?

This comment has been minimized.

@matthewd

matthewd Jun 2, 2017

Member

This still only yields once

@matthewd

matthewd Jun 2, 2017

Member

This still only yields once

This comment has been minimized.

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

We had a test for that even before, see this.

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

We had a test for that even before, see this.

This comment has been minimized.

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

But actually, looking at the test, it was pretty bad to begin with. It doesn't exercise the multiple invocation. 🤦‍♂️

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

But actually, looking at the test, it was pretty bad to begin with. It doesn't exercise the multiple invocation. 🤦‍♂️

This comment has been minimized.

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

Fixed it and added a test for the multiple yield.

@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

Fixed it and added a test for the multiple yield.

@gsamokovarov

This comment has been minimized.

Show comment
Hide comment
@gsamokovarov

gsamokovarov Jun 2, 2017

Contributor

I think I have cleaned the history and addressed all the comments. Can you guys go over this one again?

Contributor

gsamokovarov commented Jun 2, 2017

I think I have cleaned the history and addressed all the comments. Can you guys go over this one again?

@kaspth kaspth merged commit 704209b into rails:master Jun 4, 2017

2 checks passed

codeclimate no new or fixed issues
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@kaspth

This comment has been minimized.

Show comment
Hide comment
@kaspth

kaspth Jun 4, 2017

Member

Well done 👏

Member

kaspth commented Jun 4, 2017

Well done 👏

bogdanvlviv added a commit to bogdanvlviv/rails that referenced this pull request Apr 3, 2018

Use `:default` option in order to set default value of `finalize_comp…
…iled_template_methods`

Since we introduced default option for `class_attribute` and
`mattr_accessor` family of methods and changed all occurrences of setting
default values by using of `:default` option I think it would be fine to use
`:default` option in order to set default value of `finalize_compiled_template_methods`
since it expresses itself very well.

Related to #29294

bogdanvlviv added a commit to bogdanvlviv/rails that referenced this pull request Apr 3, 2018

Use `:default` option in order to set default value of `finalize_comp…
…iled_template_methods`

Since we introduced default option for `class_attribute` and
`mattr_accessor` family of methods and changed all occurrences of setting
default values by using of `:default` option I think it would be fine to use
`:default` option in order to set default value of `finalize_compiled_template_methods`
since it expresses itself very well.

Related to #29294, #32418
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment