In order not to go far, we will immediately define the terms.
Taken from the wiki . In the Ruby programming language with encapsulation, everything seems to be fine. With concealment at first glance, too, local variables, instance variables, different levels of access to methods ( public
, protected
, private
) are available to us. But sometimes this may not be enough.
Consider the following example.
class User class Address < String def ==(other_object) # end end def initialize(name:, address: nil) @name = name @address = Address.new(address) end end
We declare the Address
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 User
objects. And what's more, we do not want this Address
be available from any place of the program, i.e. Not only do we encapsulate it inside User
, but also want to hide it for all other objects. How to do it?
You can try through private
.
class User private class Address < String def ==(other_object) # end end end
pry
and execute for example inside pry
and we get:
User::Address => User::Address User::Address.new => ""
This ensures that the private
modifier does not work in this context. But there is simply a magic method private_constant
which will work as it should. After all, classes in Ruby are also constant. Now we can write private_constant :Address
and catch an error when trying to access User::Address
:
NameError: private constant User::Address referenced
Now we set the task more difficult. Add a caching class that redis will use.
#shared_cache.rb require 'redis' class SharedCache end
And it seems that nothing will foreshadow trouble, as long as somewhere in the middle of the View, inside the erb template, someone does not want to write directly to redis.get
\ redis.set
, even bypassing 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 to remove_const
we remove Redis from actually Top-Level object visibility. But before this we put Redis inside SharedCache
. Then we can restrict access to SharedCache::Redis
through private_constant
. However, in this case, we will not be able to reach the Redis
class in any way, even if we want to use it somewhere else. We ennoble and let require
making inside several classes:
class SharedCache require_to 'redis', :Redis private_constant :Redis def storage Redis end end class SharedCache2 require_to 'redis', :Redis private_constant :Redis end
Attempts to call Redis:
[1] pry(main)> SharedCache::Redis NameError: private constant SharedCache::Redis referenced from (pry):1:in `<main>' [2] pry(main)> require 'redis' => false [3] pry(main)> Redis NameError: uninitialized constant Redis from (pry):6:in `<main>' [4] pry(main)> SharedCache.new.storage => Redis [5] pry(main)> SharedCache2::Redis NameError: private constant SharedCache2::Redis referenced from (pry):1:in `<main>'
What it can be used for:
And an example of the implementation of require_to
which moves constants from Top-Level to the desired level of visibility.
class Object def const_hide sym, obj _hidden_consts.const_set sym, obj Object.send :remove_const, sym end def hidden_constants _hidden_consts.constants end def hidden_const sym _hidden_consts.const_get sym end def require_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 end end
Source: https://habr.com/ru/post/419969/
All Articles