So I understand you aren't supposed to to directly subclass Fixnum, Float or Integer, as they don't have a #new method. Using DelegateClass seems to work though, but is it the best way? Anyone know what the reason behind these classes not having #new is?
I need a class which behaves like a Fixnum, but has some extra methods, and I'd like to be able to refer to its value through self from within the class, for example:
class Foo < Fixnum
def initialize value
super value
end
def increment
self + 1
end
end
Foo.new(5).increment + 4 # => 10
You can pretty easily set up a quick forwarding implementation yourself:
class MyNum
def initialize(number)
#number = number
end
def method_missing(name, *args, &blk)
ret = #number.send(name, *args, &blk)
ret.is_a?(Numeric) ? MyNum.new(ret) : ret
end
end
Then you can add whatever methods you want on MyNum, but you'll need to operate on #number in those methods, rather than being able to call super directly.
IIRC, the main implementation of Ruby stores Fixnums as immediate values, using some of the low bits of the word to tag it as a Fixnum instead of a pointer to an object on the heap. That's why, on a 32-bit machine, Fixnums are only 29-bits (or whatever it is) instead of a full word.
So because of that, you can't add methods to a single "instance" of Fixnum, and you can't subclass it.
If you're dead-set on having a "Fixnum-like" class, you'll probably have to make a class that has a Fixnum instance variable, and forward method calls appropriately.
Could you not extend FixNum itself? Like...
class Fixnum
def even?
self % 2 == 0
end
end
42.even?
Related
First of all, this is really just a golf question. My code works fine as it is. But I feel like there is probably a better (i.e. cooler) way to do this.
So I've got a class that acts a lot like a hash. However, it really internally generates a hash for each call to its hash-ish methods. The private method for generating that hash is calculated(). So my code currently has a lot of method definitions like this:
def each(&block)
return calculated.each(&block)
end
def length()
return calculated.length
end
Is there a concise way to delegate all those method calls to the calculated method?
I figured it out and it's incredibly simple. Just delegate to the name of the method. Here's a working example:
class MyClass
extend Forwardable
delegate %w([] []=) => :build_hash
def build_hash
return {'a'=>1}
end
end
edit: don't do this; I forgot Forwardable existed
You can write a "macro" for this. Well, Ruby doesn't technically have actual "macros" but it's a fairly common pattern nonetheless. Rails in particular uses it extensively - stuff like belongs_to, validates, etc are all class methods which are being used to generate instance-level functionality.
module DelegateToFunc
def delegate_to_func(delegate, delegators)
delegators.each do |func_name|
# Note: in Ruby 2.7 you can use |...| instead of |*args, &blk|
define_method(func_name) do |*args, &blk|
send(delegate).send(func_name, *args, &blk)
end
end
end
end
class SequenceBuilder
extend DelegateToFunc
delegate_to_func(:calculated, [:length, :each])
attr_accessor :min, :max
def initialize(min:, max:)
#min, #max = min, max
end
def calculated
min.upto(max).to_a
end
end
SequenceBuilder.new(min: 5, max: 10).length # => 6
SequenceBuilder.new(min: 1, max: 4).each { |num| print num } # => 1234
I will say, though, that methods generated by metaprogramming can sometimes be hard to track down and can make a program confusing, so try and use them tastefully ...
For example, do you really need your object to expose these hash-like methods? Why not just let the caller read the hash via calculated, and then call the hash methods directly on that?
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
I have a class Klass, and its constructor accepts an argument. We should be able to call methods on this object that are not defined in Klass.
We can chain multiple methods, but in the end, we have to use Klass#result to get the result like:
Klass.new(5).pred.pred.result
and the output here should be 3. I tried using method_missing in Klass and using send on the object's class, but that would have worked without the result method that I have to use. Can someone explain how this can be done with delegation?
You could do something like this:
class Klass
def initialize(number)
#number = number
end
def result
#number
end
def method_missing(method_name, *arguments, &block)
if #number.respond_to?(method_name)
#number = #number.method(method_name).call(*arguments, &block)
return self
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
# be sure to implement this...
end
end
puts Klass.new(5).pred.pred.result # => 3
But it's problematic. In this particular example, since #pred returns a new object (it doesn't modify the object it was called on), we have to reassign the instance variable to the result. It works for pred and other methods that return new Integers, but some methods on Integer don't return an Integer (e.g. Integer#even). In this case you'd get this sort of behavior:
puts Klass.new(4).even?.result # => true
Depending on your particular situation, that might be what you're after. Or, it might be that in your situation all methods the object being delegated to mutate that object, rather than return new instances of the object, in which case the reassignment isn't needed.
I don't think you can use Ruby's existing Delegator and SimpleDelegator constructs, because the only way you can chain the final #result call onto the end is if every delegated call returns the instance of Klass. Using those existing constructs would cause delegated calls to return their normal return values, and the chaining would then be on whatever objects those return values return. For example, using the above code, you'd see this behavior:
puts Klass.new(5).pred.pred.class # => "Klass"
Using SimpleDelegator, you'd see this behavior
require 'delegate'
class Klass2 < SimpleDelegator
# Klass2 methods...
end
puts Klass2.new(5).pred.pred.class # => "Fixnum"
Hope that helps.
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
I am trying to write two instance methods, where method2 could process the output of method1.
For example, something like this:
puts Numbers::new(2,2).sum.sqrt
>16
I thought that the code would look something like this:
class Numbers
def initialize(x,y)
#x=x
#y=y
end
def sum
#z=#x+#y
end
def sqrt
#z**2
end
end
That is not the case and I get a NoMethodError when I try to call sqrt.
I know I am missing something easy and fundamental here, but I couldn't find a straight answer.
Thank you!
The sum method returns the value of #z which is a Fixnum. Ruby is trying to execute the sqrt method on the Fixnum instance and hence the exception.
May be this is what you want:
class Fixnum
def square
self**2
end
end
class Array
def sum
reduce(0, &:+)
end
end
Now you can:
[1,2].sum.square # 9
In order to call an instance method, you need an instance--your sum method needs to return the instance to allow chaining (same with sqrt).
Method chaining is common, but IMO a bit counter-intuitive in this case.