Hooking subscript operations in a hash

In order to help debug something, I’d like to hook the hash subscript
operation.

I’ve tried this (and several variants) in irb … but it doesn’t do what
I want it to.

module RalphMod
def []=(index, value)
p index
p value
if index == ‘PATH_INFO’ and value == ‘/undefined’
puts ‘found it!’
end
super index, value
end
end

class Hash
extend RalphMod
end

env = {}
env[‘X’] = ‘Y’
env[‘PATH_INFO’] = ‘/undefined’

The values are not displayed and ‘found it!’ is also not displayed.

On Wed, Feb 3, 2010 at 9:38 PM, Ralph S. [email protected] wrote:

 puts 'found it!'

env[‘X’] = ‘Y’
env[‘PATH_INFO’] = ‘/undefined’

The values are not displayed and ‘found it!’ is also not displayed.

A few problems with this code.

First

class Hash
extend RalphMod
end

makes the method in RalphMod a method of the Hash class object not
hash instances.

The way to do that would be

class Hash
include RalphMod
end

but this wouldn’t work either because methods in included modules come
after methods defined in the class, so you’ll still invoke the
original []= method.

What you want is something like this:

module RalphMod
def []=(index, value)
p index
p value
if index == ‘PATH_INFO’ and value == ‘/undefined’
puts ‘found it!’
end
super index, value
end
end
env = {}
env.extend RalphMod
env[‘X’] = ‘Y’
env[‘PATH_INFO’] = ‘/undefined’

This effectively inserts your module between the instance of Hash
referenced by the variable env and it’s class.


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: Rick DeNatale - Developer - IBM | LinkedIn

RD> On Wed, Feb 3, 2010 at 9:38 PM, Ralph S. [email protected]
wrote:

In order to help debug something, I’d like to hook the hash subscript operation.

I’ve tried this (and several variants) in irb … but it doesn’t do what I want it to.

module RalphMod
def []=(index, value)
p index
p value
if index == ‘PATH_INFO’ and value == ‘/undefined’
puts ‘found it!’
end
super index, value
end
end

class Hash
extend RalphMod
end

env = {}
env[‘X’] = ‘Y’
env[‘PATH_INFO’] = ‘/undefined’

The values are not displayed and ‘found it!’ is also not displayed.

RD> A few problems with this code.

RD> First

RD> class Hash
RD> extend RalphMod
RD> end

RD> makes the method in RalphMod a method of the Hash class object
not
RD> hash instances.

RD> The way to do that would be

RD> class Hash
RD> include RalphMod
RD> end

RD> but this wouldn’t work either because methods in included modules
come
RD> after methods defined in the class, so you’ll still invoke the
RD> original []= method.

RD> What you want is something like this:

RD> module RalphMod
RD> def []=(index, value)
RD> p index
RD> p value
RD> if index == ‘PATH_INFO’ and value == ‘/undefined’
RD> puts ‘found it!’
RD> end
RD> super index, value
RD> end
RD> end
RD> env = {}
RD> env.extend RalphMod
RD> env[‘X’] = ‘Y’
RD> env[‘PATH_INFO’] = ‘/undefined’

RD> This effectively inserts your module between the instance of Hash
RD> referenced by the variable env and it’s class.

Yeah … tried that … but apparently env is going in and out of scope.

Is there any way to do it for all hash subscript operations?

Ralph S. wrote:
[…]

Is there any way to do it for all hash subscript operations?

Using include instead of extend should do the trick. You may also need
to alias the old Hash#[]= and then call it from your new one.

Best,
–Â
Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Hello,

I have two questions.

class RalphMod < Hash
 def []=(index, value)
  p index
  p value
  if index == ‘PATH_INFO’ and value == ‘/undefined’
   puts ‘found it!’
  end
  super index, value

what’s the “super” here? is it coming from which object?

 end
end

env = RalphMod.new()
env[‘X’] = ‘Y’
env[‘PATH_INFO’] = ‘/undefined’

Is env[‘X’] = ‘Y’ the same as env[]=(‘X’,‘Y’) ?
Since you defined a method with def []= so I think env[]=(‘X’,‘Y’) is
easier for understanding.

Thanks.

On Thu, Feb 4, 2010 at 8:41 AM, Ralph S. [email protected] wrote:

if index == ‘PATH_INFO’ and value == ‘/undefined’
env = {}
env[‘X’] = ‘Y’
env[‘PATH_INFO’] = ‘/undefined’

Or why can’t you use?

class RalphMod < Hash
def []=(index, value)
p index
p value
if index == ‘PATH_INFO’ and value == ‘/undefined’
puts ‘found it!’
end
super index, value
end
end

env = RalphMod.new()
env[‘X’] = ‘Y’
env[‘PATH_INFO’] = ‘/undefined’

On Feb 3, 10:50 pm, Marnen Laibow-Koser [email protected] wrote:


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

Posted viahttp://www.ruby-forum.com/.

eh as Rick already pointed out includ’ing a module will insert the
module above the including class, so the original class’s method still
gets called.

