Branch

class
Superclass
Object
Included Modules
Comparable
Treequel::Constants
Treequel::Constants::Patterns
Treequel::HashUtilities
Extended With
Loggability
Treequel::Delegation
Treequel::AttributeDeclarations
Treequel::AttributeDeclarations

The object in Treequel that wraps an entry. It knows how to construct other branches for the entries below itself, and how to search for those entries.

Constants

DEFAULT_LDIF_WIDTH

The default width of LDIF output

LDIF_FOLD_SEPARATOR

The characters to use to fold an LDIF line (newline + a space)

Attributes

directory[R]

The directory the branch's entry lives in

dn[R]

The DN of the branch.

to_s[R]

The DN of the branch.

Public Class Methods

anchor
new( directory, dn, entry=nil )

Create a new Treequel::Branch with the given directory, dn, and an optional entry. If the optional entry object is given, it will be used to fetch values from the directory; if it isn't provided, it will be fetched from the directory the first time it is needed.

# File lib/treequel/branch.rb, line 79
def initialize( directory, dn, entry=nil )
        raise ArgumentError, "nil DN" unless dn
        raise ArgumentError, "invalid DN" unless
                dn.match( Patterns::DISTINGUISHED_NAME ) || dn.empty?
        raise ArgumentError, "can't cast a %s to an LDAP::Entry" % [entry.class.name] unless
                entry.nil? || entry.is_a?( Hash )

        @directory = directory
        @dn        = dn
        @entry     = entry ? stringify_keys( entry ) : nil
        @values    = {}

        @include_operational_attrs = self.class.include_operational_attrs?

        self.log.debug "New branch (%s): entry = %p, directory = %p" % [ @dn, @entry, @directory ]
end
anchor
new_from_entry( entry, directory )

Create a new Treequel::Branch from the given entry hash from the specified directory.

# File lib/treequel/branch.rb, line 60
def self::new_from_entry( entry, directory )
        entry = Treequel::HashUtilities.stringify_keys( entry )
        dnvals = entry.delete( 'dn' ) or
                raise ArgumentError, "no 'dn' attribute for entry"

        Treequel.logger.debug "Creating Branch from entry: %p in directory: %s" %
                [ dnvals.first, directory ]
        return self.new( directory, dnvals.first, entry )
end

Public Instance Methods

anchor
+( other_branch )

Addition operator: return a Treequel::BranchCollection that contains both the receiver and other_branch.

# File lib/treequel/branch.rb, line 493
def +( other_branch )
        return Treequel::BranchCollection.new( self.branchset, other_branch.branchset )
end
anchor
<=>( other_branch )

Comparable interface: Returns -1 if other_branch is less than, 0 if other_branch is equal to, and +1 if other_branch is greater than the receiving Branch.

# File lib/treequel/branch.rb, line 463
def <=>( other_branch )
        # Try the easy cases first
        return nil unless other_branch.respond_to?( :dn ) &&
                other_branch.respond_to?( :split_dn )
        return 0 if other_branch.dn == self.dn

        # Try comparing reversed attribute pairs
        rval = nil
        pairseq = self.split_dn.reverse.zip( other_branch.split_dn.reverse )
        pairseq.each do |a,b|
                comparison = (a <=> b)
                return comparison if !comparison.nil? && comparison.nonzero?
        end

        # The branches are related, so directly comparing DN strings will work
        return self.dn <=> other_branch.dn
end
anchor
[]( attrname )

Fetch the value/s associated with the given attrname from the underlying entry.

# File lib/treequel/branch.rb, line 300
def []( attrname )
        attrsym = attrname.to_sym

        if @values.key?( attrsym )
                # self.log.debug "  value for %p is cached (%p)." % [ attrname, @values[attrsym] ]
        else
                self.log.debug "  value for %p is NOT cached." % [ attrsym ]
                value = self.get_converted_object( attrsym )
                self.log.debug "  converted value is: %p" % [ value ]
                value.freeze if
                        self.class.freeze_converted_values? &&
                        value.respond_to?( :freeze )
                @values[ attrsym ] = value if value
        end

        return @values[ attrsym ]
end
anchor
[]=( attrname, value )

Set attribute attrname to a new value.

# File lib/treequel/branch.rb, line 331
def []=( attrname, value )
        value = [ value ] unless value.is_a?( Array )
        value.collect! {|val| self.get_converted_attribute(attrname, val) }
        self.log.debug "Modifying %s to %p" % [ attrname, value ]
        self.directory.modify( self, attrname.to_s => value )
        @values.delete( attrname.to_sym )
        self.entry[ attrname.to_s ] = value
