Class: ActiveLdap::Base

Inherits:
Object
  • Object
show all
Includes:
Enumerable, Reloadable::Deprecated, Reloadable::Subclasses
Defined in:
lib/active_ldap/base.rb

Overview

Base

Base is the primary class which contains all of the core ActiveLdap functionality. It is meant to only ever be subclassed by extension classes.

Constant Summary collapse

VALID_LDAP_MAPPING_OPTIONS =
[:dn_attribute, :prefix, :scope,
:classes, :recommended_classes]
VALID_SEARCH_OPTIONS =
[:attribute, :value, :filter, :prefix,
:classes, :scope, :limit, :attributes,
:sort_by, :order]
@@configurations =
{}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = nil) {|_self| ... } ⇒ Base

new

Creates a new instance of Base initializing all class and all initialization. Defines local defaults. See examples If multiple values exist for dn_attribute, the first one put here will be authoritative

Yields:

  • (_self)

Yield Parameters:



764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
# File 'lib/active_ldap/base.rb', line 764

def initialize(attributes=nil)
  init_base
  @new_entry = true
  initial_classes = required_classes | recommended_classes
  if attributes.nil?
    apply_object_class(initial_classes)
  elsif attributes.is_a?(String) or attributes.is_a?(Array)
    apply_object_class(initial_classes)
    self.dn = attributes
  elsif attributes.is_a?(Hash)
    classes, attributes = extract_object_class(attributes)
    apply_object_class(classes | initial_classes)
    normalized_attributes = {}
    attributes.each do |key, value|
      real_key = to_real_attribute_name(key)
      normalized_attributes[real_key] = value if real_key
    end
    self.dn = normalized_attributes[dn_attribute]
    self.attributes = normalized_attributes
  else
    message = "'#{attributes.inspect}' must be either "
    message << "nil, DN value as String or Array or attributes as Hash"
    raise ArgumentError, message
  end
  yield self if block_given?
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

method_missing

If a given method matches an attribute or an attribute alias then call the appropriate method. TODO: Determine if it would be better to define each allowed method

using class_eval instead of using method_missing.  This would
give tab completion in irb.


915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
# File 'lib/active_ldap/base.rb', line 915

def method_missing(name, *args, &block)
  logger.debug {"stub: called method_missing" +
                  "(#{name.inspect}, #{args.inspect})"}
  ensure_apply_object_class

  key = name.to_s
  case key
  when /=$/
    real_key = $PREMATCH
    logger.debug {"method_missing: have_attribute? #{real_key}"}
    if have_attribute?(real_key, ['objectClass'])
      if args.size != 1
        raise ArgumentError,
                "wrong number of arguments (#{args.size} for 1)"
      end
      logger.debug {"method_missing: calling set_attribute" +
                      "(#{real_key}, #{args.inspect})"}
      return set_attribute(real_key, *args, &block)
    end
  when /(?:(_before_type_cast)|(\?))?$/
    real_key = $PREMATCH
    before_type_cast = !$1.nil?
    query = !$2.nil?
    logger.debug {"method_missing: have_attribute? #{real_key}"}
    if have_attribute?(real_key, ['objectClass'])
      if args.size > 1
        raise ArgumentError,
          "wrong number of arguments (#{args.size} for 1)"
      end
      if before_type_cast
        return get_attribute_before_type_cast(real_key, *args)
      elsif query
        return get_attribute_as_query(real_key, *args)
      else
        return get_attribute(real_key, *args)
      end
    end
  end
  super
end

Class Method Details

.add(dn, entries, options = {}) ⇒ Object



421
422
423
424
425
426
# File 'lib/active_ldap/base.rb', line 421

def add(dn, entries, options={})
  unnormalized_entries = entries.collect do |type, key, value|
    [type, key, unnormalize_attribute(key, value)]
  end
  connection.add(dn, unnormalized_entries, options)
end

.baseObject

Base.base

This method when included into Base provides an inheritable, overwritable configuration setting

This should be a string with the base of the ldap server such as ‘dc=example,dc=com’, and it should be overwritten by including configuration.rb into this class. When subclassing, the specified prefix will be concatenated.



343
344
345
346
347
348
349
350
# File 'lib/active_ldap/base.rb', line 343

