-
Notifications
You must be signed in to change notification settings - Fork 41
prevent undefined vault_password error #132
prevent undefined vault_password error #132
Conversation
@@ -39,7 +39,9 @@ def credential_type | |||
end | |||
|
|||
def vault_password | |||
@data['vault_password'] ||= inputs.vault_password.to_s | |||
@data.fetch('vault_password') do |key| | |||
@data[key] = (inputs.vault_password if inputs.respond_to?(:vault_password)).to_s |
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.
@AlexanderZagaynov can you just do @data['vault_password'] ||= inputs.vault_password.to_s if inputs.respond_to?(:vault_password)
? I think that is a little more obvious.
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.
@agrare it won't work in desired way, it will run the second part more than once:
irb(main):001:0> h = {}; h[:a] ||= b if false; h
=> {}
irb(main):002:0> h = {}; h[:a] ||= (b if false); h
=> {:a=>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.
@AlexanderZagaynov you mean it'll run inputs.respond_to?(:valut_password)
even after @data['vault_password']
is set? That's probably okay but if you're worried about it you could do @data['value_password'] = inputs.vault_password.to_s if [email protected]?('vault_password') && inputs.respond_to(:vault_password)
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.
No, in your example this key will not be even set.
Anyway, at the end of the day, you just trying to invent what I already have here. Why? :)
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.
Not sure what you mean it won't ever be set...
>> inputs = OpenStruct.new
>> inputs.vault_password = "abcd"
>> data = {}
>> data['value_password'] = inputs.vault_password.to_s if !data.key?('vault_password') && inputs.respond_to?(:vault_password)
=> "abcd"
>> data
=> {"value_password"=>"abcd"}
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.
Why do you need to return "" instead of nil?
Using fetch to set a value if it doesn't exist is not a common pattern and takes extra mental cycles to figure out what you're doing where as ||=
is immediately obvious. Its not wrong it just looks strange (just did a quick search and we don't do this anywhere in the codebase)
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.
@agrare yes, we need to have a string here, further code breaks otherwise.
If we didn't something before it doesn't means it has no right to be here. Think of it as of diversity :)
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.
yes, we need to have a string here, further code breaks otherwise.
Interesting, handling nil is something we should do in the future
If we didn't something before it doesn't means it has no right to be here
But it is reason to ask why you chose to do it that way which is what we were doing above
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.
@bdunne do you want me to do anything before you merge? |
Can you add some tests to cover the use cases? |
@bdunne done |
Here are the test cases I see:
|
as I told here we should return a string, not nil |
I don't know if we need all of it, that was the test matrix that came to mind. I marked a bunch of the cases as |
@bdunne I've made 3 cases now, please see. |
Reading back here is what I think... The current implementation is more complicated than @agrare and @bdunne's simpler Additionally, changing the key you are fetching within fetch is strange. This is akin to modifying a collection while iterating the collection which is generally considered bad practice. Also, this usage of fetch isn't really its intended purpose. fetch is more meant for when a missing key is unexpected and you want an Exception raised (i.e. for when the key should be present nearly 100% of the time). The block/default values just aid in not having to catch the exception, however generally it's preferable to just use So, I think the simplest, most readable, and consistent code that satisfies the specs is @data['vault_password'] ||= inputs.respond_to?(:vault_password) ? inputs.vault_password.to_s : ""
# ---
require 'ostruct'
@data = {}
inputs = nil
@data['vault_password'] ||= inputs.respond_to?(:vault_password) ? inputs.vault_password.to_s : ""
@data # => {"vault_password"=>""}
@data = {}
inputs = OpenStruct.new
@data['vault_password'] ||= inputs.respond_to?(:vault_password) ? inputs.vault_password.to_s : ""
@data # => {"vault_password"=>""}
@data = {}
inputs = OpenStruct.new(:vault_password => nil)
@data['vault_password'] ||= inputs.respond_to?(:vault_password) ? inputs.vault_password.to_s : ""
@data # => {"vault_password"=>""}
@data = {}
inputs = OpenStruct.new(:vault_password => "abc")
@data['vault_password'] ||= inputs.respond_to?(:vault_password) ? inputs.vault_password.to_s : ""
@data # => {"vault_password"=>"abc"}
@data = {"vault_password" => "abc"}
inputs = OpenStruct.new(:vault_password => "def")
@data['vault_password'] ||= inputs.respond_to?(:vault_password) ? inputs.vault_password.to_s : ""
@data # => {"vault_password"=>"abc"} With respect to the tests, yeah that list seems overly exhaustive, but it also has a lot of "is this possible?" things implying that @bdunne was questioning those particular lines. So, given what you have and that list, I think the only two additional tests needed are
|
@AlexanderZagaynov I just looked at your benchmark code, and it's inaccurate. The suggestion was to use require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem "benchmark-ips"
end
h_size = 1_000
Benchmark.ips do |bm|
bm.report '#fetch' do
h = Hash.new
h_size.times do |i|
k = "k#{i}"
h.fetch(k) { |k| h[k] = "abc" }
end
end
bm.report '#||=' do
h = Hash.new
h_size.times do |i|
k = "k#{i}"
h[k] ||= "abc"
end
end
end
# Warming up --------------------------------------
# #fetch 163.000 i/100ms
# #||= 181.000 i/100ms
# Calculating -------------------------------------
# #fetch 1.631k (± 5.2%) i/s - 8.150k in 5.010304s
# #||= 1.936k (± 4.3%) i/s - 9.774k in 5.059256s |
Have you seen the second gist I mentioned above? If your second example if key present and value is nil - this conditional equation will always work. In cases where it means some api request - you'll do those requests more than once. That's the point of key presence checking. |
Yes, that is one of the subtle differences, but does it apply in your case? Are you expecting anything besides String or nil as the value? Even if EDIT: rereading your comment... what do you want to happen when the vault password is nil? I thought you wanted that to be a case where you looked at inputs. |
Where did you find that? Did you read the code?
Do you understand what lazy evaluation is, and why it is needed? if I'll write
is it your suggestion or official saying?
I noticed that it is not the first time you saying something without even trying to understand and think. And without any proofs. Here is how this method works: |
@agrare I made some changes. Please check, do you think it is simple enough now? |
Checked commits AlexanderZagaynov/ansible_tower_client_ruby@72c85fe~...ad20b8d with ruby 2.4.6, rubocop 0.69.0, haml-lint 0.20.0, and yamllint 1.10.0 |
3 times. As I've said "that is one of the subtle differences". However, in this PR it makes no difference because your method returns
Regardless, while I would prefer to see
Ah, yes, you are right...it does not get to the exception bit when a block or default value is present (so that then is also not a factor in the performance difference between Aside, I'm curious why the need for the reassignment? I'm unfamiliar with the usage patterns here, so I could be wrong, but is the method called many times in that looking up the value would be overly performance intensive, and thus caching necessary? Or is the data hash reused elsewhere that you need the value present? Assigning it back technically also changes the original data, so while I'm not against it, if there was a need to have the original data intact, it would be lost. Put another way, is the following just as acceptable? @data.fetch("vault_password") { inputs.respond_to?(:vault_password) ? inputs.vault_password.to_s : "" } I'm more curious than anything else...I don't really have a preference. I am ignoring the rest of your comments because, frankly, they are offensive. |
originally it was
What the difference with the wide-used pattern
I'm giving you the proofs, but you don't. You not reading the whole story (like comment I mentioned above, about |
Due to the way that
|
Now when I got calm, let me try to explain why I advocated my first approach:
|
Fixes https://bugzilla.redhat.com/show_bug.cgi?id=1740860