How to namespace constants inside anonymous class definitions? - ruby

When creating anonymous classes through Class.new, they don't seem to have their own namespace for constants:
klass1 = Class.new do
FOO = "foo"
end
klass2 = Class.new do
FOO = "bar"
end
This gives warning: already initialized constant FOO and looks like it's right:
> klass1.const_get(:FOO)
"bar"
> klass2.const_get(:FOO)
"bar"
> FOO
"bar"
I was going to use this approach in a simple DSL for defining addons for an application, something like this:
class App
class AddonBase
attr_reader :session
def initialize(session)
#session = session
end
end
def self.addons
#addons ||= {}
end
def self.addon(name, &block)
addons[name] = Class.new(AddonBase, &block)
end
end
This works fine for simple add-ons but if defining constants, they will be under Object:: instead of becoming addons[name]::CONSTANT:
App.addon "addon1" do
PATH="/var/run/foo"
def execute
File.touch(PATH)
end
end
App.addon "addon2" do
PATH="/etc/app/config"
def execute
File.unlink(PATH)
end
end
# warning: already initialized constant PATH
The constants could be anything and the add-ons could even define their own utility subclasses, so it's not just about replacing PATH with a function.
Is there some way to work around this?

When creating anonymous classes through Class.new, they don't seem to have their own namespace for constants
They do, you can use const_set to define constants in anonymous classes:
klass1 = Class.new do
const_set(:FOO, 'foo')
end
klass2 = Class.new do
const_set(:FOO, 'bar')
end
klass1::FOO #=> "foo"
klass2::FOO #=> "bar"
Or via self::
klass1 = Class.new do
self::FOO = 'foo'
end
klass2 = Class.new do
self::FOO = 'bar'
end
klass1::FOO #=> "foo"
klass2::FOO #=> "bar"

When creating anonymous classes through Class.new, they don't seem to have their own namespace for constants
Sure, by the definition of the word “anonymous.” Compare two following snippets:
class C1; puts "|#{name}|"; end
#⇒ |C1|
C2 = Class.new { puts "|#{name}|" }
#⇒ ||
Unless assigned to the constant, the class has no name and hence all constants defined inside go to Object namespace. That said, the warning here is actually pointing out to error and Object::FOO = "bar" overrides Object::FOO = "foo" constant.
That said, one cannot use constants in this scenario. Use class-level instance variables instead, or construct unique constant names manually (I would advise avoiding polluting Object class with a bunch of unrelated constants, though.)

Actually the problem is how to define a class using a proc including constant definitions. As has already been said it is not possible the way you did it, since the proc gets class_eval'd and that doesn't allow to define constants.
I suggest another approach. Can you use modules instead of procs to define new addons mixing a module into a class?
Example:
module AddonModule
FOO = "foo"
end
klass = Class.new
klass.include AddonModule
klass::FOO # => "foo"
Usage in your DSL:
def self.addon(name, addon_module)
addon = Class.new(AddonBase)
addon.include addon_module
addons[name] = addon
end

Related

Copy block constants into module

I am trying to define a DSL where some constants are defined within a block and must be copied into a fresh made module. I got this so far:
class Foo
def self.macros(&block)
mod = Module.new do
module_eval &block
end
self.const_set(:Macros, mod)
end
macros do
Point = Struct.new :x, :y
VALUE = 5
def self.bar
"bar"
end
def foo
"foo"
end
end
end
With the code above I managed to get Foo::Macros.bar however the constants are missing.
How can I get the constants defined within the block?
I want to access them through the new module embedded inside the class, like Foo::Macros::Point
Ruby constant lookup rules doesn't change with class_eval or module_eval, so you cannot make a constant defined in a block in Foo be part of Foo::Macros, sadly.
Simply:
Foo::Value
Foo::Point
Foo::Macros is just an alias for the anonymous module you defined, it doesn't change the scope of object defined.
If you define the module first, you can access it using const_get:
module Test
end
Test.module_eval do
ANSWER = 42
end
Test.const_get('ANSWER')
=> 42

