How to overload contructor in Ruby? [duplicate] - ruby

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
In Ruby is there a way to overload the initialize constructor?
BigDecimal does not take a float as in initial value, so I am writing a constructor to handle it. It seems to be ignoring the initialize method and calling the default constructor.
It throws TypeError can't convert Float into String (TypeError)
The method format does work.
file BigDecimal.rb:
require 'bigdecimal'
class BigDecimal
def initialize
if self.class == Float
super self.to_s
end
end
def format
sprintf("%.2f", self)
end
end
Then, in file test.rb:
require 'BigDecimal' # => true
bd = BigDecimal.new('54.4477') # works
puts bd.format # 54.44
bd2 = BigDecimal.new(34.343) # TypeError: can't convert Float into String (TypeError)
Ruby 1.9.2

Problems with your code:
You use a monkey patch instead of inheriting, so in your initialize method, super will call the initialize method of Object, which is the super class of BigDecimal. To call the default constructor, you have to use some other method as showing below.
You did not put arguments for the initialize method.
BigDecimal DOES take float as constructor argument
Therefore,
You can use directly the default constructor and pass a float as:
BigDecimal.new(34.343, 5) # 5 is the precision
You can override the constructor in this way:
NOTE: we usually alias initialize method. However in this case this does not seem to work (for some unknown reason that initialize does not get called)... So we have to alias new method which is more fundamental.
require 'bigdecimal'
class BigDecimal
class << self
alias_method :__new__, :new #alias the original constructor so we can call later
def new(*args)
if args.length == 1 && args[0].is_a?(Float)
__new__(args[0].to_s)
else
__new__(*args)
end
end
end
def format
sprintf("%.2f", self)
end
end
BigDecimal.new(12.334)
#<BigDecimal:10a9a48,'0.12334E2',18(18)>

BigDecimal does not take a float as in initial value
Are you sure?
BigDecimal.new(2.4)
#=>ArgumentError: can't omit precision for a Rational.
So you have to give a precision as second argument:
BigDecimal.new(2.4, 2)
#=> #<BigDecimal:7ff689b0f2e8,'0.24E1',18(36)>
The docs don't indicate a change between 1.9.2 and 1.9.3.

Related

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?
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 :)
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

How to open String class to rewrite to_s method

I want to rewrite the to_s method so that I can print the money in number_to_currency format. How do I do it? Is there any way to print all Integer or Float variables in number_to_currency format without calling number_to_currency method?
I ran this code in the console:
require 'pry'
require 'action_view'
include ActionView::Helpers
class String
def to_s(x)
number_to_currency(x)
end
end
sum = 0
0.upto(one_fifth-1) do |line_no|
sum += lo_to_hi[line_no].last
end
ap("bottom #{one_fifth} sum:#{sum}, average #{sum/one_fifth}")
and got this exception: in `to_s': wrong number of arguments (0 for 1) (ArgumentError).
I don't think to_s should have an argument (because the definition in the parent class (probablyObject) doesn't.). You can either use to_s as it is (no arguments) or create a new method which takes an argument but isn't called to_s
In other words, if you want to override a method you have to keep the exact same method signature (that is, its name and the number of arguments it takes).
What if you try:
class String
def to_s_currency(x)
number_to_currency(x)
end
end
First, the to_s method has no argument. And it's dangerous to call other methods in to_s when you don't know if that method also calls the to_s. (It seems that the number_to_currency calls the number's to_s indeed) After several attempts, this trick may work for your float and fixnum numbers:
class Float
include ActionView::Helpers::NumberHelper
alias :old_to_s :to_s
def to_s
return old_to_s if caller[0].match(':number_to_rounded')
number_to_currency(self.old_to_s)
end
end
class Fixnum
include ActionView::Helpers::NumberHelper
alias :old_to_s :to_s
def to_s
return old_to_s if caller[0].match(':number_to_rounded')
number_to_currency(self.old_to_s)
end
end
Note that in this trick, the method uses match(':number_to_rounded') to detect the caller and avoid recursive call. If any of your methods has the name like "number_to_rounded" and calls to_s on your number, it will also get the original number.
As, you want to print all int and float variables in number_to_currency, you have to overwrite to_s function in Fixnum/Integer and Float class, something like following:
As pointed out by Stefan, Integer and Float have a common parent class: Numeric, you can just do:
class Numeric
def to_s(x)
number_to_currency(x)
end
end

How can I inherit from Rational (or any class with no constructor)?

I can easily inherit from, say, String for example, like this:
class MyString < String
def stuff
self + ' and stuff'
end
end
# This works:
MyString.new('things').stuff # => 'things and stuff'
But how can I inherit from Rational, which has no constructor? For example:
def MyRat < Rational
def inc
self + 1
end
end
# I have tried to initialize like this:
MyRat.new(10).inc # => NoMethodError: undefined method `new' for MyRat:Class
MyRat(10).inc # => NoMethodError: undefined method `MyRat' for main:Object
MyRat.send(:initialize, 10).inc # => TypeError: already initialized class
# ???
# None of it works!
I can't find a way to initialize my new class.
You can define your own object to be a proxy around Rational.
class MyRat < BasicObject
def initialize(value)
#rational = Rational(value)
end
def inc
#rational + 1
end
def method_missing(name, *args, &block)
#rational.send(name, *args, &block)
end
end
Methods defined in your class will be used, otherwise the class will delegate to the rational instance.
r = MyRat.new(10)
# MyRat#inc is used
r.inc
# => (11/1)
# to_int delegates to Rational
r.to_int
# => 10
A partial explanation of because Numeric has no initialize is available in this thread
Looking at the C code, I see that new() exists in Numeric and Float,
but it is specifically removed:
rb_cInteger = rb_define_class("Integer", rb_cNumeric);
rb_undef_alloc_func(rb_cInteger);
rb_undef_method(CLASS_OF(rb_cInteger), "new");
#....and for floats..
rb_undef_alloc_func(rb_cFloat);
rb_undef_method(CLASS_OF(rb_cFloat), "new");
The ruby source code contains no explanation for the removal of new.
That's why I'm wondering what the reasoning behind this was. It does
not seem to be technical limitation in the ruby interpreter.
Currently, it does not make much sense to me.
and the reason is because
It's an internal optimization. Fixnums do not have to be created and
they never have to be GC'ed. This goes a long way to make math faster
than it would be with ordinary objects (at least for Fixnums).
Additional suggestions and alternatives are explained in this article The Complete Numeric Class.

