and information hiding , are well known principles in object-oriented programming, common mechanisms for maintaining DRY code and enforcing the single responsibility principle . While these foundational elements imbue the programmer with significant expressive power and are essential for writing software with possessing anything more than a semblance of maintainability, they sometimes introduce restrictions that are ultimately antithetical to those goals. We, the writers of code, should, therefore, have a solid understanding of not only the standard use of these core object-oriented principles, but also, and, perhaps, more importantly, where they break down in addition to how and when to work around them.
We will look at the ways in which we are able to subvert typical safety nets, and this exploration of various means for exposing otherwise private members will also serve as an introduction to some features of the Ruby programming language less frequently encountered in day to day practice. We will, ultimately, introduce additional safety to objects that traditionally have little, in an attempt to limit the use of our newfound powers for good. Let us begin by looking at the three levels of visibility available and how they are used via a concrete, storybook example.
Ruby has three different levels of visibility, the two most common of which are public
and private
. Public members, as the name implies, are unequivocally accessible to any and all callers. Private members, on the other hand, much like Penelope, have extremely restricted access, described in the official documentation section on visibility as:
The third visibility is
private
. A private method may not be called with a receiver, not evenself
. If a private method is called with a receiver a NoMethodError will be raised.
While not necessarily clear from that description, what this means is that private members are only available in any context where they can be accessed without a receiver, specifically in an instance of a class or any of its subclasses. A separate document explains explicit receivers: in summary, as long as the dot syntax is not required, a private method may be called.
What is strange about private access in Ruby is its similarity to protected access in most other object-oriented languages (e.g. C++, Java, C#), yet Ruby also has protected members. This leads to the question of what exactly that means, and, fortunately, we can fall back on the official documentation.
The second visibility is
protected
. When calling a protected method the sender must be a subclass of the receiver or the receiver must be a subclass of the sender. Otherwise a NoMethodError will be raised.Protected visibility is most frequently used to define
==
and other comparison methods where the author does not wish to expose an object's state to any caller and would like to restrict it only to inherited classes.
That definition, and its accompanying example, are opaque enough that that it warrants looking at a simplified example. Imagine we have two kinds of Mammal
, Kitten
s and Puppy
s, both of which are capable of cuddle
ing. In this sad scenario, however, affection between species is nonexistent, so kittens and puppies will never cuddle with each other. This can be accomplished with the use of protected methods.
As you can see, our two kittens, Gweeby and Luxe, will happily share their warmth, but Bella is disallowed from partaking. To put protected access in plain English, it allows different instances of the same class (including superclasses) to access methods on each other. In this example, both instances of Kitten
can call reaction
on each other, but not on an instance of Puppy
.
In a world more worth living in, while the animosity between dogs and cats may yet persist generally, but there can also be exceptions to the rule, such as when particular animals grew up in close proximity. At this point, we will look at how we can exploit the fact that Ruby violates the open/closed principle in order to realize this possibility.
The obvious approach, for many seasoned Ruby developers, would be to simply fall back on the facilities Ruby provides for accessing non-public members, namely the functionally equivalent approaches of instance_eval
and send
.
While these approaches may be Good Enoughâ„¢, we can do better by utilizing the class ancestry of our kittens and puppies. Inspecting the output from Module#ancestors
, we can see that our two classes, as expected, are nearly identical.
Including a module on a class will actually modify the ancestors of that class, thereby affecting the path of method lookup. For example, if we open the Puppy
class using class_eval
, we can include an anonymous module, that subsequently appears in the list of ancestors.
This little bit of knowledge is not particularly useful by itself, since the method lookup will stop at the Puppy
class, where the reaction
method is still protected. This can be more easily visualized by stating that our classes for kitten and puppies are leafs in the object tree, which can only be extended through subclassing, which is not desirable in this instance. Were only there some way to inject an module before the class in which our protected method is defined. Enter the singleton class . A singleton class is a special, unique class that exists for each object. The easiest way, perhaps, to demonstrate how it operates is to again turn to our class ancestry, but, this time, by using Object#singleton_class
method.
Now we can see a point of inflection where we can potentially inject a shared ancestor that will act as a gatekeeper of sorts; for example, consider the following module, called Litter
to denote that mammals including it have had close familial relations since an early age.
As is clear, this module does nothing other than proxy the call to reaction
, but, combined with the ability to open the singleton class, this gives us the incredible ability to open protected methods to instances of certain other objects as we see fit. We can see, in the following example, that Gweeby and Bella will cuddle, based on their being part of the same litter, while Luxe and Bella will not.
We now have the means of exposing methods between two collaborating objects that share an interface, in this case, the #reaction
method. Let us imagine a world where kittens refuse to be cuddled unless they are the instigator in the interaction. Our injected gatekeepers cannot accommodate this asymmetrical relationship; we will need to use a different means of exposing our protected methods. Here, we can make use of a language feature first introduced in Ruby 2.0, the refinement .
This seldom used feature gives up the ability to change class definitions within the local context, rather than globally like a typical monkey patch. The particular rules for this locality are fairly complicated, but, most importantly, the refined class is modified from the point at which the refined module is used, to the end of the block or file. The following example shows how we can refine
the Puppy
class to make its reaction
method available in a Kitten
instance.
Now we have what may be termed a privileged consumer. The Kitten
class may instigate interactions with the Puppy
class, but no other object may do the same. This leads to some interesting consequences when we make use of these intricacies in a more practical example.
This tale of mammalian intrigue has, thus far, provided us with an avenue for the exploration of the building blocks of member access in Ruby, but, as yet, has made no stride toward convincing us that this approach can be useful in the real world. Let us, then, consider a typical Rails application, with heavy use of ActiveRecord throughout. Much of this is inspired by the book Objects on Rails , by Avdi Grimm . Therein, Grimm presents a series of refactorings that, over time, encapsulates direct access to the database within the model layer, only exposing it to consumers via a well defined interface. Here, we will take a slightly different approach, and use our newly minted concept of privileged consumers to bestow access upon certain other objects.
Imagine, further, if you will, an application with complex authorization requirements that cannot be simply defined in a single Ability
class, a pattern popularized by the cancancan gem . Instead, we want to have a separate category of objects that mediate access to the underlying models. While we could simply decorate our models with objects that perform our unsafe operations, we want to be able to programmatically enforce this restriction.
We will first need to discuss protected class methods. It may seem obvious that it is possible to protect a method by simply placing it after the protected
call, but that is not so.
Instead, we can make use of the fact that, in Ruby, a class is simply an instance of a Class
. Consequently, it too has a singleton class that is open for modification. The pattern for doing so inside the class definition is rather well known, namely the colloquial class << self
syntax. In fact, the name of the method responsible for programmatically generating class methods is Object#define_singleton_method
, a remnant of the days of yore (pre Ruby 1.9.1) when this pattern was necessary to metaprogram at the class level.
With this in hand, the first step toward protecting the query interface of our objects will be to create a Redactor
module that simply takes a class name and a list of methods to hide from the outside world via use of the protected
method on the singleton class.
This module simply opens the singleton class of the class on which we want to redact the methods, and, using Module#class_eval
, marks each method as protected. The next piece of the puzzle is to define our mediators, starting with the BaseMediator
, which ensapsulate all the complexity of redacting methods and refining them within the context of the appropriate subclasses.
The redact_and_refine!
class macro has two primary responsibilities, as indicated by its name:
Normally, we would want to simplify this for the subclass by using Class::inherited
, but, in this case, we are unable to do so, since trying to call Module::using
from within a method results in the following error: RuntimeError: Module#using is not permitted in methods
. We are, therefore, forced to have each subclass pull in its own refinement, such as in the following CustomerMediator
below.
While not ideal, this is not an onerous task for significantly increased safety. Because the CustomerMediator
is responsible for hiding the methods inside the Customer
model from the outside world, we must first load it. Then, using that same class, we are able to access the Customer::find
method, but when we try to do so directly, we are greeted by a NoMethodError
remarking that the method is protected.
In a perfect world, we would want to provide a better error message for consumers of our classes, rather than the generic default message. This desire to provide the developer with as easy an interface as possible is an example of the principle of least surprise . Attempts to add this feature, however, are not forthcoming, because of the dynamic dispatch semantics of Ruby. Take, for instance, the following modification on the existing Redactor
module and BaseMediator
class:
The major difference here is that we alias the existing method we are redacting and redefine the original method to wrap a call to the alias, catching the default exception and raising a more helpful one. The BaseMediator
then makes the new methods public, rather than the original, which is already a public wrapper. Where this fails is that we have no way to call the original method while respecting its updated visibility. The use of Object#public_send
fails, since it does not honor the refinement and believes that that method is still protected, as can be seen in the following.
Attempting to use either Object#send
or Object#method
to call the method directly both fail by ignoring the visibility rules altogether. As such, we cannot provide a better message by wrapping the call to our redacted method with some exception handling.
Another approach worthy of attempt is to inspect, via Object#public_methods
and Object#protected_methods
, which methods are available, but this also fails to produce the expected results. The following example leads us to a better understanding of what is happening behind the curtains when using refinements to modify method visibility.
When this script is run, the static call works, but the dynamic call fails. The Protected::print
method, moreover, is list as protected, not public, from within the class using the refinement! As a consequence of this final result, I cannot recommend using refinements for modifying method visibility when there is any intention to use dynamic dispatch or rely upon reflection. Having encountered this first hand, it is worth pointing out that this is, at least indirectly, mentioned in the documentation for refinements:
When using indirect method access such as
Kernel#send
,Kernel#method
orKernel#respond_to?
refinements are not honored for the caller context during method lookup.This behavior may be changed in the future.
Since this leads to somewhat unexpected results, I have decided to start a conversation about changing this behavior, which can be found here .
03 Jan 2018