I try to write DSL
class Warcraft
class << self
def config
unless #instance
yield(self)
end
#instance ||= self
end
attr_accessor :name, :battle_net
def game(&block)
#instance ||= Game.new(&block)
end
end
class Game
class << self
def new
unless #instance
yield
end
#instance ||= self
end
attr_accessor :side, :hero
def rune_appear_every(period)
#rune_appear_every = period
end
end
end
end
Warcraft.config do |war|
war.name = "Warcraft III"
war.battle_net = :iccup
war.game do |game|
game.side = :sentinels
game.hero = "Furion"
game.rune_appear_every 2.minutes
end
end
And i get such error:
dsl.rb:41:in `block (2 levels) in <main>': undefined method `side=' for nil:NilClass (NoMethodError)
The issue is here:
def new
unless #instance
yield
end
#instance ||= self
end
You're not passing in any argument when you yield, later on when you call:
war.game do |game|
The game variable is nil. So instead of just doing yield, do yield self.
Related
I'm trying to set an instance variable inside a singleton class and I can't get it to work.
Here's a simplified version of the problem:
class MyClass
class << self
attr :my_attr
#my_attr = {}
def my_method (x, y)
(#my_attr[x] ||= []) << y
end
end
end
MyClass.my_method(1, 2)
# => NoMethodError: undefined method `[]' for nil:NilClass
Here's the original code sample:
class Mic
class Req < Rack::Request; end
class Res < Rack::Response; end
class << self
attr :routes
#routes = {}
def call(env)
dup.call!(env)
end
def call!(env)
(#app||=new).call(env)
end
def get(path, opts={}, &blk)
puts #routes.inspect # nil
route 'GET', path, opts, &blk
end
def route(type, path, opts, &blk)
(#routes[type]||=[]) << {type: type, path: path, opts: opts, blk: blk}
end
end
def call(env)
#env = env
#req = Req.new(env)
#res = Res.new
#res.finish
end
end
So, abbreviated code, but what you probably want is to avoid accessing the instance variable as much as possible.
class Mic
class << self
def routes
#routes ||= {}
end
def method_which_acccess_routes
routes[:this] = :that
end
end
def instance_method_access_routes
Mic.routes[:the_other] = :nope
end
end
You can modify routes in place this way without an accessor, but if you need to completely overwrite it, you'll need an attr_writer method for routes as well.
How can I define Thread#initialize? I tried the following:
(1)
class Thread
def initialize
super
#foo = []
end
end
Thread.new{}.join
(2)
class Thread
def initialize &pr
super(&pr)
#foo = []
end
end
Thread.new{}.join
(3)
class Thread
def initialize
end
end
Thread.new{}.join
but they return an error:
Uninitialized thread - check `Thread#initialize'.
You're destroying the original Thread.initialize when open up the class this way, the call to super is Object.initialize, not to what was previously in Thread.initialize.
It's similar to what is happening here:
class Dog
def initialize
puts 'Arf'
end
end
class Dog
def initialize
super
puts 'I did not arf'
end
end
#dog.new "I did not arf"
You could subclass Thread and then use super, or you could use alias_method
class Thread
alias_method :old_initialize, :initialize
def initialize(*args,&blk)
puts 'Before initialize'
old_initialize(*args,&blk)
end
end
While playing a bit with Ruby, I wrote the following code:
class A
end
A.singleton_class.instance_eval do
undef_method :new
end
# or
# class << B
# undef_method :new
# end
A.new
> NoMethodError: undefined method `new' for A:Class
> from (irb):8
> from /home/mmsequeira/.rvm/rubies/ruby-1.9.3-p327/bin/irb:16:in `<main>'
This is cool. But how can I know which methods have been undefined in a given class?
You can't by default. Undefining a method removes it from existence. You could, however, record them as they're removed. This can be done with method hooks to capture everything and avoid ugly alias method chaining:
class Module
def method_undefined name
(#undefined_methods ||= []) << name
end
def singleton_method_undefined name
(#undefined_methods ||= []) << name
end
def undefined_methods
#undefined_methods || []
end
end
This will capture methods undefined via undef_method or undef:
class C
def foo; end
def bar; end
undef foo
undef_method :bar
end
C.undefined_methods #=> [:foo, :bar]
C.singleton_class.instance_eval { undef new }
C.singleton_class.undefined_methods #=> [:new]
Of course, you must include the hook methods in Module before anything can be captured.
Maybe you need to redefine Module#undef_method.
class Module
alias original_undef_method :undef_method
##undef_methods = {}
def undef_method *methods
methods.each{|method| ##undef_methods[[self, method]] ||= true}
original_undef_method(*methods)
end
def self.undef_methods; ##undef_methods.keys end
end
Then, you get:
class A
end
A.singleton_class.instance_eval do
undef_method :new
end
Module.undef_methods
# => [[#<Class:A>, :new]]
I want to define a set of methods that can be added to a class (C in the example) using a Mixin. These methods can be defined by any class that inherits from another class (A in the example) and should be able to call methods in the receiver instance (C instance).
I have this snippet of code:
M = Module.new
class A
class << self
def init(method)
if block_given?
M.send(:define_method, method) do
instance_exec &Proc.new
end
else
block = self.method(method).to_proc
M.send(:define_method, method) do
yield block.call
end
end
end
end
end
class B < A
init(:foo) do
"foo+".concat(c_method)
end
def self.bar
"bar+".concat(c_method)
end
init(:bar)
end
C = Class.new do
def c_method
"c_method"
end
end
c = C.new
c.extend(M)
puts c.foo
puts c.bar
Adding methods using blocks works, but last line fails :(
foo+c_method
test.rb:28:in `bar': undefined local variable or method `c_method' for B:Class (NameError)
from test.rb:15:in `call'
from test.rb:15:in `block in init'
from test.rb:46:in `<main>'
What I'm doing wrong? Or this makes no sense?
Thanks
Juan
When you prepare instance_exec &Proc.new inside if statement, this statement is executed within instance of C class as context. You can verify this by adding puts self inside block for init(:foo).
On the other hand, when you call yield block.call you yield thread execution into context of B class object (not to instance of this class, of course :) ). This place of your code doesn't know anything about C::c_method and this is cause of error.
It seems that what I'm trying to do is to unbind the method :bar from B and bind to C, what it's not allowed. You can find more info in this great post
M = Module.new
class A
class << self
def init(method)
if block_given?
M.send(:define_method, method) do
instance_exec &Proc.new
end
else
block = self.method(method).unbind
M.send(:define_method, method) do
m = block.bind(self)
puts m
end
end
end
end
end
class B < A
init(:foo) do
"foo+".concat(c_method)
end
def self.bar
"bar+".concat(c_method)
end
init(:bar)
end
C = Class.new do
def c_method
"c_method"
end
end
c = C.new
c.extend(M)
puts c.foo
puts c.bar
foo+c_method
test.rb:16:in `bind': singleton method called for a different object (TypeError)
from test.rb:16:in `block in init'
from test.rb:48:in `<main>'
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