I'm trying to understand why I need the scope resolution operator to access constants in an inherited module but not methods. Assuming:
module Foo
SOMETHING = {:one => 1, :two => 2}
def showit
p SOMETHING
end
end
and
class Bar
include Foo
def initialize
# why doesn't method name need scope resolution but const does?
showit
p Foo::SOMETHING
end
Also related, can you access instance or local variables declared in Foo from Bar? I tried something like this:
module Foo
#myar = "some string"
myvar2 = "some other string"
def showit
p #myvar
p myvar2
end
end
and calling showit from within the Bar class...printing #myvar resulted in nil and myvar2 was undefined. I would expect both strings to print...what's going on?
You actually don't have to scope the constant.
This should run just fine.
class Bar
include Foo
def initialize
showit
p SOMETHING # instead of Foo::SOMETHING
end
end
Bar.new
Related
I am working on a Rails application where in one class, constants are defined using const_set constant_name, value.
I know we can define constants directly in class by just specifying the CAPITALIZE name of the constant with its value like following
class A
RANDOM_CONSTANT = 1
end
so what is the difference between defining constants using const_set and simply the way I have declared in class A ?
class A
RANDOM_CONSTANT = 1
end
is simpler to write and read. This should be the preferred way to set a constant.
constant_name = 'RANDOM_CONSTANT'
A.const_set(constant_name, 1)
will work when the constant name is dynamically generated, and thus more flexible. You'd typically use it only when you want to do some metaprogramming magic.
Other than that, they are equivalent.
I can add to the answer from #amadan that this method, like an others, for example const_get, method_missing, define_method etc is part of tools for meta-programming in ruby.
For example:
#const_get
Let's say we have such module Foo with list of constants: BOO, BAR, BAZ
module Foo
BAR = 1
BAZ = 2
BOO = 3
end
now we need to print all of it, we can do it dynamically or manually:
def print_dynamically
Foo.constants.each do |const|
p Foo.const_get(const)
end
end
def print_manually
p Foo::BAR
p Foo::BAZ
p Foo::BOO
end
> print_dynamically
=> 1
=> 2
=> 3
> print_manually
=> 1
=> 2
=> 3
#method_missing
Let's say we have such class Foo and we want to add some friendly error catching for NoMethodError
class Foo
def boo
puts 'boo'
end
def method_missing(method_name)
puts "sorry, method: ##{method_name} not defined."
puts "Here is a list of public methods:"
# here we take all defined public methods from class Foo except `#method_missing`
p public_methods(false).reject { |n| n == :method_missing }
end
end
> foo = Foo.new
> foo.boo
=> boo
> foo.bar
=> sorry, method: #bar not defined.
=> Here is a list of public methods:
=> [:boo]
#define_method
Let's say we need to define some list of simple(or not) methods, and we don't want to just copy-paste it:
instead of:
class Foo
def foo
puts "foo"
end
def boo
puts "boo"
end
def bar
puts "bar"
end
end
> foo.foo
=> foo
> foo.boo
=> boo
> foo.bar
=> bar
you can simply write it in his way:
class Foo
%i[foo boo bar].each do |method_name|
define_method(method_name) { puts "#{method_name}" }
end
end
> foo.foo
=> foo
> foo.boo
=> boo
> foo.bar
=> bar
Summary:
From examples below you can see that Ruby provide us a lot of tools for dynamically creation of methods, getting of it, getting of constants and setting of it.
P.S.
These is far not all the methods, it's just a few of them, but I think it's enough to show that you can do with meta-programming tools in ruby.
As far as I know, there's no difference, so this:
class A; end
A.const_set('RANDOM_CONSTANT', 1)
is equivalent to:
class A
RANDOM_CONSTANT = 1
end
I would like to create a class that does the following:
Its instance accepts a block.
During instance initialization, it executes certain actions, then calls the block, then executes more actions.
Within the block, another method from that class should be available.
Here is how i want it to work:
Foo.new do
puts "Hell, I can get you a toe by 3 o'clock this afternoon..."
bar
puts "...with nail polish."
end
I have managed to achieve it with the following class:
class Foo
def initialize(&block)
puts "This represents a beginning action"
instance_eval &block
puts "This symbolizes an ending action"
end
def bar
puts "I should be available within the block."
end
end
As you see, i use the instance_eval trick. It enables using bar within the block.
It works fine, but the problem here is that instance_eval makes current local context unavailable. If i use it from within another class, i lose access to that class' methods. For example:
class Baz
def initialize
Foo.new do
bar # -> Works
quux # -> Fails with "no such method"
end
end
def quux
puts "Quux"
end
end
The question is: how do i allow executing bar within the block without losing access to quux?
The only way that comes to my newbie mind is passing bar as an argument into the block. But that requires more typing, so i would like to aviod that if possible.
instance_eval does not consider the scope of where the block is called, so every method call is only relative to what is defined inside Foo.
So you have 2 options. Either
def initialize
baz = self
Foo.new do
bar # -> Works
baz.quux # -> Works
end
end
or
def initialize
puts "This represents a beginning action"
yield self
puts "This symbolizes an ending action"
end
....
def initialize
Foo.new do |b|
b.bar # -> Works too
quux # -> Works too
end
end
I am not sure which one would be better performance wise, but the option you pick is based on your own preference.
It works fine, but the problem here is that instance_eval makes
current local context unavailable
instance_eval() does no such thing. The code inside all blocks, i.e something that looks like:
{ code here }
can see the variables that existed in the surrounding scope at the time the block was CREATED. A block cannot see the variables in the surrounding scope at the time the block is EXECUTED. In computer science jargon, a block is known as a closure because it 'closes over' the variables in the surrounding scope at the time it is created.
What instance_eval does do is assign a new value to the self variable that the block closed over. Here is an example:
puts self #=>main
func = Proc.new {puts self}
func.call #=>main
class Dog
def do_stuff(f)
puts self
f.call
end
end
d = Dog.new
d.do_stuff(func)
--output:--
#<Dog:0x000001019325b8>
main #The block still sees self=main because self was equal to main when the block was created and nothing changed the value of that self variable
Now with instance_eval:
class Dog
def do_stuff(f)
puts self
instance_eval &f
end
end
d = Dog.new
d.do_stuff(func)
--output:--
#<Dog:0x000001011425b0>
#<Dog:0x000001011425b0> #instance_eval() changed the value of a variable called self that the block `closed over` at the time the block was created
You also need to realize that when you call a method and you don't specify a 'receiver', e.g.
quux()
...then ruby converts that line to:
self.quux()
So, it is important to know the value of the variable self. Examine this code:
class Dog
def do_stuff(f)
puts self #Dog_instance
instance_eval &f #equivalent to self.instance_val &f,
#which is equivalent to Dog_instance.instance_eval &f
end
end
Because instance_eval() sets the value of the self variable inside the block to instance_eval()'s 'receiver', the value of self inside the block is set equal to a Dog_instance.
Examine your code here:
puts self #=> main
Foo.new do
puts self #=>main
bar #equivalent to self.bar--and self is not a Foo or Baz instance
#so self cannot call methods in those classes
end
Examine your code here:
class Foo
def initialize(&block)
instance_eval &block #equivalent to self.instance_eval &block
end
end
And inside Foo#initialize() self is equal to the new Foo instance. That means inside the block self is set equal to a Foo instance, and therefore if you write the following inside the block:
quux()
That is equivalent to:
self.quux()
which is equivalent to:
Foo_instance.quux()
which means quux() must be defined in Foo.
In this answer:
class Baz
def initialize
puts self #=>Baz_instance
baz = self
Foo.new do
bar # -> Works
baz.quux # -> Works
end
end
def quux
puts "Quux"
end
end
b = Baz.new
...the bar and baz lines seem to have identical 'receivers':
puts self #=>Baz_instance
baz = self #To evaluate that assignment ruby has to replace the variable self
#with its current value, so this is equivalent to baz = Baz_instance
#and baz no longer has any connection to a variable called self.
Foo.new do
bar #=> equivalent to self.bar, which is equivalent to Baz_instance.bar
baz.quux #=> equivalent to Baz_instance.quux
end
But when instance_eval() executes that block, which is everything between the do and end, instance_eval() changes the value of self:
Foo.new do #instance_eval changes self inside the block so that self = Foo_instance
bar #=> equivalent to self.bar which is now equivalent to Foo_instance.bar
baz.quux #=> the block still sees baz = Baz_instance, so equivalent to Baz_instance.bar
end
I am dynamically including a module into the Baz class in the foobarbaz method.
However, when I execute this in ruby, I get a nil puts. Doesn't the module have access to Foo's instance variables?
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
Baz.send(:include, Bar) # For Ruby < 2.1
# Baz.include(Bar) # For Ruby > 2.1
end
end
class Baz
end
module Bar
def foobar
#current_session
# 'foobar'
end
end
puts Foo.new('current_session').foobarbaz.new.foobar # nil
NOTE, for this, I was using Ruby 2.0.0. The following also does not puts desired result in Ruby 2.1.2.
Here is a meta programming for you :
#!/usr/bin/env ruby
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
session = current_session
Bar.module_eval { #current_session = session }
Baz.send(:include, Bar)
end
end
module_eval says
Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected....
Thus inside Bar.module_eval { #current_session = session }, #current_session is the instance variable of Bar only and I am setting the value of it to the instance variable value of the class Foo, which is #current_session.
Baz.send(:include, Bar) is helpfull, which returns class/module itself, which is including the other module. include(module, ...) → self.
class Baz
end
Read this post to understand the below stuff.
module Bar
class << self
attr_reader :current_session
end
def foobar
Bar.current_session
end
end
puts Foo.new('current_session').foobarbaz.new.foobar
# >> current_session
Update
As #Christian Fazzin gave a good suggestion :-
If you want Bar module to have write method also, then you have to do 2 changes -
Bar should contain then attr_accesor :current_session, instead of what it has now.
You don't need to use the power of module_eval there, rather use syntactic sugraness of write methods, like put Bar.current_session = current_session inside the method foobarbaz . Remove the lines session = current_session and Bar.module_eval { #current_session = session }.
After creating an instance of Foo (foo, say) and in doing so initializing foo's instance variable #current_session to 'current session', it appears to me that you want foo.foobarbaz to do the following:
cause Baz to include the module Bar
create an instance of Baz (baz, say)
create an instance variable named #current_session for baz and assign it the value of foo's instance variable of the same name
invoke baz.foobar to return the value of baz's instance variable #current_session.
If my understanding is correct, we can perform these four steps with four lines in Foo#foobarbaz:
class Baz
end
module Bar
def foobar
#current_session + ' in Baz'
end
end
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
Baz.include(Bar)
baz = Baz.new
baz.instance_variable_set(:#current_session, self.current_session)
baz.foobar
end
end
foo = Foo.new('current session')
foo.foobarbaz
#=> "current session in Baz"
I've slightly modified what foobarbaz returns to show where it is coming from.
Note that the third line of foobarbaz could be changed to either of the following
baz.instance_variable_set(:#current_session, #current_session)
baz.instance_variable_set(:#current_session,
instance_variable_get(:#current_session))
If the latter of these were used, #current_session's accessor would not be needed.
You'd just need to set instance variable (not class instance variable!) #current_session of class Baz.
With slightest modification of your code without need of additional class/module methods the most straightforward way is to define initialization method that sets the required variable:
class Foo
attr_accessor :current_session
def initialize(current_session)
#current_session = current_session
end
def foobarbaz
# define Baz#initialize on-the-fly, alternatively with define_method
Baz.class_eval "def initialize; #current_session = '#{#current_session}';end"
Baz.send(:include, Bar) # For Ruby < 2.1
end
end
class Baz
end
module Bar
def foobar
#current_session
# 'foobar'
end
end
puts Foo.new('current_session').foobarbaz.new.foobar
# current_session
If I declare #var in Ruby, every object of that class would have its own #var.
But what if I miss #? I mean, I declare a variable called var2 without #. Do they share the variable or it is temporarily created?
When variable is declared without scope prefix (# - instance, ## - class or $ - global) then is declared for current scope, i.e:
class Foo
def boo
#boo ||= 'some value'
var ||= 40
puts "boo: ##boo var: #{var}"
end
def foo
var ||= 50
puts "boo: ##boo var: #{var}"
end
end
c = Foo.new
c.boo # => boo: some value var: 40
c.foo # => boo: some value var: 50
def foo
$var ||= 30
puts "$var: #$var"
end
foo # => $var: 30
puts "$var: #$var" # => $var: 30
%w[some words].each do |word|
lol = word # blocks introduce new scope
end
puts lol # => NameError: undefined local variable or method `lol'
for word in %w[some words]
lol = word # but for loop not
end
puts lol # => words
Without an # it's discarded when the method it's in is done running.
class Foo
def initialize
#bing = 123
zing = 456
end
def get_bing
#bing
end
def get_zing
zing
end
end
foo = Foo.new
foo.get_bing #=> 123
foo.get_zing #=> NameError: undefined local variable or method `zing' for #<Foo:0x10b535258 #bing=123>
This shows that an instance variable #bing is saved with that instance. It's value is accessible within any method on that instance.
But a local variable zing is not persisted (in most cases), and as soon as the method is done running any local variables are discarded, and no longer accessible. When get_zing is run, it looks for a local variable or method named zing and doesn't find one, because the zing from initialize is long gone.
It will become a local variable that belongs to the local lexical scope.
Ex.
class Foo
def Bar
#fooz = 1
end
def Barz
fooz = 2
end
def test
puts #fooz
end
end
f = Foo.new
f.Bar
f.test
f.Barz
f.test
Output:
1
1 #not 2
If you use a variable called var2 it is local and is only in scope in the construct where it is declared. That is:
If you declared it within a method, it will be local to that method.
If you try to declare within a class definition but outside any method, it will raise a NameError. For example:
class Foo
bar = 2
end
Foo.new
NameError: undefined local variable or method 'bar'
Aren't they supposed to do the same thing? Why is this happening? At this point, I'm using the module_eval in my code out of necessity, but the const_set seems more readable. Regardless, I'd really like to understand why this happens.
here's the code:
class A
def foo
FOO
end
def self.foo
FOO
end
end
module B
class C < A
end
end
B.const_set(:FOO,'asdf')
>> B::C.foo
NameError: uninitialized constant A::FOO
from ./foo.rb:6:in `foo'
from (irb):1
>> B.module_eval {FOO='asdf'}
=> "asdf"
>> B::C.foo
=> "asdf"
Your module_eval isn't actually putting the constant in the module. Then, you're just accessing it from main:
module B;end
B.module_eval { FOO = 'asdf' }
>> FOO
=> "asdf"
You can fix that with self::FOO = 'asdf', then it's the same as B.const_set(:FOO,'asdf'). You can also do it more directly like this:
B::FOO = 'asdf'
The main problem with your code is that you can't access constants from other modules like that. If they're in an outer module, you need to specify the scope of the constant with the :: prefix:
def foo
B::FOO
end