views:

2383

answers:

5

Background:

I have a module which declares a number of instance methods

module UsefulThings
  def get_file; ...
  def delete_file; ...

  def format_text(x); ...
end

And I want to call some of these methods from within a class. How you normally do this in ruby is like this:

class UsefulWorker
  include UsefulThings

  def do_work
    format_text("abc")
    ...
  end
end

Problem

include UsefulThings brings in all of the methods from UsefulThings. In this case I only want format_text and explicitly do not want get_file and delete_file.

I can see several possible solutions to this:

  1. Somehow invoke the method directly on the module without including it anywhere
    • I don't know how/if this can be done. (Hence this question)
  2. Somehow include Usefulthings and only bring in some of it's methods
    • I also don't know how/if this can be done
  3. Create a proxy class, include UsefulThings in that, then delegate format_text to that proxy instance
    • This would work, but anonymous proxy classes are a hack. Yuck.
  4. Split up the module into 2 or more smaller modules
    • This would also work, and is probably the best solution I can think of, but I'd prefer to avoid it as I'd end up with a proliferation of dozens and dozens of modules - managing this would be burdensome

Why are there lots of unrelated functions in a single module? It's ApplicationHelper from a rails app, which our team has de-facto decided on as the dumping ground for anything not specific enough to belong anywhere else. Mostly standalone utility methods that get used everywhere. I could break it up into seperate helpers, but there'd be 30 of them, all with 1 method each... this seems unproductive

Thanks in advance!

A: 

Firstly, I'd recommend breaking the module up into the useful things you need. But you can always create a class extending that for your invocation:

module UsefulThings
  def a
    puts "aaay"
  end
  def b
    puts "beee"
  end
end

def test
  ob = Class.new.send(:include, UsefulThings).new
  ob.a
end

test
Dustin
+2  A: 

If you want to call these methods without including module in another class then you need to define them as module methods:

module UsefulThings
  def self.get_file; ...
  def self.delete_file; ...

  def self.format_text(x); ...
end

and then you can call them with

UsefulThings.format_text("xxx")

or

UsefulThings::format_text("xxx")

But anyway I would recommend that you put just related methods in one module or in one class. If you have problem that you want to include just one method from module then it sounds like a bad code smell and it is not good Ruby style to put unrelated methods together.

Raimonds Simanovskis
+2  A: 

Another way to do it if you ``own'' the module is to use module_function:

module UsefulThings
  def a()
    puts "aaay"
  end
  module_function :a

  def b()
    puts "beee"
  end
end

def test()
  UsefulThings.a
  UsefulThings.b # Fails!  Not a module method
end

test
Dustin
And for the case where you don't own it: UsefulThings.send :module_function, :b
Dustin
module_function converts the method to a private one (well it does in my IRB anyway), which would break other callers :-(
Orion Edwards
I actually like this approach, for my purposes at least. Now I can call `ModuleName.method :method_name` to get a method object and call it via `method_obj.call`. Otherwise I would have to bind the method to an instance of the original object, which isn't possible if the original object is a Module. In response to Orion Edwards, `module_function` does make the original instance method private. http://ruby-doc.org/core/classes/Module.html#M001642
John
+8  A: 

If a method on a module is turned into a module function you can simply call it off of Mods as if it had been declared as

module Mods
  def self.foo
     puts "Mods.foo(self)"
  end
end

The module_function approach below will avoid breaking any classes which include all of Mods.

module Mods
  def foo
    puts "Mods.foo"
  end
end

class Includer
  include Mods
end

Includer.new.foo

Mods.module_eval do
  module_function(:foo)
  public :foo
end

Includer.new.foo # this would break without public :foo above

class Thing
  def bar
    Mods.foo
  end
end

Thing.new.bar

However, I'm curious why a set of unrelated functions are all contained within the same module in the first place?

Edited to show that includes still work if public :foo is called after module_function :foo

dgtized
Updated question to satisfy curiosity :-)
Orion Edwards
As an aside, `module_function` turns the method into a private one, which would break other code - otherwise this'd be the accepted answer
Orion Edwards
I ended up doing the decent thing and refactoring my code into seperate modules. It wasn't as bad as I thought it might be. Your answer is would still solve it most correctly WRT my original constraints, so accepted!
Orion Edwards
+1  A: 

I think the shortest way to do just throw-away single call (without altering existing modules or creating new ones) would be as follows:

Class.new.extend(UsefulThings).get_file
dolzenko