Are Parentheses Still Optional in Ruby? - ruby-on-rails

If I run, per the docs
a = [:code]
a.collect { |x| x.to_s } # => ["code"]
However if I run
a = [:code]
a.collect({ |x| x.to_s }) # => SyntaxError
As far as I know, ruby has optional parens. How is my syntax getting screwed up here? This is an even bigger problem for me because I want to chain another function after this one, so I require the parens.

You aren't passing the block as a parameter to the parenthesis.
a.collect { |x| x.to_s }
is the same as
a.collect() {|x| x.to_s }
is the same as
a.collect() do |x|
x.to_s
end
And all of that is fairly close to this as well:
block = -> (x) {x.to_s} # Shortcut 'stabby' syntax for lambda{|x| x.to_s}
a.collect(&block) # Or a.collect &block

Related

How to trasform all values in a nested hash?

I want to convert all the values in a nested hash to a utf8 compatible string. I initially thought this would be easy and something like deep_apply should be available for me to use, but I am unable to find anything this simple on a quick google and SO search.
I do not want to write (maintain) a method similar to the lines of Change values in a nested hash . Is there a native API implementation or a shorthand available for this or do I have to write my own method?
I ended up implementing my own approach, that is in no way perfect but works well for my use case and should be easy to maintain. Posting it here for reference to anyone who wants to try it out
def deep_apply object, klasses, &blk
if object.is_a? Array
object.map { |obj_ele| deep_apply(obj_ele, klasses, &blk) }
elsif object.is_a? Hash
object.update(object) {|_, value| deep_apply(value, klasses, &blk) }
elsif klasses.any? { |klass| object.is_a? klass }
blk.call(object)
else
object
end
end
usage:
=> pry(main)> deep_apply({a: [1, 2, "sadsad"]}, [String, Integer]) { |v| v.to_s + "asd" }
=> {:a=>["1asd", "2asd", "sadsadasd"]}
Interesting to learn of the deep_merge approach taken in the answer by "The F". Here is another approach which requires adding a few helper methods.
First, the helper methods:
From the top answer here (converting-a-nested-hash-into-a-flat-hash):
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
From a Github repo called ruby-bury (this functionality was proposed to Ruby core, but rejected)
class Hash
def bury *args
if args.count < 2
raise ArgumentError.new("2 or more arguments required")
elsif args.count == 2
self[args[0]] = args[1]
else
arg = args.shift
self[arg] = {} unless self[arg]
self[arg].bury(*args) unless args.empty?
end
self
end
end
And then a method tying it together:
def change_all_values(hash, &blk)
# the next line makes the method "pure functional"
# but can be removed otherwise.
hash = Marshal.load(Marshal.dump(hash))
flat_hash(hash).each { |k,v| hash.bury(*(k + [blk.call(v)])) }
hash
end
A usage example:
irb(main):063:0> a = {a: 1, b: { c: 1 } }
=> {:a=>1, :b=>{:c=>1}}
irb(main):064:0> b = change_all_values(a) { |val| val + 1 }
=> {:a=>2, :b=>{:c=>2}}
irb(main):066:0> a
=> {:a=>1, :b=>{:c=>1}}
There is deep_merge
yourhash.deep_merge(yourhash) {|_,_,v| v.to_s}
Merge the hash with itself, inspect the value and call to_s on it.
This method requires require 'active_support/core_ext/hash' at the top of file if you are not using ruby on rails.
Obviously, you may handle the conversion of v inside the deep_merge as you like to meet your requirements.
In rails console:
2.3.0 :001 > h1 = { a: true, b: { c: [1, 2, 3] } }
=> {:a=>true, :b=>{:c=>[1, 2, 3]}}
2.3.0 :002 > h1.deep_merge(h1) { |_,_,v| v.to_s}
=> {:a=>"true", :b=>{:c=>"[1, 2, 3]"}}
Well, it's quite simple to write it - so why don't write your own and be absolutely sure how does it behave in all situations ;)
def to_utf8(h)
if h.is_a? String
return h.force_encoding('utf-8')
elsif h.is_a? Symbol
return h.to_s.force_encoding('utf-8').to_sym
elsif h.is_a? Numeric
return h
elsif h.is_a? Array
return h.map { |e| to_utf8(e) }.to_s
else
return h.to_s.force_encoding('utf-8')
end
return hash.to_a.map { |e| result.push(to_utf8(e[0], e[1])) }.to_h
end
You may want to check if all behavior and conversions are correct - and change it if necessary.

Rails method for having a safe sum for enumerable

