Hiding in Ruby. We also hide classes from Top-Level
In order not to go far, we will immediately define the terms.
- Encapsulation - the packaging of data and functions into a single component.
- Concealment - is a design principle, which consists in delimiting the access of different parts of the program to the internal components of each other.
Taken from the wiki . In the Ruby programming language with encapsulation, everything seems to be fine. With hiding at first sight, too, we have access to local variables, variable instances, different levels of access methods (
private). But sometimes this may not be enough.
Consider the following example.
classUserclassAddress < Stringdef==(other_object)# хитрое сравнениеendenddefinitialize(name:, address:nil) @name = name @address = Address.new(address) endend
AddressWe declare the class inside
User, we assume that it is not just some abstract address, but an address with specific logic that is needed only in the context of objects
User. And what's more, we don’t want this one to
Addressbe accessible from anywhere in the program, i.e. We not only encapsulate it inside
User, but also want to hide it for all other objects. How to do it?
You can try through
classUser private classAddress < Stringdef==(other_object)# хитрое сравнениеendendend
We load and execute for example inside
pryand we get:
User::Address => User::Address User::Address.new => ""
This ensures that the modifier
privatedoes not work in this context. But there is just a magic method
private_constantthat will work as it should. After all, classes in Ruby are also constants. Now we can write
private_constant :Addressand when trying to access to
User::Addresscatch the error:
NameError: private constant User::Address referenced
Now we set the task more difficult. Add a caching class that redis will use.
And it seems that nothing foreshadows trouble, as long as somewhere in the middle of the View, inside the erb of the template, someone does not want to write directly
redis.setbypass even SharedCache. We treat as follows:
require'redis' SharedCache.send :const_set, :Redis, Redis Object.send :remove_const, :Redis Redis NameError: uninitialized constant Redis from (pry):7:in`__pry__'
What happened? Through a call,
remove_constwe remove Redis from the actual Top-Level visibility of objects. But before these we put Redis inside
SharedCache. Further we can through
private_constantaccess limit to
SharedCache::Redis. However, in this case we will not be able to reach the class in
Redisany way, even if we want to use it somewhere else. We ennoble and allow to make
requireinside several classes:
classSharedCache require_to 'redis', :Redis private_constant :Redisdefstorage Redis endendclassSharedCache2 require_to 'redis', :Redis private_constant :Redisend
Attempts to call Redis:
 pry(main)> SharedCache::Redis NameError: private constant SharedCache::Redis referenced from (pry):1:in `<main>'  pry(main)> require 'redis' => false  pry(main)> Redis NameError: uninitialized constant Redis from (pry):6:in `<main>'  pry(main)> SharedCache.new.storage => Redis  pry(main)> SharedCache2::Redis NameError: private constant SharedCache2::Redis referenced from (pry):1:in `<main>'
What it can be used for:
- To hide internal utility classes inside another class or module.
- Encapsulation with the concealment of logic within the service classes - you can prohibit access to some classes bypassing the service objects.
- Remove "dangerous" classes from Top-Level visibility, for example, to ban access to the database from View or serializers. In Rails, you can "hide" all ActiveRecord classes and give them access selectively in specific places.
And an example of implementation
require_tothat moves constants from Top-Level to the desired level of visibility.
classObjectdefconst_hidesym, obj _hidden_consts.const_set sym, obj Object.send :remove_const, sym enddefhidden_constants _hidden_consts.constants enddefhidden_constsym _hidden_consts.const_get sym enddefrequire_to(name, sym, to:nil)require name if Object.const_defined? sym obj = Object.const_get sym const_hide sym, obj else obj = hidden_const sym end (to ||self).const_set sym, obj end private def_hidden_consts @@_hidden_consts ||= Class.new endend