def base
  _base = base_inheritable
  _base = configuration[:base] if _base.nil? and configuration
  _base ||= base_inheritable(true)
  [prefix, _base].find_all do |component|
    component and !component.empty?
  end.join(",")
end

.base_classObject



510
511
512
513
514
515
516
# File 'lib/active_ldap/base.rb', line 510

def base_class
  if self == Base or superclass == Base
    self
  else
    superclass.base_class
  end
end

.base_inheritableObject



332
# File 'lib/active_ldap/base.rb', line 332

alias_method :base_inheritable, :base

.class_local_attr_accessor(search_ancestors, *syms) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/active_ldap/base.rb', line 179

def self.class_local_attr_accessor(search_ancestors, *syms)
  syms.flatten.each do |sym|
    class_eval(<<-EOS, __FILE__, __LINE__ + 1)
      def self.#{sym}(search_superclasses=#{search_ancestors})
        @#{sym} ||= nil
        return @#{sym} if @#{sym}
        if search_superclasses
          target = superclass
          value = nil
          loop do
            break nil unless target.respond_to?("#{sym}")
            value = target.#{sym}
            break if value
            target = target.superclass
          end
          value
        else
          nil
        end
      end
      def #{sym}; self.class.#{sym}; end
      def self.#{sym}=(value); @#{sym} = value; end
      def #{sym}=(value); self.class.#{sym} = value; end
    EOS
  end
end

.create(attributes = nil, &block) ⇒ Object



255
256
257
258
259
260
261
262
263
# File 'lib/active_ldap/base.rb', line 255

def create(attributes=nil, &block)
  if attributes.is_a?(Array)
    attributes.collect {|attrs| create(attrs, &block)}
  else
    object = new(attributes, &block)
    object.save
    object
  end
end

.delete(targets, options = {}) ⇒ Object



401
402
403
404
405
406
407
# File 'lib/active_ldap/base.rb', line 401

def delete(targets, options={})
  targets = [targets] unless targets.is_a?(Array)
  targets = targets.collect do |target|
    ensure_dn_attribute(ensure_base(target))
  end
  connection.delete(targets, options)
end

.delete_all(filter = nil, options = {}) ⇒ Object



409
410
411
412
413
414
415
416
417
418
419
# File 'lib/active_ldap/base.rb', line 409

def delete_all(filter=nil, options={})
  options = {:base => base, :scope => ldap_scope}.merge(options)
  options = options.merge(:filter => filter) if filter
  targets = connection.search(options).collect do |dn, attributes|
    dn
  end.sort_by do |dn|
    dn.reverse
  end.reverse

  connection.delete(targets)
end

.destroy(targets, options = {}) ⇒ Object



380
381
382
383
384
385
# File 'lib/active_ldap/base.rb', line 380

def destroy(targets, options={})
  targets = [targets] unless targets.is_a?(Array)
  targets.each do |target|
    find(target, options).destroy
  end
end

.destroy_all(filter = nil, options = {}) ⇒ Object



387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/active_ldap/base.rb', line 387

def destroy_all(filter=nil, options={})
  targets = []
  if filter.is_a?(Hash)
    options = options.merge(filter)
    filter = nil
  end
  options = options.merge(:filter => filter) if filter
  find(:all, options).sort_by do |target|
    target.dn.reverse
  end.reverse.each do |target|
    target.destroy
  end
end

.dump(options = {}) ⇒ Object



363
364
365
366
367
368
369
370
# File 'lib/active_ldap/base.rb', line 363

def dump(options={})
  ldifs = []
  options = {:base => base, :scope => ldap_scope}.merge(options)
  connection.search(options) do |dn, attributes|
    ldifs << to_ldif(dn, attributes)
  end
  ldifs.join("\n")
end

.establish_connection(config = nil) ⇒ Object

Connect and bind to LDAP creating a class variable for use by all ActiveLdap objects.

config

config must be a hash that may contain any of the following fields: :password_block, :logger, :host, :port, :base, :bind_dn, :try_sasl, :allow_anonymous :bind_dn specifies the DN to bind with. :password_block specifies a Proc object that will yield a String to

be used as the password when called.

