Override BigDecimal to_s default in Ruby - 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.

Related

Default reader for ActiveRecord Rails 4

Is there any default reader method for ActiveRecord such that whenever I fetch a value from the DB this reader method will be called?(Property.last.name or Student.first.course). I want to override this method in such a way that if the data type is a string then convert it to uppercase. I don't want to add separate getters in each model for each column.
For ex:
def reader(column)
column.upcase if column.is_a? String
end
There is a method ActiveRecord::AttributeMethods::Read#read_attribute that returns the value after it has been typecasted. So monkey-patching (or refining) this method might give you what you want - all strings values will be transformed.
But I'd seriously reconsider this approach in general: all the data transformations should be explicit and visible if you don't want to shoot own leg.
It doesn't mean you cannot avoid redefining getters manually. For example, you can do smth. like this (disclaimer: quick and dirty example):
module TransformValues
def transform(*attributes_to_transform, transformer: nil, &block)
attributes_to_transform.each do |attribute_name|
define_method attribute_name do
case transformer = (block || transformer)
when Proc
transformer.call(self[attribute_name])
when Symbol
self[attribute_name].__send__(transformer)
else
self[attribute_name]
end
end
end
end
end
# and then
class User < ActiveRecord::Base
extend TransformValues
transform :first_name, :last_name, transformer: :upcase
transform :whatever do |val|
do_smth_with(val)
end
# etc
end
In this case all your transformations stay more or less explicit without tons of manually written boilerplate. If all you need is just upcased strings, all this can be seriously reduced to just smth. like
module UpcaseAttributes
def always_upcase(*attributes_to_transform)
attributes_to_transform.each do |attribute_name|
define_method attribute_name do
case value = self[attribute_name]
when String
value.upcase
else
value
end
end
end
end
end
class User < ActiveRecord::Base
extend UpcaseAttributes
always_upcase :first_name, :last_name
end

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 does Ruby use symbols to reference variable names?

When we are defining getter and setter methods for name in the following code:
class Animal
attr_accessor :name
def initialize(name)
#name = name
end
end
what is happening internally? How is Ruby able to take a symbol and match it with a variable?
It uses instance_variable_get and instance_variable_set for that.
Update: as Andrew Marshall noted, not exactly. Most ruby core methods are defined in C, but the result is essentially the same.
One could define attr_accessor and friends as follows:
def self.attr_accessor(*names)
names.each do |name|
define_method(name) do
instance_variable_get("##{name}")
end
define_method("#{name}=") do |new_value|
instance_variable_set("##{name}", new_value)
end
end
end
Note that I've also used define_method here to define the accessor methods.
I recommend that you take a look at some introductory texts about ruby metaprogramming, such as "Don’t Know Metaprogramming In Ruby?".
It doesn't match it with a variable, attr_accessor does essentially this (it's actually written in C, but I've written it's roughly Ruby translation here):
def attr_accessor(var_name)
ivar_name = :"##{var_name}"
define_method(var_name) do
instance_variable_get ivar_name
end
define_method("#{var_name}=") do |value|
instance_variable_set ivar_name, value
end
end
As you can see, it simply uses the symbol passed to create two methods. It doesn't do anything fancy with the symbol, and is mostly just passing it through. Note, also, that all instance variables "exist" by default and are set to nil, so this will never blow up.

How to overload contructor in Ruby? [duplicate]

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.

Resources