Class: Treequel::Branchset

Inherits:
Object
  • Object
show all
Extended by:
Loggability
Includes:
Enumerable, Constants, Control
Defined in:
lib/treequel/branchset.rb

Overview

A branchset represents an abstract set of LDAP records returned by a search in a directory. It can be used to create, retrieve, update, and delete records.

Search results are fetched on demand, so a branchset can be kept around and reused indefinitely (branchsets never cache results):

people = directory.ou( :people )
davids = people.filter(:firstName => 'david') # no records are retrieved
davids.all # records are retrieved
davids.all # records are retrieved again

Most branchset methods return modified copies of the branchset (functional style), so you can reuse different branchsets to access data:

# (employeeId < 2000)
veteran_davids = davids.filter( :employeeId < 2000 )

# (&(employeeId < 2000)(|(deactivated >= '2008-12-22')(!(deactivated=*))))
active_veteran_davids =
    veteran_davids.filter([:or, ['deactivated >= ?', Date.today], [:not, [:deactivated]] ])

# (&(employeeId < 2000)(|(deactivated >= '2008-12-22')(!(deactivated=*)))(mobileNumber=*))
active_veteran_davids_with_cellphones =
    active_veteran_davids.filter( [:mobileNumber] )

Branchsets are Enumerable objects, so they can be manipulated using any of the Enumerable methods, such as map, inject, etc.

Constant Summary collapse

DEFAULT_SCOPE =

The default scope to use when searching if none is specified

:subtree
DEFAULT_FILTER =

The default filter to use when searching if non is specified

:objectClass
DEFAULT_OPTIONS =

The default options hash for new Branchsets

{
  :filter  => DEFAULT_FILTER,
  :scope   => DEFAULT_SCOPE,
  :timeout => 0,                  # Floating-point timeout -> sec, usec
  :select  => [],                 # Attributes to return -> attrs
  :order   => '',                 # Sorting criteria -> s_attr/s_proc
  :limit   => 0,                  # Limit -> number of results
}.freeze

Constants included from Constants

Constants::CONTROL_NAMES, Constants::CONTROL_OIDS, Constants::EXTENSION_NAMES, Constants::EXTENSION_OIDS, Constants::FEATURE_NAMES, Constants::FEATURE_OIDS, Constants::MINIMAL_OPERATIONAL_ATTRIBUTES, Constants::SCOPE, Constants::SCOPE_NAME

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Control

#get_client_controls, #get_server_controls

Constructor Details

#initialize(branch, options = {}) ⇒ Branchset

Create a new Branchset for a search from the DN of the specified branch (a Treequel::Branch), with the given options.



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/treequel/branchset.rb', line 81

def initialize( branch, options={} )
  @branch = branch
  @options = DEFAULT_OPTIONS.merge( options )
  self.log.debug "Setting up %p for branch %p with options: %p" %
    [ self.class, @branch, @options ]

  if @branch.directory.registered_controls.empty?
    self.log.debug "  no registered controls."
  else
    @branch.directory.registered_controls.each do |control|
      self.log.debug "  extending with %p" % [ control ]
      self.extend( control )
    end
  end

  super()
end

Instance Attribute Details

#branchObject

The branchset’s base branch that will be used when searching as the basedn



110
111
112
# File 'lib/treequel/branchset.rb', line 110

def branch
  @branch
end

#optionsObject

The branchset’s search options hash



107
108
109
# File 'lib/treequel/branchset.rb', line 107

def options
  @options
end

Instance Method Details

#+(other) ⇒ Object

If given another Branchset or a BranchCollection, return a BranchCollection that includes them both. If given anything else, execute the search and return the results plus other in an Array.



194
195
196
197
198
199
200
# File 'lib/treequel/branchset.rb', line 194

def +( other )
  if other.is_a?( Treequel::BranchCollection ) || other.is_a?( Treequel::Branchset )
    return Treequel::BranchCollection.new( self, other )
  else
    return self.all + Array( other )
  end
end

#-(other_object) ⇒ Object

Return the results of executing the search without the other_object.



204
205
206
207
# File 'lib/treequel/branchset.rb', line 204

def -( other_object )
  other_dn = other_object.dn
  return self.reject {|branch| branch.dn.downcase == other_dn.downcase }
end

#as(branchclass) ⇒ Object

Return a clone of the receiving Branchset that will return instances of the give branchclass instead of Treequel::Branch objects. This may be a subclass of Treequel::Branch, but it doesn’t need to be as long as they duck-type the same.



433
434
435
436
437
# File 'lib/treequel/branchset.rb', line 433

def as( branchclass )
  newset = self.clone
  newset.branch = branchclass.new( self.branch.directory, self.branch.dn )
  return newset
end

#base_dnObject

Returns the DN of the Branchset’s branch.



125
126
127
# File 'lib/treequel/branchset.rb', line 125

def base_dn
  return self.branch.dn
end

#clone(options = {}) ⇒ Object

Override the default clone method to support cloning with different options.



131
132
133
134
135
136
# File 'lib/treequel/branchset.rb', line 131

def clone( options={} )
  self.log.debug "cloning %p with options = %p" % [ self, options ]
  newset = super()
  newset.options = @options.merge( options )
  return newset
end

#collectionObject

Create a BranchCollection from the results of the Branchset and return it.



186
187
188
# File 'lib/treequel/branchset.rb', line 186

def collection
  return Treequel::BranchCollection.new( self.all )
end

#each(&block) ⇒ Object

Iterate over the entries which match the current criteria and yield each of them as Treequel::Branch objects to the supplied block.

Raises:



212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/treequel/branchset.rb', line 212

def each( &block )
  raise LocalJumpError, "no block given" unless block

  self.branch.search( self.scope, self.filter,
    :selectattrs => self.select,
    :timeout => self.timeout,
    # :sortby => self.order,
    :limit => self.limit,
    :client_controls => self.get_client_controls,
    :server_controls => self.get_server_controls,
    &block
    )
end

#empty?Boolean

Return true if no entries match the Branchset’s current criteria.

Returns:



249
250
251
# File 'lib/treequel/branchset.rb', line 249

def empty?
  return self.first.nil? ? true : false
end

#extend(*modules) ⇒ Object

Extend the Branchset with one or more modules. Overridden to also call the modules’ initializers if they have them.



115
116
117
118
119
120
121
# File 'lib/treequel/branchset.rb', line 115

def extend( *modules )
  super
  modules.each do |mod|
    mod.instance_method( :initialize ).bind( self ).call if
      mod.private_instance_methods.map( &:to_sym ).include?( :initialize )
  end
end

#filter(*filterspec) ⇒ Object

Returns a clone of the receiving Branchset with the given filterspec added to it.



303
304
305
306
307
308
309
310
311
312
313
314
# File 'lib/treequel/branchset.rb', line 303

def filter( *filterspec )
  if filterspec.empty?
    opts = self.options
    opts[:filter] = Treequel::Filter.new(opts[:filter]) unless
      opts[:filter].is_a?( Treequel::Filter )
    return opts[:filter]
  else
    self.log.debug "cloning %p with filterspec: %p" % [ self, filterspec ]
    newfilter = Treequel::Filter.new( *filterspec )
    return self.clone( :filter => self.filter + newfilter )
  end
end

#filter_stringObject

Return an LDAP filter string made up of the current filter components.



180
181
182
# File 'lib/treequel/branchset.rb', line 180

def filter_string
  return self.filter.to_s
end

#first(n = nil) ⇒ Object

Fetch the first n entries which matches the current criteria and return them as instances of the object that is set as the branch (e.g., Treequel::Branch). If n is nil, returns just the first object in the Array.



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/treequel/branchset.rb', line 230