:logger specifies a preconfigured Log4r::Logger to be used for all

logging

:host sets the LDAP server hostname :port sets the LDAP server port :base overwrites Base.base - this affects EVERYTHING :try_sasl indicates that a SASL bind should be attempted when binding

to the server (default: false)

:sasl_mechanisms is an array of SASL mechanism to try

(default: ["GSSAPI", "CRAM-MD5", "EXTERNAL"])

:allow_anonymous indicates that a true anonymous bind is allowed when

trying to bind to the server (default: true)

:retries - indicates the number of attempts to reconnect that will be

undertaken when a stale connection occurs. -1 means infinite.

:sasl_quiet - if true, sets @sasl_quiet on the Ruby/LDAP connection :method - whether to use :ssl, :tls, or :plain (unencrypted) :retry_wait - seconds to wait before retrying a connection :ldap_scope - dictates how to find objects. ONELEVEL by default to

avoid dn_attr collisions across OUs. Think before changing.

:timeout - time in seconds - defaults to disabled. This CAN interrupt

search() requests. Be warned.

:retry_on_timeout - whether to reconnect when timeouts occur. Defaults

to true

See lib/configuration.rb for defaults for each option



247
248
249
250
251
252
253
# File 'lib/active_ldap/base.rb', line 247

def establish_connection(config=nil)
  super
  ensure_logger
  connection.connect
  # Make irb users happy with a 'true'
  true
end

.exists?(dn, options = {}) ⇒ Boolean

Returns:

  • (Boolean)


454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/active_ldap/base.rb', line 454

def exists?(dn, options={})
  prefix = /^#{Regexp.escape(truncate_base(ensure_dn_attribute(dn)))}/ #
  dn_suffix = nil
  not search({:value => dn}.merge(options)).find do |_dn,|
    if prefix.match(_dn)
      begin
        dn_suffix ||= DN.parse(base)
        dn_prefix = DN.parse(_dn) - dn_suffix
        true
      rescue DistinguishedNameInvalid, ArgumentError
        false
      end
    else
      false
    end
  end.nil?
end

.find(*args) ⇒ Object

find

Finds the first match for value where |value| is the value of some |field|, or the wildcard match. This is only useful for derived classes. usage: Subclass.find(:attribute => “cn”, :value => “some*val”)

Subclass.find('some*val')


441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/active_ldap/base.rb', line 441

def find(*args)
  options = extract_options_from_args!(args)
  args = [:first] if args.empty? and !options.empty?
  case args.first
  when :first
    find_initial(options)
  when :all
    find_every(options)
  else
    find_from_dns(args, options)
  end
end

.human_attribute_name(attribute_key_name) ⇒ Object



518
519
520
# File 'lib/active_ldap/base.rb', line 518

def human_attribute_name(attribute_key_name)
  attribute_key_name.humanize
end

.ldap_mapping(options = {}) ⇒ Object

This class function is used to setup all mappings between the subclass and ldap for use in activeldap

Example:

ldap_mapping :dn_attribute => 'uid', :prefix => 'ou=People',
             :classes => ['top', 'posixAccount'],
             :scope => :sub


315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
# File 'lib/active_ldap/base.rb', line 315

def ldap_mapping(options={})
  validate_ldap_mapping_options(options)
  dn_attribute = options[:dn_attribute] || default_dn_attribute
  prefix = options[:prefix] || default_prefix
  classes = options[:classes]
  recommended_classes = options[:recommended_classes]
  scope = options[:scope]

  self.dn_attribute = dn_attribute
  self.prefix = prefix
  self.ldap_scope = scope
  self.required_classes = classes
  self.recommended_classes = recommended_classes

  public_class_method :new
end

.ldap_scope=(scope) ⇒ Object



353
354
355
356
357
358
359
360
361
# File 'lib/active_ldap/base.rb', line 353

def ldap_scope=(scope)
  scope = scope.to_sym if scope.is_a?(String)
  if scope.nil? or scope.is_a?(Symbol)
    self.ldap_scope_without_validation = scope
  else
    raise ConfigurationError,
            ":ldap_scope '#{scope.inspect}' must be a Symbol"
  end
end

.ldap_scope_without_validation=Object



352
# File 'lib/active_ldap/base.rb', line 352

alias_method :ldap_scope_without_validation=, :ldap_scope=

.load(ldifs) ⇒ Object



376
377
378
# File 'lib/active_ldap/base.rb', line 376

def load(ldifs)
  connection.load(ldifs)
end

.modify(dn, entries, options = {}) ⇒ Object



428
429
430
431
432
433
# File 'lib/active_ldap/base.rb', line 428

def modify(dn, entries, options={})
  unnormalized_entries = entries.collect do |type, key, value|
    [type, key, unnormalize_attribute(key, value)]
  end
  connection.modify(dn, unnormalized_entries, options)
end

.search(options = {}, &block) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/active_ldap/base.rb', line 265

def search(options={}, &block)
  validate_search_options(options)
  attr = options[:attribute]
  value = options[:value] || '*'
  filter = options[:filter]
  prefix = options[:prefix]
  classes = options[:classes]

  value = value.first if value.is_a?(Array) and value.first.size == 1
  if filter.nil? and !value.is_a?(String)
    raise ArgumentError, "Search value must be a String"
  end

  _attr, value, _prefix = split_search_value(value)
  attr ||= _attr || dn_attribute || "objectClass"
  prefix ||= _prefix
  if filter.nil?
    filter = "(#{attr}=#{escape_filter_value(value, true)})"
    filter = "(&#{filter}#{object_class_filters(classes)})"
  end
  _base = [prefix, base].compact.reject{|x| x.empty?}.join(",")
  search_options = {
    :base => _base,
    :scope => options[:scope] || ldap_scope,
    :filter => filter,
    :limit => options[:limit],
    :attributes => options[:attributes],
    :sort_by => options[:sort_by],
    :order => options[:order],
  }
  connection.search(search_options) do |dn, attrs|
    attributes = {}
    attrs.each do |key, value|
      normalized_attr, normalized_value = make_subtypes(key, value)
      attributes[normalized_attr] ||= []
      attributes[normalized_attr].concat(normalized_value)
    end
    value = [dn, attributes]
    value = yield(value) if block_given?
    value
  end
end

.to_ldif(dn, attributes) ⇒ Object



372
373
374
# File 'lib/active_ldap/base.rb', line 372

def to_ldif(dn, attributes)
  connection.to_ldif(dn, unnormalize_attributes(attributes))
end

.update(dn, attributes, options = {}) ⇒ Object



472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/active_ldap/base.rb', line 472

def update(dn, attributes, options={})
  if dn.is_a?(Array)
    i = -1
    dns = dn
    dns.collect do |dn|
      i += 1
      update(dn, attributes[i], options)
    end
  else
    object = find(dn, options)
    object.update_attributes(attributes)
    object
  end
end

.update_all(attributes, filter = nil, options = {}) ⇒ Object



487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
# File 'lib/active_ldap/base.rb', line 487

def update_all(attributes, filter=nil, options={})
  search_options = options
  if filter
    if filter.is_a?(String) and /[=\(\)&\|]/ !~ filter
      search_options = search_options.merge(:value => filter)
    else
      search_options = search_options.merge(:filter => filter)
    end
  end
  targets = search(search_options).collect do |dn, attrs|
    dn
  end

  entries = attributes.collect do |name, value|
    normalized_name, normalized_value = normalize_attribute(name, value)
    [:replace, normalized_name,
     unnormalize_attribute(normalized_name, normalized_value)]
  end
  targets.each do |dn|
    connection.modify(dn, entries, options)
  end
end

Instance Method Details

#==(comparison_object) ⇒ Object

Returns true if the comparison_object is the same object, or is of the same type and has the same dn.



793
794
795
796
797
798
# File 'lib/active_ldap/base.rb', line 793

def ==(comparison_object)
  comparison_object.equal?(self) or
    (comparison_object.instance_of?(self.class) and
     comparison_object.dn == dn and
     !comparison_object.new_entry?)
end

#[](name, force_array = false) ⇒ Object



1053
1054
1055
1056
1057
1058
1059
# File 'lib/active_ldap/base.rb', line 1053

def [](name, force_array=false)
  if name == "dn"
    array_of(dn, force_array)
  else
    get_attribute(name, force_array)
  end