end
anchor
branchset()

Return a Treequel::Branchset that will use the receiver as its base.

# File lib/treequel/branch.rb, line 215
def branchset
        return Treequel::Branchset.new( self )
end
anchor
children()

Return the Branch's immediate children as Treeque::Branch objects.

# File lib/treequel/branch.rb, line 209
def children
        return self.search( :one, '(objectClass=*)' )
end
anchor
copy( newdn, attributes={} )

Copy the entry for this Branch to a new entry with the given newdn and merge in the specified attributes.

# File lib/treequel/branch.rb, line 418
def copy( newdn, attributes={} )

        # Fully-qualify RDNs
        newdn = newdn + ',' + self.parent_dn unless newdn.index(',')

        self.log.debug "Creating a copy of %p at %p" % [ self.dn, newdn ]
        newbranch = self.class.new( self.directory, newdn )

        attributes = self.entry.merge( stringify_keys(attributes) )

        self.log.debug "  merged attributes: %p" % [ attributes ]
        self.directory.create( newbranch, attributes )

        return newbranch
end
anchor
create( attributes={} )

Create the entry for this Branch with the specified attributes. The attributes should, at a minimum, contain the pair :objectClass => [:someStructuralObjectClass].

groups = dir.ou( :groups )
newgroup = groups.cn( :staff )
newgroup.create( :objectClass => ['posixGroup'], :gidNumber => 2100 )
# => #<Treequel::Branch:0x1086a0ac8 cn=staff,ou=groups,dc=example,dc=com>
# File lib/treequel/branch.rb, line 409
def create( attributes={} )
        self.directory.create( self, attributes )
        self.clear_caches
        return self
end
anchor
delete( *attributes )

Delete the specified attributes, which are the attributes to delete either as attribute names (in which case all values of the attribute are deleted), or Hashes of attributes and the Array of value/s which should be deleted.

# Delete all 'description' attributes
branch.delete( :description )

# Delete the 'inetOrgPerson' and 'posixAccount' objectClasses from the entry
branch.delete( :objectClass => [:inetOrgPerson, :posixAccount] )

# Delete any blank 'description' or 'cn' attributes:
branch.delete( :description => '', :cn => '' )
# File lib/treequel/branch.rb, line 366
def delete( *attributes )

        # If no attributes are given, delete the whole entry
        if attributes.empty?
                self.log.info "No attributes specified; deleting entire entry for %s" % [ self.dn ]
                self.directory.delete( self )

        # Otherwise, gather up the LDAP::Mod objects that will delete the given attributes
        else
                self.log.debug "Deleting attributes: %p" % [ attributes ]
                mods = attributes.flatten.collect do |attribute|

                        # Delete particular values of the attribute
                        if attribute.is_a?( Hash )
                                attribute.collect do |key,vals|
                                        vals = [ vals ] unless vals.is_a?( Array )
                                        vals.collect! {|val| self.get_converted_attribute(key, val) }
                                        LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, key.to_s, vals )
                                end

                        # Delete all values of the attribute
                        else
                                LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, attribute.to_s, [] )
                        end

                end

                self.directory.modify( self, mods.flatten )
        end

        self.clear_caches

        return true
end
anchor
dn=( newdn )

Change the DN the Branch uses to look up its entry to newdn.

# File lib/treequel/branch.rb, line 121
def dn=( newdn )
        self.clear_caches
        @dn = newdn
end
anchor
entry()

Return the LDAP::Entry associated with the receiver, fetching it from the directory if necessary. Returns nil if the entry doesn't exist in the directory.

# File lib/treequel/branch.rb, line 145
def entry
        @entry ||= self.lookup_entry
end
anchor
eql?( other )

Comparison-by-value method – returns true if the receiver has the same DN as other.

# File lib/treequel/branch.rb, line 449
def eql?( other )
        return false unless other.class.eql?( self.class )
        return self.hash == other.hash
end
anchor
exists?()

Returns true if there is an entry currently in the directory with the branch's DN.

# File lib/treequel/branch.rb, line 152
def exists?
        return self.entry ? true : false
end
anchor
get_child( rdn )

Fetch a new Treequel::Branch object for the child of the receiver with the specified rdn.

# File lib/treequel/branch.rb, line 484
def get_child( rdn )
        self.log.debug "Getting child %p from base = %p" % [ rdn, self.dn ]
        newdn = [ rdn, self.dn ].reject {|part| part.empty? }.join( ',' )
        return self.class.new( self.directory, newdn )
