G
Gavin Kistner
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, revious_sibling, arent_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 arent_node, :next_sibling, revious_sibling
attr_writer :first_child, :last_child
#:startdoc:
end
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, revious_sibling, arent_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 arent_node, :next_sibling, revious_sibling
attr_writer :first_child, :last_child
#:startdoc:
end