Rails provides a very convenient construct for allowing the summation of elements of an array.
foo = [1,2,3]
foo.sum
=> 6
However, you aren't always sure if your array will have non nil values, in that case the above method fails
bar = [1,2,nil,4]
bar.sum
TypeError: nil can't be coerced into Fixnum
Is there anything like a safe_sum that ignores the nil values?
You can use Array#compact method:
bar.compact.sum
# => 7
or, you can go with Enumerable#inject:
bar.inject(0) { |sum, el| el ? sum + el : sum }
# => 7
Although the first approach looks much prettier and more convenient to me.
You can even use compact
bar.compact.sum
module Enumerable
def safe_sum(identity = 0, &block)
if block_given?
map(&block).compact.sum(identity)
else
reduce { |sum, element| sum + element.to_f } || identity
end
end
end

What is ->() { } in Ruby?

I've just seen this expression in a Ruby/Rails app:
def method(a, b = nil, &c)
c ||= ->(v) { v }
I understand the first part, but not the ->() { ... } syntax. What does it mean?
The variable names have been changed for briefness. I tried searching, but the non-alphanumeric characters are obviously a nightmare for SEO.
It is a lambda literal. Put the block variables inside () and the body inside {}.
->(x, y){x + y}
In the example, ->(v){v} takes a single argument v and returns it, in other words, it is an identity function. If a block is passed to method, then that is assigned to c. If not, the identity function is assigned to c as default.
That is a lambda literal, introduced in Ruby 1.9:
irb> l = ->(v) { v }
# => #<Proc:0x007f4acea30410#(irb):1 (lambda)>
irb> l.call(1)
# => 1
It is equivalent to write:
irb> l = lambda { |v| v }
# => #<Proc:0x00000001daf538#(irb):1 (lambda)>
In the example you posted it is used to provide a default block to the method when none is specified, consider this:
def method(a, &c)
c ||= ->(v) { v }
c.call(a)
end
method(1)
# => 1
method(1) { |v| v * 2 }
# => 2

Is there a better Ruby or Rails idiom for checking for the presence of values in a nested hash?

If the value:
myhash['first_key']['second_key']
exists, then I need to get it. But 'second_key' may not be present at all in my_hash, and I don't want that line to throw an exception if it is not.
Right now I am wrapping the whole thing in an ugly conditional like so:
if myhash['first_key'].present? and myhash['first_key']['second_key'].present?
...
end
I'm sure there must be something simpler.
You can always use try:
hsh.try(:[], 'first_key').try(:[], 'second_key')
FYI: if you're doing a lot of these checks, you might want to refactor your code to avoid these situations.
When in doubt, write a wrapper:
h = {
first_key: {
second_key: 'test'
}
}
class Hash
def fetch_path(*parts)
parts.reduce(self) do |memo, key|
memo[key] if memo
end
end
end
h.fetch_path(:first_key, :second_key) # => "test"
h.fetch_path(:first_key, :third_key) # => nil
h.fetch_path(:first_key, :third_key, :fourth_key) # => nil
h.fetch_path(:foo, :third_key) # => nil
Try this neat and clean solution. Hash default values:
h = Hash.new( {} ) # sets a hash as default value
Now do what you like:
h[:some_key] # => {}
h[:non_existent_key][:yet_another_non_existent_key] # => nil
Nice?
Say you have an existing hash, which is already populated:
h = { a: 1, b: 2, c: 3 }
So you just set its default to return a new hash:
h.default = {}
And there you go again:
h[:d] # => {}
h[:d][:e] # => nil
I'd point you to the excellent Hashie::Mash
An example:
mash = Hashie::Mash.new
# Note: You used to be able to do : `mash.hello.world` and that would return `nil`
# However it seems that behavior has changed and now you need to use a `!` :
mash.hello!.world # => nil # Note use of `!`
mash.hello!.world = 'Nice' # Multi-level assignment!
mash.hello.world # => "Nice"
# or
mash.hello!.world # => "Nice"
You could set up some default values before processing the hash. Something like:
myhash[:first_key] ||= {}
if myhash[:first_key][:second_key]
# do work
end
Why not define a method for this?
class Hash
def has_second_key?(k1,k2)
self[k1] ? self[k1][k2] : nil
end
end
new_hash = {}
new_hash["a"] = "b"
new_hash["c"] = {"d"=>"e","f"=>"g"}
new_hash[:p] = {q:"r"}
new_hash.has_second_key?("r","p")
# =>nil
new_hash.has_second_key?("c","f")
# =>"g"
new_hash.hash_second_key?(:p,:q)
# =>"r"
To modify your code, it would be:
if myhash.has_second_key?('first-key','second-key')
...
end
This method will return nil, which is Falsey in Ruby, or will return the value of the second key which is Truthy in Ruby.
Obviously you do not have to modify the Hash class if you don't want to. You could have the method except the hash as an argument too. has_second_key?(hash,k1,k2). Then call it as:
has_second_key?(myhash,'first-key','second-key')

