Recently, I came across some of my old code that uses Rails’ delegate method. Take a look at the following example, where I’m delegating a couple of methods to an instance variable, but want to make them private.

class UserDecorator def initialize ( user ) @user = user end def full_name " #{ first_name } #{ last_name } " end private delegate :first_name , :last_name , to: :@user end

My intention was to make the first_name and last_name methods private. Only full_name was supposed to be public here. Let’s see if this works as intended.

user = User . new ( first_name: 'John' , last_name: 'Doe' ) decorated_user = UserDecorator . new ( user ) decorated_user . full_name #=> John Doe decorated_user . first_name #=> John decorated_user . last_name #=> Doe decorated_user . methods - Object . instance_methods #=> [:full_name, :last_name, :first_name]

As you can see, we can call both the delegated methods. They were supposed to be private! What happened here?

The problem is that putting delegate under the private scope has no effect. Methods in Ruby have no way of knowing that the private visibility scope is set when they are called from within a class. delegate uses module_eval to define new methods with these names, but it doesn’t know that it must make these methods private.

So what do we do if we wish to delegate a method without making it a part of the class’ public interface? Let’s dig into how delegate works. In cases like this, I like to use the pry console to interactively inspect the behavior of the class.

pry > cd UserDecorator # This pry command puts us within the scope of the UserDecorator class. pry > delegate :first_name , :last_name , to: :@user #=> [:first_name, :last_name] pry > instance_methods #=> [:full_name, :last_name, :first_name] pry > private :first_name , :last_name pry > instance_methods #=> [:full_name]

delegate returns the list of the delegated method names. We can call private with the method names to make them private. This means we can explicitly make the methods private like this:

class UserDecorator # def initialize... delegate :first_name , :last_name , to: :@user private :first_name , :last_name end

Now, how can we avoid duplicating the list of method names? Since delegate returns the list of method names, we can pass the list to private using the splat operator.

class UserDecorator # def initialize... private * delegate ( :first_name , :last_name , to: :@user ) end

Although this prevents duplication, I don’t find this syntax particularly intuitive. Luckily, Rails 6 is introducing a new private: true keyword argument to give us a cleaner syntax.

# Rails 6+ delegate :first_name , :last_name , to: :@user , private: true