📜 ⬆️ ⬇️

Hiding in Ruby. We also hide classes from Top-Level.

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.


require_to
 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