what is the best way to convert a json formatted key value pair to ruby hash with symbol as key?

I am wondering what is the best way to convert a json formatted key value pair to ruby hash with symbol as key:
example:
{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }
==>
{ :user=>{ :name => 'foo', :age =>'40', :location=>{ :city => 'bar', :state=>'ca' } } }
Is there a helper method can do this?
using the json gem when parsing the json string you can pass in the symbolize_names option. See here: http://flori.github.com/json/doc/index.html (look under parse)
eg:
>> s ="{\"akey\":\"one\",\"bkey\":\"two\"}"
>> JSON.parse(s,:symbolize_names => true)
=> {:akey=>"one", :bkey=>"two"}
Leventix, thank you for your answer.
The Marshal.load(Marshal.dump(h)) method probably has the most integrity of the various methods because it preserves the original key types recursively.
This is important in case you have a nested hash with a mix of string and symbol keys and you want to preserve that mix upon decode (for instance, this could happen if your hash contains your own custom objects in addition to highly complex/nested third-party objects whose keys you cannot manipulate/convert for whatever reason, like a project time constraint).
E.g.:
h = {
:youtube => {
:search => 'daffy', # nested symbol key
'history' => ['goofy', 'mickey'] # nested string key
}
}
Method 1: JSON.parse - symbolizes all keys recursively => Does not preserve original mix
JSON.parse( h.to_json, {:symbolize_names => true} )
=> { :youtube => { :search=> "daffy", :history => ["goofy", "mickey"] } }
Method 2: ActiveSupport::JSON.decode - symbolizes top-level keys only => Does not preserve original mix
ActiveSupport::JSON.decode( ActiveSupport::JSON.encode(h) ).symbolize_keys
=> { :youtube => { "search" => "daffy", "history" => ["goofy", "mickey"] } }
Method 3: Marshal.load - preserves original string/symbol mix in the nested keys. PERFECT!
Marshal.load( Marshal.dump(h) )
=> { :youtube => { :search => "daffy", "history" => ["goofy", "mickey"] } }
Unless there is a drawback that I'm unaware of, I'd think Method 3 is the way to go.
Cheers
There isn't anything built in to do the trick, but it's not too hard to write the code to do it using the JSON gem. There is a symbolize_keys method built into Rails if you're using that, but that doesn't symbolize keys recursively like you need.
require 'json'
def json_to_sym_hash(json)
json.gsub!('\'', '"')
parsed = JSON.parse(json)
symbolize_keys(parsed)
end
def symbolize_keys(hash)
hash.inject({}){|new_hash, key_value|
key, value = key_value
value = symbolize_keys(value) if value.is_a?(Hash)
new_hash[key.to_sym] = value
new_hash
}
end
As Leventix said, the JSON gem only handles double quoted strings (which is technically correct - JSON should be formatted with double quotes). This bit of code will clean that up before trying to parse it.
Recursive method:
require 'json'
def JSON.parse(source, opts = {})
r = JSON.parser.new(source, opts).parse
r = keys_to_symbol(r) if opts[:symbolize_names]
return r
end
def keys_to_symbol(h)
new_hash = {}
h.each do |k,v|
if v.class == String || v.class == Fixnum || v.class == Float
new_hash[k.to_sym] = v
elsif v.class == Hash
new_hash[k.to_sym] = keys_to_symbol(v)
elsif v.class == Array
new_hash[k.to_sym] = keys_to_symbol_array(v)
else
raise ArgumentError, "Type not supported: #{v.class}"
end
end
return new_hash
end
def keys_to_symbol_array(array)
new_array = []
array.each do |i|
if i.class == Hash
new_array << keys_to_symbol(i)
elsif i.class == Array
new_array << keys_to_symbol_array(i)
else
new_array << i
end
end
return new_array
end
Of course, there is a json gem, but that handles only double quotes.
Another way to handle this is to use YAML serialization/deserialization, which also preserves the format of the key:
YAML.load({test: {'test' => { ':test' => 5}}}.to_yaml)
=> {:test=>{"test"=>{":test"=>5}}}
Benefit of this approach it seems like a format that is better suited for REST services...
The most convenient way is by using the nice_hash gem: https://github.com/MarioRuiz/nice_hash
require 'nice_hash'
my_str = "{ 'user': { 'name': 'foo', 'age': 40, 'location': { 'city' : 'bar', 'state': 'ca' } } }"
# on my_hash will have the json as a hash
my_hash = my_str.json
# or you can filter and get what you want
vals = my_str.json(:age, :city)
# even you can access the keys like this:
puts my_hash._user._location._city
puts my_hash.user.location.city
puts my_hash[:user][:location][:city]
If you think you might need both string and symbol keys:
JSON.parse(json_string).with_indifferent_access

Resources