Secondary Block in Ruby Enumerable - ruby

I can't find what this is called to find out more about why it works and how it works, but we've found that you can do this:
Person = Struct.new(:name)
people = [Person.new('foo'), Person.new('bar')]
# => [#<struct Person name="foo">, #<struct Person name="bar">]
people.find { |person| person.name == 'baz' }
# => nil
people.find(->{ [] }) { |person| person.name == 'baz' }
# => []
I'd like to think it's something of a fallthrough block whenever something returns nil, but perhaps someone could shed some light on how this works?

From the documentation:
find(ifnone = nil) {| obj | block } → obj or nil
find(ifnone = nil) → an_enumerator
Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns
its result when it is specified, or returns nil otherwise.
If no block is given, an enumerator is returned instead.

Related

#dig wrapper method to accept a dynamic number of variables

I'm trying to write a method that accepts an unknown number of arguments and performs a Hash#dig on them.
def unknown_dig(hash, *args)
# do some magic?
hash.dig(non_array_args)
end
#example usage
unknown_dig(hash, 'a', 'b', 'c')
Is this possible?
Hash#dig was bestowed upon us in Ruby v2.3. To support earlier Ruby versions you can use Enumerable#reduce (aka inject). This is how we did it when I was a kid.
def dig_it(h,*keys)
keys.reduce(h) { |obj,k| obj && obj[k] }
end
h = { a: { b: 1 } }
dig_it(h, :a, :b)
#=> 1
dig_it(h, :a)
#=> {:b=>1}
dig_it(h, :a, :c)
#=> nil
dig_it(h, :c, :b)
#=> nil
If obj is a hash, as it is initially (h), when k is passed to the block obj[k] #=> nil if obj does not have a key k (or if obj has a key k whose value if nil), in which case obj && obj[k] #=> obj && nil #=> nil. The block calculation will therefore be obj && obj[k] #=> nil && obj[k] #=> nil for each of the remaining elements of keys that are passed to the block. (nil[k] would raise an exception but it is never executed.) If the hash obj has a key k with value false, the same outcome will result except false (rather than nil) will be returned.
Hash#dig (defined in Ruby 2.3) already does this:
hash = { a: { b: 1 } }
hash.dig(:a, :b) == hash.dig(*[:a, :b])
If you want to it more functional style (where you pass the hash as an argument instead of calling the method on it), it's easy:
def hash_dig(hash, *args)
hash.dig(*args)
end

Ruby struct reset method

