I'm trying to merge to hashes, one using symbols as keys (the defined
default values for my class) and the other using strings as keys
(taken from the params hash).
default = { :name => "Joe", :age => 50 }
params = { "name" => "Bill" }
new_hash = default.merge(params)
{ :name => "Joe", :age => 50, "name" => "Bill }
What's the Ruby way to handle this so that it overwrites :name with
"name"? Do I need to implement a stringify_keys or symbolize_keys
method like in Rails? I'd like to avoid using strings as the keys in
my default hash.
I'm trying to merge to hashes, one using symbols as keys (the defined
default values for my class) and the other using strings as keys
(taken from the params hash).
default = { :name => "Joe", :age => 50 }
params = { "name" => "Bill" }
new_hash = default.merge(params)
{ :name => "Joe", :age => 50, "name" => "Bill }
What's the Ruby way to handle this so that it overwrites :name with
"name"? Do I need to implement a stringify_keys or symbolize_keys
method like in Rails? I'd like to avoid using strings as the keys in
my default hash.
I would think it would be easiest to use something like symbolize_keys.
Or just lift the whole HashWithIndifferentAccess class from Rails.
rekey takes a block, without a block it is the same as:
rekey(&:to_sym)
···
On Oct 30, 12:10 pm, shenry <stuarthe...@gmail.com> wrote:
I'm trying to merge to hashes, one using symbols as keys (the defined
default values for my class) and the other using strings as keys
(taken from the params hash).
What's the Ruby way to handle this so that it overwrites :name with
"name"? Do I need to implement a stringify_keys or symbolize_keys
method like in Rails? I'd like to avoid using strings as the keys in
my default hash.
Hi, I made a small module called SymbolizeKeys that will allow you to extend
the hash you are interested in applying this behaviour to, you can then use
it like this:
I'm going through "Ruby Best Practices" right now, which touches on testing
in the first chapter. This seemed like a good opportunity to practice that
(while I eagerly await PragProg's RSpec book), so I'll include the tests I
wrote.
If anyone has relevant thoughts / criticisms, I welcome them (I don't
promise to agree, though). Is extending the object a wise approach? Are my
tests appropriate / follow good testing methodologies? Is there a better way
to implement anything I've done?
# file: symbolize_keys.rb
module SymbolizeKeys
# converts any current string keys to symbol keys
def self.extended(hash)
hash.each do |key,value|
if key.is_a?(String)
hash.delete key
hash[key] = value #through overridden =
end
end
end
# assigns a new key/value pair
# converts they key to a symbol if it is a string
def =(*args)
args[0] = args[0].to_sym if args[0].is_a?(String)
super
end
# returns new hash which is the merge of self and other hashes
# the returned hash will also be extended by SymbolizeKeys
def merge(*other_hashes , &resolution_proc )
merged = Hash.new.extend SymbolizeKeys
merged.merge! self , *other_hashes , &resolution_proc
end
# merges the other hashes into self
# if a proc is submitted , it's return will be the value for the key
def merge!( *other_hashes , &resolution_proc )
# default resolution: value of the other hash
resolution_proc ||= proc{ |key,oldval,newval| newval }
# merge each hash into self
other_hashes.each do |hash|
hash.each{ |k,v|
# assign new k/v into self, resolving conflicts with resolution_proc
self[k] = self.has_key?(k) ? resolution_proc[k,self[k],v] : v
}
end
self
end
end
···
On Fri, Oct 30, 2009 at 11:10 AM, shenry <stuarthenry@gmail.com> wrote:
I'm trying to merge to hashes, one using symbols as keys (the defined
default values for my class) and the other using strings as keys
(taken from the params hash).
What's the Ruby way to handle this so that it overwrites :name with
"name"? Do I need to implement a stringify_keys or symbolize_keys
method like in Rails? I'd like to avoid using strings as the keys in
my default hash.
# this method was written by Gregory Brown
# and comes from
module Test::Unit
# Used to fix a minor minitest/unit incompatibility in flexmock
AssertionFailedError = Class.new(StandardError)
class TestCase
def self.must(name, &block)
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
defined = instance_method(test_name) rescue false
raise "#{test_name} is already defined in #{self}" if defined
if block_given?
define_method(test_name, &block)
else
define_method(test_name) do
flunk "No implementation provided for #{name}"
end
end
end
end
end
class ExtendingWithSymbolizeKeysTest < Test::Unit::TestCase
def setup @default = {
:age => 50 ,
'initially a string' => 51 ,
/neither string nor symbol/ => 52 ,
} @default.extend SymbolizeKeys
end
must "convert string keys to symbols when extended" do
assert_nil @default[ 'initially a string']
assert_equal @default[:'initially a string'] , 51
end
must "leave symbol keys as symbols" do
assert_equal @default[:age] , 50
end
must 'leave non symbols / strings as they are' do
assert_equal @default[/neither string nor symbol/] , 52
end
end
class SettingKeysWithSymbolizeKeysTest < Test::Unit::TestCase
def setup @default = Hash.new.extend SymbolizeKeys
end
must "convert string keys to symbols" do @default['foo'] = :bar
assert_equal @default[:foo] , :bar
end
must "leave symbol keys as symbols" do @default[:foo] = :bar
assert_equal @default[:foo] , :bar
end
must 'leave non symbols / strings as they are' do @default[/foo/] = :bar
assert_equal @default[/foo/] , :bar
end
must "retain new keys for merge" do
merged = @default.merge(@params2)
assert_equal merged[12] , 'favourite number'
end
must "retain new keys for merge!" do @default.merge!(@params2)
assert_equal @default[12] , 'favourite number'
end
must "replace current values with new values for merge" do
merged = @default.merge(@params1)
assert_equal merged[:name] , 'Bill'
end
must "replace current values with new values for merge!" do @default.merge!(@params1)
assert_equal @default[:name] , 'Bill'
end
must "not change original hash for merge" do @default.merge(@params1)
assert_equal @default[:name] , 'Joe'
end
must "receive [key,oldval,newval] as params to block" do
h1 = {:no_conflict_1 => 1 , :conflict => 2 }.extend(SymbolizeKeys)
h2 = {:conflict => 3 , :no_conflict_2 => 4}
resolution_proc_params = nil
h1.merge(h2){ |*params| resolution_proc_params = params }
assert_equal resolution_proc_params , [:conflict,2,3]
end
must "replace resolve conflicts with block for merge" do
merged = @default.merge(@params1){ |key,oldval,newval| oldval }
assert_equal merged[:name] , 'Joe'
must "replace resolve conflicts with block for merge!" do @default.merge!(@params1){ |key,oldval,newval| oldval }
assert_equal @default[:name] , 'Joe'
@default.merge!(@params1){ |key,oldval,newval| newval }
assert_equal @default[:name] , 'Bill'
end
must "convert string keys to symbols for merge" do
merged = @default.merge(@params1)
assert_nil merged['alias']
assert_equal merged[ :alias] , 'Billy'
end
must "convert string keys to symbols for merge!" do @default.merge!(@params1)
assert_nil @default['alias']
assert_equal @default[ :alias] , 'Billy'
end
must "merge with multiple hashes" do
merged = @default.merge(@params1,@params2)
assert_equal merged[:'from default'] , :default
assert_equal merged[:'from params1'] , :params1
assert_equal merged[:'from params2'] , :params2
end
must "merge! with multiple hashes" do @default.merge!(@params1,@params2)
assert_equal @default[:'from default'] , :default
assert_equal @default[:'from params1'] , :params1
assert_equal @default[:'from params2'] , :params2
end
must "return object that is extended with SymbolizeKeys, for merge" do
merged = @default.merge(@params1)
assert_kind_of SymbolizeKeys , merged
end
must "not modify original hash, for merge" do
original = @default.dup @default.merge(@params1,@params2)
assert_equal original , @default
end
I decided that I wasn't happy with the tests, it should be able to access
the same object through either a string or a symbol (previously it just
turned everything into a symbol, then if you tried to access that object w/
the string, it would not find it).
So I overrode and has_key? also, and changed some of the tests.
Here is the updated version
# file: symbolize_keys.rb
module SymbolizeKeys
# converts any current string keys to symbol keys
def self.extended(hash)
hash.each do |key,value|
if key.is_a?(String)
hash.delete key
hash[key] = value #through overridden =
end
end
end
#considers string keys and symbol keys to be the same
def (key)
key = convert_key(key)
super(key)
end
#considers string keys and symbol keys to be the same
def has_key?(key)
key = convert_key(key)
super(key)
end
# assigns a new key/value pair
# converts they key to a symbol if it is a string
def =(*args)
args[0] = convert_key(args[0])
super
end
# returns new hash which is the merge of self and other hashes
# the returned hash will also be extended by SymbolizeKeys
def merge(*other_hashes , &resolution_proc )
merged = Hash.new.extend SymbolizeKeys
merged.merge! self , *other_hashes , &resolution_proc
end
# merges the other hashes into self
# if a proc is submitted , it's return will be the value for the key
def merge!( *other_hashes , &resolution_proc )
# default resolution: value of the other hash
resolution_proc ||= proc{ |key,oldval,newval| newval }
# merge each hash into self
other_hashes.each do |hash|
hash.each{ |k,v|
# assign new k/v into self, resolving conflicts with resolution_proc
self[k] = self.has_key?(k) ? resolution_proc[k.to_sym,self[k],v] : v
}
end
self
end
private
def convert_key(key)
key.is_a?(String) ? key.to_sym : key
end
end
···
On Fri, Oct 30, 2009 at 11:10 AM, shenry <stuarthenry@gmail.com> wrote:
I'm trying to merge to hashes, one using symbols as keys (the defined
default values for my class) and the other using strings as keys
(taken from the params hash).
What's the Ruby way to handle this so that it overwrites :name with
"name"? Do I need to implement a stringify_keys or symbolize_keys
method like in Rails? I'd like to avoid using strings as the keys in
my default hash.
# this method was written by Gregory Brown
# and comes from
module Test::Unit
# Used to fix a minor minitest/unit incompatibility in flexmock
AssertionFailedError = Class.new(StandardError)
class TestCase
def self.must(name, &block)
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
defined = instance_method(test_name) rescue false
raise "#{test_name} is already defined in #{self}" if defined
if block_given?
define_method(test_name, &block)
else
define_method(test_name) do
flunk "No implementation provided for #{name}"
end
end
end
end
end
class ExtendingWithSymbolizeKeysTest < Test::Unit::TestCase
def setup @default = {
:age => 50 ,
'initially a string' => 51 ,
/neither string nor symbol/ => 52 ,
} @default.extend SymbolizeKeys
end
must "convert string keys to symbols when extended" do
assert_equal @default[:'initially a string'] , 51
end
must "sym/str keys can access through either, but only one key" do
assert_equal @default[ 'initially a string'] , 51
assert_equal @default[:'initially a string'] , 51
assert_equal @default[:'initially a string'] , @default['initially a
string']
assert_equal @default.size , 3
end
must "leave symbol keys as symbols" do
assert_equal @default[:age] , 50
end
must 'leave non symbols / strings as they are' do
assert_equal @default[/neither string nor symbol/] , 52
end
end
class SettingKeysWithSymbolizeKeysTest < Test::Unit::TestCase
def setup @default = Hash.new.extend SymbolizeKeys
end
must "enable access to strings through symbols" do @default['foo'] = 'bar'
assert_equal @default[:foo] , 'bar'
assert_same @default[:foo] , @default['foo']
end
must "enable access to symbols through strings" do @default[:foo] = 'bar'
assert_equal @default['foo'] , 'bar'
assert_same @default[:foo] , @default['foo']
end
must 'leave non symbols / strings as they are' do @default[/foo/] = :bar
assert_equal @default[/foo/] , :bar
end
must "retain new keys for merge" do
merged = @default.merge(@params2)
assert_equal merged[12] , 'favourite number'
end
must "retain new keys for merge!" do @default.merge!(@params2)
assert_equal @default[12] , 'favourite number'
end
must "replace current values with new values for merge" do
merged = @default.merge(@params1)
assert_equal merged[:name] , 'Bill'
end
must "replace current values with new values for merge!" do @default.merge!(@params1)
assert_equal @default[:name] , 'Bill'
end
must "not change original hash for merge" do @default.merge(@params1)
assert_equal @default[:name] , 'Joe'
end
must "receive [key,oldval,newval] as params to block" do
h1 = {:no_conflict_1 => 1 , :conflict => 2 }.extend(SymbolizeKeys)
h2 = {:conflict => 3 , :no_conflict_2 => 4}
resolution_proc_params = nil
h1.merge(h2){ |*params| resolution_proc_params = params }
assert_equal resolution_proc_params , [:conflict,2,3]
end
must "only invoke the resolution proc on conflicts" do
conflict_count = 0
conflicts = { :name => false , :alias => false } @params1.extend(SymbolizeKeys).merge(@params2) do |k,ov,nv|
conflict_count += 1
conflicts[k] = true
end
assert_equal conflict_count , 2
assert conflicts[:name]
assert conflicts[:alias]
end
must "replace resolve conflicts with block for merge" do
merged = @default.merge(@params1){ |key,oldval,newval| oldval }
assert_equal merged[:name] , 'Joe'
Josh, I believe it's better to create a gist for code of that length
and paste the link only. That makes it easier to follow - especially
since you'll get code highlighting and versioning for free.
Is there some standard that I can follow, because there seems to be
conflicts of opinion regarding the best approach.
···
On Tue, Nov 3, 2009 at 7:06 AM, Robert Klemme <shortcutter@googlemail.com>wrote:
2009/11/3 Josh Cheek <josh.cheek@gmail.com>:
> Here is the updated version
Josh, I believe it's better to create a gist for code of that length
and paste the link only. That makes it easier to follow - especially
since you'll get code highlighting and versioning for free.
A big difference here is that a pastie is only available through
pastie.org, a gist is a git repository that can be cloned.
···
On Tue, Nov 3, 2009 at 1:22 PM, Josh Cheek <josh.cheek@gmail.com> wrote:
On Tue, Nov 3, 2009 at 7:06 AM, Robert Klemme <shortcutter@googlemail.com>wrote:
2009/11/3 Josh Cheek <josh.cheek@gmail.com>:
> Here is the updated version
Josh, I believe it's better to create a gist for code of that length
and paste the link only. That makes it easier to follow - especially
since you'll get code highlighting and versioning for free.
Josh, I believe it's better to create a gist for code of that length
and paste the link only. That makes it easier to follow - especially
since you'll get code highlighting and versioning for free.
Well, it seems both sides have valid and good arguments. The only
footnote I'd put on that statement is: the real disadvantage of
pastebin and similar services is that they are typically intended for
short lived data only and may or actually do delete older content. A
gist on the other hand belongs to an account and lives as long as that
account lives or the gist is deleted. Of course, since this is
separate from the posting the issue remains that the posting can be
available and the code not...
Is there some standard that I can follow, because there seems to be
conflicts of opinion regarding the best approach.
No written standard as far as I know. So you're back to square and
have to decide yourself.
Kind regards
robert
···
2009/11/3 Josh Cheek <josh.cheek@gmail.com>:
On Tue, Nov 3, 2009 at 7:06 AM, Robert Klemme <shortcutter@googlemail.com>wrote:
It might be of interest to note that this code basically take the
opposite tack from the HashWithIndifferentAccess which is part of
ActiveSupport and therefore Rails.
HashWithIndifferentAccess (which I'll abbreviate to HWIA) converts
symbol keys to strings in the access methods (, = etc.) Contrary
to some people's thinking (including mine when I first encountered it)
the actual keys in the hash are strings, not symbols.
The trade-offs between the two approaches include:
1) Symbols once created can't be garbage collected. Since Rails uses
HWIA for things like the params hash, and the keys come from parts of
arbitrary client provided URIs this could be an opening for a DOS
attack which creates tons of useless extra parameters which result in
non-GCable symbols. Since HWIA uses strings, for the keys, keys
coming from query parameters and the like never get turned into
symbols. The only symbols will be those coming from the application
itself.
2) Using strings for the keys is slightly slower on access because
computing the hash value of a string is O(length of the string) while
the hash value of a symbol is computed once and is O(1) subsequently.
On the other hand this only becomes significant if the same key is
used to access the hash multiple times, since interning a string as a
symbol requires computing the hash of the string anyway.
In practice, and particularly for the typical usage in Rails apps, I
doubt that there's any real effect on performance from storing strings
rather than symbols for the keys. The main advantage of HWIA is that
it allows you to save a keystroke
params[:id]
vs
params['id']
and some think the former looks a little bit nicer. Although this is
no doubt a potential source of controversy. I for one have a slight
preference for the :id form, but that might be the result of Stockholm
syndrome having worked on so many Rails apps.
···
On Tue, Nov 3, 2009 at 8:40 AM, Josh Cheek <josh.cheek@gmail.com> wrote:
On Tue, Nov 3, 2009 at 7:27 AM, Paul Smith <paul@pollyandpaul.co.uk> wrote:
A big difference here is that a pastie is only available through
pastie.org, a gist is a git repository that can be cloned.
and some think the former looks a little bit nicer. Although this is
no doubt a potential source of controversy. I for one have a slight
preference for the :id form, but that might be the result of Stockholm
syndrome having worked on so many Rails apps.
When it involves Rails, we call it "Copenhagen Syndrome"
David
--
The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com
I have to repeat the story I told at the end of my RubyConf
presentation last year, about the classification of OO languages by
geographic origin:
Smalltalk (which came from Xerox Parc in California) is the surfer's
language, Smalltalk programmers wear baggy swimsuits, and hang loose.
ObjectiveC (which came from an ITT lab in Connecticut) was a "New
England Yankee" language, it did just what it had to do and nothing
more. (Not sure this is still true)
Eiffel (the brainchild of Bertrand Meyer) was/is "quintessentially French")
and
C++ (from Bjarne Stroustrup, a compatriot of DHH, and who at the
time was at Bell/ATT labs) is like Haagen Dazs Ice Cream, you think
it's from Scandanavia, but it's really an industrial product from New
Jersey!
···
On Tue, Nov 3, 2009 at 9:31 AM, David A. Black <dblack@rubypal.com> wrote:
Hi --
On Tue, 3 Nov 2009, Rick DeNatale wrote:
params[:id]
vs
params['id']
and some think the former looks a little bit nicer. Although this is
no doubt a potential source of controversy. I for one have a slight
preference for the :id form, but that might be the result of Stockholm
syndrome having worked on so many Rails apps.
When it involves Rails, we call it "Copenhagen Syndrome"