# Hash#open! / Hash#close!

**URL:** <https://www.ruby-forum.com/t/hash-open-hash-close/82244>\
**Category:** Ruby\
**Created:** [January 18, 2007, 2:13pm UTC](https://www.ruby-forum.com/t/hash-open-hash-close/82244 "2007-01-18T14:13:18Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Trans](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/dfb087/32.png) [@Trans](https://www.ruby-forum.com/u/Trans)\
**Post date:** [January 18, 2007, 2:13pm UTC](https://www.ruby-forum.com/t/hash-open-hash-close/82244/1 "2007-01-18T14:13:18Z")

</div>

hi–

have a look:

class Hash

```
def open!
  class << self
    @_were_public = public_instance_methods - ['close!']
    @_were_public.each { |m| private m }
    def method_missing(s,*a)
      if s.to_s[-1,1] == '='
        self[s] = a.first
      else
        return self[s]
      end
    end
  end
end

def close!
  class << self
    @_were_public.each { |m| public m }
    @_were_public = nil
    remove_method(:method_missing)
  end
end

```

end

usage:

h = {a=\>1, :sort\_by=\>2}  
h.a #=\> NoMethodError  
h.sort\_by #=\> LocalJumpError  
h.open!  
h.a #=\> 1  
h.sort\_by #=\> 2  
h.close!  
h.a #=\> NoMethodError  
h.sort\_by #=\> LocalJumpError

thoughts? improvements? useful? bad-news?

T

ps. i came across an oddity while messing with this:

irb(main):019:0\> q = {}  
=\> {}  
irb(main):020:0\> q.sort\_by  
=\> []  
irb(main):021:0\> q[:a] = 1  
=\> 1  
irb(main):022:0\> q.sort\_by  
LocalJumpError: no block given  
from (irb):22:in `sort\_by’  
from (irb):22  
from :0  
ruby 1.8.4 on debian

---

<div class="post-metadata">

**Author:** ![Trans](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/dfb087/32.png) [@Trans](https://www.ruby-forum.com/u/Trans)\
**Post date:** [January 18, 2007, 2:35pm UTC](https://www.ruby-forum.com/t/hash-open-hash-close/82244/2 "2007-01-18T14:35:30Z")

</div>

On 1/18/07, Trans [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> =\> []  
> irb(main):021:0\> q[:a] = 1  
> =\> 1  
> irb(main):022:0\> q.sort\_by  
> LocalJumpError: no block given  
> from (irb):22:in `sort\_by’  
> from (irb):22  
> from :0  
> ruby 1.8.4 on debian

Hmm is it really an oddity (I have the same behavior on 1.8.5/debian)?  
An empty hash sort just cannot apply the block to any elements, but I  
guess  
you know that ;).  
Would you prefer that sort\_by just checks for the presence of a block  
anyway?

I gotto think a lot about the rest you have written most interesting  
stuff,  
just some very quick thoughts

- There are structs you know 😉
- #open! could return self so that one can write h={:value=\>42}.open!  
#close! should than too for symmetry.
- Would it not be better not to hide a method in case the hash has no  
key?  
That would be a mess right? One would not know if h.size is 1 because of  
h[:size]=1 or h={:a=\>1}.open!
- I’d like to use #open! behind the scenes with a block form like  
class Hash  
def use\_opened &blk  
open!  
blk.call(self) if blk  
close!  
end  
end  
or even  
class Hash  
def in\_context &blk  
open!  
instance\_eval &blk  
…

Conclusion:  
Basically it is a nice idea but maybe you really want a struct.  
I would not think it should be in the core but in an extension like  
facet  
of course it might  
be spot on 😉

In any case that is the kind of post which make this ML so much fun and  
education.

Cheers  
Robert

---

<div class="post-metadata">

**Author:** ![Trans](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/dfb087/32.png) [@Trans](https://www.ruby-forum.com/u/Trans)\
**Post date:** [January 18, 2007, 2:37pm UTC](https://www.ruby-forum.com/t/hash-open-hash-close/82244/3 "2007-01-18T14:37:30Z")

</div>

On 18.01.2007 14:12, Trans wrote:

> ```
> def method_missing(s,*a)
> class << self
> 
> ```
> 
> h = {a=\>1, :sort\_by=\>2}  
> h.a #=\> NoMethodError  
> h.sort\_by #=\> LocalJumpError  
> h.open!  
> h.a #=\> 1  
> h.sort\_by #=\> 2  
> h.close!  
> h.a #=\> NoMethodError  
> h.sort\_by #=\> LocalJumpError
> 
> thoughts? improvements? useful? bad-news?

What’s the advantage over an OpenStruct?

irb(main):001:0\> h = OpenStruct.new(:a=\>1, :sort\_by=\>2)  
=\> #  
irb(main):002:0\> h.a  
=\> 1  
irb(main):003:0\> h.sort\_by  
=\> 2

I’d probably rather make the logic from OpenStruct’s constructor  
available as update or merge method. But this is just a gut feeling.

> ```
> from (irb):22:in `sort_by'
> from (irb):22
> from :0
> 
> ```
> 
> ruby 1.8.4 on debian

Same for Array. Not a big deal IMHO because the extra overhead of  
checking for the block makes the average case (sorting non empty  
collections) slower.

Kind regards

robert

---

<div class="post-metadata">

**Author:** ![Trans](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/dfb087/32.png) [@Trans](https://www.ruby-forum.com/u/Trans)\
**Post date:** [January 18, 2007, 3:07pm UTC](https://www.ruby-forum.com/t/hash-open-hash-close/82244/4 "2007-01-18T15:07:39Z")

</div>

Robert D. wrote:

> Hmm is it really an oddity (I have the same behavior on 1.8.5/debian)?  
> An empty hash sort just cannot apply the block to any elements, but I guess  
> you know that ;).  
> Would you prefer that sort\_by just checks for the presence of a block  
> anyway?

No, I think I’d rather it default to a nominal { |x| x } block, or  
something. but yea, it’s not a big deal, just sort of suprised me.

> I gotto think a lot about the rest you have written most interesting stuff,  
> just some very quick thoughts
> 
> - There are structs you know 😉
> - #open! could return self so that one can write h={:value=\>42}.open!

good idea.

> ```
> close!
> 
> ```
> 
> Basically it is a nice idea but maybe you really want a struct.  
> I would not think it should be in the core but in an extension like facet  
> of course it might  
> be spot on 😉

Good points. probably right about the struct. maybe i should eleborate  
on why i thought of this at all… i like to be as flexiable as  
possible, in my current project i have many methods that take a single  
hash (or “ducked-hash”) argument. i know what to expect in the argument  
and i want to access the data with a method notation rather than  
hash[‘key’] notation.

def amethod( hashything={} )  
hashything.foo  
end

it’s not just a matter of presonal preference either. even if i left it  
as a hash i would need to massage the data to make sure all the keys  
are strings or symbols:

def amethod( hashything={} )  
hashything = hashything.rekey(:to\_s)  
hashything[‘foo’]  
end

so either way i have to effect the argument in some manner. hence i  
considered the idea of open!/close!

def amethod( hashything={} )  
hashything.open!  
hashything.foo  
hashything.close!  
end

as you suggested, a block form might be better.

of course i could just go back to conveting the hashything to an  
OpenStruct (actually I use Facets’ OpenObject) but i was just wondering  
if maybe there’s a nicer/easier/conciser way.

ideally, i wish i didn’t have to even worry about this.

> In any case that is the kind of post which make this ML so much fun and  
> education.

glad to hear 🙂

t.

---

<div class="post-metadata">

**Author:** ![Trans](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/dfb087/32.png) [@Trans](https://www.ruby-forum.com/u/Trans)\
**Post date:** [January 18, 2007, 4:43pm UTC](https://www.ruby-forum.com/t/hash-open-hash-close/82244/5 "2007-01-18T16:43:15Z")

</div>

On Thu, 18 Jan 2007, Trans wrote:

> ```
> def method_missing(s,*a)
> class << self
> 
> ```
> 
> h = {a=\>1, :sort\_by=\>2}
> 
> T

the only real disadvantage i see is that all these

```
 find_all
 keys
 []=
 each
 object_id
 singleton_methods
 inject
 delete
 value?
 to_hash
 equal?
 taint
 sort_by
 frozen?
 instance_variable_get
 max
 kind_of?
 each_pair
 respond_to?
 to_a
 delete_if
 merge!
 index
 select
 merge
 length
 type
 partition
 protected_methods
 store
 grep
 eql?
 instance_variable_set
 hash
 is_a?
 values
 reject
 to_s
 send
 default
 class
 size
 tainted?
 private_methods
 __send__
 member?
 default=
 default_proc
 untaint
 find
 each_with_index
 reject!
 id
 invert
 instance_eval
 collect
 inspect
 has_key?
 replace
 all?
 ==
 ===
 indexes
 entries
 clone
 public_methods
 extend
 each_value
 fetch
 detect
 freeze
 values_at
 zip
 display
 update
 __id__
 shift
 method
 has_value?
 empty?
 map
 =~
 methods
 clear
 any?
 rehash
 nil?
 sort
 dup
 indices
 key?
 min
 instance_variables
 include?
 []
 instance_of?
 each_key

```

cannot be keys since method\_missing is use to set the key. an impl like  
this  
could get around that

harp:~ \> cat a.rb  
class Hash  
class Proxy  
alias\_method ‘ **instance\_eval** ’, ‘instance\_eval’

```
   instance_methods.each{|m| undef_method m unless m[/__/]}

   def initialize h, &b
     @h = h
     __instance_eval__ &b if b
   end

   def method_missing m, *a, &b
     k = m.to_s.delete '=!?'
     case a.size
     when 0
       @h[k]
     when 1
       @h[k] = a.shift
     else
       @h[k] = a
     end
   end
 end

 def open! &b
   proxy = Proxy.new self, &b
   b ? self : proxy
 end

```

end

h = { ‘k’ =\> ‘v’ }  
p h

h.open!{ k 42 }  
p h

h.open!{ self.a = 42.0 }  
p h

proxy = h.open!  
proxy.b = ‘forty-two’  
p h

h.open!{ sort ‘can\_be\_set’ }  
p h

harp:~ \> ruby a.rb  
{“k”=\>“v”}  
{“k”=\>42}  
{“k”=\>42, “a”=\>42.0}  
{“k”=\>42, “a”=\>42.0, “b”=\>“forty-two”}  
{“k”=\>42, “a”=\>42.0, “b”=\>“forty-two”, “sort”=\>“can\_be\_set”}

neat idea though.

-a

---

<div class="post-metadata">

**Author:** ![Trans](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/dfb087/32.png) [@Trans](https://www.ruby-forum.com/u/Trans)\
**Post date:** [January 18, 2007, 5:19pm UTC](https://www.ruby-forum.com/t/hash-open-hash-close/82244/6 "2007-01-18T17:19:28Z")

</div>

removed\_email\_address@domain.invalid wrote:

> > ```
> > @_were_public = public_instance_methods - ['close!']
> > 
> > ```
> > 
> > usage:
> 
> ```
> object_id
> max
> type
> to_s
> untaint
> all?
> freeze
> =~
> instance_variables
> include?
> []
> instance_of?
> each_key
> 
> ```
> 
> cannot be keys since method\_missing is use to set the key. an impl like this  
> could get around that

actually they are made private in my implementation (well, ones with  
only alphanumeric characters) and that allows method\_missing to get at  
them – yea, one of those esoteric peices of knowledge about ruby one  
doesn’t readily remember even when it is known.

T.

---

<div class="post-metadata">

**Author:** ![Trans](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/dfb087/32.png) [@Trans](https://www.ruby-forum.com/u/Trans)\
**Post date:** [January 19, 2007, 4:29pm UTC](https://www.ruby-forum.com/t/hash-open-hash-close/82244/7 "2007-01-19T16:29:43Z")

</div>

What about this one? It uses my personal, small, generic  
delegator.

There’s at least one thing I don’t like: Since it’s a  
delegator, operations on h2 directly affect h1. A pure  
functional approach would be more appropriate: Going from h1 to  
h2 results in a copy of the data. When un\_delegate’ing, we  
should once again copy the data. But that consumes a bit more  
memory… ;]

(I’ll work out the functional one and post it in a couple of  
minutes…)

gegroet,  
Erik V. - [http://www.erikveen.dds.nl/](http://www.erikveen.dds.nl/)

* * *

module EV  
class Delegator  
def initialize(real\_object)  
@real\_object = real\_object  
end

```
 def method_missing(method_name, *args, &block)
   @real_object. __send__ (method_name, *args, &block)
 end

 def self.delegate(*args, &block)
   res = self.new(*args)
 end

 def self.un_delegate(delegator_object)
   delegator_object.instance_variable_get("@real_object")
 end

 def self.open(*args, &block)
   res = delegate(*args)

   if block
     begin
       block.call(res)
     ensure
       res = un_delegate(res)
     end
   end

   res
 end

```

end  
end

class HashWithMethods \< EV::Delegator  
def method\_missing(method\_name, \*args, &block)  
method\_name = method\_name.to\_s

```
 if method_name =~ /=$/
   key = method_name[0..-2]
   value = args[0]

   @real_object[key] = value
 else
   key = method_name

   @real_object[key]
 end

```

end  
end

if **FILE** == $0  
h1 = {“a”=\>111, “b”=\>222}

HashWithMethods.open(h1) do |h2|  
h2.b = 22222  
h2.c = 33333  
end

p h1  
end

---

<div class="post-metadata">

**Author:** ![Trans](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/dfb087/32.png) [@Trans](https://www.ruby-forum.com/u/Trans)\
**Post date:** [January 19, 2007, 4:30pm UTC](https://www.ruby-forum.com/t/hash-open-hash-close/82244/8 "2007-01-19T16:30:00Z")

</div>

On 1/18/07, Trans [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> > > ```
> > > end
> > > 
> > > ```
> > > 
> > > h.close!  
> > > keys  
> > > sort\_by  
> > > select  
> > > is\_a?  
> > > member?  
> > > inspect  
> > > each\_value  
> > > has\_value?  
> > > indices  
> > > this  
> > > Just an idea about that, is there already an RCR out about method  
> > > wrappers.  
> > > I’ll browse the archives just thought that you’d probably know.

Cheers  
Robert

---

<div class="post-metadata">

**Author:** ![Trans](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/dfb087/32.png) [@Trans](https://www.ruby-forum.com/u/Trans)\
**Post date:** [January 19, 2007, 4:31pm UTC](https://www.ruby-forum.com/t/hash-open-hash-close/82244/9 "2007-01-19T16:31:56Z")

</div>

> There’s at least one thing I don’t like: Since it’s a  
> delegator, operations on h2 directly affect h1. A pure  
> functional approach would be more appropriate: Going from h1  
> to h2 results in a copy of the data. When un\_delegate’ing, we  
> should once again copy the data. But that consumes a bit more  
> memory… ;]
> 
> (I’ll work out the functional one and post it in a couple of  
> minutes…)

That was easy… ;]

gegroet,  
Erik V. - [http://www.erikveen.dds.nl/](http://www.erikveen.dds.nl/)

* * *

— hashy1.rb 2007-01-19 14:57:38.000000000 +0100  
+++ hashy2.rb 2007-01-19 14:57:44.000000000 +0100  
@@ -52,10 +52,12 @@  
if **FILE** == $0  
h1 = {“a”=\>111, “b”=\>222}

- HashWithMethods.open(h1) do |h2|

- h3 =

- HashWithMethods.open(h1.dup) do |h2|  
h2.b = 22222  
h2.c = 33333  
end

- p h3  
end