You’ll need to “monkey patch” Hash’s []=, and alias the original []=
prior-to.

On 2/3/2010 8:11 PM, Ralph S. wrote:

Is there any way to do it for all hash subscript operations?

irb(main):012:0> class Hash
irb(main):013:1> alias_method :old, :[]=
irb(main):014:1> def []=(index, value)
irb(main):015:2> p index
irb(main):016:2> p value
irb(main):017:2> old index, value
irb(main):018:2> end
irb(main):019:1> end
=> nil
irb(main):020:0> x={}
=> {}
irb(main):021:0> x[:foo]=:bar
:foo
:bar
=> :bar
irb(main):022:0>

That should get you where your trying to go.

Best of luck!

On 04.02.2010 04:11, Ralph S. wrote:

if index == ‘PATH_INFO’ and value == ‘/undefined’
env = {}
RD> class Hash
RD> end
RD> p value

RD> This effectively inserts your module between the instance of Hash
RD> referenced by the variable env and it’s class.

Yeah … tried that … but apparently env is going in and out of scope.

Well, that’s not a problem. As long as you retain a reference to the
object somewhere it will stay there.

Is there any way to do it for all hash subscript operations?

You have been shown solutions but frankly I would not do it. This might
have totally unwanted side effects on other code which uses Hash as
well. Better not modify core classes and instead either inherit them or

  • even better - encapsulate them in another class. This has the added
    advantage that nobody can modify the Hash in unwanted ways and you are
    exposing only that part of Hash’s interface that you intend to.

class RalphHash
def initialize
@h = {}
end

def []=(k,v)
if k == ‘PATH_INFO’ and v == ‘/undefined’
puts ‘found it!’
end

 @h[k]=v

end

def
@h[k]
end
end

You can also use delegate for this.

http://ruby-doc.org/stdlib/libdoc/delegate/rdoc/index.html

Kind regards

robert

RN> Hello,

RN> I have two questions.

class RalphMod < Hash
def []=(index, value)
p index
p value
if index == ‘PATH_INFO’ and value == ‘/undefined’
puts ‘found it!’
end
super index, value

RN> what’s the “super” here? is it coming from which object?

What it means is “pretend you are calling the enclosing parent class’s
method of the same name with these arguments.” Well, that’s my informal
way of putting it.

The Pickaxe book puts it more formally:
Within the body of a method, a call to super acts just like a call to
that original method, except that the search for a method body starts in
the superclass of the object that was found to contain the original
method.


Not in direct answer to your question but as an aside …

I think I could replace
super index, value
with
super
since the function takes the same arguments

Wednesday, February 3, 2010, 8:53:49 PM, you wrote:

WH> On 2/3/2010 8:11 PM, Ralph S. wrote:

Is there any way to do it for all hash subscript operations?

irb(main):012:0>> class Hash
irb(main):013:1>> alias_method :old, :[]=
irb(main):014:1>> def []=(index, value)
irb(main):015:2>> p index
irb(main):016:2>> p value
irb(main):017:2>> old index, value
irb(main):018:2>> end
irb(main):019:1>> end
=>> nil
irb(main):020:0>> x={}
=>> {}
irb(main):021:0>> x[:foo]=:bar
WH> :foo
WH> :bar
=>> :bar

WH> That should get you where your trying to go.

WH> Best of luck!

So I tested this in irb and it seems to work fine …

Then I stuck this into init.rb as part of a Rails app and … boom …

