Class: Constraint::Shell

Inherits:
Object
  • Object
show all
Extended by:
Helper, Forwardable
Includes:
Helper
Defined in:
lib/constraint.rb

Overview

If CONSTRAINT_DISABLE is set to true before loading this library, constraint checking is turned off.

Direct Known Subclasses

CArray

Instance Attribute Summary collapse

Attributes included from Helper

#constaint_descriptions, #constaint_handlers, #constraint_attributes, #constraints

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helper

and_constraint, constraint_attr, log_constraint_exception, on_constraint_violation, or_constraint, replicate_constraints

Constructor Details

#initialize(core = nil, predecessor = nil, &block) ⇒ Shell

def ===(other)

if other.kind_of?(Shell)
    super
else
    @constrained_value === other
end

end



282
283
284
285
286
287
288
289
# File 'lib/constraint.rb', line 282

def initialize(core=nil, predecessor=nil, &block)
    predecessor ||= self.class
    replicate_constraints(predecessor)
    @constrained_value = core || new_constrained
    unless CONSTRAINT_DISABLE
        check_constrained_value
    end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

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

Delegate unknown methods to @constrained_value and filter the return value through #check_constraints.

See also the notes on #with_constraints and Shell.with_arguments_constrained.



462
463
464
# File 'lib/constraint.rb', line 462

def method_missing(method, *args, &block)
    with_constraints(@constrained_value.send(method, *args, &block))
end

Instance Attribute Details

#constrained_valueObject (readonly)

The actual value that is covered by the Shell.



190
191
192
# File 'lib/constraint.rb', line 190

def constrained_value
  @constrained_value
end

Class Method Details

.delegate_to_constrained_value(meth, alias_name) ⇒ Object



201
202
203
204
# File 'lib/constraint.rb', line 201

