0

I'm in the process of creating a library that powers a network of APIs. The center of that library is currently a Client class which each API client subclasses. Since I'm the one writing all my APIs, they'll all function similarly (restful, authorization through access_token, etc).

However unlike other ruby API Client libraries (Twitter's, etc), the client class should not be instantiated directly. That's because the library isn't restricted to a single API. Instead, each API client will subclass the Client class. My question is as follows:

Is there a way to require that a Ruby Class is only initialized through a subclass?

Additionally, in reading this question I decided that a class is better over a mixin here.

For those that want code, here's an example:

class A
    def initialize(options = {})
        #what goes on in here doesn't really matter for the purpose of this question
        #I just don't want it to be initialized directly
        options.each do |k,v|
            instance_variable_set("@#{k}",v) unless v.nil?
        end
    end
end

class B < A
    attr_accessor :class_specific_opt
    def initialize( options = {} )
        @class_specific_opt = "val" unless options[:class_specific_opt].nil?
        super(options)
    end
end

Any thoughts?

Community
  • 1
  • 1
Nick ONeill
  • 7,341
  • 10
  • 47
  • 61
  • If you do `B.new`, then `B#initialize` will be called, which will call `A#initialize`. `A#initialize` will not be called directly unless you do `A.new`. What is wrong with it? – sawa Nov 26 '12 at 17:52
  • The issue is that someone could still theoretically call A#initialize – Nick ONeill Nov 26 '12 at 18:38
  • `initialize` is a private method. You cannot call it with a receiver. – sawa Nov 26 '12 at 19:08

5 Answers5

2

There's an answer here on abstract-like classes: How to implement an abstract class in ruby?, Though you may still be better off providing A as a module and including it in its implementors.

Community
  • 1
  • 1
dan
  • 121
  • 5
2

You could do something like this:

class A
  def self.initialize_allowed?
    raise "You cannot initialize instances of this class."
  end

  def self.allow_initialize
    class << self
      def initialize_allowed?
        true
      end
    end
  end

  def initialize(options = {})
    self.class.initialize_allowed?
    options.each do |k,v|
      instance_variable_set("@#{k}", v)
    end
  end
end

Calling A.new raises a RuntimeError and halts the initialize method. You can then override the initialized_allowed? method in subclasses by calling allow initialize on them. (maybe this is overkill, but I think allow_initialize is easier to read than def self.initialize_allowed?;end):

class B < A
  allow_initialize
end

B.new #=> #<B:0x00000102837d10>
Zach Kemp
  • 11,736
  • 1
  • 32
  • 46
1

If you don't want A.new to be called, simply do:

class <<A
  undef :new
end

Then, you will not be able to call it:

A.new #=> NoMethodError: undefined method `new' for A:Class
sawa
  • 165,429
  • 45
  • 277
  • 381
0

Add

private_class_method :new

to class A, and

public_class_method :new

to class B.

steenslag
  • 79,051
  • 16
  • 138
  • 171
0

I think the best way would be to make the new method private. This is exactly what the ruby Singleton module does. Singleton Module Documentation

class A
  private_class_method :new
end

class B < A
  public_class_method :new
end
ShadyKiller
  • 700
  • 8
  • 16