end

#[]=(name, value) ⇒ Object



1061
1062
1063
# File 'lib/active_ldap/base.rb', line 1061

def []=(name, value)
  set_attribute(name, value)
end

#attribute_namesObject

attributes

Return attribute methods so that a program can determine available attributes dynamically without schema awareness



827
828
829
830
831
# File 'lib/active_ldap/base.rb', line 827

def attribute_names
  logger.debug {"stub: attribute_names called"}
  ensure_apply_object_class
  return @attr_methods.keys
end

#attribute_present?(name) ⇒ Boolean

Returns:

  • (Boolean)


833
834
835
836
# File 'lib/active_ldap/base.rb', line 833

def attribute_present?(name)
  values = get_attribute(name, true)
  !values.empty? or values.any? {|x| not (x and x.empty?)}
end

#attributesObject

This returns the key value pairs in @data with all values cloned



989
990
991
# File 'lib/active_ldap/base.rb', line 989

def attributes
  Marshal.load(Marshal.dump(@data))
end

#attributes=(hash_or_assoc) ⇒ Object

This allows a bulk update to the attributes of a record without forcing an immediate save or validation.

It is unwise to attempt objectClass updates this way. Also be sure to only pass in key-value pairs of your choosing. Do not let URL/form hackers supply the keys.



999
1000
1001
1002
1003
1004
# File 'lib/active_ldap/base.rb', line 999

def attributes=(hash_or_assoc)
  targets = remove_attributes_protected_from_mass_assignment(hash_or_assoc)
  targets.each do |key, value|
    set_attribute(key, value) if have_attribute?(key)
  end
end

#destroyObject

destroy

Delete this entry from LDAP



883
884
885
886
887
888
889
890
891
# File 'lib/active_ldap/base.rb', line 883

def destroy
  logger.debug {"stub: delete called"}
  begin
    self.class.delete(dn)
    @new_entry = true
  rescue Error
    raise DeleteError.new("Failed to delete LDAP entry: '#{dn}'")
  end
end

#dnObject

dn

Return the authoritative dn



855
856
857
858
859
860
861
862
863
864
865
# File 'lib/active_ldap/base.rb', line 855

def dn
  logger.debug {"stub: dn called"}
  dn_value = id
  if dn_value.nil?
    raise DistinguishedNameNotSetError.new,
            "#{dn_attribute} value of #{self} doesn't set"
  end
  _base = base
  _base = nil if _base.empty?
  ["#{dn_attribute}=#{dn_value}", _base].compact.join(",")
end

#dn=(value) ⇒ Object Also known as: id=



875
876
877
# File 'lib/active_ldap/base.rb', line 875

def dn=(value)
  set_attribute(dn_attribute, value)
end

#eachObject



1065
1066
1067
1068
1069
# File 'lib/active_ldap/base.rb', line 1065

def each
  @data.each do |key, values|
    yield(key.dup, values.dup)
  end
end

#eql?(comparison_object) ⇒ Boolean

Delegates to ==

Returns:

  • (Boolean)


801
802
803
# File 'lib/active_ldap/base.rb', line 801

def eql?(comparison_object)
  self == (comparison_object)
end

#exists?Boolean

exists?

Return whether the entry exists in LDAP or not

Returns:

  • (Boolean)


841
842
843
# File 'lib/active_ldap/base.rb', line 841

def exists?
  self.class.exists?(dn)
end

#hashObject

Delegates to id in order to allow two records of the same type and id to work with something like:

[ User.find("a"), User.find("b"), User.find("c") ] &
  [ User.find("a"), User.find("d") ] # => [ User.find("a") ]


809
810
811
# File 'lib/active_ldap/base.rb', line 809

def hash
  dn.hash
end

#have_attribute?(name, except = []) ⇒ Boolean Also known as: has_attribute?

Returns:

  • (Boolean)


1033
1034
1035
1036
# File 'lib/active_ldap/base.rb', line 1033

def have_attribute?(name, except=[])
  real_name = to_real_attribute_name(name)
  real_name and !except.include?(real_name)
end

#idObject



867
868
869
# File 'lib/active_ldap/base.rb', line 867

