Accessing a class variable in Struct.new block - ruby

I'm using Struct.new to create new classes on the fly (we're using some entity modelling middleware, and I want to generate concrete types on the fly for serialization).
In essence I have this code:
module A
def self.init_on(target)
target.foo = 123
end
end
$base_module = A
module Test
C = Struct.new(:id) do
include $base_module
##base = $base_module
def initialize
##base.init_on(self)
end
attr_accessor :foo
end
end
c = Test::C.new
puts c.foo
I get this error when I run my test:
test2.rb:17:in initialize': uninitialized class variable ##base in Test::C (NameError)
from test2.rb:24:innew'
from test2.rb:24:in `'
From my understanding of Struct.new, the block is executed with the context of the class being created, so ##base should be resolvable.
Thanks for your time!
Edit:
Thanks - I made init_on self.init_on and used class_variable_set rather than instance_variable_set. It now works!

Why not try to use something like self.instance_variable_set(:##base, $base_module). I think that may work, since you are just setting an instance variable of the class object.

Related

Why am I getting a NoMethodError when calling an instance method from global scope?

I have searched around for the answer to this and I can see a lot of similar problems but I still do not understand what I am doing wrong here. I have declared a Ruby class and attempted to new it and then call some instance methods on the instance, so why do I get the NoMethodError on my start method?
class MyClass
def initialize
self.class.reset
end
def self.reset
...
end
def self.start(port)
...
end
end
test = MyClass.new
test.start '8082' <- here <- undefined method `start' for #<MyClass:0x2f494b0> (NoMethodError)
As you can see I am a Ruby noob. Any help would be appreciated. I can change my class structure but I would really like to understand what I am doing wrong here.
here start is a class method.
By your current approach, you can use it in the following way
MyClass.start '8080'
But if you want to use it on instance of class then use the following code
class MyClass
def initialize
self.class.reset
end
def self.reset
...
end
def start(port)
...
end
end
test = MyClass.new
test.start '8080'
You are using start as a Class variable, the method names preceded with self-keyword make those methods as Class methods. So if you really want to not change your class then you should call it like this:
MyClass.start '8080'
Else you can remove the self from your reset and start methods and make them as Instance methods and use them as:
test = MyClass.new
test.start '8082'

What can't I access class variable from object in ruby?

I wan't to set a class variable of a class from the outside(via attr_accessor), and then access it from inside one of its objects. I'm using ruby 1.9.2. This is my code:
class Service
def initialize(id)
#my_id = id
end
class << self
attr_accessor :shared_id
end
def system_id
#my_id + ##shared_id
end
end
If I set Service.shared_id = "A2", and then call Service.new("A").system_id, this doesn't return "AA2". It displays the following error:
uninitialized class variable ##shared_id in Service
The behaviour is like if I didn't set the Service.service_id. Can someone please explain why this happens?
attr_accessor creates methods to manipulate instance variables — it does not create instance or class variables. To create a class variable, you must set it to something:
##shared_id = something
There's no helper method to generate accessor for class variables, so you have to write them yourself.
However, class variables, because of their weird lookup rules, are rarely used — avoided, even. Instead, instance variables at class-level are used.
class Service
#shared_id = thing
class << self
attr_accessor :shared_id
end
def system_id
# use self.class.shared_id; you could add a shared_id helper to generate it, too.
end
end
How about cattr_accessor?
Remember that ##class_var is global for all classes.

Dynamically define named classes in Ruby

I am writing an internal DSL in Ruby. For this, I need to programmatically create named classes and nested classes. What is the best way to do so? I recon that there are two ways to do so:
Use Class.new to create an anonymous class, then use define_method to add methods to it, and finally call const_set to add them as named constants to some namespace.
Use some sort of eval
I've tested the first way and it worked, but being new to Ruby, I am not sure that putting classes as constants is the right way.
Are there other, better ways? If not, which of the above is preferable?
If you want to create a class with a dynamic name, you'll have to do almost exactly what you said. However, you do not need to use define_method. You can just pass a block to Class.new in which you initialize the class. This is semantically identical to the contents of class/end.
Remember with const_set, to be conscientious of the receiver (self) in that scope. If you want the class defined globally you will need to call const_set on the TopLevel module (which varies in name and detail by Ruby).
a_new_class = Class.new(Object) do
attr_accessor :x
def initialize(x)
print #{self.class} initialized with #{x}"
#x = x
end
end
SomeModule.const_set("ClassName", a_new_class)
c = ClassName.new(10)
...
You don't really need to use const_set. The return value of Class.new can be assigned to
a constant and the block of Class.new is class_eval.
class Ancestor; end
SomeClass = Class.new(Ancestor) do
def initialize(var)
print "#{self.class} initialized with #{var}"
end
end
=> SomeClass
SomeClass.new("foo")
# SomeClass initialized with foo=> #<SomeClass:0x668b68>
Should be like this
a_new_class = Class.new(Object) do
attr_accessor :x
def initialize(x)
#x = x
end
end
SomeModule = Module.new
SomeModule.const_set("ClassName", a_new_class)
c = SomeModule::ClassName.new(10)

ruby closure issue (cannot access var)

class SomeClass
end
some_local_var = 5
sc = SomeClass.new
def sc.should_work_closure
puts some_local_var # how can I access "some_local_var", # doesn't this work like a closure ?
end
sc.should_work_closure()
Line 9:in should_work_closure': undefined local variable or methodsome_local_var' for # (NameError)
from t.rb:12
No, def does not work like a closure.
To make sc available in the def you could make it a constant, make it global (usually a bad idea) or use define_method with a block (which are closures).
However since you're not inside a class and define_method is a method for classes (and modules), you can't just use it. You have to use class_eval on the eigenclass of sc to get inside the class.
Example:
class <<sc; self end.class_eval
define_method(:should_work_closure)
puts some_local_var
end
end
This will work, but it looks a bit scary. It usually is a bad idea to access local variables from the surrounding scope in method definitions.

Writing Ruby Libraries - hiding methods from outside the module

I'm writing a Ruby library which has a module with a bunch of classes inside it. Many of these classes need to be usable and modifiable by calling scripts, but I don't want (some of) the initializers to be visible/callable:
module MyLib
class Control
def initialize
# They can use this
end
def do_stuff
Helper.new('things')
end
end
class Helper
# Shouldn't be visible
def initialize(what)
#what = what
end
def shout
#what
end
end
end
c = MyLib::Control.new
h = c.do_stuff
p h.shout
# => "things"
# ^ All of this is desired
# v This is undesirable
p MyLib::Helper.new('!')
# => <MyLib::Helper #what='!'>
If it's a simple thing, then I'd also appreciate the generated RDoc not even include the .new method for the Helper class either. Any ideas?
Thanks for reading!
My original answer was completely wrong, as #Matthew pointed out. But there are other workarounds. For instance, you can assign an anonymous class to a class variable on Control, and still define methods as normal by using class_eval:
module MyLib
class Control
def initialize
end
def do_stuff
##helper.new('things')
end
##helper = Class.new
##helper.class_eval do
def initialize(what)
#what = what
end
def shout
#what
end
end
end
end
The snippet
c = MyLib::Control.new
h = c.do_stuff
p h.shout
still writes "things", but now there's no way to access ##helper except through the class variable. If someone really wants to access it my reopening the Control class or using class_eval, there's nothing to stop them, but that's just something you have to deal with in a dynamic language.
I chose to assign the anonymous class to a class variable so that it would only be created once; but if you don't care about redefining the anonymous class many times, there's no reason it couldn't be an instance variable.
Ruby has access control.

Resources