end
anchor
hash()

Generates a Fixnum hash for the receiver.

# File lib/treequel/branch.rb, line 456
def hash
        return [ self.class, self.dn ].hash
end
anchor
include_operational_attributes=( new_setting )
anchor
include_operational_attrs=( new_setting )

Enable (if new_setting is true) or disable fetching of operational attributes (RC4512, section 3.4).

# File lib/treequel/branch.rb, line 129
def include_operational_attrs=( new_setting )
        self.clear_caches
        @include_operational_attrs = new_setting ? true : false
end
anchor
inspect()

Returns a human-readable representation of the object suitable for debugging.

# File lib/treequel/branch.rb, line 222
def inspect
        return "#<%s:0x%0x %s @ %s entry=%p>" % [
                self.class.name,
                self.object_id * 2,
                self.dn,
                self.directory,
                @entry,
          ]
end
anchor
loaded?()

Returns true if the Branch's entry has been fetched from the directory.

# File lib/treequel/branch.rb, line 158
def loaded?
        return @entry ? true : false
end
anchor
may_attribute_types( *additional_object_classes )

Return Treequel::Schema::AttributeType instances for each of the receiver's objectClass's MAY attributeTypes. If any additional_object_classes are given, include the MAY attributeTypes for them as well. This can be used to predict what optional attributes could be added to the entry if the additional_object_classes were added to it.

# File lib/treequel/branch.rb, line 588
def may_attribute_types( *additional_object_classes )
        return self.object_classes( *additional_object_classes ).
                collect {|oc| oc.may }.flatten.uniq
end
anchor
may_attributes_hash( *additional_object_classes )

Return a Hash of the optional attributes allowed by the Branch's objectClasses. If any additional_object_classes are given, include the attributes that would be available for the entry if it had them.

# File lib/treequel/branch.rb, line 608
def may_attributes_hash( *additional_object_classes )
        entry = self.entry
        attrhash = {}

        self.may_attribute_types( *additional_object_classes ).each do |attrtype|
                # self.log.debug "  adding attrtype %p to the MAY attributes hash" % [ attrtype.named ]

                if attrtype.single?
                        attrhash[ attrtype.name ] = nil
                else
                        attrhash[ attrtype.name ] = []
                end
        end

        # :FIXME: Does the resulting hash need the additional objectClasses? objectClass is
        #         MUST via 'top', so it should already exist in that hash when merged with
        #         this one...
        # attrhash[ :objectClass ] |= additional_object_classes

        return attrhash
end
anchor
may_oids( *additional_object_classes )

Return OIDs (numeric OIDs as Strings, named OIDs as Symbols) for each of the receiver's objectClass's MAY attributeTypes. If any additional_object_classes are given, include the OIDs of the MAY attributes for them as well. This can be used to predict what optional attributes could be added to the entry if the additional_object_classes were added to it.

# File lib/treequel/branch.rb, line 599
def may_oids( *additional_object_classes )
        return self.object_classes( *additional_object_classes ).
                collect {|oc| oc.may_oids }.flatten.uniq
end
anchor
merge( attributes )

Make the changes to the entry specified by the given attributes.

branch.merge( :description => ['The syadmin group'], :cn => ['sysadmin'] )

# File lib/treequel/branch.rb, line 344
def merge( attributes )
        self.directory.modify( self, attributes )
        self.clear_caches

        return true
end
Also aliased as: modify
anchor
modify( attributes )
Alias for: merge
anchor
move( rdn )

Move the entry associated with this branch to a new entry indicated by rdn. If any attributes are given, also replace the corresponding attributes on the new entry with them.

# File lib/treequel/branch.rb, line 438
def move( rdn )
        self.log.debug "Asking the directory to move me to an entry called %p" % [ rdn ]
        self.directory.move( self, rdn )
        self.clear_caches

        return self
end
anchor
must_attribute_types( *additional_object_classes )

Return Treequel::Schema::AttributeType instances for each of the receiver's objectClass's MUST attributeTypes. If any additional_object_classes are given, include the MUST attributeTypes for them as well. This can be used to predict what attributes would need to be present for the entry to be saved if it added the additional_object_classes to its own.

# File lib/treequel/branch.rb, line 542
def must_attribute_types( *additional_object_classes )
        oclasses = self.object_classes( *additional_object_classes )
        types = oclasses.map( &:must ).flatten.uniq

        return types
end
anchor
must_attributes_hash( *additional_object_classes )

