treequel 1.0.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog +130 -1
- data/Rakefile +8 -3
- data/Rakefile.local +2 -0
- data/bin/treeirb +6 -2
- data/bin/treequel +5 -4
- data/lib/treequel/branch.rb +133 -72
- data/lib/treequel/branchcollection.rb +16 -5
- data/lib/treequel/branchset.rb +37 -6
- data/lib/treequel/constants.rb +12 -0
- data/lib/treequel/directory.rb +96 -41
- data/lib/treequel/filter.rb +42 -1
- data/lib/treequel/model/objectclass.rb +111 -0
- data/lib/treequel/model.rb +363 -0
- data/lib/treequel/monkeypatches.rb +65 -0
- data/lib/treequel/schema/attributetype.rb +108 -18
- data/lib/treequel/schema/ldapsyntax.rb +15 -0
- data/lib/treequel/schema/matchingrule.rb +24 -0
- data/lib/treequel/schema/matchingruleuse.rb +24 -0
- data/lib/treequel/schema/objectclass.rb +70 -5
- data/lib/treequel/schema/table.rb +5 -15
- data/lib/treequel/schema.rb +64 -1
- data/lib/treequel.rb +5 -5
- data/rake/documentation.rb +27 -0
- data/rake/hg.rb +14 -2
- data/rake/manual.rb +1 -1
- data/spec/lib/constants.rb +9 -7
- data/spec/lib/control_behavior.rb +1 -0
- data/spec/lib/matchers.rb +1 -0
- data/spec/treequel/branch_spec.rb +229 -20
- data/spec/treequel/branchcollection_spec.rb +73 -39
- data/spec/treequel/branchset_spec.rb +59 -8
- data/spec/treequel/control_spec.rb +1 -0
- data/spec/treequel/controls/contentsync_spec.rb +1 -0
- data/spec/treequel/controls/pagedresults_spec.rb +1 -0
- data/spec/treequel/controls/sortedresults_spec.rb +1 -0
- data/spec/treequel/directory_spec.rb +46 -10
- data/spec/treequel/filter_spec.rb +28 -5
- data/spec/treequel/mixins_spec.rb +7 -14
- data/spec/treequel/model/objectclass_spec.rb +330 -0
- data/spec/treequel/model_spec.rb +433 -0
- data/spec/treequel/monkeypatches_spec.rb +118 -0
- data/spec/treequel/schema/attributetype_spec.rb +116 -0
- data/spec/treequel/schema/ldapsyntax_spec.rb +8 -0
- data/spec/treequel/schema/matchingrule_spec.rb +6 -1
- data/spec/treequel/schema/matchingruleuse_spec.rb +5 -0
- data/spec/treequel/schema/objectclass_spec.rb +41 -3
- data/spec/treequel/schema/table_spec.rb +31 -18
- data/spec/treequel/schema_spec.rb +13 -16
- data/spec/treequel_spec.rb +22 -0
- data.tar.gz.sig +1 -0
- metadata +40 -7
- metadata.gz.sig +0 -0
- data/spec/treequel/utils_spec.rb +0 -49
data/lib/treequel/directory.rb
CHANGED
@@ -9,6 +9,7 @@ require 'treequel'
|
|
9
9
|
require 'treequel/schema'
|
10
10
|
require 'treequel/mixins'
|
11
11
|
require 'treequel/constants'
|
12
|
+
require 'treequel/branch'
|
12
13
|
|
13
14
|
|
14
15
|
# The object in Treequel that represents a connection to a directory, the
|
@@ -27,17 +28,33 @@ class Treequel::Directory
|
|
27
28
|
:connect_type => :tls,
|
28
29
|
:base_dn => nil,
|
29
30
|
:bind_dn => nil,
|
30
|
-
:pass => nil
|
31
|
+
:pass => nil,
|
32
|
+
:results_class => Treequel::Branch,
|
31
33
|
}
|
32
34
|
|
33
|
-
# Default mapping of SYNTAX OIDs to conversions
|
34
|
-
# information on what a valid conversion is.
|
35
|
-
|
36
|
-
OIDS::BIT_STRING_SYNTAX
|
37
|
-
OIDS::BOOLEAN_SYNTAX
|
38
|
-
OIDS::GENERALIZED_TIME_SYNTAX
|
39
|
-
OIDS::UTC_TIME_SYNTAX
|
40
|
-
OIDS::INTEGER_SYNTAX
|
35
|
+
# Default mapping of SYNTAX OIDs to conversions from an LDAP string.
|
36
|
+
# See #add_attribute_conversions for more information on what a valid conversion is.
|
37
|
+
DEFAULT_ATTRIBUTE_CONVERSIONS = {
|
38
|
+
OIDS::BIT_STRING_SYNTAX => lambda {|bs, _| bs[0..-1].to_i(2) },
|
39
|
+
OIDS::BOOLEAN_SYNTAX => { 'TRUE' => true, 'FALSE' => false },
|
40
|
+
OIDS::GENERALIZED_TIME_SYNTAX => lambda {|string, _| Time.parse(string) },
|
41
|
+
OIDS::UTC_TIME_SYNTAX => lambda {|string, _| Time.parse(string) },
|
42
|
+
OIDS::INTEGER_SYNTAX => lambda {|string, _| Integer(string) },
|
43
|
+
OIDS::DISTINGUISHED_NAME_SYNTAX => lambda {|dn, directory|
|
44
|
+
resclass = directory.results_class
|
45
|
+
resclass.new( directory, dn )
|
46
|
+
},
|
47
|
+
}
|
48
|
+
|
49
|
+
# Default mapping of SYNTAX OIDs to conversions to an LDAP string from a Ruby object.
|
50
|
+
# See #add_object_conversion for more information on what a valid conversion is.
|
51
|
+
DEFAULT_OBJECT_CONVERSIONS = {
|
52
|
+
OIDS::BIT_STRING_SYNTAX => lambda {|bs, _| bs.to_i.to_s(2) },
|
53
|
+
OIDS::BOOLEAN_SYNTAX => lambda {|obj, _| obj ? 'TRUE' : 'FALSE' },
|
54
|
+
OIDS::GENERALIZED_TIME_SYNTAX => lambda {|time, _| time.ldap_generalized },
|
55
|
+
OIDS::UTC_TIME_SYNTAX => lambda {|time, _| time.ldap_utc },
|
56
|
+
OIDS::INTEGER_SYNTAX => lambda {|obj, _| Integer(obj).to_s },
|
57
|
+
OIDS::DISTINGUISHED_NAME_SYNTAX => lambda {|obj, _| obj.dn },
|
41
58
|
}
|
42
59
|
|
43
60
|
# :NOTE: the docs for #search_ext2 lie. The method signature is actually:
|
@@ -102,22 +119,26 @@ class Treequel::Directory
|
|
102
119
|
### The DN of the user to bind as; if unset, binds anonymously.
|
103
120
|
### @option options [String] :pass (nil)
|
104
121
|
### The password to use when binding.
|
122
|
+
### @option options [CLass] :results_class (Treequel::Branch)
|
123
|
+
### The class to instantiate by default for entries fetched from the Directory.
|
105
124
|
def initialize( options={} )
|
106
|
-
options
|
125
|
+
options = DEFAULT_OPTIONS.merge( options )
|
107
126
|
|
108
|
-
@host
|
109
|
-
@port
|
110
|
-
@connect_type
|
127
|
+
@host = options[:host]
|
128
|
+
@port = options[:port]
|
129
|
+
@connect_type = options[:connect_type]
|
130
|
+
@results_class = options[:results_class]
|
111
131
|
|
112
|
-
@conn
|
113
|
-
@bound_user
|
132
|
+
@conn = nil
|
133
|
+
@bound_user = nil
|
114
134
|
|
115
|
-
@base_dn
|
135
|
+
@base_dn = options[:base_dn] || self.get_default_base_dn
|
116
136
|
|
117
|
-
@base
|
137
|
+
@base = nil
|
118
138
|
|
119
|
-
@
|
120
|
-
@
|
139
|
+
@object_conversions = DEFAULT_OBJECT_CONVERSIONS.dup
|
140
|
+
@attribute_conversions = DEFAULT_ATTRIBUTE_CONVERSIONS.dup
|
141
|
+
@registered_controls = []
|
121
142
|
|
122
143
|
# Immediately bind if credentials are passed to the initializer.
|
123
144
|
if ( options[:bind_dn] && options[:pass] )
|
@@ -134,7 +155,7 @@ class Treequel::Directory
|
|
134
155
|
def_method_delegators :base, *DELEGATED_BRANCH_METHODS
|
135
156
|
|
136
157
|
# Delegate some methods to the connection via the #conn method
|
137
|
-
def_method_delegators :conn, :controls, :referrals
|
158
|
+
def_method_delegators :conn, :controls, :referrals, :root_dse
|
138
159
|
|
139
160
|
|
140
161
|
# The host to connect to.
|
@@ -149,6 +170,10 @@ class Treequel::Directory
|
|
149
170
|
# @return [Symbol]
|
150
171
|
attr_accessor :connect_type
|
151
172
|
|
173
|
+
# The Class to instantiate when wrapping results fetched from the Directory.
|
174
|
+
# @return [Class]
|
175
|
+
attr_accessor :results_class
|
176
|
+
|
152
177
|
# The base DN of the directory
|
153
178
|
# @return [String]
|
154
179
|
attr_accessor :base_dn
|
@@ -165,7 +190,7 @@ class Treequel::Directory
|
|
165
190
|
### Fetch the Branch for the base node of the directory.
|
166
191
|
### @return [Treequel::Branch]
|
167
192
|
def base
|
168
|
-
return @base ||=
|
193
|
+
return @base ||= self.results_class.new( self, self.base_dn )
|
169
194
|
end
|
170
195
|
|
171
196
|
|
@@ -330,8 +355,8 @@ class Treequel::Directory
|
|
330
355
|
###
|
331
356
|
### @option options [Class] :results_class (Treequel::Branch)
|
332
357
|
### The Class to use when wrapping results; if not specified, defaults to the class
|
333
|
-
### of +base+ if it responds to #new_from_entry, or
|
334
|
-
### if it does not.
|
358
|
+
### of +base+ if it responds to #new_from_entry, or the directory object's
|
359
|
+
### #results_class if it does not.
|
335
360
|
### @option options [Array<String, Symbol>] :selectattrs (['*'])
|
336
361
|
### The attributes to return from the search; defaults to '*', which means to
|
337
362
|
### return all non-operational attributes. Specifying '+' will cause the search
|
@@ -372,7 +397,9 @@ class Treequel::Directory
|
|
372
397
|
if options.key?( :results_class )
|
373
398
|
collectclass = options.delete( :results_class )
|
374
399
|
else
|
375
|
-
collectclass = base.class.respond_to?( :new_from_entry ) ?
|
400
|
+
collectclass = base.class.respond_to?( :new_from_entry ) ?
|
401
|
+
base.class :
|
402
|
+
self.results_class
|
376
403
|
end
|
377
404
|
|
378
405
|
# Format the arguments in the way #search_ext2 expects them
|
@@ -395,7 +422,6 @@ class Treequel::Directory
|
|
395
422
|
]]
|
396
423
|
|
397
424
|
results = []
|
398
|
-
|
399
425
|
self.conn.search_ext2( base_dn, scope, filter, *parameters ).each do |entry|
|
400
426
|
branch = collectclass.new_from_entry( entry, self )
|
401
427
|
branch.include_operational_attrs = base.include_operational_attrs? if
|
@@ -495,25 +521,41 @@ class Treequel::Directory
|
|
495
521
|
end
|
496
522
|
|
497
523
|
|
524
|
+
### Add +conversion+ mapping for attributes of specified +oid+ to a Ruby object. A
|
525
|
+
### conversion is any object that responds to #[] with a String
|
526
|
+
### argument(e.g., Proc, Method, Hash); the argument is the raw value String returned
|
527
|
+
### from the LDAP entry, and it should return the converted value. Adding a mapping
|
528
|
+
### with a nil +conversion+ effectively clears it.
|
529
|
+
### @see #convert_to_object
|
530
|
+
def add_attribute_conversion( oid, conversion=nil )
|
531
|
+
conversion = Proc.new if block_given?
|
532
|
+
@attribute_conversions[ oid ] = conversion
|
533
|
+
end
|
534
|
+
|
535
|
+
|
498
536
|
### Add +conversion+ mapping for the specified +oid+. A conversion is any object that
|
499
|
-
### responds to #[] with
|
500
|
-
### the
|
501
|
-
###
|
502
|
-
|
537
|
+
### responds to #[] with an object argument(e.g., Proc, Method, Hash); the argument is
|
538
|
+
### the Ruby object that's being set as a value in an LDAP entry, and it should return the
|
539
|
+
### raw LDAP string. Adding a mapping with a nil +conversion+ effectively clears it.
|
540
|
+
### @see #convert_to_attribute
|
541
|
+
def add_object_conversion( oid, conversion=nil )
|
503
542
|
conversion = Proc.new if block_given?
|
504
|
-
@
|
543
|
+
@object_conversions[ oid ] = conversion
|
505
544
|
end
|
506
545
|
|
507
546
|
|
508
547
|
### Register the specified +modules+
|
509
548
|
def register_controls( *modules )
|
510
|
-
|
511
|
-
|
549
|
+
supported_controls = self.supported_control_oids
|
550
|
+
self.log.debug "Got %d supported controls: %p" %
|
551
|
+
[ supported_controls.length, supported_controls ]
|
512
552
|
|
513
553
|
modules.each do |mod|
|
514
554
|
oid = mod.const_get( :OID ) if mod.const_defined?( :OID )
|
515
555
|
raise NotImplementedError, "%s doesn't define an OID" % [ mod.name ] if oid.nil?
|
516
556
|
|
557
|
+
self.log.debug "Checking for directory support for %p (%s)" % [ mod, oid ]
|
558
|
+
|
517
559
|
if supported_controls.include?( oid )
|
518
560
|
@registered_controls << mod
|
519
561
|
else
|
@@ -524,17 +566,30 @@ class Treequel::Directory
|
|
524
566
|
end
|
525
567
|
|
526
568
|
|
527
|
-
### Map the specified +
|
569
|
+
### Map the specified LDAP +attribute+ to its Ruby datatype if one is registered for the given
|
528
570
|
### syntax +oid+. If there is no conversion registered, just return the +value+ as-is.
|
529
|
-
def
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
return
|
571
|
+
def convert_to_object( oid, attribute )
|
572
|
+
return attribute unless conversion = @attribute_conversions[ oid ]
|
573
|
+
|
574
|
+
if conversion.respond_to?( :call )
|
575
|
+
return conversion.call( attribute, self )
|
576
|
+
else
|
577
|
+
return conversion[ attribute ]
|
534
578
|
end
|
579
|
+
end
|
535
580
|
|
536
|
-
|
537
|
-
|
581
|
+
|
582
|
+
### Map the specified Ruby +object+ to its LDAP string equivalent if a conversion is
|
583
|
+
### registered for the given syntax +oid+. If there is no conversion registered, just
|
584
|
+
### returns the +value+ as a String (via #to_s).
|
585
|
+
def convert_to_attribute( oid, object )
|
586
|
+
return object.to_s unless conversion = @object_conversions[ oid ]
|
587
|
+
|
588
|
+
if conversion.respond_to?( :call )
|
589
|
+
return conversion.call( object, self )
|
590
|
+
else
|
591
|
+
return conversion[ object ]
|
592
|
+
end
|
538
593
|
end
|
539
594
|
|
540
595
|
|
@@ -619,7 +674,7 @@ class Treequel::Directory
|
|
619
674
|
|
620
675
|
### Fetch the default base dn for the server from the server's Root DSE.
|
621
676
|
def get_default_base_dn
|
622
|
-
dse = self.
|
677
|
+
dse = self.root_dse
|
623
678
|
return '' if dse.nil? || dse.empty?
|
624
679
|
return dse.first['namingContexts'].first
|
625
680
|
end
|
data/lib/treequel/filter.rb
CHANGED
@@ -62,6 +62,15 @@ class Treequel::Filter
|
|
62
62
|
return self.filters.collect {|f| f.to_s }.join
|
63
63
|
end
|
64
64
|
|
65
|
+
|
66
|
+
### Append operator: add the +other+ filter to the list.
|
67
|
+
### @param [Treequel::Filter] other the new filter to add
|
68
|
+
### @return [Treequel::Filter::FilterList] self (for chaining)
|
69
|
+
def <<( other )
|
70
|
+
@filters << other
|
71
|
+
return self
|
72
|
+
end
|
73
|
+
|
65
74
|
end # class FilterList
|
66
75
|
|
67
76
|
|
@@ -140,6 +149,12 @@ class Treequel::Filter
|
|
140
149
|
return '&' + @filterlist.to_s
|
141
150
|
end
|
142
151
|
|
152
|
+
### Add an additional filter to the list of requirements
|
153
|
+
### @param [Treequel::Filter] filter the new requirement
|
154
|
+
def add_requirement( filter )
|
155
|
+
@filterlist << filter
|
156
|
+
end
|
157
|
+
|
143
158
|
end # AndComponent
|
144
159
|
|
145
160
|
|
@@ -160,6 +175,12 @@ class Treequel::Filter
|
|
160
175
|
return '|' + @filterlist.to_s
|
161
176
|
end
|
162
177
|
|
178
|
+
### Add an additional filter to the list of alternatives
|
179
|
+
### @param [Treequel::Filter] filter the new alternative
|
180
|
+
def add_alternation( filter )
|
181
|
+
@filterlist << filter
|
182
|
+
end
|
183
|
+
|
163
184
|
end # class OrComponent
|
164
185
|
|
165
186
|
|
@@ -215,8 +236,10 @@ class Treequel::Filter
|
|
215
236
|
self.log.debug "creating a new %s %s for %p and %p" %
|
216
237
|
[ filtertype, self.class.name, attribute, value ]
|
217
238
|
|
218
|
-
|
239
|
+
# Handle Sequel :attribute.identifier
|
240
|
+
attribute = attribute.value if attribute.respond_to?( :value )
|
219
241
|
|
242
|
+
filtertype = filtertype.to_s.downcase.to_sym
|
220
243
|
if FILTERTYPE_OP.key?( filtertype )
|
221
244
|
# no-op
|
222
245
|
elsif FILTEROP_NAMES.key?( filtertype.to_s )
|
@@ -688,6 +711,24 @@ class Treequel::Filter
|
|
688
711
|
alias_method :+, :&
|
689
712
|
|
690
713
|
|
714
|
+
### OR two filters together
|
715
|
+
### @param [Treequel::Filter] other_filter
|
716
|
+
def |( other_filter )
|
717
|
+
return other_filter if self.promiscuous?
|
718
|
+
return self.dup if other_filter.promiscuous?
|
719
|
+
|
720
|
+
# Collapse nested ORs into a single one with an additional alternation
|
721
|
+
# if possible.
|
722
|
+
if self.component.respond_to?( :add_alternation )
|
723
|
+
self.log.debug "collapsing nested ORs..."
|
724
|
+
newcomp = self.component.dup
|
725
|
+
newcomp.add_alternation( other_filter )
|
726
|
+
return self.class.new( newcomp )
|
727
|
+
else
|
728
|
+
return self.class.new( :or, [self, other_filter] )
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
691
732
|
|
692
733
|
end # class Treequel::Filter
|
693
734
|
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
require 'treequel'
|
5
|
+
require 'treequel/model'
|
6
|
+
require 'treequel/mixins'
|
7
|
+
require 'treequel/constants'
|
8
|
+
|
9
|
+
|
10
|
+
# Mixin that provides Treequel::Model characteristics to a mixin module.
|
11
|
+
module Treequel::Model::ObjectClass
|
12
|
+
|
13
|
+
### Extension callback -- add data structures to the extending +mod+.
|
14
|
+
### @param [Module] mod the mixin module to be extended
|
15
|
+
def self::extended( mod )
|
16
|
+
# mod.instance_variable_set( :@model_directory, nil )
|
17
|
+
# mod.instance_variable_set( :@model_bases, [] )
|
18
|
+
mod.instance_variable_set( :@model_class, Treequel::Model )
|
19
|
+
mod.instance_variable_set( :@model_objectclasses, [] )
|
20
|
+
mod.instance_variable_set( :@model_bases, [] )
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
### Inclusion callback -- Methods should be applied to the module rather than an instance.
|
26
|
+
### Warn the user if they use include() and extend() instead.
|
27
|
+
def self::included( mod )
|
28
|
+
warn "extending %p rather than appending features to it" % [ mod ]
|
29
|
+
mod.extend( self )
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
### Declare which Treequel::Model subclasses the mixin will register itself with. If this is
|
34
|
+
### used, it should be declared *before* declaring the mixin's bases and/or objectClasses.
|
35
|
+
def model_class( mclass=nil )
|
36
|
+
if mclass
|
37
|
+
|
38
|
+
# If there were already registered objectclasses, remove them from the previous
|
39
|
+
# model class
|
40
|
+
unless @model_objectclasses.empty? && @model_bases.empty?
|
41
|
+
Treequel.log.warn "%p: model_class should come before model_objectclasses" % [ self ]
|
42
|
+
@model_class.unregister_mixin( self )
|
43
|
+
mclass.register_mixin( self )
|
44
|
+
end
|
45
|
+
@model_class = mclass
|
46
|
+
end
|
47
|
+
|
48
|
+
return @model_class
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
### Set or get objectClasses that the mixin requires. Also registers the mixin with
|
53
|
+
### Treequel::Model.
|
54
|
+
###
|
55
|
+
### @param [Array<Symbol>] objectclasses the objectClasses the mixin will apply to, as an
|
56
|
+
### array of Symbols
|
57
|
+
### @return [Array<Symbol>] the objectClasses that the module requires
|
58
|
+
def model_objectclasses( *objectclasses )
|
59
|
+
unless objectclasses.empty?
|
60
|
+
@model_objectclasses = objectclasses
|
61
|
+
@model_class.register_mixin( self )
|
62
|
+
end
|
63
|
+
return @model_objectclasses.dup
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
### Set or get base DNs that the mixin applies to.
|
68
|
+
def model_bases( *base_dns )
|
69
|
+
unless base_dns.empty?
|
70
|
+
@model_bases = base_dns.collect {|dn| dn.gsub(/\s+/, '') }
|
71
|
+
@model_class.register_mixin( self )
|
72
|
+
end
|
73
|
+
|
74
|
+
return @model_bases.dup
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
### Return a Branchset (or BranchCollection if the receiver has more than one
|
79
|
+
### base) that can be used to search the given +directory+ for entries to which
|
80
|
+
### the receiver applies.
|
81
|
+
###
|
82
|
+
### @param [Treequel::Directory] directory the directory to search
|
83
|
+
### @return [Treequel::Branchset, Treequel::BranchCollection] the encapsulated search
|
84
|
+
def search( directory )
|
85
|
+
bases = self.model_bases
|
86
|
+
objectclasses = self.model_objectclasses
|
87
|
+
|
88
|
+
raise Treequel::ModelError, "%p has no search criteria defined" % [ self ] if
|
89
|
+
bases.empty? && objectclasses.empty?
|
90
|
+
|
91
|
+
Treequel.log.debug "Creating search for %s using model class %p" %
|
92
|
+
[ self.name, self.model_class ]
|
93
|
+
|
94
|
+
# Start by making a Branchset or BranchCollection for the mixin's bases. If
|
95
|
+
# the mixin doesn't have any bases, just use the base DN of the directory
|
96
|
+
# to be searched
|
97
|
+
bases = [directory.base_dn] if bases.empty?
|
98
|
+
search = bases.
|
99
|
+
map {|base| self.model_class.new(directory, base).branchset }.
|
100
|
+
inject {|branch1,branch2| branch1 + branch2 }
|
101
|
+
|
102
|
+
Treequel.log.debug "Search branch after applying bases is: %p" % [ search ]
|
103
|
+
|
104
|
+
return self.model_objectclasses.inject( search ) do |branchset, oid|
|
105
|
+
Treequel.log.debug " adding filter for objectClass=%s to %p" % [ oid, branchset ]
|
106
|
+
branchset.filter( :objectClass => oid )
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end # Treequel::Model::ObjectClass
|
111
|
+
|