Accessing the name of an anonymous class in superclass' self.inherited

I would like to access a class' name in its superclass MySuperclass' self.inherited method. It works fine for concrete classes as defined by class Foo < MySuperclass; end but it fails when using anonymous classes. I tend to avoid creating (class-)constants in tests; I would like it to work with anonymous classes.
Given the following code:
class MySuperclass
def self.inherited(subclass)
super
# work with subclass' name
end
end
klass = Class.new(MySuperclass) do
def self.name
'FooBar'
end
end
klass#name will still be nil when MySuperclass.inherited is called as that will be before Class.new yields to its block and defines its methods.
I understand a class gets its name when it's assigned to a constant, but is there a way to set Class#name "early" without creating a constant?
I prepared a more verbose code example with failing tests to illustrate what's expected.
Probably #yield has taken place after the ::inherited is called, I saw the similar behaviour with class definition. However, you can avoid it by using ::klass singleton method instead of ::inherited callback.
def self.klass
#klass ||= (self.name || self.to_s).gsub(/Builder\z/, '')
end
I am trying to understand the benefit of being able to refer to an anonymous class by a name you have assigned to it after it has been created. I thought I might be able to move the conversation along by providing some code that you could look at and then tell us what you'd like to do differently:
class MySuperclass
def self.inherited(subclass)
# Create a class method for the subclass
subclass.instance_eval do
def sub_class() puts "sub_class here" end
end
# Create an instance method for the subclass
subclass.class_eval do
def sub_instance() puts "sub_instance here" end
end
end
end
klass = Class.new(MySuperclass) do
def self.name=(name)
#name = Object.const_set(name, self)
end
def self.name
#name
end
end
klass.sub_class #=> "sub_class here"
klass.new.sub_instance #=> "sub_instance here"
klass.name = 'Fido' #=> "Fido"
kn = klass.name #=> Fido
kn.sub_class #=> "sub_class here"
kn.new.sub_instance #=> "sub_instance here"
klass.name = 'Woof' #=> "Woof"
kn = klass.name #=> Fido (cannot change)
There is no way in pure Ruby to set a class name without assigning it to a constant.
If you're using MRI and want to write yourself a very small C extension, it would look something like this:
VALUE
force_class_name (VALUE klass, VALUE symbol_name)
{
rb_name_class(klass, SYM2ID(symbol_name));
return klass;
}
void
Init_my_extension ()
{
rb_define_method(rb_cClass, "force_class_name", force_class_name, 1);
}
This is a very heavy approach to the problem. Even if it works it won't be guaranteed to work across various versions of ruby, since it relies on the non-API C function rb_name_class. I'm also not sure what the behavior will be once Ruby gets around to running its own class-naming hooks afterward.
The code snippet for your use case would look like this:
require 'my_extension'
class MySuperclass
def self.inherited(subclass)
super
subclass.force_class_name(:FooBar)
# work with subclass' name
end
end

Initializing instance variables in Mixins

