Thermatix
Thermatix

Reputation: 2929

Definied Anonymous class in rspec won't respond to new

so I have the following anonymous class definition:

  let!(:fake_class) do
    Class.new(Integer) do
      def initialize(value)
        @value = value
      end

      def ==(other)
        @value == other
      end

      def coerce(other)
        [@value, other]
      end

      def to_s
        @value.to_s
      end
    end
  end

But when I do:

fake_class.new 4

I just get undefined method 'new' for #<Class:0x00007fc065377c88>

I've tried doing

define_method :initialize do |value|
  @value = value
end

no difference

the only way it responds to new is if I do

class << self
  def new(value)
    @value = value
  end
end

but that obviously won' work as I need it to act like a real class.

Why do I see lots of tutorials using intialize and it working as expected yet it doesn't seem to work for me? Is it becuase i'm defining it in rspec or somthing?

Upvotes: 2

Views: 1279

Answers (2)

lukad
lukad

Reputation: 17873

Your code is correct but Integer does not respond to .new and so your child class will also not respond to .new.

irb(main):001:0> Integer.new
NoMethodError (undefined method `new' for Integer:Class)

When you call Integer(123) you actually call a global function defined here: https://github.com/ruby/ruby/blob/v2_5_1/object.c#L3987 https://github.com/ruby/ruby/blob/v2_5_1/object.c#L3178

Upvotes: 2

Tom Lord
Tom Lord

Reputation: 28305

The issue here is nothing to do with rspec, nor anonymous classes.

The problem is that in ruby, you cannot subclass Integer*.

Ruby stores small Integers (formerly known as Fixnums) as immediate values, using some of the low bits of the word to tag it as such, instead of a pointer to an object on the heap. Because of that, you can't add methods to a single "instance" of Integer, and you can't subclass it.

If you really want an "Integer-like" class, you could construct a workaround with a class that has an integer instance variable, and forward method calls appropriately:

class FakeInteger
  def initialize(integer)
    @integer = integer
  end

  def method_missing(name, *args, &blk)
    ret = @integer.send(name, *args, &blk)
    ret.is_a?(Numeric) ? FakeInteger.new(ret) : ret
  end
end

* Technically you can, but since you cannot instantiate any objects from it, it's pretty useless :)

Upvotes: 4

Related Questions