I want to be able to define a block, and later evaluate that block from within a dynamically generated module/class. It seems like I could accomplish this somehow using eval and block.binding, but I haven't figured it out.
I have this as the base:
def define_module(name, &block)
name = name.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
parts = name.split("::")
parts.each_with_index do |part, index|
sub_name = parts[0..index].join("::")
eval("module #{sub_name}; end")
end
clazz = eval(name)
clazz.class_eval(&block) if block_given?
clazz
end
def add_module(name, &block)
module_block = block
define_module(name).class_eval <<-EOF
def self.included(base)
base.class_eval do
# something like this, I'm stuck
instance_eval(&#{module_block})
end
end
EOF
end
And I want to use it like this:
add_module("My::Library") do
def a_method
"added 'a_method'"
end
end
class ::User
include My::Library
end
user = ::User.new
assert_equal "added 'a_method'", user.a_method
Is there any way to do something like that?
This works:
def add_module(name, &block)
define_module(name).class_eval do
class << self; self; end.send(:define_method, :included) { |base|
base.class_eval(&block)
}
end
end
add_module("My::Library") do
def a_method
"added 'a_method'"
end
end
class ::User
include My::Library
end
user = ::User.new
user.a_method #=> "added a_method"
EDIT:
Why don't you just do this instead? Much simpler, and it's actually the job of a module:
def add_module(name, &block)
define_module(name).class_eval(&block)
end
Related
I have a class that was built for subclassing.
class A
def initialize(name)
end
def some
# to define in subclass
end
end
# usage
p A.new('foo').some
#=> nil
In my use case, I don't want to create a subclass since I need just one instance. Therefore, I'll change the initialize method to support the following usage.
p A.new('foo') { 'YEAH' }.some
#=> YEAH
How could I support the usage above?
BTW: I found the following solutions for a Ruby 1.8.7 project, but they look awkward to me.
class A
def singleton_class
class << self; self; end
end
def initialize(name, &block)
#name = name
self.singleton_class.send(:define_method, :some) { block.call } if block_given?
end
def some
# to define in subclass
end
end
You can store the block argument in an instance variable and call it later on:
class A
def initialize(name, &block)
#name = name
#block = block
end
def some
#block.call
end
end
A.new('foo') { 'YEAH' }.some
#=> "YEAH"
You can also pass arguments into the block:
class A
# ...
def some
#block.call(#name)
end
end
A.new('foo') { |s| s.upcase }.some
#=> "FOO"
Or instance_exec the block in the context of the receiver:
class A
# ...
def some
instance_exec(&#block)
end
end
Which allows you to bypass encapsulation:
A.new('foo') { #name.upcase }.some
#=> "FOO"
I have a function which defines and returns a new class, with some pre-built methods. E.g.:
def define_class(name, options={}, &block)
klass = Class.new(Class) do
def say_hello
puts "Hello!"
end
def say_goodbye
puts "Adios!"
end
end
parent_class.const_set(form_class, klass)
klass
end
So, for example, this works:
define_class("testing").new.say_hello #=> "Hello!"
But I would like to be able to pass in custom methods through a block, which would then be added to my class, like so:
define_class "testing" do
# ... custom methods
end
Such that this would work:
klass = define_class "testing" do
def interject
puts "Excuse me?"
end
end
klass.new.interject #=> "Excuse me?"
I can't figure out how to make that work though; I've tried instance_eval, class_eval, and yield, and none are producing the desired result.
Try simply:
def define_class(name, options={}, &block)
klass = Class.new(&block)
parent_class.const_set(form_class, klass)
klass
end
If you want to call the block and your own block, you should use class_eval:
def define_class(name, options={}, &block)
klass = Class.new do
def say_hello
puts "Hello!"
end
def say_goodbye
puts "Adios!"
end
class_eval(&block)
end
parent_class.const_set(form_class, klass)
klass
end
Heres what I have/want:
module Observable
def observers; #observers; end
def trigger(event, *args)
good = true
return good unless (#observers ||= {})[event]
#obersvers[event].each { |e| good = false and break unless e.call(self, args) }
good
end
def on(event, &block)
#obersvers ||= {}
#obersvers[event] ||= []
#observers[event] << block
end
end
class Item < Thing
include Observable
def pickup(pickuper)
return unless trigger(:before_pick_up, pickuper)
pickuper.add_to_pocket self
trigger(:after_pick_up, pickuper)
end
def drop(droper)
return unless trigger(:before_drop, droper)
droper.remove_from_pocket self
trigger(:after_drop, droper)
end
# Lots of other methods
end
# How it all should work
Item.new.on(:before_pickup) do |item, pickuper|
puts "Hey #{pickuper} thats my #{item}"
return false # The pickuper never picks up the object
end
While starting on trying to create a game in Ruby, I thought it would be great if it could be based all around Observers and Events. The problem is have to write all of these triggers seems to be a waste, as it seems like a lot of duplicated code. I feel there must be some meta programming method out there to wrap methods with functionality.
Ideal Sceanrio:
class CustomBaseObject
class << self
### Replace with correct meta magic
def public_method_called(name, *args, &block)
return unless trigger(:before_+name.to_sym, args)
yield block
trigger(:after_+name.to_sym, args)
end
###
end
end
And then I have all of my object inherit from this Class.
I'm still new to Ruby's more advanced meta programming subjects, so any knowledge about this type of thing would be awesome.
There are a several ways to do it with the help of metaprogramming magic. For example, you can define a method like this:
def override_public_methods(c)
c.instance_methods(false).each do |m|
m = m.to_sym
c.class_eval %Q{
alias #{m}_original #{m}
def #{m}(*args, &block)
puts "Foo"
result = #{m}_original(*args, &block)
puts "Bar"
result
end
}
end
end
class CustomBaseObject
def test(a, &block)
puts "Test: #{a}"
yield
end
end
override_public_methods(CustomBaseObject)
foo = CustomBaseObject.new
foo.test(2) { puts 'Block!' }
# => Foo
Test: 2
Block!
Bar
In this case, you figure out all the required methods defined in the class by using instance_methods and then override them.
Another way is to use so-called 'hook' methods:
module Overrideable
def self.included(c)
c.instance_methods(false).each do |m|
m = m.to_sym
c.class_eval %Q{
alias #{m}_original #{m}
def #{m}(*args, &block)
puts "Foo"
result = #{m}_original(*args, &block)
puts "Bar"
result
end
}
end
end
end
class CustomBaseObject
def test(a, &block)
puts "Test: #{a}"
yield
end
include Overrideable
end
The included hook, defined in this module, is called when you include that module. This requires that you include the module at the end of the class definition, because included should know about all the already defined methods. I think it's rather ugly :)
(Big edit, I got part of the way there…)
I've been hacking away and I've come up with this as a way to specify things that need to be done before attributes are read:
class Class
def attr_reader(*params)
if block_given?
params.each do |sym|
define_method(sym) do
yield
self.instance_variable_get("##{sym}")
end
end
else
params.each do |sym|
attr sym
end
end
end
end
class Test
attr_reader :normal
attr_reader(:jp,:nope) { changethings if #nope.nil? }
def initialize
#normal = "Normal"
#jp = "JP"
#done = false
end
def changethings
p "doing"
#jp = "Haha!"
#nope = "poop"
end
end
j = Test.new
p j.normal
p j.jp
But changethings isn't being recognised as a method — anyone got any ideas?
You need to evaluate the block in the context of the instance. yield by default will evaluate it in its native context.
class Class
def attr_reader(*params, &blk)
if block_given?
params.each do |sym|
define_method(sym) do
self.instance_eval(&blk)
self.instance_variable_get("##{sym}")
end
end
else
params.each do |sym|
attr sym
end
end
end
end
Here's another alternative approach you can look at. It's not as elegant as what you're trying to do using define_method but it's maybe worth looking at.
Add a new method lazy_attr_reader to Class
class Class
def lazy_attr_reader(*vars)
options = vars.last.is_a?(::Hash) ? vars.pop : {}
# get the name of the method that will populate the attribute from options
# default to 'get_things'
init_method = options[:via] || 'get_things'
vars.each do |var|
class_eval("def #{var}; #{init_method} if !defined? ##{var}; ##{var}; end")
end
end
end
Then use it like this:
class Test
lazy_attr_reader :name, :via => "name_loader"
def name_loader
#name = "Bob"
end
end
In action:
irb(main):145:0> t = Test.new
=> #<Test:0x2d6291c>
irb(main):146:0> t.name
=> "Bob"
IMHO changing the context of the block is pretty counter-intuitive, from a perspective of someone who would use such attr_reader on steroids.
Perhaps you should consider plain ol' "specify method name using optional arguments" approach:
def lazy_attr_reader(*args, params)
args.each do |e|
define_method(e) do
send(params[:init]) if params[:init] && !instance_variable_get("##{e}")
instance_variable_get("##{e}")
end
end
end
class Foo
lazy_attr_reader :foo, :bar, :init => :load
def load
#foo = 'foo'
#bar = 'bar'
end
end
f = Foo.new
puts f.bar
#=> bar
I'm trying to make a method similar to attr_reader but I can't seem to get the instance of the class that the method gets called in.
class Module
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
variables = symbols.collect { |sym| ("#" << sym.to_s).to_sym }
attr_reader *symbols
(class << ModifyMethods; self; end).instance_eval do
define_method(*symbols) do
mod.instance_variable_get(*variables)
end
end
end
end
class Object
module ModifyMethods; end
def modify(&block)
ModifyMethods.instance_eval(&block)
end
end
class Klass
modifiable_reader :readable
def initialize
#readable = "this"
end
end
my_klass = Klass.new
my_klass.modify do
puts "Readable: " << readable.to_s
end
I'm not sure what it is you're trying to do.
If it helps, the spell for attr_reader is something like this:
#!/usr/bin/ruby1.8
module Kernel
def my_attr_reader(symbol)
eval <<-EOS
def #{symbol}
##{symbol}
end
EOS
end
end
class Foo
my_attr_reader :foo
def initialize
#foo = 'foo'
end
end
p Foo.new.foo # => "foo"
What I can understand from your code is that you want to have the modify block to respond to the instance methods of Klass, that's as simple as:
class Klass
attr_reader :modifiable
alias_method :modify, :instance_eval
def initialize(m)
#modifiable = m
end
end
Klass.new('john').modify do
puts 'Readable %s' % modifiable
end
About this tidbit of code:
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
...
Probably this can give you a hint of what is going on:
Class.superclass # => Module
Klass.instance_of?(Class) # => true
Klass = Class.new do
def hello
'hello'
end
end
Klass.new.hello # => 'hello'
When you are adding methods to the Module class, you are also adding methods to the Class class, which will add an instance method to instances of Class (in this case your class Klass), at the end this means you are adding class methods on your Klass class