Return a Hash of the attributes required by the Branch's objectClasses. If any additional_object_classes are given, include the attributes that would be necessary for the entry to be saved with them.

# File lib/treequel/branch.rb, line 564
def must_attributes_hash( *additional_object_classes )
        attrhash = {}

        self.must_attribute_types( *additional_object_classes ).each do |attrtype|
                # self.log.debug "  adding attrtype %p to the MUST attributes hash" % [ attrtype.name ]

                if attrtype.name == :objectClass
                        attrhash[ :objectClass ] = ['top'] | additional_object_classes
                elsif attrtype.single?
                        attrhash[ attrtype.name ] = ''
                else
                        attrhash[ attrtype.name ] = ['']
                end
        end

        return attrhash
end
anchor
must_oids( *additional_object_classes )

Return OIDs (numeric OIDs as Strings, named OIDs as Symbols) for each of the receiver's objectClass's MUST attributeTypes. If any additional_object_classes are given, include the OIDs of the MUST attributes for them as well. This can be used to predict what attributes would need to be present for the entry to be saved if it added the additional_object_classes to its own.

# File lib/treequel/branch.rb, line 555
def must_oids( *additional_object_classes )
        return self.object_classes( *additional_object_classes ).
                collect {|oc| oc.must_oids }.flatten.uniq.reject {|val| val == '' }
end
anchor
object_classes( *additional_classes )

Return Treequel::Schema::ObjectClass instances for each of the receiver's objectClass attributes. If any additional_classes are given, merge them with the current list of the current objectClasses for the lookup.

# File lib/treequel/branch.rb, line 501
def object_classes( *additional_classes )
        # self.log.debug "Fetching object classes for %s" % [ self.dn ]
        schema = self.directory.schema

        oc_oids = self[:objectClass] || []
        oc_oids |= additional_classes.collect {|str| str.to_sym }
        oc_oids << 'top' if oc_oids.empty?

        oclasses = []
        oc_oids.each do |oid|
                oc = schema.object_classes[ oid.to_sym ] or
                        raise Treequel::Error, "schema doesn't have a %p objectClass" % [ oid ]
                oclasses << oc
        end

        # self.log.debug "  found %d objectClasses: %p" % [  oclasses.length, oclasses.map(&:name) ]
        return oclasses.uniq
end
anchor
operational_attribute_oids()

Return OIDs (numeric OIDs as Strings, named OIDs as Symbols) for each of the receiver's operational attributes.

# File lib/treequel/branch.rb, line 529
def operational_attribute_oids
        return self.operational_attribute_types.inject([]) do |oids, attrtype|
                oids.push( *attrtype.names )
                oids << attrtype.oid
        end
end
anchor
operational_attribute_types()

Return the receiver's operational attributes as attributeType schema objects.

# File lib/treequel/branch.rb, line 522
def operational_attribute_types
        return self.directory.schema.operational_attribute_types
end
anchor
parent()

Return the Branch's immediate parent node.

# File lib/treequel/branch.rb, line 194
def parent
        pardn = self.parent_dn or return nil
        return self.class.new( self.directory, pardn )
end
anchor
parent_dn()

Return the DN of this entry's parent, or nil if it doesn't have one.

# File lib/treequel/branch.rb, line 186
def parent_dn
        return nil if self.dn == self.directory.base_dn
        return '' if self.dn.index( ',' ).nil?
        return self.split_dn( 2 ).last
end
anchor
rdn()

Return the RDN of the branch.

# File lib/treequel/branch.rb, line 164
def rdn
        return self.split_dn( 2 ).first
end
anchor
rdn_attributes()

Return the attribute/s which make up this Branch's RDN as a Hash.

# File lib/treequel/branch.rb, line 137
def rdn_attributes
        return make_rdn_hash( self.rdn )
end
anchor
search( scope=:subtree, filter='(objectClass=*)', parameters={}, &block )

Perform a search with the specified scope, filter, and parameters using the receiver as the base. See Trequel::Directory#search for details. Returns an Array of Treequel::Branch objects.

# File lib/treequel/branch.rb, line 203
def search( scope=:subtree, filter='(objectClass=*)', parameters={}, &block )
        return self.directory.search( self, scope, filter, parameters, &block )
end
anchor
split_dn( limit=0 )

Return the receiver's DN as an Array of attribute=value pairs. If the optional limit is non-zero, only the limit-1 first pairs are split from the DN, and the remainder will be returned as the last element.

