lib/treequel/branch.rb in treequel-1.2.2 vs lib/treequel/branch.rb in treequel-1.3.0pre384

- old
+ new

@@ -36,15 +36,21 @@ ################################################################# # [Boolean] Whether or not to include operational attributes by default. @include_operational_attrs = false + # [Boolean] Whether or not to freeze values cached in @values. This helps + # prevent you from accidentally doing branch[:attr] << 'value', which + # modifies the cached values, but not the entry. + @freeze_converted_values = true + # Whether or not to include operational attributes when fetching the # entry for branches. class << self extend Treequel::AttributeDeclarations predicate_attr :include_operational_attrs + predicate_attr :freeze_converted_values end ### Create a new Treequel::Branch from the given +entry+ hash from the specified +directory+. ### @@ -52,12 +58,16 @@ ### @param [Treequel::Directory] directory The directory object the Branch is from. ### ### @return [Treequel::Branch] The new branch object. def self::new_from_entry( entry, directory ) entry = Treequel::HashUtilities.stringify_keys( entry ) - Treequel.logger.debug "Creating Branch from entry: %p in directory: %p" % [ entry, directory ] - return self.new( directory, entry['dn'].first, 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 ################################################################# ### I N S T A N C E M E T H O D S @@ -70,10 +80,11 @@ ### ### @param [Treequel::Directory] directory The directory the Branch belongs to. ### @param [String] dn The DN of the entry the Branch is wrapping. ### @param [LDAP::Entry, Hash] entry The entry object if it's already been fetched. 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 ) @@ -91,11 +102,11 @@ ###### public ###### # Delegate some other methods to a new Branchset via the #branchset method - def_method_delegators :branchset, :filter, :scope, :select, :limit, :timeout, :order + def_method_delegators :branchset, :filter, :scope, :select, :limit, :timeout, :order, :as # Delegate some methods to the Branch's directory via its accessor def_method_delegators :directory, :controls, :referrals @@ -202,11 +213,12 @@ ### Return the Branch's immediate parent node. ### @return [Treequel::Branch] def parent - return self.class.new( self.directory, self.parent_dn ) + pardn = self.parent_dn or return nil + return self.class.new( self.directory, pardn ) end ### Perform a search with the specified +scope+, +filter+, and +parameters+ ### using the receiver as the base. @@ -265,11 +277,11 @@ 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| - entry[ attribute ].each do |val| + Array( entry[attribute] ).each do |val| ldif << ldif_for_attr( attribute, val, width ) end end return LDAP::LDIF::Entry.new( ldif ) @@ -297,18 +309,20 @@ ### Fetch the value/s associated with the given +attrname+ from the underlying entry. ### @return [Array, String] def []( attrname ) attrsym = attrname.to_sym - unless @values.key?( attrsym ) + 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 value.respond_to?( :freeze ) + value.freeze if + self.class.freeze_converted_values? && + value.respond_to?( :freeze ) @values[ attrsym ] = value - else - self.log.debug " value for %p is cached." % [ attrname ] end return @values[ attrsym ] end @@ -364,27 +378,37 @@ ### @example Delete any blank 'description' or 'cn' attributes: ### branch.delete( :description => '', :cn => '' ) ### ### @return [TrueClass] if the delete succeeded 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 = Array( vals ).collect {|val| val.to_s } + 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.flatten - self.directory.modify( self, mods ) + end + + self.directory.modify( self, mods.flatten ) end self.clear_caches return true @@ -392,13 +416,14 @@ ### Create the entry for this Branch with the specified +attributes+. The +attributes+ should, ### at a minimum, contain the pair `:objectClass => :someStructuralObjectClass`. ### - ### @param [Hash<Symbol,String => Object>] attributes + ### @see Treequel::Directory#create def create( attributes={} ) self.directory.create( self, attributes ) + self.clear_caches return self end ### Copy the entry for this Branch to a new entry with the given +newdn+ and merge in the @@ -430,11 +455,14 @@ ### ### @param [String] rdn ### @param [Hash<String, Symbol => Object>] attributes def move( rdn ) self.log.debug "Asking the directory to move me to an entry called %p" % [ rdn ] - return self.directory.move( self, rdn ) + self.directory.move( self, rdn ) + self.clear_caches + + return self end ### 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. @@ -491,22 +519,21 @@ def object_classes( *additional_classes ) self.log.debug "Fetching object classes for %s" % [ self.dn ] schema = self.directory.schema oc_oids = self[:objectClass] || [] - self.log.debug " objectClass OIDs are: %p" % [ oc_oids ] oc_oids |= additional_classes.collect {|str| str.to_sym } - oc_oids << :top if oc_oids.empty? + 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 ] + self.log.debug " found %d objectClasses: %p" % [ oclasses.length, oclasses.map(&:name) ] return oclasses.uniq end ### Return the receiver's operational attributes as attributeType schema objects. @@ -537,11 +564,11 @@ oclasses = self.object_classes( *additional_object_classes ) self.log.debug "Gathering MUST attribute types for objectClasses: %p" % [ oclasses.map(&:name) ] oclasses.each do |oc| - self.log.debug " adding %p from %p" % [ oc.must, oc ] + self.log.debug " adding %p from %p" % [ oc.must.map(&:name), oc.name ] types |= oc.must end return types end @@ -569,14 +596,14 @@ ### @return [Hash<String => String>] 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 ] + # self.log.debug " adding attrtype %p to the MUST attributes hash" % [ attrtype.name ] if attrtype.name == :objectClass - attrhash[ :objectClass ] = [:top] | additional_object_classes + attrhash[ :objectClass ] = ['top'] | additional_object_classes elsif attrtype.single? attrhash[ attrtype.name ] = '' else attrhash[ attrtype.name ] = [''] end @@ -623,11 +650,11 @@ 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 ] + # self.log.debug " adding attrtype %p to the MAY attributes hash" % [ attrtype.named ] if attrtype.single? attrhash[ attrtype.name ] = nil else attrhash[ attrtype.name ] = [] @@ -770,19 +797,20 @@ ### Get the value associated with +attrsym+, convert it to a Ruby object if the Branch's ### directory has a conversion rule, and return it. def get_converted_object( attrsym ) - return nil unless self.entry - value = self.entry[ attrsym.to_s ] or return nil + value = self.entry ? self.entry[ attrsym.to_s ] : nil if attribute = self.directory.schema.attribute_types[ attrsym ] + syntax_oid = attribute.syntax.oid + if attribute.single? - value = self.directory.convert_to_object( attribute.syntax.oid, value.first ) + value = self.directory.convert_to_object( syntax_oid, value.first ) if value else - value = value.collect do |raw| - self.directory.convert_to_object( attribute.syntax.oid, raw ) + 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 @@ -793,11 +821,11 @@ ### Convert the specified +object+ according to the Branch's directory's conversion rules, ### and return it. def get_converted_attribute( attrsym, object ) if attribute = self.directory.schema.attribute_types[ attrsym ] - self.log.debug "converting %p object to a %p attribute" % - [ attrsym, attribute.syntax.desc ] + self.log.debug "converting %p object (a %p) to a %p 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