Piotr Kruczek
Piotr Kruczek

Reputation: 2390

Avoid class pollution while including module

Is there a concise way to limit method visibility within the module while including it? In other words, I'd like to limit polluting the class with helper methods only used in the included module.

module Bar
  def call
    hide_me
  end

  private

  # make this method only callable within this module
  def hide_me
    'visible'
  end
end

class Foo
  include Bar

  def unrelated_method
    hide_me
  end
end

# that's ok
Foo.new.call #=> 'visible'

# that's not
Foo.new.unrelated_method #=> 'visible'

I'm ok with calling it via Bar.instance_method(:hide_me).bind(self).call, I just don't want to worry about accessing or redefining a helper method from some module.

Upvotes: 0

Views: 83

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110685

One can do what you want by including the module to the class and then undefining the unwanted included methods.

First construct a module.

module M
  def cat
    puts "meow"
  end
  def dog
    puts "woof"
  end
  def owl
    puts "who?"
  end

  private

  def frog
    puts "ribbit"
  end
  def pig
    puts "oink"
  end
end

Confirm the methods now exist.

M.instance_methods(false)
  #=> [:cat, :dog, :owl]
M.private_instance_methods(false)
  #=> [:frog, :pig]

Create the class, including the module M.

class C
  def froggie
    frog
  end
  def cat
    puts "meow-meow"
  end
  include M
end

Check the instance methods.

C.instance_methods & [:cat, :dog, :owl, :froggie]
  #=> [:cat, :froggie, :dog, :owl]
C.private_instance_methods & [:frog, :pig]
  #=> [:frog, :pig]

and confirm :cat is owned by C and not by M.

C.instance_method(:cat).owner
  #=> C

Now use the method Module#undef_method to undefine the unwanted methods from the module.

class C
  [:cat, :owl, :pig].each { |m|
    undef_method(m) unless instance_method(m).owner == self }
end

The unless... clause is needed so that the instance method :cat defined in C is not undefined.

Confirm the methods were undefined.

C.instance_methods & [[:cat, :dog, :owl, :froggie]
  #=> [:cat, :froggie, :dog]
C.private_instance_methods & [:frog, :pig]
  #=> [:frog]

Execute the methods.

c = C.new
c.cat
  #=> "meow-meow"
c.dog
  #=> "woof"
c.froggie
  #=> "ribbit"

Upvotes: 0

eiko
eiko

Reputation: 5345

You can wrap a class into the module and use private methods within the class, like so:

module Bar

  def call
    BarClass.new.call
  end

  class BarClass
    def call
      hide_me
    end

    private 

    def hide_me
      puts "invisible"
    end

  end

end

class Foo

  include Bar

  def call_bar
    call
  end

  def this_method_fails
    hide_me
  end

end

Upvotes: 2

Related Questions