I would like to write a method in ruby that takes a class with certain methods and modifies its behavior by adding methods or changing how existing methods work. I would like to do this in a way that doesn't modify the base class so basically I want a function that takes a class and returns a new modified class without harming the initial class. I'm pretty sure this is possible but I'm not sure where to start.
You have a couple options:
you can use x = Class.new(Parent) { def meth; puts "hello"; super; puts "bye"; end } to dynamically define a class and override methods (& define new ones)
you can use a Delegator
So for instance, if you wanted to dynamically create classes that logged certain method calls:
class Class
def logging_subclass(*methods)
Class.new(self) do
methods.each do |method|
define_method(method) do |*args,&blk|
puts "calling #{method}"
ret = super(*args,&blk)
puts "#{method} returned #{ret.inspect}"
ret
end
end
end
end
end
class One
def foo
"I'm foo!"
end
end
# this prints nothing
One.new.foo #=> returns :foo
# this prints:
# > calling foo
# > foo returned "I'm foo!"
One.logging_subclass(:foo).new.foo #=> returns :foo
Note that you need ruby 1.9 to support capturing do |&blk| (capturing blocks in block arguments).
I'd suggest using inheritance or a mixin; in my opinion, the use of a mixin would be a wiser idea though using inheritance is easier for a newbie.
Remember, you can always inherit from the class and change behavior or wrap it with new code as desired.
class Mammal
def speak
"..."
end
end
class Cat < Mammal
def speak
"meow"
end
end
class Lion < Cat
def speak
"get ready for a big " + super + "!"
end
end
module Asexual_Critter
def reproduce(critter_list)
puts "*poink!*"
critter_list << self.clone
end
end
class Mutated_Kitty < Cat
include Asexual_Critter # inane example I know, but functional...
end
Just remember that if you want to play with this not to do:
critters = [Mutated_Kitty.new]
begin
critters.each { |c| c.reproduce(critters) }
end while critters.length > 0
Or else you'll be in for a long wait until you run out of RAM, or perhaps segfault.
Related
In Rails we can define a class like:
class Test < ActiveRecord::Base
before_initialize :method
end
and when calling Test.new, method() will be called on the instance. I'm trying to learn more about Ruby and class methods like this, but I'm having trouble trying to implement this in plain Ruby.
Here's what I have so far:
class LameAR
def self.before_initialize(*args, &block)
# somehow store the symbols or block to be called on init
end
def new(*args)
## Call methods/blocks here
super(*args)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end
I'm trying to figure out where to store the blocks in self.before_initialize. I originally tried an instance variable like #before_init_methods, but that instance variable wouldn't exist in memory at that point, so I couldn't store or retrieve from it. I'm not sure how/where could I store these blocks/procs/symbols during the class definition, to later be called inside of new.
How could I implement this? (Either having before_initialize take a block/proc/list of symbols, I don't mind at this point, just trying to understand the concept)
For a comprehensive description, you can always check the Rails source; it is itself implemented in 'plain Ruby', after all. (But it handles lots of edge cases, so it's not great for getting a quick overview.)
The quick version is:
module MyCallbacks
def self.included(klass)
klass.extend(ClassMethods) # we don't have ActiveSupport::Concern either
end
module ClassMethods
def initialize_callbacks
#callbacks ||= []
end
def before_initialize(&block)
initialize_callbacks << block
end
end
def initialize(*)
self.class.initialize_callbacks.each do |callback|
instance_eval(&callback)
end
super
end
end
class Tester
include MyCallbacks
before_initialize { puts "hello world" }
end
Tester.new
Left to the reader:
arguments
calling methods by name
inheritance
callbacks aborting a call and supplying the return value
"around" callbacks that wrap the original invocation
conditional callbacks (:if / :unless)
subclasses selectively overriding/skipping callbacks
inserting new callbacks elsewhere in the sequence
... but eliding all of those is what [hopefully] makes this implementation more approachable.
One way would be by overriding Class#new:
class LameAR
def self.before_initialize(*symbols_or_callables, &block)
#before_init_methods ||= []
#before_init_methods.concat(symbols_or_callables)
#before_init_methods << block if block
nil
end
def self.new(*args, &block)
obj = allocate
#before_init_methods.each do |symbol_or_callable|
if symbol_or_callable.is_a?(Symbol)
obj.public_send(symbol_or_callable)
else
symbol_or_callable.(obj)
end
end
obj.__send__(:initialize, *args, &block)
end
end
class Tester < LameAR
before_initialize :do_stuff
def do_stuff
puts "DOING STUFF!!"
end
end
This is one way I've managed to accomplish this.
class Test
class << self
attr_accessor :stuff
def thing msg
#stuff ||= ""
#stuff += msg
end
end
def initialize
#stuff = self.class.stuff
puts #stuff
end
end
# Is there a better way of accomplishing this?
class AThing < Test
thing "hello"
thing "world"
end
AThing.new
# Prints "helloworld"
The interface in AThing is what I would like as a final result. What I really hate (and I feel there must be a better way of accomplishing) is #stuff = self.class.stuff.
Is there a better way to use the eigenclass to set the default dataset for all instances of itself while maintaining a "pretty" interface?
What I want to accomplish with code like this is to have a class method, say add_something that adds something to an array stored in a class variable.
When the class is instantiated, it will use this array in its' initialize method to setup the state of that instance.
class Test
##stuff = ""
class << self
def thing msg
##stuff.concat(msg)
end
end
def initialize
puts ##stuff
end
end
class AThing < Test
thing "hello"
thing "world"
end
AThing.new
# Prints "helloworld"
Okay, I have a question about how to do something in ruby. I have a python example to show what I'm going for, so here it goes.
class TestScript:
def say(word):
pass
def x():
self.say("hello") #right now it will pass
So lets Say that module was called "tester.py" but now, in another module we can do this now:
import tester
class doScript(tester.TestScript):
def say(word):
return word #now its overrided so in this current module it will return it rather pass it
Now the previous say def that was passed is voided by the new one so now if something gets passed to say it will return it rather pass it. Is there any way to do this in ruby? thanks
Here is an example with three files: animal.rb, dog.rb, and script.rb.
# animal.rb
# Our base class.
class Animal
def speak
puts 'click-click'
end
def eat
puts 'chomp-chomp'
end
end
# dog.rb
# Dog inherits from Animal, but we override the speak() method.
require 'animal'
class Dog < Animal
def speak
puts 'woof-woof'
end
end
# script.rb
# Demo script.
require 'dog'
d = Dog.new
d.speak
d.eat
You can always inherit from other class:
class BasicSay
def say(text)
puts prepare_text(text)
end
def prepare_text(text)
do_something_with(text)
end
end
class HtmlSay < BasicSay
def say(text)
"<p>" + prepare_text(text) + "</p>"
end
end
I started looking into Ruby, since I am looking to a more dynamic alternative to Java.
I like how you can modify a class in Ruby after it's definition, for example like this:
class A
def print
"A"
end
end
class B < A
def print
super + "B"
end
end
class A
alias_method :print_orig, :print
def print
print_orig + "+"
end
end
puts B.new.print # A+B
Now I try to do the same with mixins:
class A
def print
"A"
end
end
class B < A
def print
super + "B"
end
end
module Plus
alias_method :print_orig, :print
def print
print_orig + "+"
end
end
A.extend(Plus) # variant 1
B.extend(Plus) # variant 2
class A # variant 3
include Plus
end
class B # variant 4
include Plus
end
puts B.new.print
However none of the variants produce the expected result. BTW, the expected result is the following: I want to be able to 'patch' class A with a mixin, in order to modify its behavior. I want to use mixins, since I want to 'patch' several classes with the same behavior.
Is it possible to do what I want? If yes, how?
Your module code doesn't work because it is executed in wrong context. You need to execute it in context of A, but it is instead evaluated in context of Plus. This means, you need to change self from Plus to A.
Observe:
class A
def print
"A"
end
end
class B < A
def print
super + "B"
end
end
module Plus
self # => Plus
def self.included base
self # => Plus
base # => A
base.class_eval do
self # => A
alias_method :print_orig, :print
def print
print_orig + "+"
end
end
end
end
A.send :include, Plus
B.new.print # => "A+B"
You can't really use Mixins in this way. You're generating a conflict between the class and its mixin. Mixins implicitly resolve the conflict by linearization. Bottom line is: In case of conflict, the class's method is preferred over the mixin. To fix that, you can use Sergio' Tulentsev's approach and have the mixin change its base class aggressively.
Or, you can add methods reflectively. Consider this example, which I've stolen from Mark's blog.
class Talker
[:hello, :good_bye].each do |arg|
method_name = ("say_" + arg.to_s).to_sym
send :define_method, method_name do
puts arg
end
end
end
t = Talker.new
t.say_hello
t.say_good_bye
class A
def a
puts 'in #a'
end
end
class B < A
def a
b()
end
def b
# here i want to call A#a.
end
end
class B < A
alias :super_a :a
def a
b()
end
def b
super_a()
end
end
There's no nice way to do it, but you can do A.instance_method(:a).bind(self).call, which will work, but is ugly.
You could even define your own method in Object to act like super in java:
class SuperProxy
def initialize(obj)
#obj = obj
end
def method_missing(meth, *args, &blk)
#obj.class.superclass.instance_method(meth).bind(#obj).call(*args, &blk)
end
end
class Object
private
def sup
SuperProxy.new(self)
end
end
class A
def a
puts "In A#a"
end
end
class B<A
def a
end
def b
sup.a
end
end
B.new.b # Prints in A#a
If you don't explicitly need to call A#a from B#b, but rather need to call A#a from B#a, which is effectively what you're doing by way of B#b (unless you're example isn't complete enough to demonstrate why you're calling from B#b, you can just call super from within B#a, just like is sometimes done in initialize methods. I know this is kind of obvious, I just wanted to clarify for any Ruby new-comers that you don't have to alias (specifically this is sometimes called an "around alias") in every case.
class A
def a
# do stuff for A
end
end
class B < A
def a
# do some stuff specific to B
super
# or use super() if you don't want super to pass on any args that method a might have had
# super/super() can also be called first
# it should be noted that some design patterns call for avoiding this construct
# as it creates a tight coupling between the classes. If you control both
# classes, it's not as big a deal, but if the superclass is outside your control
# it could change, w/o you knowing. This is pretty much composition vs inheritance
end
end