Difference between Ruby’s Hash and ActiveSupport’s HashWithIndifferentAccess - ruby-on-rails

What is the difference between Ruby’s Hash and ActiveSupport’s HashWithIndifferentAccess? Which is the best for dynamic hashes?

Below is the simple example that will show you difference between simple ruby hash & a "ActiveSupport::HashWithIndifferentAccess"
HashWithIndifferentAccess allows us to access hash key as a symbol or string
Simple Ruby Hash
$ irb
2.2.1 :001 > hash = {a: 1, b:2}
=> {:a=>1, :b=>2}
2.2.1 :002 > hash[:a]
=> 1
2.2.1 :003 > hash["a"]
=> nil
ActiveSupport::HashWithIndifferentAccess
2.2.1 :006 > hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1, b:2)
NameError: uninitialized constant ActiveSupport
from (irb):6
from /home/synerzip/.rvm/rubies/ruby-2.2.1/bin/irb:11:in `<main>'
2.2.1 :007 > require 'active_support/core_ext/hash/indifferent_access'
=> true
2.2.1 :008 > hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1, b:2)
=> {"a"=>1, "b"=>2}
2.2.1 :009 > hash[:a]
=> 1
2.2.1 :010 > hash["a"]
=> 1
class HashWithIndifferentAccess is inherited from ruby "Hash" & above special behavior is added in it.

In Ruby Hash:
hash[:key]
hash["key"]
are different. In HashWithIndifferentAccess as the name suggests, you can access key either way.
Quoting official documentation to this:
Implements a hash where keys :foo and "foo" are considered to be the
same.
and
Internally symbols are mapped to strings when used as keys in the
entire writing interface (calling []=, merge, etc). This mapping
belongs to the public interface. For example, given:
hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
You are
guaranteed that the key is returned as a string:
hash.keys # => ["a"]

Related

Check values in a hash

I am working on a legacy Rails project that uses Ruby 1.8 .
I have an hash of key-value where value is a float number.
For example, my_hash = ['foo'=>12.20, 'bar'=>10.0]. How can I check if my_hash contains all zero values? e.g. ['foo'=>0, 'bar'=>0, 'whatever'=>0] or ['foo'=>0.0, 'bar'=>0, 'whatever'=>0.0].
I know I can loop through & check element one by one, but I just wonder is there a more elegant way?
use below code if my_hash is:
[{"foo"=>0.0, "bar"=>0, "whatever"=>0.0}]
my_hash.first.values.all?{|item| item.zero?}
or if my_hash is array, just:
my_hash.all?{|item| item.zero?}
Check all values in the hash are zero or not?
2.2.3 :006 > h={"foo"=>0.0, "bar"=>0, "whatever"=>0.0}
=> {"foo"=>0.0, "bar"=>0, "whatever"=>0.0}
2.2.3 :007 > h.values.all?{|a| a.zero?}
=> true
If it is an array of hash then
2.2.3 :001 > h = ['foo'=>0.0, 'bar'=>0, 'whatever'=>0.0]
=> [{"foo"=>0.0, "bar"=>0, "whatever"=>0.0}]
2.2.3 :004 > h[0].values.all?{|a| a.zero? }
=> true
OR
2.2.3 :014 > h
=> [{"foo"=>0.0, "bar"=>0, "whatever"=>0.0}]
2.2.3 :015 > h.first.values.all?{|a| a.zero?}
=> true
Any element is zero
2.2.3 :009 > h={"foo"=>0.0, "bar"=>2, "whatever"=>1.1}
=> {"foo"=>0.0, "bar"=>2, "whatever"=>1.1}
2.2.3 :010 > h.values.any?{|a| a.zero?}
=> true
2.2.3 :011 > h={"foo"=>0.2, "bar"=>2, "whatever"=>1.1}
=> {"foo"=>0.2, "bar"=>2, "whatever"=>1.1}
2.2.3 :012 > h.values.any?{|a| a.zero?}
=> false

simple_format changes the text itself

In Rails 3.0, the helper method simple_format changes the parameter itself.
I expected that it only returns the wrapped text.
2.0.0-p648 :001 > Rails.version
=> "3.0.20"
2.0.0-p648 :002 > s = "Hello"
=> "Hello"
2.0.0-p648 :003 > helper.simple_format(s)
=> "<p>Hello</p>"
2.0.0-p648 :004 > s
=> "<p>Hello</p>"
I checked with Rails 4.2 and it doesn't change the text.
Can someone please explain it?
Sam
The difference between implementations of this method in Rails 4.2 and Rails 3.0 is that in Rails 3.0 the passed string is modified (mutated by gsub!) and in Rails 4.2 it's not (it just returns a new modified string):
Rails 4.2:
2.4.0 :006 > s = "hello"
=> "hello"
2.4.0 :007 > simple_format s
=> "<p>hello</p>"
2.4.0 :008 > s
=> "hello"
The source code of different implementations can be found in the documentation

How can I check for inclusion of a datetime in a range in Rails?

Using Ruby's Range#include? with a datetime range throws an exception:
> (Time.zone.now.beginning_of_day..Time.zone.now.end_of_day).include?(Time.zone.now)
TypeError: can't iterate from ActiveSupport::TimeWithZone
While using Rails' Range#overlaps? works:
> (Time.zone.now.beginning_of_day..Time.zone.now.end_of_day).overlaps?(Time.zone.now..Time.zone.now)
=> true
I dislike the fact that I have to create a single-element range to check for inclusion using overlaps? Is there a way to use include? with a datetime range in Rails?
Aha!
How is this?
Time.zone.now.between?(Time.zone.now.beginning_of_day,Time.zone.now.end_of_day)
Result:
2.3.0 :001 > Time.zone.now.between?(Time.zone.now.beginning_of_day,Time.zone.tomorrow.end_of_day)
=> true
2.3.0 :002 > Time.zone.tomorrow.between?(Time.zone.now.beginning_of_day,Time.zone.now.end_of_day)
=> false
2.3.0 :003 > Time.zone.yesterday.between?(Time.zone.now.beginning_of_day,Time.zone.now.end_of_day)
Reference: http://apidock.com/rails/ActiveSupport/TimeWithZone/between%3F

Rails n elements before last

In Rails I often do this:
Model.last(5).first
This retrieves element last-5.
Is there a built-in way of doing this?
The more common way is offset()
Model.offset(5).last
Edit (for lazy people):
1.8.7 :001 > User.first.id
=> 1
1.8.7 :002 > User.last.id
=> 143455
1.8.7 :003 > User.offset(5).last.id
=> 143450

opening Array in ruby on rails console v. irb

I wish to make my code a little more readable by calling #rando on any array and retrieve a random element (rando because a rand() method already exists and I don't want there to be any confusion).
So I opened up the class and wrote a method:
class Array
def rando
self[ rand(length) ]
end
end
This seems far too straightforward.
When I open up irb, and type arr = %w(hi bye) and then arr.rando I get either hi or bye back. That's expected. However, in my rails console, when I do the same thing, I get ArgumentError: wrong number of arguments (1 for 0)
I've been tracing Array up the rails chain and can't figure it out. Any idea?
FWIW, I'm using rails 2.3.11 and ruby 1.8.7
Works fine in my case :
Loading development environment (Rails 3.0.3)
ruby-1.9.2-p180 :001 > class Array
ruby-1.9.2-p180 :002?> def rando
ruby-1.9.2-p180 :003?> self[ rand(length) ]
ruby-1.9.2-p180 :004?> end
ruby-1.9.2-p180 :005?> end
=> nil
ruby-1.9.2-p180 :006 > arr = %w(hi bye)
=> ["hi", "bye"]
ruby-1.9.2-p180 :007 > arr.rando
=> "bye"

Resources