Merging hashes using both symbols and strings as keys

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.

Any help greatly appreciated.

Stu

shenry 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).

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.

Any help greatly appreciated.

Stu

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

shenry wrote:

I’d like to avoid using strings as the keys in
my default hash.

h1 = { :name => “Joe”, :age => 50 }
h2 = { “name” => “Bill”, :phone => “123-4567”}

h2.each do |key, val|
h1[key.to_sym] = val
end

p h1

–output:–
{:age=>50, :phone=>“123-4567”, :name=>“Bill”}

On Oct 30, 12:10 pm, shenry [email protected] wrote:

{ :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.

Any help greatly appreciated.

require ‘facets/hash/rekey’

default.merge(params.rekey)

rekey takes a block, without a block it is the same as:

rekey(&:to_sym)

2009/10/30 7stud – [email protected]:

end

p h1

–output:–
{:age=>50, :phone=>“123-4567”, :name=>“Bill”}

Typically you do not want to modify defaults so I’d probably do

irb(main):001:0> default = { :name => “Joe”, :age => 50 }.freeze
=> {:name=>“Joe”, :age=>50}
irb(main):002:0> params = { “name” => “Bill” }
=> {“name”=>“Bill”}
irb(main):003:0> new_hash = default.dup
=> {:name=>“Joe”, :age=>50}
irb(main):004:0> params.each {|k,v| new_hash[k.to_sym]=v}
=> {“name”=>“Bill”}
irb(main):005:0> new_hash
=> {:name=>“Bill”, :age=>50}

If you are allowed to change params you could do

irb(main):001:0> default = { :name => “Joe”, :age => 50 }.freeze
=> {:name=>“Joe”, :age=>50}
irb(main):002:0> params = { “name” => “Bill” }
=> {“name”=>“Bill”}
irb(main):003:0> default.each {|k,v| params[k.to_s] ||= v}
=> {:name=>“Joe”, :age=>50}
irb(main):004:0> params
=> {“name”=>“Bill”, “age”=>50}

Kind regards

robert

2009/11/3 Josh C. [email protected]:

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.

Kind regards

robert

On Tue, Nov 3, 2009 at 7:06 AM, Robert K.
[email protected]wrote:

robert


remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Hi, Robert, I always thought that too, but I was told I should put them
in
the email in case the host site went down (
What is "stack level too deep (SystemStackError)" error - Ruby - Ruby-Forum )

Is there some standard that I can follow, because there seems to be
conflicts of opinion regarding the best approach.

On Fri, Oct 30, 2009 at 11:10 AM, shenry [email protected] wrote:

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.

Any help greatly appreciated.

Stu

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 = 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


file: symbolize_keys_test.rb

require ‘test/unit’
require ‘symbolize_keys’

this method was written by Gregory B.

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

end

class MergingWithSymbolizeKeysTest < Test::Unit::TestCase
def setup
@default = {
:name => ‘Joe’ ,
:age => 50 ,
‘initially a string’ => 51 ,
/neither string nor symbol/ => 52 ,
:‘from default’ => :default ,
}
@params1 = {
:name => ‘Bill’ ,
‘alias’ => ‘Billy’ ,
:‘from params1’ => :params1 ,
}
@params2 = {
‘name’ => ‘Sam’ ,
‘alias’ => ‘Sammy’ ,
12 => ‘favourite number’ ,
:‘from params2’ => :params2 ,
}
@default.extend SymbolizeKeys
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’

merged = @default.merge(@params1){ |key,oldval,newval| newval }
assert_equal merged[:name] , 'Bill'

end

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
unique_keys = @default.keys.map{|k|k.to_s.to_sym} |
@params1.keys.map{|k|k.to_s.to_sym}
merged = @default.merge(@params1)
assert_equal merged[‘alias’] , ‘Billy’
assert_equal merged[ :alias] , ‘Billy’
assert_equal merged.size , unique_keys.size
end

must “convert string keys to symbols for merge!” do
unique_keys = @default.keys.map{|k|k.to_s.to_sym} |
@params1.keys.map{|k|k.to_s.to_sym}
@default.merge!(@params1)
assert_equal @default[‘alias’] , ‘Billy’
assert_equal @default[ :alias] , ‘Billy’
assert_equal @default.size , unique_keys.size
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

end

On Tue, Nov 3, 2009 at 1:22 PM, Josh C. [email protected] wrote:

Kind regards
What is "stack level too deep (SystemStackError)" error - Ruby - Ruby-Forum )

Is there some standard that I can follow, because there seems to be
conflicts of opinion regarding the best approach.

A big difference here is that a pastie is only available through
pastie.org, a gist is a git repository that can be cloned.


Paul S.
http://www.nomadicfun.co.uk

[email protected]

2009/11/3 Josh C. [email protected]:

On Tue, Nov 3, 2009 at 7:06 AM, Robert K. [email protected]wrote:

2009/11/3 Josh C. [email protected]:

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.

Hi, Robert, I always thought that too, but I was told I should put them in
the email in case the host site went down (
What is "stack level too deep (SystemStackError)" error - Ruby - Ruby-Forum )

Well, it seems both sides have valid and good arguments. :slight_smile: 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. :slight_smile:

Kind regards

robert

On Tue, Nov 3, 2009 at 8:40 AM, Josh C. [email protected] wrote:

On Tue, Nov 3, 2009 at 7:27 AM, Paul S. [email protected] 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.

Here is the code in a gist symbolize_keys.rb · GitHub

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.

Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

On Tue, Nov 3, 2009 at 7:27 AM, Paul S. [email protected]
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.

Here is the code in a gist symbolize_keys.rb · GitHub

On Fri, Oct 30, 2009 at 11:10 AM, shenry [email protected] wrote:

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.

Any help greatly appreciated.

Stu

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:

default = { :name => “Joe”, :age => 50 }.extend(SymbolizeKeys)

params = { “name” => “Bill” }

default.merge(params) # => {:age=>50, :name=>“Bill”}

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


file: symbolize_keys_test.rb

require ‘test/unit’
require ‘symbolize_keys’

this method was written by Gregory B.

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

end

class MergingWithSymbolizeKeysTest < Test::Unit::TestCase
def setup
@default = {
:name => ‘Joe’ ,
:age => 50 ,
‘initially a string’ => 51 ,
/neither string nor symbol/ => 52 ,
:‘from default’ => :default ,
}
@params1 = {
:name => ‘Bill’ ,
‘alias’ => ‘Billy’ ,
:‘from params1’ => :params1 ,
}
@params2 = {
‘name’ => ‘Sam’ ,
‘alias’ => ‘Sammy’ ,
12 => ‘favourite number’ ,
:‘from params2’ => :params2 ,
}
@default.extend SymbolizeKeys
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’

merged = @default.merge(@params1){ |key,oldval,newval| newval }
assert_equal merged[:name] , 'Bill'

end

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

end

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” :slight_smile:

David


The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)

On Tue, Nov 3, 2009 at 9:31 AM, David A. Black [email protected]
wrote:

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” :slight_smile:

I dunno, there IS a bit of Chicago mixed in.

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!


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale