treequel 1.2.2 → 1.3.0pre384
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/ChangeLog +3374 -0
- data/History.md +39 -0
- data/LICENSE +27 -0
- data/README.md +25 -2
- data/Rakefile +64 -29
- data/bin/treequel +16 -25
- data/bin/treewhat +318 -0
- data/lib/treequel.rb +13 -3
- data/lib/treequel/behavior/control.rb +40 -0
- data/lib/treequel/branch.rb +56 -28
- data/lib/treequel/branchset.rb +10 -2
- data/lib/treequel/controls/pagedresults.rb +4 -2
- data/lib/treequel/directory.rb +40 -50
- data/lib/treequel/exceptions.rb +18 -0
- data/lib/treequel/mixins.rb +44 -14
- data/lib/treequel/model.rb +338 -21
- data/lib/treequel/model/errors.rb +79 -0
- data/lib/treequel/model/objectclass.rb +26 -2
- data/lib/treequel/model/schemavalidations.rb +69 -0
- data/lib/treequel/monkeypatches.rb +99 -17
- data/lib/treequel/schema.rb +19 -5
- data/spec/lib/constants.rb +20 -2
- data/spec/lib/helpers.rb +25 -8
- data/spec/treequel/branch_spec.rb +73 -10
- data/spec/treequel/controls/contentsync_spec.rb +2 -11
- data/spec/treequel/controls/pagedresults_spec.rb +25 -9
- data/spec/treequel/controls/sortedresults_spec.rb +8 -10
- data/spec/treequel/directory_spec.rb +74 -63
- data/spec/treequel/model/errors_spec.rb +77 -0
- data/spec/treequel/model/objectclass_spec.rb +107 -35
- data/spec/treequel/model/schemavalidations_spec.rb +112 -0
- data/spec/treequel/model_spec.rb +294 -81
- data/spec/treequel/monkeypatches_spec.rb +49 -3
- metadata +28 -16
- metadata.gz.sig +0 -0
- data/spec/lib/control_behavior.rb +0 -47
data/lib/treequel/exceptions.rb
CHANGED
@@ -26,6 +26,24 @@ module Treequel
|
|
26
26
|
### other problem.
|
27
27
|
class ModelError < Treequel::Error; end
|
28
28
|
|
29
|
+
### Exception class raised when +raise_on_save_failure+ is set and validation fails
|
30
|
+
class ValidationFailed < Treequel::ModelError
|
31
|
+
|
32
|
+
### Create a new Treequel::ValidationFailed exception with the given +errors+.
|
33
|
+
### @param [Treequel::Model::Errors, String] errors the validaton errors
|
34
|
+
def initialize( errors )
|
35
|
+
if errors.respond_to?( :full_messages )
|
36
|
+
@errors = errors
|
37
|
+
super( errors.full_messages.join(', ') )
|
38
|
+
else
|
39
|
+
super
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Treequel::Model::Errors] the validation errors
|
44
|
+
attr_reader :errors
|
45
|
+
end
|
46
|
+
|
29
47
|
end # module Treequel
|
30
48
|
|
31
49
|
|
data/lib/treequel/mixins.rb
CHANGED
@@ -153,14 +153,6 @@ module Treequel
|
|
153
153
|
### Add logging to a Treequel class. Including classes get #log and #log_debug methods.
|
154
154
|
module Loggable
|
155
155
|
|
156
|
-
LEVEL = {
|
157
|
-
:debug => Logger::DEBUG,
|
158
|
-
:info => Logger::INFO,
|
159
|
-
:warn => Logger::WARN,
|
160
|
-
:error => Logger::ERROR,
|
161
|
-
:fatal => Logger::FATAL,
|
162
|
-
}
|
163
|
-
|
164
156
|
### A logging proxy class that wraps calls to the logger into calls that include
|
165
157
|
### the name of the calling class.
|
166
158
|
### @private
|
@@ -172,13 +164,34 @@ module Treequel
|
|
172
164
|
@force_debug = force_debug
|
173
165
|
end
|
174
166
|
|
175
|
-
### Delegate
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
167
|
+
### Delegate debug messages to the global logger with the appropriate class name.
|
168
|
+
def debug( msg=nil, &block )
|
169
|
+
Treequel.logger.add( Logger::DEBUG, msg, @classname, &block )
|
170
|
+
end
|
171
|
+
|
172
|
+
### Delegate info messages to the global logger with the appropriate class name.
|
173
|
+
def info( msg=nil, &block )
|
174
|
+
return self.debug( msg, &block ) if @force_debug
|
175
|
+
Treequel.logger.add( Logger::INFO, msg, @classname, &block )
|
176
|
+
end
|
177
|
+
|
178
|
+
### Delegate warn messages to the global logger with the appropriate class name.
|
179
|
+
def warn( msg=nil, &block )
|
180
|
+
return self.debug( msg, &block ) if @force_debug
|
181
|
+
Treequel.logger.add( Logger::WARN, msg, @classname, &block )
|
182
|
+
end
|
183
|
+
|
184
|
+
### Delegate error messages to the global logger with the appropriate class name.
|
185
|
+
def error( msg=nil, &block )
|
186
|
+
return self.debug( msg, &block ) if @force_debug
|
187
|
+
Treequel.logger.add( Logger::ERROR, msg, @classname, &block )
|
188
|
+
end
|
189
|
+
|
190
|
+
### Delegate fatal messages to the global logger with the appropriate class name.
|
191
|
+
def fatal( msg=nil, &block )
|
192
|
+
Treequel.logger.add( Logger::FATAL, msg, @classname, &block )
|
181
193
|
end
|
194
|
+
|
182
195
|
end # ClassNameProxy
|
183
196
|
|
184
197
|
#########
|
@@ -200,6 +213,7 @@ module Treequel
|
|
200
213
|
def log_debug
|
201
214
|
@log_debug_proxy ||= ClassNameProxy.new( self.class, true )
|
202
215
|
end
|
216
|
+
|
203
217
|
end # module Loggable
|
204
218
|
|
205
219
|
|
@@ -271,6 +285,22 @@ module Treequel
|
|
271
285
|
end
|
272
286
|
end
|
273
287
|
|
288
|
+
### Normalize the attributes in +hash+ to be of the form expected by the
|
289
|
+
### LDAP library (i.e., keys as Strings, values as Arrays of Strings)
|
290
|
+
def normalize_attributes( hash )
|
291
|
+
normhash = {}
|
292
|
+
hash.each do |key,val|
|
293
|
+
val = [ val ] unless val.is_a?( Array )
|
294
|
+
val.collect! {|obj| obj.to_s }
|
295
|
+
|
296
|
+
normhash[ key.to_s ] = val
|
297
|
+
end
|
298
|
+
|
299
|
+
normhash.delete( 'dn' )
|
300
|
+
|
301
|
+
return normhash
|
302
|
+
end
|
303
|
+
|
274
304
|
end # HashUtilities
|
275
305
|
|
276
306
|
|
data/lib/treequel/model.rb
CHANGED
@@ -10,12 +10,15 @@ require 'treequel/branchset'
|
|
10
10
|
|
11
11
|
# An object interface to LDAP entries.
|
12
12
|
class Treequel::Model < Treequel::Branch
|
13
|
+
require 'treequel/model/objectclass'
|
14
|
+
require 'treequel/model/errors'
|
15
|
+
require 'treequel/model/schemavalidations'
|
16
|
+
|
13
17
|
include Treequel::Loggable,
|
14
18
|
Treequel::Constants,
|
15
19
|
Treequel::Normalization,
|
16
|
-
Treequel::Constants::Patterns
|
17
|
-
|
18
|
-
require 'treequel/model/objectclass'
|
20
|
+
Treequel::Constants::Patterns,
|
21
|
+
Treequel::Model::SchemaValidations
|
19
22
|
|
20
23
|
|
21
24
|
# A prototype Hash that autovivifies its members as Sets, for use in
|
@@ -23,6 +26,34 @@ class Treequel::Model < Treequel::Branch
|
|
23
26
|
SET_HASH = Hash.new {|h,k| h[k] = Set.new }
|
24
27
|
|
25
28
|
|
29
|
+
# The hooks that are called before an action
|
30
|
+
BEFORE_HOOKS = [
|
31
|
+
:before_create,
|
32
|
+
:before_update,
|
33
|
+
:before_save,
|
34
|
+
:before_destroy,
|
35
|
+
:before_validation,
|
36
|
+
]
|
37
|
+
|
38
|
+
# The hooks that are called after an action
|
39
|
+
AFTER_HOOKS = [
|
40
|
+
:after_initialize,
|
41
|
+
:after_create,
|
42
|
+
:after_update,
|
43
|
+
:after_save,
|
44
|
+
:after_destroy,
|
45
|
+
:after_validation,
|
46
|
+
]
|
47
|
+
|
48
|
+
# Hooks the user can override
|
49
|
+
HOOKS = BEFORE_HOOKS + AFTER_HOOKS
|
50
|
+
|
51
|
+
# Defaults for #validate options
|
52
|
+
DEFAULT_VALIDATION_OPTIONS = {
|
53
|
+
:with_schema => true,
|
54
|
+
}
|
55
|
+
|
56
|
+
|
26
57
|
#################################################################
|
27
58
|
### C L A S S M E T H O D S
|
28
59
|
#################################################################
|
@@ -131,6 +162,25 @@ class Treequel::Model < Treequel::Branch
|
|
131
162
|
end
|
132
163
|
|
133
164
|
|
165
|
+
### Never freeze converted values in Model objects.
|
166
|
+
def self::freeze_converted_values?; false; end
|
167
|
+
|
168
|
+
|
169
|
+
### Create a new Treequel::Model object with the given +entry+ hash from the
|
170
|
+
### specified +directory+. Overrides Treequel::Branch.new_from_entry to pass the
|
171
|
+
### +from_directory+ flag to mark it as unmodified.
|
172
|
+
###
|
173
|
+
### @see Treequel::Branch.new_from_entry
|
174
|
+
def self::new_from_entry( entry, directory )
|
175
|
+
entry = Treequel::HashUtilities.stringify_keys( entry )
|
176
|
+
dnvals = entry.delete( 'dn' ) or
|
177
|
+
raise ArgumentError, "no 'dn' attribute for entry"
|
178
|
+
|
179
|
+
Treequel.logger.debug "Creating %p from entry: %p in directory: %s" %
|
180
|
+
[ self, dnvals.first, directory ]
|
181
|
+
return self.new( directory, dnvals.first, entry, true )
|
182
|
+
end
|
183
|
+
|
134
184
|
|
135
185
|
#################################################################
|
136
186
|
### I N S T A N C E M E T H O D S
|
@@ -138,9 +188,25 @@ class Treequel::Model < Treequel::Branch
|
|
138
188
|
|
139
189
|
### Override the default to extend new instances with applicable mixins if their
|
140
190
|
### entry is set.
|
141
|
-
def initialize(
|
191
|
+
def initialize( directory, dn, entry=nil, from_directory=false )
|
192
|
+
if from_directory
|
193
|
+
super( directory, dn, entry )
|
194
|
+
else
|
195
|
+
super( directory, dn )
|
196
|
+
@values = entry ? symbolify_keys( entry ) : {}
|
197
|
+
@dirty = true
|
198
|
+
end
|
199
|
+
|
200
|
+
self.apply_applicable_mixins( @dn, @entry )
|
201
|
+
self.after_initialize
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
### Copy initializer -- re-apply mixins to duplicates, too.
|
206
|
+
def initialize_copy( other )
|
142
207
|
super
|
143
|
-
self.apply_applicable_mixins( @dn, @entry )
|
208
|
+
self.apply_applicable_mixins( @dn, @entry )
|
209
|
+
self.after_initialize
|
144
210
|
end
|
145
211
|
|
146
212
|
|
@@ -148,6 +214,229 @@ class Treequel::Model < Treequel::Branch
|
|
148
214
|
public
|
149
215
|
######
|
150
216
|
|
217
|
+
### Set up the empty hook methods
|
218
|
+
HOOKS.each do |hook|
|
219
|
+
define_method( hook ) do |*args|
|
220
|
+
self.log.debug "#{hook} default hook called."
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
### Tests whether the object has been modified since it was loaded from
|
226
|
+
### the directory.
|
227
|
+
def modified?
|
228
|
+
return @dirty ? true : false
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
### Mark the object as unmodified.
|
233
|
+
def reset_dirty_flag
|
234
|
+
@dirty = false
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
### Index set operator -- set attribute +attrname+ to a new +value+.
|
239
|
+
### Overridden to make Model objects defer writing changes until
|
240
|
+
### {Treequel::Model#save} is called.
|
241
|
+
###
|
242
|
+
### @param [Symbol, String] attrname attribute name
|
243
|
+
### @param [Object] value the attribute value
|
244
|
+
def []=( attrname, value )
|
245
|
+
attrtype = self.find_attribute_type( attrname.to_sym ) or
|
246
|
+
raise ArgumentError, "unknown attribute %p" % [ attrname ]
|
247
|
+
value = Array( value ) unless attrtype.single?
|
248
|
+
|
249
|
+
self.mark_dirty
|
250
|
+
@values[ attrtype.name.to_sym ] = value
|
251
|
+
|
252
|
+
# If the objectClasses change, we (may) need to re-apply mixins
|
253
|
+
if attrname.to_s.downcase == 'objectclass'
|
254
|
+
self.log.debug " objectClass change -- reapplying mixins"
|
255
|
+
self.apply_applicable_mixins( self.dn )
|
256
|
+
else
|
257
|
+
self.log.debug " no objectClass changes -- no need to reapply mixins"
|
258
|
+
end
|
259
|
+
|
260
|
+
return value
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
### Make the changes to the object specified by the given +attributes+.
|
265
|
+
### Overridden to make Model objects defer writing changes until
|
266
|
+
### {Treequel::Model#save} is called.
|
267
|
+
###
|
268
|
+
### @param attributes (see Treequel::Directory#modify)
|
269
|
+
### @return [TrueClass] if the merge succeeded
|
270
|
+
def merge( attributes )
|
271
|
+
attributes.each do |attrname, value|
|
272
|
+
self[ attrname ] = value
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
### Delete the specified attributes.
|
278
|
+
### Overridden to make Model objects defer writing changes until
|
279
|
+
### {Treequel::Model#save} is called.
|
280
|
+
###
|
281
|
+
### @see Treequel::Branch#delete
|
282
|
+
def delete( *attributes )
|
283
|
+
return super if attributes.empty?
|
284
|
+
|
285
|
+
self.log.debug "Deleting attributes: %p" % [ attributes ]
|
286
|
+
self.mark_dirty
|
287
|
+
|
288
|
+
attributes.flatten.each do |attribute|
|
289
|
+
|
290
|
+
# With a hash, delete each value for each key
|
291
|
+
if attribute.is_a?( Hash )
|
292
|
+
self.delete_specific_values( attribute )
|
293
|
+
|
294
|
+
# With an array of attributes to delete, replace
|
295
|
+
# MULTIPLE attribute types with an empty array, and SINGLE
|
296
|
+
# attribute types with nil
|
297
|
+
elsif attribute.respond_to?( :to_sym )
|
298
|
+
attrtype = self.find_attribute_type( attribute.to_sym )
|
299
|
+
if attrtype.single?
|
300
|
+
@values[ attribute.to_sym ] = nil
|
301
|
+
else
|
302
|
+
@values[ attribute.to_sym ] = []
|
303
|
+
end
|
304
|
+
else
|
305
|
+
raise ArgumentError,
|
306
|
+
"can't convert a %p to a Symbol or a Hash" % [ attribute.class ]
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
return true
|
311
|
+
end
|
312
|
+
|
313
|
+
|
314
|
+
### Returns the validation errors associated with this object.
|
315
|
+
### @see Treequel::Model::Errors.
|
316
|
+
def errors
|
317
|
+
return @errors ||= Treequel::Model::Errors.new
|
318
|
+
end
|
319
|
+
|
320
|
+
|
321
|
+
### Return +true+ if the model object passes all of its validations.
|
322
|
+
def valid?( opts={} )
|
323
|
+
self.errors.clear
|
324
|
+
|
325
|
+
return false if self.before_validation == false
|
326
|
+
self.validate
|
327
|
+
self.after_validation
|
328
|
+
|
329
|
+
return self.errors.empty? ? true : false
|
330
|
+
end
|
331
|
+
|
332
|
+
|
333
|
+
### Validate the object with the specified +options+.
|
334
|
+
### @param [Hash] options options for validation.
|
335
|
+
### @option options [Boolean] :with_schema whether or not to run the schema validations
|
336
|
+
def validate( options={} )
|
337
|
+
options = DEFAULT_VALIDATION_OPTIONS.merge( options )
|
338
|
+
|
339
|
+
self.errors.add( :objectClass, 'must have at least one' ) if self.object_classes.empty?
|
340
|
+
|
341
|
+
super( options )
|
342
|
+
end
|
343
|
+
|
344
|
+
|
345
|
+
### Write any pending changes in the model object to the directory.
|
346
|
+
def save
|
347
|
+
self.log.debug "Saving %s..." % [ self.dn ]
|
348
|
+
raise Treequel::ValidationFailed, self.errors unless self.valid?
|
349
|
+
self.log.debug " validation succeeded."
|
350
|
+
|
351
|
+
unless mods = self.modifications
|
352
|
+
self.log.debug " no modifications... no save necessary."
|
353
|
+
return false
|
354
|
+
end
|
355
|
+
|
356
|
+
self.log.debug " got %d modifications." % [ mods.length ]
|
357
|
+
self.before_save( mods ) or return nil
|
358
|
+
|
359
|
+
if self.exists?
|
360
|
+
self.log.debug " entry already exists: updating..."
|
361
|
+
self.before_update( mods ) or return nil
|
362
|
+
self.modify( mods )
|
363
|
+
self.after_update( mods )
|
364
|
+
|
365
|
+
else
|
366
|
+
self.log.debug " entry doesn't exist: creating..."
|
367
|
+
self.before_create( mods ) or return nil
|
368
|
+
self.create( mods )
|
369
|
+
self.after_create( mods )
|
370
|
+
end
|
371
|
+
|
372
|
+
self.after_save( mods )
|
373
|
+
|
374
|
+
return true
|
375
|
+
end
|
376
|
+
|
377
|
+
|
378
|
+
### Return any pending changes in the model object.
|
379
|
+
### @return [Array<LDAP::Mod>] the changes as LDAP modifications
|
380
|
+
def modifications
|
381
|
+
return unless self.modified?
|
382
|
+
self.log.debug "Gathering modifications..."
|
383
|
+
|
384
|
+
mods = []
|
385
|
+
entry = self.entry || {}
|
386
|
+
self.log.debug " directory entry is: %p" % [ entry ]
|
387
|
+
|
388
|
+
@values.sort_by {|k, _| k.to_s }.each do |attribute, vals|
|
389
|
+
vals = [ vals ] unless vals.is_a?( Array )
|
390
|
+
vals = vals.compact
|
391
|
+
vals.collect! {|val| self.get_converted_attribute(attribute, val) }
|
392
|
+
self.log.debug " comparing %s values to entry: %p vs. %p" %
|
393
|
+
[ attribute, vals, entry[attribute.to_s] ]
|
394
|
+
|
395
|
+
entryvals = (entry[attribute.to_s] || [])
|
396
|
+
attrmods = { :add => [], :delete => [] }
|
397
|
+
|
398
|
+
Diff::LCS.sdiff( entryvals.sort, vals.sort ) do |change|
|
399
|
+
self.log.debug " found a change: %p" % [ change ]
|
400
|
+
if change.adding?
|
401
|
+
attrmods[:add] << change.new_element
|
402
|
+
elsif change.changed?
|
403
|
+
attrmods[:add] << change.new_element
|
404
|
+
attrmods[:delete] << change.old_element
|
405
|
+
elsif change.deleting?
|
406
|
+
attrmods[:delete] << change.old_element
|
407
|
+
# else
|
408
|
+
# self.log.debug " no mod necessary for %p" % [ change.action ]
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
mods << LDAP::Mod.new( LDAP::LDAP_MOD_DELETE, attribute.to_s, attrmods[:delete] ) unless
|
413
|
+
attrmods[:delete].empty?
|
414
|
+
mods << LDAP::Mod.new( LDAP::LDAP_MOD_ADD, attribute.to_s, attrmods[:add] ) unless
|
415
|
+
attrmods[:add].empty?
|
416
|
+
end
|
417
|
+
|
418
|
+
self.log.debug " mods are: %p" % [ mods ]
|
419
|
+
|
420
|
+
return mods
|
421
|
+
end
|
422
|
+
|
423
|
+
|
424
|
+
### Return the pending modifications for the object as an LDIF string.
|
425
|
+
def modification_ldif
|
426
|
+
mods = self.modifications
|
427
|
+
return LDAP::LDIF.mods_to_ldif( self.dn, mods )
|
428
|
+
end
|
429
|
+
|
430
|
+
|
431
|
+
### Revert to the attributes in the directory, discarding any pending changes.
|
432
|
+
def revert
|
433
|
+
self.clear_caches
|
434
|
+
@dirty = false
|
435
|
+
|
436
|
+
return true
|
437
|
+
end
|
438
|
+
|
439
|
+
|
151
440
|
### Override Branch#search to inject the 'objectClass' attribute to the
|
152
441
|
### selected attribute list if there is one.
|
153
442
|
def search( scope=:subtree, filter='(objectClass=*)', parameters={}, &block )
|
@@ -197,6 +486,29 @@ class Treequel::Model < Treequel::Branch
|
|
197
486
|
protected
|
198
487
|
#########
|
199
488
|
|
489
|
+
|
490
|
+
### Mark the object as having been modified.
|
491
|
+
def mark_dirty
|
492
|
+
@dirty = true
|
493
|
+
end
|
494
|
+
|
495
|
+
|
496
|
+
### Delete specific key/value +pairs+ from the entry.
|
497
|
+
### @param [Hash] pairs key/value pairs to delete from the entry.
|
498
|
+
def delete_specific_values( pairs )
|
499
|
+
self.log.debug " hash-delete..."
|
500
|
+
|
501
|
+
# Ensure the value exists, and its values converted and cached, as
|
502
|
+
# the delete needs Ruby object instead of string comparison
|
503
|
+
pairs.each do |key, vals|
|
504
|
+
next unless self[ key ]
|
505
|
+
self.log.debug " deleting %p: %p" % [ key, vals ]
|
506
|
+
|
507
|
+
@values[ key ].delete_if {|val| vals.include?(val) }
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
|
200
512
|
### Search for the Treequel::Schema::AttributeType associated with +sym+.
|
201
513
|
###
|
202
514
|
### @param [Symbol,String] name the name of the attribute to find
|
@@ -205,7 +517,7 @@ class Treequel::Model < Treequel::Branch
|
|
205
517
|
def find_attribute_type( name )
|
206
518
|
attrtype = nil
|
207
519
|
|
208
|
-
#
|
520
|
+
# Try both the name as-is, and the camelCased version of it
|
209
521
|
camelcased_sym = name.to_s.gsub( /_(\w)/ ) { $1.upcase }.to_sym
|
210
522
|
attrtype = self.valid_attribute_type( name ) ||
|
211
523
|
self.valid_attribute_type( camelcased_sym )
|
@@ -268,13 +580,13 @@ class Treequel::Model < Treequel::Branch
|
|
268
580
|
def make_reader( attrtype )
|
269
581
|
self.log.debug "Generating an attribute reader for %p" % [ attrtype ]
|
270
582
|
attrname = attrtype.name
|
271
|
-
return lambda
|
583
|
+
return lambda do |*args|
|
272
584
|
if args.empty?
|
273
585
|
self[ attrname ]
|
274
586
|
else
|
275
587
|
self.traverse_branch( attrname, *args )
|
276
588
|
end
|
277
|
-
|
589
|
+
end
|
278
590
|
end
|
279
591
|
|
280
592
|
|
@@ -329,30 +641,35 @@ class Treequel::Model < Treequel::Branch
|
|
329
641
|
|
330
642
|
|
331
643
|
### Apply mixins that are applicable considering the receiver's DN and the
|
332
|
-
### objectClasses from
|
333
|
-
def apply_applicable_mixins( dn, entry )
|
334
|
-
|
644
|
+
### objectClasses from the given entry merged with any unsaved values.
|
645
|
+
def apply_applicable_mixins( dn, entry=nil )
|
646
|
+
entry ||= {}
|
647
|
+
entry.merge!( stringify_keys(@values) )
|
648
|
+
return unless entry['objectClass']
|
649
|
+
|
650
|
+
# self.log.debug "Applying mixins applicable to %s" % [ dn ]
|
335
651
|
schema = self.directory.schema
|
336
652
|
|
653
|
+
# self.log.debug " entry is: %p" % [ entry ]
|
337
654
|
ocs = entry['objectClass'].collect do |oc_oid|
|
338
655
|
explicit_oc = schema.object_classes[ oc_oid ]
|
339
656
|
explicit_oc.ancestors.collect {|oc| oc.name }
|
340
657
|
end.flatten.uniq
|
341
|
-
self.log.debug " got %d candidate objectClasses: %p" % [ ocs.length, ocs ]
|
658
|
+
# self.log.debug " got %d candidate objectClasses: %p" % [ ocs.length, ocs ]
|
342
659
|
|
343
660
|
oc_mixins = self.class.mixins_for_objectclasses( *ocs )
|
344
661
|
dn_mixins = self.class.mixins_for_dn( dn )
|
345
|
-
self.log.debug " found %d mixins by objectclass (%s), and %d by base (%s)" % [
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
]
|
662
|
+
# self.log.debug " found %d mixins by objectclass (%s), and %d by base (%s)" % [
|
663
|
+
# oc_mixins.length,
|
664
|
+
# oc_mixins.map(&:name).join(', '),
|
665
|
+
# dn_mixins.length,
|
666
|
+
# dn_mixins.map(&:name).join(', ')
|
667
|
+
# ]
|
351
668
|
|
352
669
|
# The applicable mixins are those in the intersection of the ones
|
353
670
|
# inferred by its objectclasses and those that apply to its DN
|
354
671
|
mixins = ( oc_mixins & dn_mixins )
|
355
|
-
self.log.debug " %d mixins remain after intersection: %p" % [ mixins.length, mixins ]
|
672
|
+
# self.log.debug " %d mixins remain after intersection: %p" % [ mixins.length, mixins ]
|
356
673
|
|
357
674
|
mixins.each {|mod| self.extend(mod) }
|
358
675
|
end
|
@@ -369,9 +686,9 @@ class Treequel::Model < Treequel::Branch
|
|
369
686
|
def attribute_from_method( methodname )
|
370
687
|
|
371
688
|
case methodname.to_s
|
372
|
-
when /^(?:has_)?([a-z]\w
|
689
|
+
when /^(?:has_)?([a-z]\w*)\?$/i
|
373
690
|
return $1.to_sym, :predicate
|
374
|
-
when /^([a-z]\w
|
691
|
+
when /^([a-z]\w*)(=)?$/i
|
375
692
|
return $1.to_sym, ($2 ? :writer : :reader )
|
376
693
|
end
|
377
694
|
end
|