# File lib/treequel/branch.rb, line 172
def split_dn( limit=0 )
        return self.dn.split( /\s*,\s*/, limit )
end
anchor
to_hash()

Return the Branch as a Hash.

# File lib/treequel/branch.rb, line 284
def to_hash
        entry = self.entry || self.valid_attributes_hash
        self.log.debug "  making a Hash from an entry: %p" % [ entry ]

        return entry.keys.inject({}) do |hash, attribute|
                if attribute == 'dn'
                        hash[ attribute ] = self.dn
                else
                        hash[ attribute ] = self[ attribute ]
                end
                hash
        end
end
anchor
to_ldif( width=DEFAULT_LDIF_WIDTH )

Return the Branch as an LDAP::LDIF::Entry.

# File lib/treequel/branch.rb, line 267
def to_ldif( width=DEFAULT_LDIF_WIDTH )
        ldif = "dn: %s\n" % [ self.dn ]

        entry = self.entry || self.valid_attributes_hash
        self.log.debug "  making LDIF from an entry: %p" % [ entry ]

        entry.keys.reject {|k| k == 'dn' }.each do |attribute|
                Array( entry[attribute] ).each do |val|
                        ldif << ldif_for_attr( attribute, val, width )
                end
        end

        return LDAP::LDIF::Entry.new( ldif )
end
anchor
to_ufn()

Return the entry's DN as an RFC1781-style UFN (User-Friendly Name).

