Tomasz Szkaradek, 2021-01-28

Ruby 3.0. Ruby and lesser known privacy control methods

One of the most beloved features of Ruby is its very flexible syntax. Personally, I love Ruby for how many possibilities we have in defining classes and their properties, and this is what I will discuss in this article.

Basic solutions

Let's assume that we are using class Foo that has one public method and one private method:

class Foo
  def bar
    :awesome
  endprivatedef baz
    :something_private
  end
end

Everything is great, we see such a solution in virtually every project. Running Foo.new.baz will cause the error NoMethodError (private method 'baz' called for # <Foo: 0x00007f884b1b3b88>) and that's what we meant to do. What happens if we try to change the save format and add private as a prefix in the class definition?

class Foo
  def bar
    :awesome
  endprivate def baz
    :something_private
  end
end

As you can see after running the code, it actually works! Why can we enter the visibility of the method before doing it? Because when defining a method, def returns the name of the method as a symbol. This expression is not only a part of the syntax, but de facto a method derived from the Module class and treating this symbol as an argument. For more information, please see the documentation in this link. Since it started so easy with private, let's try to change the visibility of the private method.

class Foo
  def bar
    :awesome
  endprivate def baz
    :something_private
  endpublic :baz
end

What will happen after running the code?

irb(main):012:0> Foo.new.baz
=> :something_private

Success! Our method of bases became public because we made it visible twice. Of course, the same operation applies to the modules. ​ Great, but where does it get us? ​ This functionality gives us a lot because we can freely change the visibility of a method while defining it, or even change the visibility of methods when inheriting them.

Free code review

​Now, let's take a look at what Ruby 2.7 can do in terms of changing the visibility of aliases and accessors.

class Foo
  private attr_accessor :awesome_variable
end

Unfortunately, we get an error as the private method expects symbols and attr_accessor. The code returns nil and thus this method is not compatible with the private use in Ruby 2.7. So what are our options?

  1. We can use attr_accessor under the private keyword to make it work, i.e. we will get an error when we want to refer to the awesome_variableawesome_variable method.
class Foo
  private
​
  attr_accessor :awesome_variable
end
  1. The second option is to execute the private method on methods generated by attr_attribute; in this case, we also have to remember to enter the setter there.
class Foo
  attr_accessor :awesome_variableprivate :awesome_variable, :awesome_variable=
end

Problems with the attr_ * methods are not the only obstacles. We can encounter the same difficulty when we want to create a private alias.

class Foo
  private alias :bar, :awesome_bar
end

Ruby 3.0 and our business

Fortunately, Ruby 3.0 introduces a great change as the visibility methods can take array as an argument and methods alias, attr_ *, can reset the array with the names of the methods that have been defined. You can read more here.

Now, let's see some examples in the latest euba and check if the changes have actually been made and how we can use them. ​ In the first example, let’s use private before attr accessor:

class Foo
  private attr_accessor :awesome_variable
end

Such a call will not cause errors in parsing the syntax and, what is important, the awesome_variable andawesome_variable =methods become private. ​ The alias method will do the same as now it also returns a symbol as the name of the new method and makes it visible.

class Foo
  private alias :bar, :awesome_bar
end

An interesting fact is that we can also delve into further methods, e.g. the awesome_print module can be called between private and attr_reader; it is important that such a method returns an array with the names of the methods that are on the right side of the expression.

class Module
  def awesome_print(names)
    puts names
    names
  end
end
class Foo
  private awesome_print attr_reader :awesome_bar
end

Summary

Hope you will find this article useful! In case of more news about Ruby 3.0. read more here.

Happy coding!

product development

Read more:

Shut up and take your money #1: Hidden costs and real agility in product development process

CTO challenges – scale-up and growth of software products

A hard(ware) nut to crack – a few words about hardware product development