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