⬆️ ⬇️

I hate constants in ruby

Ruby is a very complex programming language. It is incredibly beautiful and readable, but it has many themes and features that can remain a “dark forest” even for an experienced Ruby developer. One of these topics is the search for constants.



Despite the title, there will be no anger in the post.



The purpose of this post is not a detailed explanation of the search algorithm. I would say that the goal is to attract the attention of developers to the topic. In part, this is just a cry from the heart.



Example



I will consider one small example. To begin with, we define several constants:



module M A = 'm' end module Namespace A = 'ns' class C include M end end 


We have one mixin M , a Namespace module and its own class C In the modules, definitely by the constant A , which we will be looking for.



What do you think that the following code will display? I will post the answers below so that they do not catch the eye.



 puts Namespace::C::A module Namespace class C puts A end end 


Now let's define a couple of methods:



 module M def m A end end module Namespace class C def f A end end end class Namespace::C def g A end end x = Namespace::C.new puts xf puts xg puts xm 


Do you think there is a difference between them?



Answers



Here is the full code of our example with the answers in the comments:



 module M A = 'm' end module Namespace A = 'ns' class C include M end end puts Namespace::C::A # m module Namespace class C puts A # ns end end module M def m A end end module Namespace class C def f A end end end class Namespace::C def g A end end x = Namespace::C.new puts xf # ns puts xg # m puts xm # m 


Those. the output of the program will be:



 m ns ns m m 


Mini explanation



In short, the search for constants occurs in several stages:



  1. Search in the so-called lexical scope . Those. the search will occur depending on where the current line of code is defined. For example, in the very first output, the interpreter is at the top level (top-level) and outputs the Namespace::C::A constant, and in the second output, it first enters the Namespace module, then enters the C class, and only then does the puts . You can learn more about this by reading about nesting, in particular, the Module.nesting method.
  2. If the first stage was not successful, then the interpreter starts a "poll" of mixins and parent classes. For each of the modules surveyed in the first stage.
  3. If the previous stage did not produce results, the top level (top-level) is checked. In fact, you can omit this item, because it is essentially included in the second, because top-level is an Object class
  4. At this stage, the constant is considered to be not found and the const_missing method is const_missing by analogy to method_missing . I suppose this method is utilized in Ruby on Rails for autoloading and reloading code.


In this way:



 #    . #       #       M puts Namespace::C::A # m module Namespace class C #   Namespace -> Namespace::C #       Namespace puts A # ns end end module M def m #    M.       A # m end end module Namespace class C def f #    Namespace -> Namespace::C A # ns end end end class Namespace::C def g #    Namespace::C (  Namespace   ) #      #          A # m end end 


Conclusion



It can be said that Ruby makes us, when writing constants in code, calculate their value relative to the written code, and not relative to the execution context (sounds very strange, I'm sorry).



Ruby style guide defines one good rule :

it is necessary to define and rediscover nested classes / modules explicitly. Those. never need to write class A::B This simple rule is enough to avoid surprises and in most cases not to think about finding constants at all.



What you can read:





Update



The user DsideSPb gave a helpful comment about the additional feature of searching for constants. True, it was removed in the latest (2.5.0) release.



Personally, I do not know all the details, but in some circumstances, if you specify the wrong path to a constant, the interpreter can replace it with a top-level one. However, this does not work in all cases:



 # 1.rb class A; end class B; end A::B #  B,    # 2.rb class A; end module M; end A::M # ==> M   M::A # ==> NameError 


')

Source: https://habr.com/ru/post/347272/



All Articles