def first( n=nil )
  results = self.branch.search( self.scope, self.filter,
    :selectattrs => self.select,
    :timeout => self.timeout,
    # :sortby => self.order,
    :client_controls => self.get_client_controls,
    :server_controls => self.get_server_controls,
    :limit => n || 1
    )

  if n
    return results.first( n )
  else
    return results.first
  end
end

#from(other_dn) ⇒ Object

Return a clone of the receiving Branchset that will perform its search from other_dn instead of its own.



442
443
444
445
446
447
# File 'lib/treequel/branchset.rb', line 442

def from( other_dn )
  newset = self.clone
  other_dn = other_dn.dn if other_dn.respond_to?( :dn )
  newset.branch = newset.branch.class.new( self.branch.directory, other_dn )
  return newset
end

#inspectObject

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



165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/treequel/branchset.rb', line 165

def inspect
  "#<%s:0x%0x base_dn='%s', filter=%s, scope=%s, select=%s, limit=%d, timeout=%0.3f>" % [
    self.class.name,
    self.object_id * 2,
    self.base_dn,
    self.filter_string,
    self.scope,
    self.select.empty? ? '*' : self.select.join(','),
    self.limit,
    self.timeout,
  ]
end

#limit(new_limit = nil) ⇒ Object

If called with a new_limit, returns a clone of the receiving Branchset that will fetch (at most) new_limit Branches. If no new_limit argument is specified, returns the Branchset’s current limit. A limit of ‘0’ means that all Branches will be fetched.



394
395
396
397
398
399
400
401
# File 'lib/treequel/branchset.rb', line 394

def limit( new_limit=nil )
  if new_limit.nil?
    return self.options[:limit]
  else
    self.log.debug "cloning %p with new limit: %p" % [ self, new_limit ]
    return self.clone( :limit => Integer(new_limit) )
  end
end

#map(attribute = nil, &block) ⇒ Object

Either maps entries which match the current criteria into an Array of the given attribute, or falls back to the block form if no attribute is specified. If both an attribute and a block are given, the block is called once for each attribute value instead of with each Branch.



258
259
260
261
262
263
264
265
266
267
268
# File 'lib/treequel/branchset.rb', line 258

def map( attribute=nil, &block ) # :yields: branch or attribute
  if attribute
    if block
      super() {|branch| block.call(branch[attribute]) }
    else
      super() {|branch| branch[attribute] }
    end
  else
    super( &block )
  end
end

#not(*filterspec) ⇒ Object

Add a clause made from a negated filterspec to an existing filter.



332
333
334
335
336
# File 'lib/treequel/branchset.rb', line 332

def not( *filterspec )
  self.log.debug "cloning %p with negated filterspec: %p" % [ self, filterspec ]
  notfilter = Treequel::Filter.new( :not, *filterspec )
  return self.clone( :filter => self.filter + notfilter )
end

#or(*filterspec) ⇒ Object

Add an alternate filter to an existing filter by ORing it with filterspec.

Raises:



318
319
320
321
322
323
324
325
326
327
328
# File 'lib/treequel/branchset.rb', line 318

def or( *filterspec )
  opts = self.options
  existing_filter = self.filter
  raise Treequel::ExpressionError, "no existing filter" if
    existing_filter.promiscuous?

  newfilter = Treequel::Filter.new( *filterspec )

  self.log.debug "cloning %p with alternative filterspec: %p" % [ self, filterspec ]
  return self.clone( :filter => (self.filter | newfilter) )
end

#scope(new_scope = nil) ⇒ Object

If called with no argument, returns the current scope of the Branchset. If called with an argument (which should be one of the keys of Treequel::Constants::SCOPE), returns a clone of the receiving Branchset with the new_scope.



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

def scope( new_scope=nil )
  if new_scope
    self.log.debug "cloning %p with new scope: %p" % [ self, new_scope ]
    return self.clone( :scope => new_scope.to_sym )
  else
    return @options[:scope]
  end
end

#select(*attributes) ⇒ Object

If called with one or more attributes, returns a clone of the receiving Branchset that will only fetch the attributes specified. If no attributes are specified, return the list of attributes that will be fetched by the receiving Branchset. An empty Array means that it should fetch all attributes, which is the default.



358
359
360
361
362
363
364
365
# File 'lib/treequel/branchset.rb', line 358

def select( *attributes )
  if attributes.empty?
    return self.options[:select].collect {|attribute| attribute.to_s }
  else
    self.log.debug "cloning %p with new selection: %p" % [ self, attributes ]
    return self.clone( :select => attributes )
  end
end

#select_allObject

Returns a clone of the receiving Branchset that will fetch all attributes.



369
370
371
# File 'lib/treequel/branchset.rb', line 369

def select_all
  return self.clone( :select => [] )
end

#select_more(*attributes) ⇒ Object

Return a clone of the receiving Branchset that will fetch the specified attributes in addition to its own.



376
377
378
# File 'lib/treequel/branchset.rb', line 376

def select_more( *attributes )
  return self.select( *(Array(@options[:select]) | attributes) )
end

#timeout(seconds = nil) ⇒ Object

Return a clone of the receiving Branchset that will search with its timeout set to seconds, which is in floating-point seconds.



413
414
415
416
417
418
419
# File 'lib/treequel/branchset.rb', line 413

def timeout( seconds=nil )
  if seconds
    return self.clone( :timeout => seconds )
  else
    return @options[:timeout]
  end
end

#to_hash(keyattr, valueattr = nil) ⇒ Object

Map the results returned by the search into a hash keyed by the first value of keyattr in the entry. If the optional valueattr argument is given, the values will be the first corresponding attribute, else the value will be the whole entry.



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/treequel/branchset.rb', line 274

def to_hash( keyattr, valueattr=nil )
  return self.inject({}) do |hash, branch|
    key = branch[ keyattr ]
    key = key.first if key.respond_to?( :first )

    # Extract either the valueattr, or the whole entry hash if no valueattr was given.
    if valueattr
      self.log.debug "  extracting value for attribute %p" % [ valueattr ]
      if branch[ valueattr ].respond_to?( :first )
        hash[ key ] = branch[ valueattr ].first
      else
        hash[ key ] = branch[ valueattr ]
      end
    else
      self.log.debug "  using the whole entry hash (%p)"
      hash[ key ] = branch.entry
    end

    hash
  end
end

#to_sObject

Return the Branchset as a stringified URI.



159
160
161
# File 'lib/treequel/branchset.rb', line 159

def to_s
  return "%s/%s" % [ self.branch.dn, self.filter_string ]
end

#uriObject

Return a string representation of the Branchset’s filter



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/treequel/branchset.rb', line 140

def uri
  # :scheme,
  # :host, :port,
  # :dn,
  # :attributes,
  # :scope,
  # :filter,
  # :extensions,
  uri = self.branch.uri
  uri.attributes = self.select.join(',')
  uri.scope = SCOPE_NAME[ self.scope ]
  uri.filter = self.filter_string
  # :TODO: Add extensions? Support extensions in Branchset?

  return uri
end

#with_operational_attributesObject

Return a clone of the receiving Branchset that will fetch any operational attributes in addition to its own. This is exactly equivalent to:

branchset.select( :+ ).


385
386
387
# File 'lib/treequel/branchset.rb', line 385

def with_operational_attributes
  return self.select( :+ )
end

#without_limitObject

Return a clone of the receiving Branchset that has no restriction on the number of Branches that will be fetched.



406
407
408
# File 'lib/treequel/branchset.rb', line 406

def without_limit
  return self.clone( :limit => 0 )
end

#without_timeoutObject

Return a clone of the receiving Branchset that will not use a timeout when searching.



424
425
426
# File 'lib/treequel/branchset.rb', line 424

def without_timeout
  return self.clone( :timeout => 0 )
end