Alright, so this is driving me crazy. The point of this code is that I ought to be able to add a method dynamically provided it is of the form object.plusnum, where num is any number. I'm not quite sure how to get this to work. This is my best shot at it so far, but I'm currently getting several errors.
Code:
class Adder
def initialize(_val)
#start_value = _val
end
def method_missing(method_name, *args)
method = method_name.to_s
if method.start_with?("plus") then
num = method[4 .. method.length]
if (/^[\d]+(\.[\d]+){0,1}$/ === num) then
number = Integer(num)
self.class_eval("def #{method} return #start_value + #{number} end")
else
super
end
else
super
end
end
end
The error I'm currently getting is that "class_eval" is undefined. I am pretty new to metaprogramming and ruby, and this is driving me crazy.
I think you got it all wrong :)
Calling a method for the first time yields different result, than calling it second time so probably you would like this method to be called instantly after defining. Also -- you are using fairly complex regex and then converting value to Integer and dropping all digits after dot.
You are using class_eval and passing string to it, which is usually bad idea, and block should be used whenever possible, for security and performance reasons.
How I see it could look:
class Adder
def initialize(val)
#start_value = val
end
def method_missing(method_name, *args)
if method_name.to_s =~ /^plus(\d+)$/
self.class.class_eval do
define_method(method_name) { #start_value + $1.to_i }
end
self.send(method_name)
else
super
end
end
end
class Adder
def initialize(_val)
#start_value = _val
end
def method_missing(method_name, *args)
method = method_name.to_s
if method.start_with?("plus") then
num = method[4 .. method.length]
if (/^[\d]+(\.[\d]+){0,1}$/ === num) then
number = Integer(num)
self.class.class_eval("def #{method}() return #start_value + #{number} end")
eval(method)
else
super
end
else
super
end
end
end
a = Adder.new(0)
a.plus1
Make sure to add eval(method) in the end to call the method otherwise it will return nil for just creating the method. Or you can simple return with return #start_value + #{number}
You have to call class_eval on the Adder class, not on an instance of adder.
The string isn't valid Ruby. Put parentheses after #{method}.
The new version of the code:
class Adder
def initialize(_val)
#start_value = _val
end
def method_missing(method_name, *args)
method = method_name.to_s
if method.start_with?("plus") then
num = method[4 .. method.length]
if (/^[\d]+(\.[\d]+){0,1}$/ === num) then
number = Integer(num)
self.class.class_eval("def #{method}() return #start_value + #{number} end")
else
super
end
else
super
end
end
end
a = Adder.new(0)
a.plus1
Speaking for myself, the way I would have built this method up is just to start off with
class Adder
end
Adder.class_eval("def plus1() return 0 + 1 end")
a = Adder.new
a.plus1
and then gradually replaced the hard-wired values with configurable values, rather than writing everything at once.
Related
Given a set of class definitions:
class Application
def self.open_current
return Current.new()
end
end
class Current
def get_row(row)
Row.new(row)
end
end
class Row
def get_col(row)
#...
end
end
Design a Proxy class which will:
Create Proxy<<Class>> versions of each class which are extendable via
class ProxyApplication
def myMethod()
#...
end
end
#...
Wrap all return values of all methods in each class such that a proxied class is always used instead of a standard class. I.E.
app = Proxy.new(Application) #app == ProxyApplication
current = app.open_current #current == #<ProxyCurrent>
Ultimately, the definition of Proxy must be dynamic rather than static definitions.
I've been working on this problem for about 6 hours now. I've got the following code. This includes 3 sections:
Initial class setup
Proxy class definition
Testing proxy class
Currently I've got to the point where pApplication=Proxy.new(Application) returns #<Proxy> and pApplication.open_current returns #<ProxyCurrent> which seems kind of on the correct line. However currently it errors when delegate.rb tries to call test() with 2,3 arguments instead of 0...
But my question is, realistically am I going about this correctly? Is using SimpleDelegator the easiest way to do this? One current problem is I'm basically having to add new functionality to the existing SimpleDelegator. I've also looked at using Forwardable, but having to delegate methods manually is not where I want to go with this project, if possible.
Any ideas?
Due to numerous limitations with the initial idea of packing all the calls into a single class, I redesigned it a bit, but it works in exactly the same way.
def proxy__enwrap(obj)
isClass = obj.is_a?(Class)
oldClass = isClass ? obj : obj.class
sNewClass = "Proxy#{oldClass.to_s}"
code = <<-EOF
class #{sNewClass}
include InstanceProxy
def self.__cinit__(obj)
##__cobj__ = obj
end
def self.__cget__
##__cobj__
end
def self.method_missing(m,*args,&block)
if ##__cobj__.respond_to? m
retVal = ##__cobj__.public_send(m,*args,*block)
return proxy__enwrap(retVal)
else
puts "ERROR " + m.to_s + "(" + args.to_s + ") + block?"
#Throw error
end
end
end
#{sNewClass}.__cinit__(#{oldClass.to_s})
if isClass
return #{sNewClass}
else
return #{sNewClass}.new(obj)
end
EOF
::Kernel.eval(code)
end
module InstanceProxy
def method_missing(m,*args,&block)
retVal = #__obj__.__send__(m,*args,&block)
return proxy__enwrap(retVal)
end
def initialize(obj)
#__obj__ = obj
end
end
XXApplication = Application
::Object.const_set "Application", proxy__enwrap(Application)
Currently the only issue is that the object doesn't wrap correctly around yielded objects... I'm not sure if that's even possible though.
Edit:
I've improved the system to also wrap objects passed in as blocks:
def proxy__enwrap(obj)
isClass = obj.is_a?(Class)
oldClass = isClass ? obj : obj.class
sNewClass = "Proxy#{oldClass.to_s}"
code = <<-EOF
class #{sNewClass}
include InstanceProxy
def self.__cinit__(obj)
##__cobj__ = obj
end
def self.__cget__
##__cobj__
end
def self.to_ary
#fix for puts (puts calls to_ary. see: https://stackoverflow.com/questions/8960685/ruby-why-does-puts-call-to-ary)
[self.to_s]
end
def self.method_missing(m,*args,&block)
#Wrap block arguments:
newBlock = Proc.new {}
if block_given?
newBlock = Proc.new do |*args|
args = args.map {|arg| proxy__enwrap(arg)}
block.call(*args)
end
end
#Call delegated functions. Raise error if object doesn't respond to method.
#Return wrapped value
if ##__cobj__.respond_to? m
retVal = ##__cobj__.public_send(m,*args,*block)
return proxy__enwrap(retVal)
else
raise ArgumentError.new("Method '\#\{m.to_s}' doesn't exist.")
end
end
end
#{sNewClass}.__cinit__(#{oldClass.to_s})
if isClass
return #{sNewClass}
else
return #{sNewClass}.new(obj)
end
EOF
::Kernel.eval(code)
end
module InstanceProxy
def method_missing(m,*args,&block)
#Wrap block arguments:
newBlock = Proc.new {}
if block_given?
newBlock = Proc.new do |*args|
args = args.map {|arg| proxy__enwrap(arg)}
block.call(*args)
end
end
#Call delegated functions. Raise error if object doesn't respond to method.
#Return wrapped value
if #__obj__.respond_to? m
retVal = #__obj__.__send__(m,*args,&newBlock)
return proxy__enwrap(retVal)
else
raise ArgumentError.new("Method '#{m.to_s}' doesn't exist.")
end
end
def initialize(obj)
#__obj__ = obj
end
def to_ary
#fix for puts (puts calls to_ary. see: https://stackoverflow.com/questions/8960685/ruby-why-does-puts-call-to-ary)
[self.to_s]
end
end
#
XXApplication = Application
#Silence warnings of overwriting constant
original_verbosity = $VERBOSE
$VERBOSE = nil
::Object.const_set "Application", proxy__enwrap(Application)
$VERBOSE = original_verbosity
Sandi Metz says in SOLID OOPS concepts from GORUCO that presence of if..else blocks in Ruby can be considered to be a deviation from Open-Close Principle. What all methods can be used to avoid not-urgent if..else conditions? I tried the following code:
class Fun
def park(s=String.new)
puts s
end
def park(i=Fixnum.new)
i=i+2
end
end
and found out that function overloading does not work in Ruby. What are other methods through which the code can be made to obey OCP?
I could have simply gone for:
class Fun
def park(i)
i=i+2 if i.class==1.class
puts i if i.class=="asd".class
end
end
but this is in violation to OCP.
With your current example, and wanting to avoid type detection, I would use Ruby's capability to re-open classes to add functionality you need to Integer and String:
class Integer
def park
puts self + 2
end
end
class String
def park
puts self
end
end
This would work more cleanly when altering your own classes. But maybe it doesn't fit your conceptual model (it depends what Fun represents, and why it can take those two different classes in a single method).
An equivalent but keeping your Fun class might be:
class Fun
def park_fixnum i
puts i + 2
end
def park_string s
puts s
end
def park param
send("park_#{param.class.to_s.downcase}", param)
end
end
As an opinion, I am not sure you will gain much writing Ruby in this way. The principles you are learning may be good ones (I don't know), but applying them forcefully "against the grain" of the language may create less readable code, regardless of whether it meets a well-intentioned design.
So what I would probably do in practice is this:
class Fun
def park param
case param
when Integer
puts param + 2
when String
puts param
end
end
end
This does not meet your principles, but is idiomatic Ruby and slightly easier to read and maintain than an if block (where the conditions could be far more complex so take longer for a human to parse).
You could just create handled classes for Fun like so
class Fun
def park(obj)
#parker ||= Object.const_get("#{obj.class}Park").new(obj)
#parker.park
rescue NameError => e
raise ArgumentError, "expected String or Fixnum but recieved #{obj.class.name}"
end
end
class Park
def initialize(p)
#park = p
end
def park
#park
end
end
class FixnumPark < Park
def park
#park += 2
end
end
class StringPark < Park
end
Then things like this will work
f = Fun.new
f.park("string")
#=> "string"
f.instance_variable_get("#parker")
#=> #<StringPark:0x1e04b48 #park="string">
f = Fun.new
f.park(2)
#=> 4
f.instance_variable_get("#parker")
#=> #<FixnumPark:0x1e04b48 #park=4>
f.park(22)
#=> 6 because the instance is already loaded and 4 + 2 = 6
Fun.new.park(12.3)
#=> ArgumentError: expected String or Fixnum but received Float
You could do something like this:
class Parent
attr_reader :s
def initialize(s='')
#s = s
end
def park
puts s
end
end
class Child1 < Parent
attr_reader :x
def initialize(s, x)
super(s)
#x = x
end
def park
puts x
end
end
class Child2 < Parent
attr_reader :y
def initialize(s, y)
super(s)
#y = y
end
def park
puts y
end
end
objects = [
Parent.new('hello'),
Child1.new('goodbye', 1),
Child2.new('adios', 2),
]
objects.each do |obj|
obj.park
end
--output:--
hello
1
2
Or, maybe I overlooked one of your twists:
class Parent
attr_reader :x
def initialize(s='')
#x = s
end
def park
puts x
end
end
class Child1 < Parent
def initialize(x)
super
end
def park
x + 2
end
end
class Child2 < Parent
def initialize(x)
super
end
def park
x * 2
end
end
objects = [
Parent.new('hello'),
Child1.new(2),
Child2.new(100),
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
[nil, 4, 200]
And another example using blocks, which are like anonymous functions. You can pass in the desired behavior to park() as a function:
class Function
attr_reader :block
def initialize(&park)
#block = park
end
def park
raise "Not implemented"
end
end
class StringFunction < Function
def initialize(&park)
super
end
def park
block.call
end
end
class AdditionFunction < Function
def initialize(&park)
super
end
def park
block.call 1
end
end
class DogFunction < Function
class Dog
def bark
puts 'woof, woof'
end
end
def initialize(&park)
super
end
def park
block.call Dog.new
end
end
objects = [
StringFunction.new {puts 'hello'},
AdditionFunction.new {|i| i+2},
DogFunction.new {|dog| dog.bark},
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
woof, woof
[nil, 3, nil]
Look at the is_a? method
def park(i)
i.is_a?(Fixnum) ? (i + 2) : i
end
But even better not to check a type, but use duck typing:
def park(i)
i.respond_to?(:+) ? (i + 2) : i
end
UPD: After reading comments. Yes, both examples above don't solve the OCP problem. That is how I would do it:
class Fun
# The method doesn't know how to pluck data. But it knows a guy
# who knows the trick
def pluck(i)
return __pluck_string__(i) if i.is_a? String
__pluck_fixnum__(i) if i.is_a? Fixnum
end
private
# Every method is responsible for plucking data in some special way
# Only one cause of possible changes for each of them
def __pluck_string__(i)
puts i
end
def __pluck_fixnum__(i)
i + 2
end
end
I understand or equal to operation in ruby but can you explain what
you have done with:
Object.const_get("#{obj.class}Park").new(obj)
In ruby, something that starts with a capital letter is a constant. Here is a simpler example of how const_get() works:
class Dog
def bark
puts 'woof'
end
end
dog_class = Object.const_get("Dog")
dog_class.new.bark
--output:--
woof
Of course, you can also pass arguments to dog_class.new:
class Dog
attr_reader :name
def initialize(name)
#name = name
end
def bark
puts "#{name} says woof!"
end
end
dog_class = Object.const_get("Dog")
dog_class.new('Ralph').bark
--output:--
Ralph says woof!
And the following line is just a variation of the above:
Object.const_get("#{obj.class}Park").new(obj)
If obj = 'hello', the first portion:
Object.const_get("#{obj.class}Park")
is equivalent to:
Object.const_get("#{String}Park")
And when the String class object is interpolated into a string, it is simply converted to the string "String", giving you:
Object.const_get("StringPark")
And that line retrieves the StringPark class, giving you:
Object.const_get("StringPark")
|
V
StringPark
Then, adding the second portion of the original line gives you:
StringPark.new(obj)
And because obj = 'hello', that is equivalent to:
StringPark.new('hello')
Capice?
I'm trying to figure out a way to have a method trigger another method by creating a new Listener class. I'd really like the code to be simplified and not involve adding anything specific to the callback method or the trigger method. Basically, what I'm trying to do is this:
def level_up
level += 1
end
def print_level
puts "Level Up! (#{level})"
end
notify_level = Listener.new(:level_up, :print_level);
What my Listener class is (right now) is this:
# Listener.new(attached_to, callbacks)
class Listener
def initialize(attached_to, function)
#owner, #callback = attached_to, function
end
def owner
#owner
end
def callback
#callback
end
def trigger
# execute callback manually
self.method(#owner).call
self.method(#callback).call
end
end
In order to call both, I need to execute notify_level.trigger itself, but what I want is to execute level_up and call print_level. I know someone will mention something about observers, but I need more than just that. I want to hold fast to DRY. Manually adding observers and listeners to every single method is just terrible, especially since I can't add or remove them with ease.
Personally I'm not a big fan of this pattern but this is kind of a fun question so here is my solution. Should work in Ruby 1.9 and greater.
module MethodListener
##observed_methods = {}
def method_added(method)
alias_name = "__#{method}_orig"
return if method_defined?(alias_name) || method.match(/__.*_orig/)
alias_method alias_name, method
define_method(method) do |*args|
ret = send(alias_name, *args)
(##observed_methods[method] || []).each {|callback| send(callback)}
ret
end
end
def listen(owner, callback)
(##observed_methods[owner] ||= []) << callback
end
end
Usage example:
class A
extend MethodListener
def b(a,b)
puts "b #{a} #{b}"
true
end
def c
puts 'c'
end
listen :b, :c
end
A.new.b(1,2) # => true
# Prints:
# b 1 2
# c
I changed my original code to be more semantic and so it would make more sense.
class Event
def initialize(event, callback_array = [])
if callback_array.kind_of? Array
#callbacks = callback_array
else
#callbacks = [callback_array]
end
#event = event
end
def trigger(*args)
self.method(#event).call *args
#callbacks.each{ |callback|
if callback.instance_of? Event
callback.trigger *args
else
method(callback).call *args
end
}
end
def add(callback)
#callbacks.push callback
end
def remove(callback)
#callbacks.delete_at(#callbacks.index(callback) || #callbacks.length)
end
def event_name
#event
end
end
Usage:
$infinite_break = 10
def infinite_loop_a(type)
puts "#{$infinite_break} points of #{type} damage taken"
$infinite_break -= 1
if $infinite_break > 0
$infinite.trigger(type)
else
$infinite.remove(:infinite_loop_a)
end
end
def infinite_loop_b(type)
puts "player is dealing #{$infinite_break} damage"
end
$infinite = Event.new(:infinite_loop_b, :infinite_loop_a)
$infinite.trigger('fire')
Also, I know I'm calling the infinite_loop_b inside infinite_loop_a, but that's for a specific reason. The Event instances can have another Event as a callback.
I'm trying to make a small game where you go from room to room but when I try to return the value for the next room instead of being the string I intended I get something that looks like this:
#<Roseroom:0x007fe40984c778>
instead of
"skullroom"
This happens whether I use a $global variable or try to return "a string"
Perhaps I'm going about this completely wrong, so if you have any suggestions that would be appreciated as well.
Here is my code, the problem is with class Roseroom not being about to send "skullroom" back to the map class (which is the runner).
$next_room = ''
class Engine
def initialize(stage)
#stage = stage
puts "succesfully initialized game"
#map = Map.new(#stage)
end
end
class Map
def initialize(start)
#start = start
#rooms = {"roseroom" => method(:enterRose),
"skullroom" => method(:enterSkull)
}
runner(#rooms, #start)
end
def runner(map, start)
$next_room = start
while true
room = map[$next_room]
puts $next_room
$next_room = room.call()
#method(:enterSkull).call() #This work if I call it directly
end
end
def enterRose()
#roseroom = Roseroom.new
end
def enterSkull()
#skullroom = Skullroom.new
end
end
class Roseroom
def initialize
puts "succesfully initialized roseroom"
#$next_room = "skullroom"
return "skullroom"
end
def exit
end
end
class Skullroom
def initialize
puts "succesfully initialized skullroom"
Process.exit(1)
end
end
game = Engine.new("roseroom")
I have it here on codepad if that helps:
http://codepad.org/AlpkRIGb
Thanks!
There is nothing in Roseroom class that would return "skullroom"... you may be under the impression that because the last line in initialize is return "skullroom" you would then see "skullroom" returned on a a Roseroom.new but that's not what happens... doing Roseroom.new will always return a new Roseroom object.
you'd be better off defining a next_room method within Roseroom that returns "skullroom"
class Roseroom
def next_room
return "skullroom"
end
Then when you do...
def enterRose
i_am_here = Roseroom.new
i_am_here.next_room
end
Hope this helps.
I am trying to write a simple DSL (against Redis) and I would like to define []+= myself
I have
def []=(key,val)
#redis.zadd(#name,val,key)
end
and I would like to define
def []+=(key,val)
#redis.zincrby(#name,val,key)
end
But my understanding is that Ruby provides the "[]+=" operator automaticallygiven []=
Is there a way to over-ride this behavior
Obviously I don't want this because I would not be able to, say, run this in pipeline mode
No, <operator>= can not be redefined in Ruby.
You can try to get really fancy and wrap your return values in classes that delegate to the actual value. This way, they behave like the actual value, but you can play tricks, for instance with +.
Here's a simple example:
require 'delegate'
module Redis
class Set
class Value < SimpleDelegator
def +(val)
Increment.new(self, val)
end
end
class Increment < SimpleDelegator
attr_reader :increment
def initialize(source, increment)
super(source.__getobj__ + increment)
#increment = increment
end
end
def [](key)
Value.new(#redis.not_sure_what(#name, key))
end
def []=(key,val)
if val.is_a?(Increment)
#redis.zincrby(#name,val.increment,key)
else
#redis.zadd(#name,val,key)
end
end
end
end
This is just a starting point. You'll have to be more careful than this, for example by checking the key is the same. In my simplistic example, redis[:foo] = redis[:bar] + 1 would actually be equivalent to redis[:foo] += 1...
Nope. x[y] += z expands to exactly x[y] = x[y] + z:
class << (object = Object.new)
def [](key)
puts "[#{key.inspect}]"
key
end
def []=(key, value)
puts "[#{key.inspect}] = #{value.inspect}"
value
end
end
# These are all equivalent
object['See?'] += " It's impossible."
object['See?'] = object['See?'] + " It's impossible."
object.[]=('See?', object.[]('See?').+(" It's impossible."))
# They all produce the same output:
# ["See?"]
# ["See?"] = "See? It's impossible."
# => "See? It's impossible."
You will have to create a separate method.