Search code examples
rubyoopprivate-methods

Call Private methods outside class definition


I'm trying to use a Gem that provides me with a DSL i need to apply on some of my classes. But using it directly makes my class definitions not as clean as i want them to be, so i want to move the code that uses the DSL somewhere else, which bring me to this problem, that i will describe in an abstract/general way:

If i have a ruby class that includes methods from another gem as private, and in the docs they tell to call those methods inside class definition.

for example:

class A
  include ModuleB::PrivateMethods
end

class B < A
  do_z :param do
    set_property 'a', :x, :y, false
    set_property 'b', :x, :y, false
    set_property 'only for class B', :x, :y, true
  end

  def whatever
  end
end

# this is from the gem
module ModuleZ
  module PrivateMethods
    def self.included(base)
      base.extend Zmethods
    end
    module Zmethods
      private
      def do_z(param1, &block)
        # this method do something and calls the block
      end
    end
  end
end

Is there a way to DRY up those calls to do_z if, for example any class that inherit from A have to do this:

  do_z :param do
    set_property 'a', :x, :y, false
    set_property 'b', :x, :y, false
  end

and

  do_z :param do
    set_property 'only for class B', :x, :y, true
  end

is only needed for class B and i don't want to write this calls inside the class definition but somewhere else?

Like another module that, when included make those calls even when those methods are private?

So i can write the class definition with something like this?

class B < A
  include TheModuleForAllClases::AndTheOneForBclass

  def whatever
  end
end

I could call #do_z on the base class, and then again for each specialized class to only make the calls needed on each implementation, but they are still many and the blocks are very large, so my class definitions get really long, and the actual method implementation of the class get buried behind those calls.

if wondering, the Gem is swagger-docs look: documenting-a-controller on Rails.

Greetings!


Solution

  • Something like this should work

    module AllClassesMethods
      def self.included(base)
        base.class_eval do
          do_z :param do
            set_property 'a', :x, :y, false
            set_property 'b', :x, :y, false
          end
        end
      end
    end
    
    module OnlyBMethods
      def self.included(base)
        base.class_eval do
          do_z :param do
            set_property 'only for class B', :x, :y, true
          end
        end
      end
    end
    
    class A
      include ModuleB::PrivateMethods
      include AllClassesMethods
    
      def self.inherited(klass)
        klass.include AllClassesMethods
      end
    end
    
    class B < A
      include OnlyBMethods
    end
    

    A and any class that inherits from A will include AllClassesMethods, running the code in its included method. It has to be explicitly included on each inherited class, or else the included method will only get called for the parent A. The class_eval block executes within the including class's context, so it's just like opening up the class in your class definition. Only B is including OnlyBMethods, and therefore is the only one triggerring the included implementation of both Modules.

    There's another approach you could use. If you define a class method macro in an extended module, the class method will be executed within the class context, also giving you easy access to it's private methods (I say "easy" access because in Ruby you can always access an object's private methods from any context by using send)

    module AllClassesMethods
      def does_z
        do_z :param do
          set_property 'a', :x, :y, false
          set_property 'b', :x, :y, false
        end
      end
    
      def does_z_for_b
        do_z :param do
          set_property 'only for class B', :x, :y, true
        end
      end
    end
    
    class A
      include ModuleB::PrivateMethods
      extend AllClassesMethods
    
      does_z
    
      def self.inherited(klass)
        klass.does_z
      end
    end
    
    class B < A
      does_z_for_b
    end