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 ( public, protected, 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 private.


    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.


    #shared_cache.rbrequire'redis'classSharedCacheend

    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.get/ 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:


    [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:


    • 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.


    require_to
    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

    Also popular now: