From: "Brian Schröder" Date: 2005-06-01T22:49:44+09:00 Subject: Re: Implementing a Read-Only array On 01/06/05, Gavin Kistner wrote: > Right up front, let me say that I realize that I can't prevent > modifications to objects referenced by my array - that's OK. > > SUMMARY > My desire is to create an Array which is read-only from the outside, > but which my class can modify internally. #freeze is not an option, > nor is #dup. > > BACKGROUND > The goal here is to mimic the NodeList class in the W3C DOM model. A > NodeList is an 'array' of items returned from various methods, which > is immutable by the external consumer but which is also 'live' - > references to the returned list of nodes will be updated as the DOM > tree is modified. > > In my implementation, I'm creating an array that provides an ordered > list of children for each Node. Each Node also has #previous_sibling, > #next_sibling, #first_child, #last_child, and #parent_node > attributes, which must be properly kept in-sync. > > A really nice implementation would cause direct manipulation of the > Array to update all associated attributes. In the end I may do that. > However, for now, what I want is to return an array which will not > let the user modify the array itself, but which my own class can > modify when necessary. > > The code I have currently follows (minus comments and some edge > checking, for terseness). What I have works, but I'm wondering if > there is a better way than just aliasing the methods. (I know that > #instance_eval and the like means that I cannot truly lock down a > class, but security-through-obscurity seems less than ideal.) > > class ReadOnlyArray < Array > alias_method :'__ro_<<', :'<<' #:nodoc: > alias_method :__ro_insert, :insert #:nodoc: > alias_method :__ro_delete_at, :delete_at #:nodoc: > affectors = %w| << []= clear concat delete delete_at delete_if > fill flatten! insert map! pack pop push reject! replace reverse! > shift slice! sort! uniq! unshift | > affectors.each{ |name| undef_method name } > end > > module OrderedTreeNode > attr_reader :next_sibling, :previous_sibling, :parent_node > attr_reader :first_child, :last_child, :child_nodes > > def insert_before( new_child, ref_child=nil ) > kids = child_nodes > > #Find the index of the ref_child, if given, or use the end > dest_slot = ref_child ? kids.index( ref_child ) : kids.length > > #Shove the child into the array and update its pointers > kids.__ro_insert( dest_slot, new_child ) > new_child.previous_sibling = kids[ dest_slot - 1 ] > new_child.next_sibling = kids[ dest_slot + 1 ] > new_child.parent_node = self > > new_child.previous_sibling.next_sibling = new_child if > new_child.previous_sibling > new_child.next_sibling.previous_sibling = new_child if > new_child.next_sibling > > new_child > end > > def child_nodes #:nodoc: > @__phrogzdomorderedtreenode_childnodes ||= ReadOnlyArray.new > end > > #:stopdoc: > protected > attr_writer :parent_node, :next_sibling, :previous_sibling > attr_writer :first_child, :last_child > #:startdoc: > > end > > Why not create a proxy class that contains an array and only forwards the reading methods. It seems to me, that this would achieve exactly what you want. best regards, Brian -- http://ruby.brian-schroeder.de/ Stringed instrument chords: http://chordlist.brian-schroeder.de/