Kri-ban
Kri-ban

Reputation: 546

How to create an object in Ruby without using new

It's possible to create a Complex number in Ruby using

c = Complex.new(1,2)

but, it can be shortened to

c = Complex(1,2)

Is it possible to achieve the same functionality without having to define a function outside the class, like in the example below?

class Bits
  def initialize(bits)
    @bits = bits
  end
end

def Bits(list) # I would like to define this function inside the class
  Bits.new list
end

b = Bits([0,1])

I think Ruby should allow at least one of the proposed constructors below

class Bits
  def initialize(bits)
    @bits = bits
  end

  def self.Bits(list) # version 1
    new list
  end

  def Bits(list)      # version 2
    new list
  end

  def Bits.Bits(list) # version 3
    new list
  end
end

Upvotes: 9

Views: 4509

Answers (5)

Here's another hack that you could (but shouldn't) use, inspired by this blog post:

def method_missing(sym, *args, **kwargs, &blk)
    Object.const_get(sym).new(*args, **kwargs, &blk)
end

This simply expects any unknown method name to be the name of a class and calls :new on the class.

With rudimentary error handling:

alias sys_method_missing method_missing

def method_missing(sym, *args, **kwargs, &blk)
    cls = Object.const_get(sym) if Object.constants.include? sym
    if cls.is_a?(Class) then cls.new(*args, **kwargs, &blk)
    else sys_method_missing(sym, *args, **kwargs, &blk) end
end

If an unknown method name is the name of a class, this calls :new on the class. Otherwise, it delegates the call to the original implementation of method_missing().

Usage:

class Foo
end

foo = Foo()
p foo

Result:

#<Foo:0x00007f8fe0877180>

Upvotes: 0

Saturn
Saturn

Reputation: 18149

Have this snippet:

def make_light_constructor(klass)
    eval("def #{klass}(*args) #{klass}.new(*args) end")
end

Now you can do this:

class Test
    make_light_constructor(Test)
    def initialize(x,y)
        print x + y
    end 
end

t = Test(5,3)

Yes, I know you're still defining a function outside a class - but it is only one function, and now any class you want can make use of its implementation rather than making one function per class.

Upvotes: 7

Darek Nędza
Darek Nędza

Reputation: 1420

If you pack your classes into some module you can use 2 methods:

  • self.included - called when you include Mod
  • self.extend - called when you extend Mod

I have created very basic method using self.included.

Cons: It is hard to write. You can say it is complex; It may not contain all features.
Pros: It looks exactly like Complex(2,3) (it uses () instead of [] as in https://stackoverflow.com/a/24351316/2597260 answer); You create just initialize, self.included create the rest.

module M1
  # some random classes
  class A; end 
  class B
    def initialize list
        @list = list
    end
    attr_accessor :list
  end
  class C
    def initialize var1
        @var1 = var1
    end
    attr_accessor :var1
  end
  Answer = 42
  # called on `include module_name`
  def self.included mod
    # classes are constants (in normal cases)
    constants.each do |cons| 
      class_eval do
        # I don't like hard-coded `::M1`
        klass = ::M1.const_get cons
        if klass.class==Class 
            define_method cons do |*args, &block|
              klass.new *args, &block 
            end
        end
      end
    end
  end
end

include M1


p A()

b = B([1,2,3])
p b.list

c = C 42
p c.var1

puts Answer() 
# NoMethodError: undefined method `Answer' for main:Object
# thats good, because Answer is not a class!

Upvotes: 1

steenslag
steenslag

Reputation: 80065

c = Complex(1,2)

is actually calling a method on Kernel

Upvotes: 5

Martin
Martin

Reputation: 7714

Basically you can't - the () operator cannot be overriden in Ruby (Complex class is written in C).

You could achieve something similar using []:

class Bits
  def self.[](list)
    Bits.new list
  end
end

Which would allow something like:

b = Bits[[1,2]]

Upvotes: 2

Related Questions