Class: Safer::Protocol
- Inherits:
-
Object
- Object
- Safer::Protocol
- Defined in:
- lib/safer/protocol.rb
Overview
Named set of class- and instance- methods, with associated numbers of arguments. Call create_from_class
or create_from_instance
to create a new Protocol
object. Call class_conforms?
or instance_conforms?
to verify that a Class or instance of the class (respectively) conforms to a protocol.
Usage
In the example, we define a class with a single instance variable, initialized from an argument to #initialize. We must verify that the object corresponding to this argument implements a particular protocol.
class YourClass
# Define a class or module containing all the class- and instance-
# methods that you require from objects passed into your methods. Here,
# we expect the +Class+ object to implement a +foo+ method that takes
# three arguments, and for instances to implement a +bar+ method that
# takes one required argument, followed by any number of optional
# arguments:
class FooInterface
# Note that we have a good place to document what these interfaces
# mean, in standard RDoc fashion.
def self.foo(arg1, arg2, arg3)
end
def (arg1, *rest)
end
end
# Derive a Safer::Protocol object from this class:
FOOPROTOCOL = Safer::Protocol.create_from_class(FooProtocol)
# Note: we could also do this step as follows:
# FOOPROTOCOL = Safer::Protocol.create_from_instance(FooProtocol.new)
# Our #initialize method takes an object that should conform to
# FOOPROTOCOL.
def initialize(obj)
# verify that 'obj' conforms to FOOPROTOCOL. If it does not, a
# Safer::Protocol::Error::InstanceError exception will be raised.
FOOPROTOCOL.instance_conforms?(obj)
@obj = obj
end
# Now, functions can assume that @obj implements FOOPROTOCOL.
def quux
@obj.class.foo(self, 2, 3)
@obj.(@obj)
end
# including client functions.
attr_reader :obj
end
class Foo
# implements YourClass::FOOPROTOCOL
def self.foo(arg1, arg2, arg3)
return arg1 + arg2 + arg3
end
def (arg1, *rest)
"#{arg1.class.to_s}:#{rest.inspect}"
end
end
# should succeed.
a = YourClass.new(Foo.new)
# should raise a Safer::Protocol::Error::InstanceError.
b = YourClass.new(15)
Rationale
It is often the case that we expect objects passed to our methods to follow some usage protocol. There are two ways this is usually accomplished in Ruby:
-
We can ensure that objects are of a particular type, by calling Object.kind_of? and related functions, disabling features or signaling error when the object is of the wrong type. If the object is of a particular type, then we can reasonably expect that the object will conform to that type’s interface.
-
We can verify that objects respond to desired interfaces using Object#respond_to? and related functions, disabling features or signaling error when these objects do not support certain interfaces.
The first method cannot always be used, as there exist circumstances in which the inheritance hierarchy cannot (or should not be used to) predict if an object can be used in a particular way. (For example, the object hierarchy does not determine if an object is serializable – there is no base class for which all sub-classes are serializable, and only sub-classes are serializable.) So we must sometimes rely on directly verifying that an object supports a set of interfaces.
Safer::Protocol is intended to improve the maintainability of code that must directly verify that objects implement desired interfaces. A Protocol object is derived from a class definition that includes the set of methods for which the Protocol should test, and provides a simple means of testing whether another class object (or instance objects) implement that set of methods. If the method set is not fully supported, a report object (Safer::Protocol::Error) is generated describing the differences between the object under test and the Protocol’s requirements.
Safer::Protocol is also intended to make it easier to document the interfaces required from a Protocol-implementing object. Protocol objects are derived from Class objects, so one can document the interfaces required by the protocol by documenting the Class definition.
Defined Under Namespace
Classes: Error, Match, Signature, Violation
Class Method Summary collapse
-
._array_to_table(array, &block) ⇒ Object
Utility function used during Protocol operations.
-
.create_from_class(klass, base = Object) ⇒ Object
Given a
Class
objectklass
and aClass
objectbase
, create aProtocol
object representing the methods implemented inklass
that are not present inbase
. -
.create_from_instance(instance, baseclass = Object) ⇒ Object
Given an instance object
instance
and aClass
objectbaseclass
, create aProtocol
object representing the methods implemented inklass
that are not present inbase
.
Instance Method Summary collapse
-
#class_conforms?(klass) ⇒ Boolean
Check that a Class object conforms to this protocol.
-
#initialize(name, class_signature, instance_signature) ⇒ Protocol
constructor
Create a
Protocol
object. -
#instance_conforms?(instance) ⇒ Boolean
Check that an instance object conforms to this protocol.
-
#self ⇒ Object
:attr_reader: match_instance Object provides === operation matching when #violations_from_instance would return no violations.
-
#violations_from_class(klass) ⇒ Object
Given a Class object, find the protocol methods that are not supported by a class, or its instances.
-
#violations_from_instance(instance) ⇒ Object
Given an instance object, find the protocol methods that are not supported by the instance, or its class.
Constructor Details
#initialize(name, class_signature, instance_signature) ⇒ Protocol
Create a Protocol
object. Used internally.
name
-
Value for
name
field object. class_signature
-
Value for
class_signature
field. instance_signature
-
Value for
instance_signature
field.
398 399 400 401 402 403 404 |
# File 'lib/safer/protocol.rb', line 398 def initialize(name, class_signature, instance_signature) self.safer_protocol__name = name self.safer_protocol__class_signature = class_signature self.safer_protocol__instance_signature = instance_signature self.safer_protocol__match_class = Safer::Protocol::Match.new(self.method(:violations_from_class)) self.safer_protocol__match_instance = Safer::Protocol::Match.new(self.method(:violations_from_instance)) end |
Class Method Details
._array_to_table(array, &block) ⇒ Object
Utility function used during Protocol operations. Given an Array
, construct a Hash
with the values in the Array
as keys. If no block is provided, then the values for all keys in the Hash
will be true
. If a block is provided, the block is called for each value in the Array
, and its return value will be stored as the value for that element in the Hash
. If the block returns nil
, then the element will not be stored in the resulting Hash
.
112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/safer/protocol.rb', line 112 def self._array_to_table(array, &block) if ! block block = proc do |h, el| true ; end end array.inject(Hash.new) do |h, el| newval = block.call(h, el) if newval h[el] = newval end h end end |
.create_from_class(klass, base = Object) ⇒ Object
Given a Class
object klass
and a Class
object base
, create a Protocol
object representing the methods implemented in klass
that are not present in base
.
klass
-
Class
object from which to derive aProtocol
. base
-
Class
object describing routines that will be implemented byklass
, but which should not be included in the returnedProtocol
.
467 468 469 470 471 472 473 474 475 476 477 478 479 |
# File 'lib/safer/protocol.rb', line 467 def self.create_from_class(klass, base = Object) class_signature = Signature.create(klass, :methods, :method) do |method_name| ! Class.respond_to?(method_name) end baseinstance_table = self._array_to_table(base.instance_methods) instance_signature = Signature.create(klass, :instance_methods, :instance_method) do |method_name| ! baseinstance_table.has_key?(method_name) end self.new(klass.to_s, class_signature, instance_signature) end |
.create_from_instance(instance, baseclass = Object) ⇒ Object
Given an instance object instance
and a Class
object baseclass
, create a Protocol
object representing the methods implemented in klass
that are not present in base
.
485 486 487 488 489 490 491 492 493 494 495 496 497 |
# File 'lib/safer/protocol.rb', line 485 def self.create_from_instance(instance, baseclass = Object) class_signature = Signature.create(instance.class, :methods, :method) do |method_name| ! Class.method_defined?(method_name) end baseinstance_table = self._array_to_table(base.instance_methods) instance_signature = Signature.create(instance, :methods, :method) do |method_name| ! baseinstance_table.has_key?(method_name) end self.new(instance.class.to_s, class_signature, instance_signature) end |
Instance Method Details
#class_conforms?(klass) ⇒ Boolean
Check that a Class object conforms to this protocol.
441 442 443 444 445 446 447 |
# File 'lib/safer/protocol.rb', line 441 def class_conforms?(klass) violations = self.violations_from_class(klass) if violations raise violations end true end |
#instance_conforms?(instance) ⇒ Boolean
Check that an instance object conforms to this protocol.
451 452 453 454 455 456 457 |
# File 'lib/safer/protocol.rb', line 451 def instance_conforms?(instance) violations = self.violations_from_instance(instance) if violations raise violations end true end |
#self ⇒ Object
:attr_reader: match_instance Object provides === operation matching when #violations_from_instance would return no violations.
370 |
# File 'lib/safer/protocol.rb', line 370 Safer::IVar.export_reader(self, :name) |
#violations_from_class(klass) ⇒ Object
Given a Class object, find the protocol methods that are not supported by a class, or its instances.
- returns
-
If protocol violations are found, returns a Safer::Protocol::Error::ClassError object describing the violations. Otherwise, returns
nil
.
412 413 414 415 416 417 418 419 420 421 |
# File 'lib/safer/protocol.rb', line 412 def violations_from_class(klass) class_violations = self.safer_protocol__class_signature.find_violations(klass, :method) instance_violations = self.safer_protocol__instance_signature.find_violations(klass, :instance_method) if class_violations || instance_violations Error::ClassError.new(klass, self, class_violations, instance_violations) else nil end end |
#violations_from_instance(instance) ⇒ Object
Given an instance object, find the protocol methods that are not supported by the instance, or its class.
- returns
-
If protocol violations are found, returns a Safer::Protocol::Error::InstanceError object describing the violations. Otherwise, returns
nil
.
429 430 431 432 433 434 435 436 437 |
# File 'lib/safer/protocol.rb', line 429 def violations_from_instance(instance) class_violations = self.safer_protocol__class_signature.find_violations(instance.class, :method) instance_violations = self.safer_protocol__instance_signature.find_violations(instance, :method) if class_violations || instance_violations Error::InstanceError.new(instance, self, class_violations, instance_violations) else nil end end |