I have a large struct, and after creating an object and setting some of the members to values, I want to be able to set all the members of the object to nil. I tried the following, which doesn't work for some reason:
My_struct = Struct.new(
:member1,
:member2,
:member3
) do
def reset
self.each {|x| x = nil }
end
end
myblock = My_struct.new
puts myblock.member1
myblock.member1 = "value"
puts myblock.member1
myblock.reset
puts myblock.member1
I expected that last puts to return "" but it returns "value". What have I done wrong? Is there a native method, other than destroying and recreating the object each time?
self.each is iterating over the values, not the members/keys of the struct. This should do what you want.
My_struct = Struct.new(
:member1,
:member2,
:member3
) do
def reset
self.members.each {|k| send("#{k}=", nil)}
end
end
myblock = My_struct.new
myblock.member1
#=> nil
myblock.member1 = "value"
#=> "value"
myblock.member1
#=> "value"
myblock.reset
myblock.member1
#=> nil
Problem
I have a large struct, and after creating an object and setting some of the members to values, I want to be able to set all the members of the object to nil.
Solution: Just Replace the Whole Struct Object
Unless you have a special need to keep the object ID the same, the easiest thing to do is simply to create a new Struct, because the documentation for Struct#new says:
Unset parameters default to nil.
For example:
My_struct = Struct.new :member1, :member2, :member3
m = My_struct.new 'foo', 'bar', 'baz'
#=> #<struct My_struct member1="foo", member2="bar", member3="baz">
m = My_struct.new
#=> #<struct My_struct member1=nil, member2=nil, member3=nil>
Obviously, m.object_id will differ every time you assign a new My_struct instance to m, but there are very few use cases where this would (or should) matter. It's certainly a lot easier and less prone to errors than trying to reassign individual members, but your mileage may vary.
My_struct = Struct.new(:member1, :member2, :member3) do
def reset
initialize
end
end
myblock = My_struct.new
myblock.member1 = "value"
p myblock.object_id # => 15379000
myblock.reset
p myblock.member1 # => nil
p myblock.object_id # => 15379000; same object
My_struct = Struct.new(
:member1,
:member2,
:member3
) do
def reset
members.each { |m| self[m] = nil }
end
end
m = My_struct.new #=> #<struct My_struct member1=nil, member2=nil, member3=nil>
m.member1 = "Billy-Bob"
m.member2 = "Girtie"
m.member3 = "Hector"
m.member1 #=> "Billy-Bob"
m.member2 #=> "Girtie"
m.member3 #=> "Hector"
m.reset
m.member1 #=> nil
m.member2 #=> nil
m.member3 #=> nil
I've tried with ruby 2.2.4. All fields a nil by default?
But I've found a way you could change fields you like
require 'pp'
MyStruct = Struct.new( :member, :user, :parent ) do
def initialize
self.members.each { | elem | self.send( "#{elem}=", 1 ) }
end
end
pp MyStruct.new
UPDATE: I had to changed the code. Comment below was right. Now it's working

Define a method that find the first element or return nil