def id
  get_attribute(dn_attribute)
end

#mayObject



813
814
815
816
# File 'lib/active_ldap/base.rb', line 813

def may
  ensure_apply_object_class
  @may
end

#methods(inherited_too = true) ⇒ Object

Add available attributes to the methods



957
958
959
960
961
962
963
964
# File 'lib/active_ldap/base.rb', line 957

def methods(inherited_too=true)
  ensure_apply_object_class
  target_names = @attr_methods.keys + @attr_aliases.keys
  target_names -= ['objectClass', Inflector.underscore('objectClass')]
  super + target_names.uniq.collect do |x|
    [x, "#{x}=", "#{x}?", "#{x}_before_type_cast"]
  end.flatten
end

#mustObject



818
819
820
821
# File 'lib/active_ldap/base.rb', line 818

def must
  ensure_apply_object_class
  @must
end

#new_entry?Boolean

new_entry?

Return whether the entry is new entry in LDAP or not

Returns:

  • (Boolean)


848
849
850
# File 'lib/active_ldap/base.rb', line 848

def new_entry?
  @new_entry
end

#reloadObject

Raises:



1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
# File 'lib/active_ldap/base.rb', line 1039

def reload
  _, attributes = self.class.search(:value => id).find do |_dn, _attributes|
    dn == _dn
  end
  raise EntryNotFound, "Can't find dn '#{dn}' to reload" if attributes.nil?

  @ldap_data.update(attributes)
  classes, attributes = extract_object_class(attributes)
  apply_object_class(classes)
  self.attributes = attributes
  @new_entry = false
  self
end

#respond_to?(name, include_priv = false) ⇒ Boolean

Returns:

  • (Boolean)


967
968
969
970
971
972
# File 'lib/active_ldap/base.rb', line 967

def respond_to?(name, include_priv=false)
  have_attribute?(name.to_s) or
    (/(?:=|\?|_before_type_cast)$/ =~ name.to_s and
     have_attribute?($PREMATCH)) or
    super
end

#respond_to_without_attributes?Object



966
# File 'lib/active_ldap/base.rb', line 966

alias_method :respond_to_without_attributes?, :respond_to?

#saveObject

save

Save and validate this object into LDAP either adding or replacing attributes TODO: Relative DN support



898
899
900
# File 'lib/active_ldap/base.rb', line 898

def save
  create_or_update
end

#save!Object



902
903
904
905
906
# File 'lib/active_ldap/base.rb', line 902

def save!
  unless create_or_update
    raise EntryNotSaved, "entry #{dn} can't saved"
  end
end

#to_ldifObject



1006
1007
1008
# File 'lib/active_ldap/base.rb', line 1006

def to_ldif
  self.class.to_ldif(dn, normalize_data(@data))
end

#to_paramObject



871
872
873
# File 'lib/active_ldap/base.rb', line 871

def to_param
  id
end

#to_xml(options = {}) ⇒ Object



1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
# File 'lib/active_ldap/base.rb', line 1010

def to_xml(options={})
  root = options[:root] || Inflector.underscore(self.class.name)
  result = "<#{root}>\n"
  result << "  <dn>#{dn}</dn>\n"
  normalize_data(@data).sort_by {|key, values| key}.each do |key, values|
    targets = []
    values.each do |value|
      if value.is_a?(Hash)
        value.each do |option, real_value|
          targets << [real_value, " #{option}=\"true\""]
        end
      else
        targets << [value]
      end
    end
    targets.sort_by {|value, attr| value}.each do |value, attr|
      result << "  <#{key}#{attr}>#{value}</#{key}>\n"
    end
  end
  result << "</#{root}>\n"
  result
end

#update_attribute(name, value) ⇒ Object

Updates a given attribute and saves immediately



975
976
977
978
# File 'lib/active_ldap/base.rb', line 975

def update_attribute(name, value)
  set_attribute(name, value) if have_attribute?(name)
  save
end

#update_attributes(attrs) ⇒ Object

This performs a bulk update of attributes and immediately calls #save.



982
983
984
985
# File 'lib/active_ldap/base.rb', line 982

def update_attributes(attrs)
  self.attributes = attrs
  save
end