Is there any clean way to initialize instance variables in a Module intended to be used as Mixin? For example, I have the following:
module Example
def on(...)
#handlers ||= {}
# do something with #handlers
end
def all(...)
#all_handlers ||= []
# do something with #all_handlers
end
def unhandled(...)
#unhandled ||= []
# do something with unhandled
end
def do_something(..)
#handlers ||= {}
#unhandled ||= []
#all_handlers ||= []
# potentially do something with any of the 3 above
end
end
Notice that I have to check again and again if each #member has been properly initialized in each function -- this is mildly irritating. I would much rather write:
module Example
def initialize
#handlers = {}
#unhandled = []
#all_handlers = []
end
# or
#handlers = {}
#unhandled = []
# ...
end
And not have to repeatedly make sure things are initialized correctly. However, from what I can tell this is not possible. Is there any way around this, besides adding a initialize_me method to Example and calling initialize_me from the extended Class? I did see this example, but there's no way I'm monkey-patching things into Class just to accomplish this.
module Example
def self.included(base)
base.instance_variable_set :#example_ivar, :foo
end
end
Edit: Note that this is setting a class instance variable. Instance variables on the instance can't be created when the module is mixed into the class, since those instances haven't been created yet. You can, though, create an initialize method in the mixin, e.g.:
module Example
def self.included(base)
base.class_exec do
def initialize
#example_ivar = :foo
end
end
end
end
There may be a way to do this while calling the including class's initialize method (anybody?). Not sure. But here's an alternative:
class Foo
include Example
def initialize
#foo = :bar
after_initialize
end
end
module Example
def after_initialize
#example_ivar = :foo
end
end
Perhaps this is a little hacky, but you can use prepend to get the desired behavior:
module Foo
def initialize(*args)
#instance_var = []
super
end
end
class A
prepend Foo
end
Here is the output from the console:
2.1.1 :011 > A.new
=> #<A:0x00000101131788 #instance_var=[]>
modules provides hooks, as Module#included. I suggest you check out ruby doc on the topic, or use ActiveSupport::Concern, which provides some helpers on modules.
I think there may be a simpler answer to this. The module should have an initializer that initialises the variables as you normally would do. In the initializer for the class that includes the module, invoke super() to invoke the initializer in the included module. This is simply following the method dispatch rules in Ruby.
On reflection, this will not work so well if the class including the module also has a superclass that needs to be initialised. The initializer in the module would need to accept a variable parameter list and pass this up to the superclass. It looks like a good avenue to explore though.

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)

How do you access an instance variable within a mixin method?

How do you access an instance variable within a mixin method? I can think of 2 ways, but both seem problematic.
Have the mixin method access the instance variable directly as any class method would, e.g self.text. Problem with this is that it places restrictions on where the mixin method can be used, and forces the class doing the mixing to have a particular instance method named in a particular way.
Pass the instance variable as a parameter to the mixin method, which would result in code like this:
example
self.do_something(self.text)
or
#thing.do_something(#thing.text)
which looks nasty to me, and doesn't conform to the principles of object orientation.
Is there any other way to do it?, am I right to be concerned?
In general, avoid having mixins access member variables: It's a very tight form of coupling that can make future refactoring unnecessarily difficult.
One useful strategy is for the Mixin to always access variables via accessors. So, instead of:
#!/usr/bin/ruby1.8
module Mixin
def do_something
p #text
end
end
class Foo
include Mixin
def initialize
#text = 'foo'
end
end
Foo.new.do_something # => "foo"
the mixin accesses the "text" accessor, which is defined by the including class:
module Mixin
def do_something
p text
end
end
class Foo
attr_accessor :text
include Mixin
def initialize
#text = 'foo'
end
end
Foo.new.do_something # => "foo"
What if you need to include the Mixin in this class?
class Foo
def initialize
#text = "Text that has nothing to do with the mixin"
end
end
Using generic and common data names in mixins can lead to conflicts when the including class uses the same name. In that case, have the mixin look for data with a less common name:
module Mixin
def do_something
p mixin_text
end
end
and let the including class define the appropriate accessor:
class Foo
include Mixin
def initialize
#text = 'text that has nothing to do with the mixin'
#something = 'text for the mixin'
end
def mixin_text
#something
end
end
Foo.new.do_something # => "text for the mixin"
In this way, the accessor acts as sort of "impedance matcher" or "translator" between the mix-in's data and the including class's data.
Instance variable names start in ruby with an # eg. #variable. You can acces them with this name from a Module you include
module M
def t
#t
end
end
class A
include M
def initialize(t)
#t= t
end
end
A.new(23).t # => 23
If you wan't to access #t when it's not defined in your class before you can do it this way
module M
def t
instance_variable_defined?("#t") ? #t : nil
end
end
You can provide this instance method yourself in this module, but you have to be careful not to overwrite existing method
Example (in module you are mixing in):
def text
#text ||= ""
end

Resources