# File lib/treequel/branch.rb, line 234
def to_ufn
        if LDAP.respond_to?( :dn2ufn )
                return LDAP.dn2ufn( self.dn )

        # An implementation for LDAP libraries with no
        # dn2ufn
        else
                ufn = ''
                tuples = self.split_dn

                # Separate the trailing domainComponents
                dcs = []
                dcs << tuples.pop while tuples.last =~ /^dc\s*=/i

                # Append the non-dc tuples with their attributes stripped first
                ufn << tuples.collect do |rdn|
                        rdn.
                                gsub(/\b#{ATTRIBUTE_TYPE}\s*=/, '').
                                gsub(/\s*\+\s*/, ' + ')
                end.join( ', ' )

                # Now append the DCs joined with dots
                unless dcs.empty?
                        ufn << ', '
                        ufn << dcs.reverse.map {|rdn| rdn.sub(/dc\s*=\s*/i, '') }.join( '.' )
                end

                return ufn
        end
end
anchor
uri()

Return the LDAP URI for this branch

# File lib/treequel/branch.rb, line 178
def uri
        uri = self.directory.uri
        uri.dn = self.dn
        return uri
end
anchor
valid_attribute?( attroid )

Return true if the specified attrname is a valid attributeType given the receiver's current objectClasses. Does not include operational attributes.

# File lib/treequel/branch.rb, line 659
def valid_attribute?( attroid )
        return !self.valid_attribute_type( attroid ).nil?
end
anchor
valid_attribute_oids()

Return a uniqified Array of OIDs (numeric OIDs as Strings, named OIDs as Symbols) for the set of all of the receiver's MUST and MAY attributeTypes plus the operational attributes.

# File lib/treequel/branch.rb, line 643
def valid_attribute_oids
        return self.must_oids | self.may_oids
end
anchor
valid_attribute_type( attroid )

If the attribute associated with the given attroid is in the list of valid attributeTypes for the receiver given its objectClasses, return the AttributeType object that corresponds with it. If it isn't valid, return nil. Includes operational attributes.

# File lib/treequel/branch.rb, line 652
def valid_attribute_type( attroid )
        return self.valid_attribute_types.find {|attr_type| attr_type.valid_name?(attroid) }
end
anchor
valid_attribute_types()

Return Treequel::Schema::AttributeType instances for the set of all of the receiver's MUST and MAY attributeTypes plus the operational attributes.

# File lib/treequel/branch.rb, line 633
def valid_attribute_types
        return self.must_attribute_types |
               self.may_attribute_types  |
               self.operational_attribute_types
end
anchor
valid_attributes_hash( *additional_object_classes )

Return a Hash of all the attributes allowed by the Branch's objectClasses. If any additional_object_classes are given, include the attributes that would be available for the entry if it had them.

# File lib/treequel/branch.rb, line 667
def valid_attributes_hash( *additional_object_classes )
        self.log.debug "Gathering a hash of all valid attributes:"
        must = self.must_attributes_hash( *additional_object_classes )
        self.log.debug "  MUST attributes: %p" % [ must ]
        may  = self.may_attributes_hash( *additional_object_classes )
        self.log.debug "  MAY attributes: %p" % [ may ]

        return may.merge( must )
end
anchor
values_at( *attributes )

Fetch one or more values for the specified attributes from the entry.

branch.values_at( :cn, :objectClass ) => [[“sysadmin”], [“top”, “posixGroup”, “apple-group”]]

# File lib/treequel/branch.rb, line 323
def values_at( *attributes )
        return attributes.collect do |attribute|
                self[ attribute ]
        end
end

Protected Instance Methods

anchor
clear_caches()

Clear any cached values when the structural state of the object changes.

# File lib/treequel/branch.rb, line 778
def clear_caches
self.log.debug "Clearing entry and values caches."
        @entry = nil
        @values.clear
end
anchor
get_converted_attribute( attrsym, object )

Convert the specified object according to the Branch's directory's conversion rules, and return it.

# File lib/treequel/branch.rb, line 765
def get_converted_attribute( attrsym, object )
        if attribute = self.directory.schema.attribute_types[ attrsym ]
                self.log.debug "converting %p object (a %p) to a %s attribute" %
                        [ attrsym, object.class, attribute.syntax.desc ]
                return self.directory.convert_to_attribute( attribute.syntax_oid, object )
        else
                self.log.info "no attributeType for %p" % [ attrsym ]
                return object.to_s
        end
end
anchor
get_converted_object( attrsym )

Get the value associated with attrsym, convert it to a Ruby object if the Branch's directory has a conversion rule, and return it.

# File lib/treequel/branch.rb, line 741
def get_converted_object( attrsym )
        value = self.entry ? self.entry[ attrsym.to_s ] : nil

        if attribute = self.directory.schema.attribute_types[ attrsym ]
                syntax = attribute.syntax
                syntax_oid = syntax.oid if syntax

                if attribute.single?
                        value = self.directory.convert_to_object( syntax_oid, value.first ) if value
                else
                        value = Array( value ).collect do |raw|
                                self.directory.convert_to_object( syntax_oid, raw )
                        end
                end
        else
                self.log.info "no attributeType for %p" % [ attrsym ]
        end

        return value
end
anchor
lookup_entry()

Fetch the entry from the Branch's directory.

# File lib/treequel/branch.rb, line 721
def lookup_entry
        self.log.debug "Looking up entry for %s" % [ self.dn ]
        entry = nil

        if self.include_operational_attrs?
                self.log.debug "  including operational attributes."
                entry = self.directory.get_extended_entry( self )
        else
                self.log.debug "  not including operational attributes."
                entry = self.directory.get_entry( self )
        end

        entry.delete( 'dn' ) if entry
        self.log.debug "  entry is: %p" % [ entry ]
        return entry
end
anchor
method_missing( attribute, value=nil, additional_attributes={} )

Proxy method: call traverse_branch if attribute is a valid attribute and value isn't nil.

# File lib/treequel/branch.rb, line 684
def method_missing( attribute, value=nil, additional_attributes={} )
        return super( attribute ) if value.nil?
        return self.traverse_branch( attribute, value, additional_attributes )
end
anchor
traverse_branch( attribute, value, additional_attributes={} )

If attribute matches a valid attribute type in the directory's schema, return a new Branch for the RDN of attribute and value and additional_attributes (if it's a multi-value RDN).

# (Called via #method_missing)
branch = Treequel::Branch.new( directory, 'ou=people,dc=acme,dc=com' )
branch.uid( :chester ).dn
# => 'uid=chester,ou=people,dc=acme,dc=com'
branch.uid( :chester, :employeeType => 'admin' ).dn
# => 'uid=chester+employeeType=admin,ou=people,dc=acme,dc=com'

Raises a NoMethodError if the attribute or any additional_attributes are not valid attributeTypes.

# File lib/treequel/branch.rb, line 703
def traverse_branch( attribute, value, additional_attributes={} )
        valid_types = self.directory.schema.attribute_types

        # Raise if either the primary attribute or any secondary attributes are invalid
        if !valid_types.key?( attribute )
                raise NoMethodError, "undefined method `%s' for %p" % [ attribute, self ]
        elsif invalid = additional_attributes.keys.find {|ex_attr| !valid_types.key?(ex_attr) }
                raise NoMethodError, "invalid secondary attribute `%s' for %p" %
                        [ invalid, self ]
        end

        # Make a normalized RDN from the arguments and return the Branch for it
        rdn = rdn_from_pair_and_hash( attribute, value, additional_attributes )
        return self.get_child( rdn )
end