I'm using a Ruby Component inside an Audio Application Environment. This Ruby Component is represented by an instance of a RubyEdit class. This provide the interface between my Ruby code and the Environment.
For example if I write self it outputs the instance of the RubyEdit class that represents that component:
the nice things is that I can/must "implement/extend" some methods that RubyEdit will call when it get some events. I can define my custom method event for that instance:
def event
# my own event code
end
and it will be called when the Environment get some events outside this Ruby Component. Or I can call a class method called redraw, and it will call my custom method draw:
def draw
# my own draw code (this will called after invoking redraw from Ruby Component)
end
I need to understand some hierarchy of this process, so I'm making a simulation of that RubyEdit class in Ruby.
How it will looks? I mean: how can I provide methods that will be defined "later"?
This is how RubyEdit will look I think:
class RubyEdit
def self.redraw
# calling this class method should invoke my extended draw method
end
def draw
end
def event
end
end
but I don't understand how to place event and draw methods extendible. Interfaces?
module ExtendRubyEdit
def self.included(base)
base.extend(ClassMethods)
end
def draw
end
def event
end
module ClassMethods
def redraw
end
end
end
class RubyEdit
include ExtendRubyEdit
end
Related
In the Ruby programming language, I am creating a class with a class-level macro, as follows:
class Timer
def self.add_time
def time
STDERR.puts Time.now.strftime("%H:%M:%S")
end
end
end
The class method add_time, when executed, will generate a time method.
Now, I can execute that class-level macro in another class Example as follows:
class Example < Timer
add_time
end
When I now call time on an instance of class Example, the time method is present there, as I intended:
ex = Example.new
ex.time
and prints the current time: 23:18:38.
But now I would like to put the add_time macro in a module and still have the same overall effect. I tried with an include like this:
module Timer
def self.add_time
def time
STDERR.puts Time.now.strftime("%H:%M:%S")
end
end
end
class Example
include Timer
add_time
end
ex = Example.new
ex.time
but then I receive an error that the method add_time is not defined on the class Example: NameError: undefined local variable or method ‘add_time’ for Example:Class. So then I tried with an extend instead like this:
class Example
extend Timer
add_time
end
but it gives me a similar error.
So the question is: How can I get the same effect as in my original example where the Timer was defined as a class, but using a module instead?
As #CarySwoveland pointed out, the method def self.add_time in the module Timer gets disregarded upon inclusion or extension in a class. Only the module's instance methods are added to the class as instance method of the class (in case of inclusion) or as class methods of the class (in case of extends).
module Timer
def add_time # INSTANCE METHOD !
def time
STDERR.puts Time.now.strftime("%H:%M:%S")
end
end
end
So the first step of the solution is to declare the method def add_time as an instance method of the module. Next, we extend the class Example with that module, so that the module's instance method gets added as a class method in the class Example, and we call the add_timemethod:
class Example
extend Timer # EXTEND INSTEAD OF INCLUDE
add_time
end
However, this doesn't quite work as desired yet as the time method has now been generated as a class method: Example.time prints the current time 01:30:37, but an instance ex of class Example does not understand the method time.
The solution is thus to generate the method def time as an instance method rather than as a class method. This can be done using class_eval, which leads us to the following working solution:
module Timer
def add_time # INSTANCE METHOD !
self.class_eval do # USE class_eval TO DEFINE AN INSTANCE METHOD !
def time
STDERR.puts Time.now.strftime("%H:%M:%S")
end
end
end
end
class Example
extend Timer # USE EXTEND TO ADD add_time AS A CLASS METHOD
add_time
end
ex = Example.new
ex.time
module Hello
def self.log()
p "log called"
end
class Shape
def self.test
log
end
end
def self.test1
log
end
end
Hello::Shape.test # undefined local variable or method `log'
Hello.test1 # prints "log called"
I know the first statement will work if I prefix log with Hello like :Hello.log
But why cant Shape access the log method even if it is within the same module?
Whenever you type a method call without an explicit receiver like
log
That's the same as
self.log
So you're basically doing
class Shape
def self.test
self.log
end
end
self is Shape and Shape doesn't have a log method. That's why you get the error. So to do what you want to do, you need to add Hello's method to Shape. But this is impossible! def self.log adds a method to Hello's singleton class, and when it comes to singleton methods Ruby really doesn't let you move them around. Singleton methods only go with a single instance.
So how can you get around this? The key is to define log as a regular instance method - then you can easily move it around to other classes and modules.
module Hello
# add all instance methods as class methods
extend self
def log
p "log called"
end
class Shape
# Module.nesting.last is just a clever way of referring to Hello
extend Module.nesting.last
def self.test
log
end
end
def test1
log
end
end
Since the value of self is Shape, by default Ruby will look for log method defined in Shape class. You don't have a log method so it will raise an error. Even if the Shape is within the same module, when you use the keyword 'class', Ruby opens a new context, which means all the definitions of methods, variables etc defined within the module Hello goes out of scope within the Shape class. You have to explicity specify which log method you want to call by prefixing it with the class or module name within the Shape class.
I have some namespaces that contain duck-type classes, and they all inherit from Base namespace, like below:
module Base
class Client
def self.greet
puts Wrapper
end
def do_stuff
puts Wrapper
end
end
class Wrapper
end
end
module Website
class Client < Base::Client
end
class Wrapper < Base::Wrapper
end
end
Website::Client.greet
Website::Client.new.do_stuff
---output---
Base::Wrapper
Base::Wrapper
I would like the above code to print (and reference) Website::Wrapper instead, is there a way to accomplish this by changing my inheritance structure?
If a constant named Wrapper is in the top-level namespace you can write ::Wrapper to refer to it, but usually just writing Wrapper is sufficient.
In Website::Client, if I attempt to call another class such as
Wrapper, it will call Base::Wrapper...
I'm not seeing that:
module Base
class Client
end
class Wrapper
end
end
module Website
class Wrapper < Base::Wrapper
end
class Client < Base::Client
p Wrapper #=>Website::Wrapper
def self.greet #Create class method
p Wrapper #=>Website::Wrapper
end
def do_stuff
p Wrapper #=>Website::Wrapper
end
end
end
Website::Client.greet
Website::Client.new.do_stuff
--output:--
Website::Wrapper
Website::Wrapper
Website::Wrapper
Can you modify that example to show the problem you are having?
Response to modified question:
Is this just bad practice?
Wouldn't it be surprising to call Base::Client.greet and get Website::Wrapper?
Is there an easy way to have methods called in the subclass default to
that class' namespace?
What do you mean by that? There is no class method named greet defined in Website::Client's singleton class. If you want to override Base::Client.greet you can do that.
Also, you are writing Website::Client when you call Website::Client.greet, so you already know from which class the method call originates...but inside Base::Client.greet, you can add the line puts self, and that will identify the object that called the method, which is the class Website::Client.
I got the following code from #avdi's Ruby Tapas episode for today:
module Eventful
def self.included(other)
other.extend(Macros)
end
def add_listener(listener)
(#listeners ||= []) << listener
end
def notify_listeners(event, *args)
(#listeners || []).each do |listener|
listener.public_send("on_#{event}", *args)
end
end
module Macros
def event(name)
module_eval(%Q{
def #{name}(*args)
notify_listeners(:#{name}, *args)
end
})
end
end
end
class Dradis
include Eventful
event :new_contact
end
class ConsoleListener
def on_new_contact(direction, range)
puts "DRADIS contact! #{range} kilometers, bearing #{direction}"
end
end
dradis = Dradis.new
dradis.add_listener(ConsoleListener.new)
dradis.new_contact(120, 23000)
I understand the concept of events and listeners and the observer pattern, but don't get how/why this syntax is working, and haven't seen it in any manuals. The class Dradis has this:
event :new_contact
At first, I thought that event was a method and :new_contact was an argument so that I would call event on an instance of Dradis, something like:
dradis = Dradis.new
dradis.event
but instead, new_contact is called on an instance of Dradis like:
dradis = Dradis.new
dradis.add_listener(ConsoleListener.new)
dradis.new_contact(120, 23000)
and that triggers the event method in the Macro module.
Can anyone explain why it works like this? calling :new_contact on an instance dradis to trigger the event method?
I didn't watch the episode, but look, it's right there.
module Macros
def event(name)
module_eval(%Q{
def #{name}(*args)
notify_listeners(:#{name}, *args)
end
})
end
end
event is a method which defines another method (new_contact) which calls notify_listeners from Eventful.
and that triggers the event method in the Macro module
Incorrect. That method has finished its work a long time ago and it doesn't get invoked again. It produced a new method using module_eval / def and that new method (new_contact) is what's getting called.
It's important to understand that event method runs only once, when the Dradis class is parsed and loaded. It does not get run on every instantiation of Dradis.
Several separated features of ruby is used: In the line event :new_contact the "evnet" is class method (http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_classes.html#UE).
Usually class methods are defined by:
class A
def A.my_class_method
#code
end
end
A.my_class_method #executing class_method
or
class A
def <<self #usefull when you want delare several class methods
def A.my_class_method
#code
end
end
end
A.my_class_method #executing class_method
In the code the method is included by the module Macros.
The key thing is, that (class method) event is dynamicaly creating new (instance) method (in this case new_contact)
The name of the method is passed as argument to event). And this method providing calling of the listener.
Can anyone explain why it works like this? calling :new_contact on an instance dradis to trigger the event method?
This is by the dynammically created method new_contact which is calling notify_listeners(:#{name}, *args)
I'm trying to create a component system in Ruby using the observer pattern. Components must be defined as modules because they exist only to be mixed in to a ComponentContainer. But there are certain methods that Components have, which I'd ideally like to define in some kind of base class, but I can't do that since they're modules.
Here's what I'd like to do:
module Component
def self.on(event, &block)
#definition..
end
def self.fire(event)
#pass event to subscribers
end
end
module FooComponent < Component
on :foo_event do |param1, param2|
#...
end
end
The different types of Components use the on and fire methods, but they can't inherit them, because modules can't have parents. What should I do? Is this not ruby-like?
I could get this to work by making Component and FooComponent classes, but then I can't mix them into a ComponentContainer using extend or include.
A clean way to do this is to abstract away the use of extend using the Module#included hook method. This method is called on a module with a reference to the base that is including it. What this code does is creates a Component module that automatically extends the base with the desired methods:
module Component
def self.included(base)
base.extend Methods
end
module Methods
def on(event, &block)
# ...
end
def fire(event)
# ...
end
end
end
module FooComponent
include Component
on :foo_event do |param1, param2|
# ...
end
end
What if you created a separate module called BaseComponent that defined the basic methods all component modules should have, and then in your custom components, include that BaseComponent.
Then, you should still be able to mixin those custom components into your ComponentContainer class.
You can extend the Component's methods into your "child"-module:
module Component
extend self # (only if you also want to allow calling via Component.on like in the original example)
def on(event, &block)
#definition..
end
def fire(event)
#pass event to subscribers
end
end
module FooComponent
extend Component
on :foo_event do |param1, param2|
#...
end
end