On Wednesday, July 28, 2010 07:54:03 pm travis.ml-ruby-talk / subspacefield.org 
wrote:
> So I have a class that I may want to instantiate multiple ways.
> 
> Here's the signatures of the some so far:
> 
>   def create_from_file(absolute_filename, archive_filename, fsg=nil,
> verbose=false, debug=false) def create_from_parts(archive_filename, mtime,
> sha512hash)
>   def create_from_string(str)

Ah, that answers my question from earlier.

Do an options hash. Five arguments is too many.

Foo.new :absolute_filename => '/foo/bar', :archive_filename => 'bar'
Foo.new :archive_filename => '/foo/bar', :mtime => 123, :hash => 'abc'

Now you can trivially branch on them.

> So I looked into this a bit, and I assume something like this could
> work:
> 
>   def self.create_from_file(*args)
>       return self.new.create_from_file(*args)
>   end
> 
> Then I realized that I've got six methods now to do initialization;
> three class methods (which are a little obscure to noobs)

They shouldn't be. They're just instance methods that happen to be on a Class 
object. Java's "static" methods screw it up by putting them in the same 
namespace, that's all.

> and three
> instance methods.  That's a lot of verbosity... is there a better way?

Sure:

class Foo
  class << self
    %w(file parts string).map{|x| :"create_from_#{x}"}.each do |type|
      define_method type do |*args|
        new.send type, *args
      end
    end
  end
end

Ok, that's a little verbose. If this is a common pattern, you could extract it 
into a library so you could end up doing something like this:

class Foo
  # implementation of the DefineMethods module left as an exercise
  # to the reader...
  include YourInitializers
  initializer :string do |str|
    ...
  end
  initializer :parts do |archive_filename, mtime, hash|
    ...
  end

end

> Then it dawned on me:
> 
>   def initialize(method=nil, *args)
>     case method
>       when :file then create_from_file(*args)
>       when :parts then create_from_parts(*args)
>       when :string then create_from_string(*args) # unused
>       # if no method, do no intialization
>     end
>   end

If you're calling that directly, that's a little gross...

> I don't have to do any retarded run-time type checking to
> emulate polymorphism,

In my last message, I suggested exactly that, but for a different reason. The 
main reason to do that would be convenience -- usually, you're testing between 
an options hash and something else that's clearly not a hash, and not much 
beyond that.

But you're right to be cautious -- it gets ridiculous beyond one or two types.

> PS: As you see above, I'm passing verbose and debug around a lot and
> hate it.  I'd like to use globals, but it seems kind of fugly to have
> classes accessing globals which are only used when the __FILE__ ==
> $0.  In fact, I always want verbose and debug off except when invoked
> as a script.  Is there any cleaner way to deal with this?

What do you mean "as a script"?

Maybe not globals, then. Maybe module-specific constants. For example, 
hopefully, with a project this large, you've got a single module (or class, 
but probably module) serving as a base namespace:

module SomeBackupProgram
  DEBUG = false
end

Even better, you could do this:

module A
  class << self
    attr_accessor :debug
  end

  module B
    class << self
      attr_writer :debug
      def debug
        instance_variable_defined?(:@debug) ? @debug : A.debug
      end
    end

    attr_writer :debug
    def debug
      instance_variable_defined?(:@debug) ? @debug : self.class.debug
    end
  end
end

I'm guessing there are libraries out there to make that less verbose, and it 
shouldn't be too hard to write one yourself. The idea is that you could then 
set it globally:

A.debug = true

Or override it locally, while leaving the global setting alone:

A::B.debug = true

Or override it per-instance:

foo = A::B.new
foo.debug = true

But, if you leave it alone, you get one global switch to turn on and off, and 
it's available locally.