def delegate_to_constrained_value(meth, alias_name)
    class_eval %{alias #{alias_name} #{meth}}
    def_delegator(:@constrained_value, meth)
end

.inherited(subclass) ⇒ Object



206
207
208
209
210
211
# File 'lib/constraint.rb', line 206

def inherited(subclass)
    parent = self
    subclass.class_eval do
        replicate_constraints(parent)
    end
end

.with_all_arguments_constrained(*methods) ⇒ Object

Wrap methods so that all arguments are filtered through #check_constraints.

methods

Method names as symbols



216
217
218
219
220
# File 'lib/constraint.rb', line 216

def with_all_arguments_constrained(*methods)
    with_arguments_constrained(*methods) do |checker, *args| 
        args.collect {|e| checker.call(e)}
    end
end

.with_arguments_constrained(*methods, &block) ⇒ Object

Wrap methods so that some arguments are filtered through #check_constraints. This is useful in two situations:

  • if filtering the return value would be inefficient – e.g. with methods performing trivial Array operations where the return value will comply with the constraints if its argument do.

  • if the return value of the method call is not an instance of the actual value’s class (only then will the value’s integrity be checked)

See also the notes on Shell#with_constraints.

methods

Method names as symbols

block

Select the arguments that should be filtered



238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/constraint.rb', line 238

def with_arguments_constrained(*methods, &block)
    unless CONSTRAINT_DISABLE
        methods.each do |method|
            define_method(method) do |*args|
                unless args.empty?
                    checker = lambda {|a| check_constraints(a)}
                    args    = block.call(checker, *args)
                end
                with_constraints(@constrained_value.send(method, *args))
            end
        end
    end
end

.without_constraints(*methods) ⇒ Object

Delegate methods to @constrained_value without taking any measures.



254
255
256
257
258
259
260
# File 'lib/constraint.rb', line 254

def without_constraints(*methods)
    methods.each do |method|
        define_method(method) do |*args|
            with_constraints(@constrained_value.send(method, *args))
        end
    end
end

Instance Method Details

#check_constrained_valueObject



387
388
389
# File 'lib/constraint.rb', line 387

def check_constrained_value
    process_constrained_value {|e| check_constraints(e)}
end

#check_constraints(object) ⇒ Object

Check if object complies with @constraints.



397
398
399
# File 'lib/constraint.rb', line 397

def check_constraints(object)
    object
end

#cloneObject



295
296
297
# File 'lib/constraint.rb', line 295

def clone
    replicate_constraint_shell(super)
end

#collect_constrained_valuesObject

Collect all values. Shell#generate_constrained_value must provide a means to decide when no more values should be generated.



371
372
373
374
375
376
377
# File 'lib/constraint.rb', line 371

def collect_constrained_values
    collect_constrained_values_control do |val, acc|
        acc << self.dup
        next_constrained_value!
        true
    end
end

#collect_constrained_values_until(&block) ⇒ Object

Return an array of constrained values starting from the current value until a value for which block is true. Requires #generate_constrained_value to be defined.



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

def collect_constrained_values_until(&block)
    collect_constrained_values_control do |val, acc|
        if (rv = !block.call(val))
            acc << self.dup
            next_constrained_value!
        end
        rv
    end
end

#collect_constrained_values_while(&block) ⇒ Object

Return an array of constrained values starting from the current value until a value for which block is true. Requires #generate_constrained_value to be defined.



345
346
347
348
349
350
351
352
353
# File 'lib/constraint.rb', line 345

def collect_constrained_values_while(&block)
    collect_constrained_values_control do |val, acc|
        if (rv = block.call(val))
            acc << self.dup
            next_constrained_value!
        end
        rv
    end
end

#dupObject



291
292
293
# File 'lib/constraint.rb', line 291

def dup
    replicate_constraint_shell(super)
end

#generate_constrained_value(value) ⇒ Object

Generate the next value for #next_constrained_value.



307
308
309
# File 'lib/constraint.rb', line 307

def generate_constrained_value(value)
    raise Constraint::NoMoreValues, "No more values: #{value}"
end

#handle_constraint_violation(name, object) ⇒ Object

Handle constraint violations. Can be overwritten by subclasses in order to rescue the constraint. The return value will replace the original object.

name

The constraint’s name that didn’t succeed

object

The object that caused the violation



438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/constraint.rb', line 438

def handle_constraint_violation(name, object)
    handlers = @constaint_handlers[name]
    if handlers
        for h in handlers
            begin
                return h.call(self, object)
            rescue Constraint::Violation
                next
            end
        end
    end
    raise_constraint_violation(name, object)
end

#new_constrainedObject

Construct a new instance. By default, this method calls generate_constrained_value(nil).



301
302
303
304
# File 'lib/constraint.rb', line 301

def new_constrained
    generate_constrained_value(nil)
    # raise 'Subclass responsibility'
end

#next_constrained_value(value = nil) ⇒ Object

Return an object with an increased value (see #generate_constrained_value).



338
339
340
# File 'lib/constraint.rb', line 338

def next_constrained_value(value=nil)
    self.clone.next_constrained_value!(value)
end

#next_constrained_value!(value = nil) ⇒ Object

“Increase” the current value to the next valid one (see #generate_constrained_value). Modify the object in-place.



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/constraint.rb', line 313

def next_constrained_value!(value=nil)
    begin
        cv = @constrained_value
        if value
            @constrained_value = value
        end
        begin
            while (@constrained_value = generate_constrained_value(@constrained_value))
                check_constrained_value
                return self
            end
        rescue Constraint::NoMoreValues => e
            raise e
        rescue Constraint::Violation => e
            retry
        end
        raise_constraint_violation(nil, value, "No more values: #{value}")
    rescue Exception => e
        @constrained_value = cv
        raise e
    end
end

#process_constrained_valueObject

Filters @constrained_value through the supplied block (usually a call to #check_constraints) and collects the output in @constrained_value).

block

The filter



383
384
385
# File 'lib/constraint.rb', line 383

def process_constrained_value
    @constrained_value = yield @constrained_value
end

#respond_to?(*args) ⇒ Boolean

Forward to @constrained_value.

Returns:

  • (Boolean)


471
472
473
# File 'lib/constraint.rb', line 471

def respond_to?(*args)
    super or @constrained_value.respond_to?(*args)
end

#with_constraints(new_value) ⇒ Object

Maintain object identity according to the return value of block. This method can be used when overwriting specific methods and if you don’t want to use the provided wrapper methods (e.g., Shell.with_arguments_constrained).

If the return value is of the same kind as @constrained_value and if block can’t be trusted, then the return value is filtered through #check_constraints.

In order to filter the arguments of methods that don’t return an object of the same kind as @constrained_value, you have to define an explicit delegator using Shell.with_arguments_constrained and friends.

new_value

The result from the method call

block

Optional, called @constrained_value is new_value



422
423
424
425
426
427
428
429
430
431
# File 'lib/constraint.rb', line 422

def with_constraints(new_value)
    if new_value.equal?(@constrained_value)
        yield if block_given?
        return self
    elsif new_value.instance_of?(@constrained_value.class)
        return self.class.new(new_value, self)
    else
        return new_value
    end
end