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
Related
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
Assuming a module is included, not extended, what's difference between module instance variable and class variable?
I do not see any difference between two.
module M
#foo = 1
def self.foo
#foo
end
end
p M.foo
module M
##foo = 1
def self.foo
##foo
end
end
p M.foo
I have been using # as ## within a module, and I recently saw other codes are using ## within a module. Then I thought I might have been using it incorrectly.
Since we cannot instantiate a module, there must be no difference between # and ## for a module. Am I wrong?
----------------------- added the following --------------------
To answer some of questions on comments and posts, I also tested the following.
module M
#foo = 1
def self.bar
:bar
end
def baz
:baz
end
end
class C
include M
end
p [:M_instance_variabies, M.instance_variables] # [#foo]
p [:M_bar, M.bar] # :bar
c = C.new
p c.instance_variables
p [:c_instance_variabies, c.instance_variables] # []
p [:c_baz, c.baz] :baz
p [:c_bar, c.bar] # undefined method
When you include a module within a class, module class variables and class methods are not defined in a class.
Class variables can be shared between modules and classes where these modules are included.
module A
##a = 5
end
class B
include A
puts ##a # => 5
end
Meanwhile, instance variables belong to self. When you include module A into class B, self object of A is not the same as self object of B, therefore you will not be able to share instance variables between them.
module A
#a = 5
end
class B
include A
puts #a # => nil
end
## refers to class variable and # refers to instance variable. While using this with module makes big difference. When you include a module you add class methods to your class while extending add instance methods to your class
Following is a nice article on this
http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/
I found this SO post that talks about how to create class variables in modules, "they are supported natively." One commenter even says the name 'class variable' is misleading since classes are just modules with a few extra powers.
All I'm sure of is nearly every article I've read about class variables thinks they're evil and to avoid them at all costs because of the weird inheritance issues you can encounter.
I found an interesting problem: http://rubeque.com/problems/fixing-bad-code-the-wrong-way/solutions
Generally we have a simple class (notice that we don't have attr_accessor here):
class Foo
def itnialize(name)
self.foo = name
end
def set_bar
self.bar = 'it will fail..'
end
end
I thought that ruby will raise no method error when I call Foo.new but it passes without any problems. The code will fail when I try Foo.new.bar
How is it possible and how to access Foo.new.foo variable?
You have a typo and have miss-spelt initialize as itnialize so it won't be being called - so no error.
It looks like you're trying to create an instance variable - to do so you need, somewhere, to define it with the # prefix. So you might do:
def initialize(name)
#foo = name
end
which would then mean you are able to access #foo inside the class.
self.foo can only ever refer to a method foo, so you need to define that method if you want to call it, either explicitly or by using one of the attr variants.
However, in this case, you could just do
def set_bar
#bar = 'it will succeed!'
end
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? 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