I want to create a method that returns the first element of an array, or nil in case it doesnt exist.
def by_port(port)
#collection.select{|x| x.port == port }
end
I know I can assign the result to a variable and return nil if array is empty or first if not, like:
I think you've missed something in your description of the problem - you seem to want the first element of an array that matches a certain condition, or nil if none does. I get that impression because of the use of the block with #select.
So, actually, the method you want already exists: it's Array#detect:
detect(ifnone = nil) { |obj| block } → obj or nil
detect(ifnone = nil) → an_enumerator
Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or returns nil otherwise.
And it's examples:
(1..10).detect { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
(1..100).find { |i| i % 5 == 0 and i % 7 == 0 } #=> 35
So, in your case:
#collection.detect { |x| x.port == port }
should work.
def foo array
array.first
end
foo([1]) # => 1
foo([]) # => nil

Easy way to parse hashes and arrays

Typically, parsing XML or JSON returns a hash, array, or combination of them. Often, parsing through an invalid array leads to all sorts of TypeErrors, NoMethodErrors, unexpected nils, and the like.
For example, I have a response object and want to find the following element:
response['cars'][0]['engine']['5L']
If response is
{ 'foo' => { 'bar' => [1, 2, 3] } }
it will throw a NoMethodError exception, when all I want is to see is nil.
Is there a simple way to look for an element without resorting to lots of nil checks, rescues, or Rails try methods?
Casper was just before me, he used the same idea (don't know where i found it, is a time ago) but i believe my solution is more sturdy
module DeepFetch
def deep_fetch(*keys, &fetch_default)
throw_fetch_default = fetch_default && lambda {|key, coll|
args = [key, coll]
# only provide extra block args if requested
args = args.slice(0, fetch_default.arity) if fetch_default.arity >= 0
# If we need the default, we need to stop processing the loop immediately
throw :df_value, fetch_default.call(*args)
}
catch(:df_value){
keys.inject(self){|value,key|
block = throw_fetch_default && lambda{|*args|
# sneak the current collection in as an extra block arg
args << value
throw_fetch_default.call(*args)
}
value.fetch(key, &block) if value.class.method_defined? :fetch
}
}
end
# Overload [] to work with multiple keys
def [](*keys)
case keys.size
when 1 then super
else deep_fetch(*keys){|key, coll| coll[key]}
end
end
end
response = { 'foo' => { 'bar' => [1, 2, 3] } }
response.extend(DeepFetch)
p response.deep_fetch('cars') { nil } # nil
p response.deep_fetch('cars', 0) { nil } # nil
p response.deep_fetch('foo') { nil } # {"bar"=>[1, 2, 3]}
p response.deep_fetch('foo', 'bar', 0) { nil } # 1
p response.deep_fetch('foo', 'bar', 3) { nil } # nil
p response.deep_fetch('foo', 'bar', 0, 'engine') { nil } # nil
I tried to look through both the Hash documentation and also through Facets, but nothing stood out as far as I could see.
So you might want to implement your own solution. Here's one option:
class Hash
def deep_index(*args)
args.inject(self) { |e,arg|
break nil if e[arg].nil?
e[arg]
}
end
end
h1 = { 'cars' => [{'engine' => {'5L' => 'It worked'}}] }
h2 = { 'foo' => { 'bar' => [1, 2, 3] } }
p h1.deep_index('cars', 0, 'engine', '5L')
p h2.deep_index('cars', 0, 'engine', '5L')
p h2.deep_index('foo', 'bonk')
Output:
"It worked"
nil
nil
If you can live with getting an empty hash instead of nil when there is no key, then you can do it like this:
response.fetch('cars', {}).fetch(0, {}).fetch('engine', {}).fetch('5L', {})
or save some types by defining a method Hash#_:
class Hash; def _ k; fetch(k, {}) end end
response._('cars')._(0)._('engine')._('5L')
or do it at once like this:
["cars", 0, "engine", "5L"].inject(response){|h, k| h.fetch(k, {})}
For the sake of reference, there are several projects i know of that tackle the more general problem of chaining methods in the face of possible nils:
andand
ick
zucker's egonil
methodchain
probably others...
There's also been considerable discussion in the past:
Ruby nil-like object - One of many on SO
Null Objects and Falsiness - Great article by Avdi Grimm
The 28 Bytes of Ruby Joy! - Very interesting discussion following J-_-L's post
More idiomatic way to avoid errors when calling method on variable that may be nil? on ruby-talk
et cetera
Having said that, the answers already provided probably suffice for the more specific problem of chained Hash#[] access.
I would suggest an approach of injecting custom #[] method to instances we are interested in:
def weaken_checks_for_brackets_accessor inst
inst.instance_variable_set(:#original_get_element_method, inst.method(:[])) \
unless inst.instance_variable_get(:#original_get_element_method)
singleton_class = class << inst; self; end
singleton_class.send(:define_method, :[]) do |*keys|
begin
res = (inst.instance_variable_get(:#original_get_element_method).call *keys)
rescue
end
weaken_checks_for_brackets_accessor(res.nil? ? inst.class.new : res)
end
inst
end
Being called on the instance of Hash (Array is OK as all the other classes, having #[] defined), this method stores the original Hash#[] method unless it is already substituted (that’s needed to prevent stack overflow during multiple calls.) Then it injects the custom implementation of #[] method, returning empty class instance instead of nil/exception. To use the safe value retrieval:
a = { 'foo' => { 'bar' => [1, 2, 3] } }
p (weaken_checks_for_brackets_accessor a)['foo']['bar']
p "1 #{a['foo']}"
p "2 #{a['foo']['bar']}"
p "3 #{a['foo']['bar']['ghgh']}"
p "4 #{a['foo']['bar']['ghgh'][0]}"
p "5 #{a['foo']['bar']['ghgh'][0]['olala']}"
Yielding:
#⇒ [1, 2, 3]
#⇒ "1 {\"bar\"=>[1, 2, 3]}"
#⇒ "2 [1, 2, 3]"
#⇒ "3 []"
#⇒ "4 []"
#⇒ "5 []"
Since Ruby 2.3, the answer is dig

Nested hash defined?() [duplicate]

This question already has answers here:
How to avoid NoMethodError for missing elements in nested hashes, without repeated nil checks?
(16 answers)
Closed 7 years ago.
What's the most concise way to determine if #hash[:key1][:key2] is defined, that does not throw an error if #hash or #hash[:key1] are nil?
defined?(#hash[:key1][:key2]) returns True if #hash[:key1] exists (it does not determine whether :key2 is defined)
When using ActiveSupport (Rails) or Backports, you can use try:
#hash[:key1].try(:fetch, :key2)
You could even handle #hash being nil:
#hash.try(:fetch, :key1).try(:fetch, :key2)
If you want #hash to always return a hash for a missing key:
#hash = Hash.new { |h,k| h[k] = {} }
#hash[:foo] # => {}
You could also define this recursive:
def recursive_hash
Hash.new { |h,k| h[k] = recursive_hash }
end
#hash = recursive_hash
#hash[:foo][:bar][:blah] = 10
#hash # => {:foo => {:bar => {:blah => 10}}}
But to answer your question:
module HasNestedKey
Hash.send(:include, self)
def has_nested_key?(*args)
return false unless sub = self[args.shift]
return true if args.empty?
sub.respond_to?(:has_nested_key?) and sub.has_nested_key?(*args)
end
end
#hash.has_nested_key? :key1, :key2
Perhaps I am missing something, but if all you care about is concise...why not:
#hash && #hash[:key1] && #hash[:key1][:key2]
or if you want to save a few characters
#hash && (h = #hash[:key1]) && h[:key2]
if any part of this fails, it returns nil otherwise it returns the value associated with :key2 or true.
The reason the defined? returns true even if :key2 is not there is because it just checks whether the object you are referencing exists, which in that case is the method [] which is an alias for the method fetch which does exist on the hash #hash[:key1] but if that were to return nil, there is no fetch method on nil and it would return nil. That being said, if you had to go n deep into an embedded hash, at some point it would become more efficient to call:
defined?(#hash[:key1][:key2][:key3]) && #hash[:key1][:key2][:key3]
Using Hash#fetch
You can use the Hash#fetch method with a default of {} so that it is safe to call has_key? even if the first level key doesn't exist. e.g.
!hash.nil? && hash.fetch(key1, {}).has_key?(key2)
Alternative
Alternatively you can use the conditional operator e.g.
!hash.nil? && (hash.has_key?(key1) ? hash[key1].has_key?(key2) : false)
i.e. if hash doesn't have key key1 then just return false without looking for the second level key. If it does have key1 then return the result of checking key1's value for key2.
Also, if you want to check that hash[key1]'s value has a has_key? method before calling it:
!hash.nil? && (hash.has_key?(key1) ? hash[key1].respond_to?(:has_key?) &&
hash[key1].has_key?(key2) : false)
#hash[:key1].has_key? :key2
If you don't care about distinguishing nonexistent #hash[:key1][:key2] (at any of 3 levels) from #hash[:key1][:key2] == nil, this is quite clean and works for any depth:
[:key1,:key2].inject(hash){|h,k| h && h[k]}
If you want nil to be treated as existing, use this instead:
(hash[:key1].has_key?(:key2) rescue false)
Another option, one that I just discovered, is to extend Hash with a seek method. Technique comes from Corey O'Daniel.
Stick this in an initializer:
class Hash
def seek(*_keys_)
last_level = self
sought_value = nil
_keys_.each_with_index do |_key_, _idx_|
if last_level.is_a?(Hash) && last_level.has_key?(_key_)
if _idx_ + 1 == _keys_.length
sought_value = last_level[_key_]
else
last_level = last_level[_key_]
end
else
break
end
end
sought_value
end
end
Then just call:
#key_i_need = #hash.seek :one, :two, :three
You'll get the value, or nil if it doesn't exist.

Resources