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

Attribute defaults are easy to accidentally modify #174

Open
mblythe86 opened this issue Nov 26, 2024 · 1 comment
Open

Attribute defaults are easy to accidentally modify #174

mblythe86 opened this issue Nov 26, 2024 · 1 comment
Labels
Status: Untriaged An issue that has yet to be triaged.

Comments

@mblythe86
Copy link

Version:

Artifactory gem version 3.0.17

Environment:

ruby 2.7.2 RHEL7 (but this bug seem agnostic to ruby/OS version)

Scenario:

Create a new object derived from Artifactory::Resource::Base that has some attributes defined (e.g. PermssionTarget), and modify that object's attribute values. When creating a new object of the same type, the default attribute values are now the same as the first object.

Steps to Reproduce:

Example rspec tests:

require 'rspec'
require 'artifactory'

describe Artifactory::Resource::PermissionTarget do
  it 'does not modify default attribute principals' do
    a = Artifactory::Resource::PermissionTarget.new
    a.users['user_a'] = %w[read]
    b = Artifactory::Resource::PermissionTarget.new
    expect(b.users).to eq({})
  end

  it 'does not modify default attribute includes_pattern' do
    a = Artifactory::Resource::PermissionTarget.new
    tmp = a.includes_pattern
    tmp.gsub!(/\*\*/, 'new_pattern')
    b = Artifactory::Resource::PermissionTarget.new
    expect(b.includes_pattern).to eq('**')
  end

  it 'does not modify default attribute excludes_pattern' do
    a = Artifactory::Resource::PermissionTarget.new
    tmp = a.excludes_pattern
    tmp.replace 'new_pattern'
    b = Artifactory::Resource::PermissionTarget.new
    expect(b.excludes_pattern).to eq('')
  end
end

Expected Result:

I expect those rspec tests to pass.

Actual Result:

>rspec 'tests/artifactory_test.rb'
FFF

Failures:

  1) Artifactory::Resource::PermissionTarget does not modify default attribute principals
     Failure/Error: expect(b.users).to eq({})
     
       expected: {}
            got: {"user_a"=>["read"]}
     
       (compared using ==)
     
       Diff:
       @@ -1 +1,2 @@
       +"user_a" => ["read"],
       
     # ./tests/artifactory_test.rb:12:in `block (2 levels) in <top (required)>'

  2) Artifactory::Resource::PermissionTarget does not modify default attribute includes_pattern
     Failure/Error: expect(b.includes_pattern).to eq('**')
     
       expected: "**"
            got: "new_pattern"
     
       (compared using ==)
     # ./tests/artifactory_test.rb:20:in `block (2 levels) in <top (required)>'

  3) Artifactory::Resource::PermissionTarget does not modify default attribute excludes_pattern
     Failure/Error: expect(b.excludes_pattern).to eq('')
     
       expected: ""
            got: "new_pattern"
     
       (compared using ==)
     # ./tests/artifactory_test.rb:28:in `block (2 levels) in <top (required)>'

Finished in 0.02337 seconds (files took 0.24672 seconds to load)
3 examples, 3 failures

Failed examples:

rspec ./tests/artifactory_test.rb:8 # Artifactory::Resource::PermissionTarget does not modify default attribute principals
rspec ./tests/artifactory_test.rb:15 # Artifactory::Resource::PermissionTarget does not modify default attribute includes_pattern
rspec ./tests/artifactory_test.rb:23 # Artifactory::Resource::PermissionTarget does not modify default attribute excludes_pattern
@mblythe86 mblythe86 added the Status: Untriaged An issue that has yet to be triaged. label Nov 26, 2024
@mblythe86
Copy link
Author

Note that this is exactly the same issue that's called out in the documentation for Hash https://ruby-doc.org/3.3.6/Hash.html#class-Hash-label-Default+Values

Note that the default value is used without being duplicated. It is not advised to set the default value to a mutable object:

synonyms = Hash.new([])
synonyms[:hello] # => []
synonyms[:hello] << :hi # => [:hi], but this mutates the default!
synonyms.default # => [:hi]
synonyms[:world] << :universe
synonyms[:world] # => [:hi, :universe], oops
synonyms.keys # => [], oops

To use a mutable object as default, it is recommended to use a default proc

I'm putting together a Pull Request to fix this issue, and in my fix, I use that recommendation - using a default proc instead of a default value.

mblythe86 added a commit to mblythe86/artifactory-client that referenced this issue Nov 26, 2024
Either freeze them, in the case of Strings or empty Hash/Array,
or call a proc to return a unique instance of a mutable object
(mostly relevant for the 'principals' attribute of PermissionTarget).

Receiving a FrozenError in this case may be unexpected for users,
but it will force them into the preferred method of setting attributes
(i.e. using the "#{name}=" setter).

Fixes issue chef#174.
mblythe86 added a commit to mblythe86/artifactory-client that referenced this issue Nov 26, 2024
Either freeze them, in the case of Strings or empty Hash/Array,
or call a proc to return a unique instance of a mutable object
(mostly relevant for the 'principals' attribute of PermissionTarget).

Receiving a FrozenError in this case may be unexpected for users,
but it will force them into the preferred method of setting attributes
(i.e. using the "#{name}=" setter).

Fixes issue chef#174.

Signed-off-by: Matthew Blythe <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Status: Untriaged An issue that has yet to be triaged.
Projects
None yet
Development

No branches or pull requests

1 participant