f:\Ralph-Rails-Apps\WebOfTrust>ruby script/server webrick --debugger
=> Booting WEBrick
=> Rails 2.3.5 application starting on http://0.0.0.0:3000
F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/validates_captcha-0.9.6/rail
s/init.rb:11:in alias_method': undefined method []=’ for class
Rails::Plugin: :Hash' (NameError) from F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/validates_captc ha-0.9.6/rails/init.rb:11:in evaluate_init_rb’
from
F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/rails-2.3.5/lib
/rails/plugin.rb:158:in evaluate_init_rb' from F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/activesupport-2 .3.5/lib/active_support/core_ext/kernel/reporting.rb:11:in silence_warnings’
from
F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/rails-2.3.5/lib
/rails/plugin.rb:154:in evaluate_init_rb' from F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/rails-2.3.5/lib /rails/plugin.rb:48:in load’
from
F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/rails-2.3.5/lib
/rails/plugin/loader.rb:38:in load_plugins' from F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/rails-2.3.5/lib /rails/plugin/loader.rb:37:in each’
from
F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/rails-2.3.5/lib
/rails/plugin/loader.rb:37:in load_plugins' ... 10 levels... from F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/rails-2.3.5/lib /commands/server.rb:84 from F:/InstantRails-2.0-win/ruby/lib/ruby/site_ruby/1.8/rubygems/custom _require.rb:31:in gem_original_require’
from
F:/InstantRails-2.0-win/ruby/lib/ruby/site_ruby/1.8/rubygems/custom
_require.rb:31:in `require’
from script/server:3

I am so lost.

Rails has a plugin for Hash?!?!?!? And []= is undefined?!?!?

RN> Is env[‘X’] = ‘Y’ the same as env[]=(‘X’,‘Y’) ?
RN> Since you defined a method with def []= so I think env[]=(‘X’,‘Y’)
is
RN> easier for understanding.

First … I think you meant
env[]={‘X’, ‘Y’}
(braces instead of parenthesis)

The first form
env[]={‘X’, ‘Y’}
means
Create and/or replace a hash table called “env” and stuff it with an
index of ‘X’ and a value of ‘Y’

The second form
env[‘X’] = ‘Y’
means
Take an existing hash table called “env” and add/replace the index ‘X’
in that table with the value ‘Y’

Ralph S. wrote:

RN> Is env[‘X’] = ‘Y’ the same as env[]=(‘X’,‘Y’) ?
RN> Since you defined a method with def []= so I think env[]=(‘X’,‘Y’)
is
RN> easier for understanding.

First … I think you meant
env[]={‘X’, ‘Y’}
(braces instead of parenthesis)

But you are wrong. It’s just like any other method call:
object.method(args)
so
env.[]=(‘X’, ‘Y’)

But never do it this way. env[‘X’] = ‘Y’ is far clearer.

The first form
env[]={‘X’, ‘Y’}
means
Create

No. It won’t create env.

and/or replace a hash table

Hash, not hash table.

called “env” and stuff it with an
index of ‘X’ and a value of ‘Y’

The second form
env[‘X’] = ‘Y’
means
Take an existing hash table called “env” and add/replace the index ‘X’
in that table with the value ‘Y’

No. The two forms are 100% equivalent.

Best,
–Â
Marnen Laibow-Koser
http://www.marnen.org
[email protected]

On Thu, Feb 4, 2010 at 6:21 AM, Ralph S. [email protected] wrote:

irb(main):017:2>> old index, value
irb(main):018:2>> end
irb(main):019:1>> end
=>> nil
irb(main):020:0>> x={}
=>> {}
irb(main):021:0>> x[:foo]=:bar
WH> :foo
WH> :bar
=>> :bar

s/init.rb:11:in alias_method': undefined method []=’ for class Rails::Plugin: :Hash' (NameError) from F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/validates_captc ha-0.9.6/rails/init.rb:11:in evaluate_init_rb’
from F:/InstantRails-2.0-win/ruby/lib/ruby/gems/1.8/gems/rails-2.3.5/lib
/rails/plugin.rb:158:in `evaluate_init_rb’

I’m guessing a bit here, but I suspect that you inserted this code
into the init.rb of the validates_captcha plugin.

This is being evaluated as source from the rails plugin initialization
code, and the name space is Rails:Plugin

So the unscoped name Hash is not the normal and already created Hash
class

and

class Hash

end

creates a new class called Rails::Plugin::Hash with no relationship to
::Hash

Now changing that code to

class ::Hash …

would get you around this immediate problem, but I’m not sure you
really want to do this. Your Rails app is going to spew that
debugging information for EVERY instance of Hash or one of it’s
subclasses.


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: Rick DeNatale - Developer - IBM | LinkedIn

On Thu, Feb 4, 2010 at 9:54 PM, Marnen Laibow-Koser [email protected]
wrote:

But you are wrong. Â It’s just like any other method call:
object.method(args)
so
env.[]=(‘X’, ‘Y’)

But never do it this way. Â env[‘X’] = ‘Y’ is far clearer.

Thanks. I lost the “.”, yes it should be env.[]=(…)
OK I got it that env[‘X’]=‘Y’ is better, thanks again.

RD> I’m guessing a bit here, but I suspect that you inserted this code
RD> into the init.rb of the validates_captcha plugin.

RD> This is being evaluated as source from the rails plugin
initialization
RD> code, and the name space is Rails:Plugin

RD> So the unscoped name Hash is not the normal and already created Hash
class

RD> and

RD> class Hash
RD> …
RD> end

RD> creates a new class called Rails::Plugin::Hash with no relationship
to ::Hash

RD> Now changing that code to

RD> class ::Hash …

Thank you, thank you, thank you.

On Thu, Feb 4, 2010 at 8:54 AM, Marnen Laibow-Koser [email protected]
wrote:

But you are wrong. It’s just like any other method call:

Create
The second form
env[‘X’] = ‘Y’
means
Take an existing hash table called “env” and add/replace the index ‘X’
in that table with the value ‘Y’

No. The two forms are 100% equivalent.

Actually more like only 99 44/100% equivalent.

In cases where a ‘setter’ method returns a value other than the
argument there’s a subtle diference:

class MyHash
def []=(key, value)
“Hey Look, No Hands!”
end
end

mh = MyHash.new

a = mh[1] = 2
a # => 2

b = mh.[]=(1, 2)
b # => “Hey Look, No Hands!”

Ruby sees mh[index]=value as an assignment, and compiles it to the
equivalent of

mh,[]=(index, value)
a = value


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: Rick DeNatale - Developer - IBM | LinkedIn