Ruby and modifying self for a Float instance

I would like to change the self value of a float instance.
I have the following method :
class Float
def round_by(precision)
(self * 10 ** precision).round.to_f / 10 ** precision
end
end
And I would like to add the round_by! method which will modify the self value.
class Float
def round_by!(precision)
self = self.round_by(precision)
end
end
But I got an error saying I can't change the value of self.
Any idea ?
You can't change the value of self. It always points to the current object, you can't make it point to something else.
When you want to mutate the value of an object, you either do this by calling other mutating methods or setting or changing the values of instance variables, not by trying to reassign self. However in this case, that won't help you, because Float doesn't have any mutating methods, and setting instance variables won't buy you anything, because none of the default float operations are affected by any instance variables.
So the bottom line is: you can't write mutating methods on floats, at least not in the way you want.
You can also create a class and store the float in a instance variable:
class Variable
def initialize value = nil
#value = value
end
attr_accessor :value
def method_missing *args, &blk
#value.send(*args, &blk)
end
def to_s
#value.to_s
end
def round_by(precision)
(#value * 10 ** precision).round.to_f / 10 ** precision
end
def round_by!(precision)
#value = round_by precision
end
end
a = Variable.new 3.141592653
puts a #=> 3.141592653
a.round_by! 4
puts a #=> 3.1416
More about using "class Variable" here.
This is actually a really good question and I'm sorry to say that you can't - at least not with the Float class. It's immutable. My suggestion would be to create your own class the implements Float (aka inherits all the methods), like so in pseudo-code
class MyFloat < Float
static CURRENT_FLOAT
def do_something
CURRENT_FLOAT = (a new float with modifications)
end
end

Override BigDecimal to_s default in Ruby

As I retrieve data from a database table an array is populated. Some of the fields are defined as decimal & money fields and within the array they are represented as BigDecimal.
I use these array values to populate a CSV file, but the problem is that all BigDecimal values are by default represented in Scientific format (which is the default behaviour of the BigDecimal to_s method). I can show the values by using to_s('F'), but how can I override the default?
This is built on #Farrel's answer, but without polluting the method namespace with a useless old_xyz method. Also, why not use default arguments directly?
class BigDecimal
old_to_s = instance_method :to_s
define_method :to_s do |param='F'|
old_to_s.bind(self).(param)
end
end
In Ruby 1.8, this gets slightly uglier:
class BigDecimal
old_to_s = instance_method :to_s
define_method :to_s do |param|
old_to_s.bind(self).call(param || 'F')
end
end
Or, if you don't like the warning you get with the above code:
class BigDecimal
old_to_s = instance_method :to_s
define_method :to_s do |*param|
old_to_s.bind(self).call(param.first || 'F')
end
end
class BigDecimal
alias old_to_s to_s
def to_s( param = nil )
self.old_to_s( param || 'F' )
end
end
Ruby makes this easy. Behold:
class BigDecimal
def to_s
return "Whatever weird format you want"
end
end
# Now BigDecimal#to_s will do something new, for all BigDecimal objects everywhere.
This technique is called monkey patching. As you might guess from the name, it's something you should use cautiously. This use seems reasonable to me, though.

Resources