So I have three different ways I can have class level methods on a module:
I usually do it this way if I have 3 or fewer:
module Foo
def self.speak
"Foo"
end
end
If I have more, I've traditionally done it this way:
module Bar
class << self
def speak
"Bar"
end
end
end
But recently, I came across this nifty way and have started using this more often:
module FooBar
extend self
def speak
"FooBar"
end
end
They all work:
Foo.speak => "Foo"
Bar.speak => "Bar"
FooBar.speak => "FooBar"
So, is there any material differences or gotchas to be aware of, esp. with the last form? The only real gotcha I can think of is that once you "extend self" all method defs following are class-level.
I've tried to think of some edge cases where one form works, but the other doesn't. Are there any?
The third form, unlike the first two, creates both an instance method :speak and a module method :speak:
module Foo
def self.speak
"Foo"
end
end
Foo.methods.include?(:speak) #=> true
Foo.instance_methods.include?(:speak) #=> false
class A
include FooBar
end
A.instance_methods.include?(:speak) #=> false
module Bar
class << self
def speak
"Bar"
end
end
end
Bar.methods.include?(:speak) #=> true
Bar.instance_methods.include?(:speak) #=> false
class A
include Bar
end
A.instance_methods.include?(:speak) #=> false
module FooBar
extend self
def speak
"FooBar"
end
end
FooBar.methods.include?(:speak) #=> true
FooBar.instance_methods.include?(:speak) #=> true
class A
include FooBar
end
A.instance_methods.include?(:speak) #=> true
Related
When working in a class defined in a module, I'd like to access another class defined in the same module.
When using this code
module SomeModule
class Foo
def self.get_bar
Bar
end
end
end
module SomeModule
class Bar
end
end
# works and returns SomeModule::Bar
SomeModule::Foo.get_bar
Looking up Bar from SomeModule::Foo works. It is not found in local scope SomeModule::Foo, so it looks one level up to SomeModule and finds SomeModule::Bar.
But when using the shortcut notation class A::B to define the classes, the lookup does not work anymore:
module SomeModule
end
class SomeModule::Foo
def self.get_bar
Bar
end
end
class SomeModule::Bar
end
# does not work and raises a NameError
SomeModule::Foo.get_bar
It produces the error NameError: uninitialized constant SomeModule::Foo::Bar. But for me both codes look identical and should produce the same output. I'm obvisouly missing a key concept here.
Can someone explain why the lookup works in one case and not the other? And is it possible to know in advance if the lookup will work or fail by introspecting the class?
There is an awesome post explaining how it works in detail. The summary will be that Ruby's constant lookup is based on lexical scope.
There is a method Module.nesting which returns an array of constants where Ruby is looking for a required const first.
module SomeModule
class Buzz
def self.get_bar
p Module.nesting #=> [SomeModule::Buzz, SomeModule]
Bar
end
end
end
class SomeModule::Foo
def self.get_bar
p Module.nesting #=> [SomeModule::Foo]
Bar rescue puts "Oops, can not find it"
end
end
class SomeModule::Bar
end
SomeModule::Buzz.get_bar
SomeModule::Foo.get_bar
Ruby constant lookup is based on the lexical scope, and these two examples are quite different with regards to that. Take a look:
module SomeModule
puts Module.nesting.inspect #=> [SomeModule]
puts Module.nesting.map(&:constants).inspect # => [[]], we didn't define Foo yet
class Foo
puts Module.nesting.inspect #=> [SomeModule::Foo, SomeModule]
puts Module.nesting.map(&:constants).inspect #=> [[], [:Foo]], at this point SomeModule is already "aware" of Foo
def self.get_bar
Bar
end
end
end
module SomeModule
puts Module.nesting.inspect #=> [SomeModule]
puts Module.nesting.map(&:constants).inspect #=> [[:Foo]], we didn't define Bar yet
class Bar
puts Module.nesting.inspect #=> [SomeModule::Bar, SomeModule]
puts Module.nesting.map(&:constants).inspect #=> [[], [:Foo, :Bar]]
end
end
and the second one
module SomeModule
puts Module.nesting.inspect #=> [SomeModule]
puts Module.nesting.map(&:constants).inspect #=> [[]]
end
class SomeModule::Foo
puts Module.nesting.inspect #=> [SomeModule:Foo]
puts Module.nesting.map(&:constants).inspect #=> [[]]
def self.get_bar
Bar
end
end
class SomeModule::Bar
puts Module.nesting.inspect #=> [SomeModule:Bar]
puts Module.nesting.map(&:constants).inspect #=> [[]]
end
As you see, Bar in 1st and 2nd case is being resolved in quite different lexical scopes which in turn leads to quite different result.
Regarding your question
is it possible to know in advance if the lookup will work or fail by introspecting the class
It is possible for isolated piece of code, but application-wide I wouldn't rely on that. When in doubt just specify the nesting explicitly starting from the outermost context (::SomeModule::Bar)...
Two modules Foo and Baa respectively define a method with the same name name, and I did include Foo and include Baa in a particular context.
When I call name, how can I disambiguate whether to call the name method of Foo or Baa?
Only the order of modules inclusion decides which one will get called. Can't have both with the same name - the latter will override the former.
Of course, you can do any tricks, just from the top of my head:
module A
def foo
:foo_from_A
end
end
module B
def foo
:foo_from_B
end
end
class C
def initialize(from)
#from = from
end
def foo
from.instance_method(__method__).bind(self).call
end
private
attr_reader :from
end
C.new(A).foo #=> :a_from_A
C.new(B).foo #=> :a_from_B
But that's no good for real life use cases :)
Technically, there is no name collision because the method foo is redefined.
In the following exemple, A.foo is redefined and is never called
module A
def foo
raise "I'm never called"
end
end
module B
def foo
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_B
If you write A and B module, you can use super to call previous definition of foo. As if it where an inherited method.
module A
def foo
puts :foo_from_A
end
end
module B
def foo
super
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_A
# foo_from_B
There are side effects and I would not use this but this is doing the trick :
module A
def foo
puts :foo_from_A
end
end
module B
def foo
puts :foo_from_B
end
end
class C
def self.include_with_suffix(m, suffix)
m.instance_methods.each do |method_name|
define_method("#{method_name}#{suffix}", m.instance_method(method_name))
end
end
include_with_suffix A, "_from_A"
include_with_suffix B, "_from_B"
end
c= C.new
c.foo_from_A
c.foo_from_B
begin
c.foo
rescue NoMethodError
puts "foo is not defined"
end
# =>
# foo_from_A
# foo_from_B
# foo is not defined
Provided none of the methods of Foo or Baa call name (which seems a reasonable assumption), one can simply create aliases.
module Foo
def name; "Foo#name"; end
end
module Baa
def name; "Baa#name"; end
end
class C
include Foo
alias :foo_name :name
include Baa
alias :baa_name :name
undef_method :name
end
c = C.new
c.foo_name
#=> "Foo#name"
c.baa_name
#=> "Baa#name"
C.instance_methods & [:foo_name, :baa_name, :name]
#=> [:foo_name, :baa_name]
The keyword alias is documented here. One may alternatively use the method #alias_method. See this blog for a comparison of the two.
Module#undef_method is not strictly necessary. It's just to ensure that an exception is raised if name is called.
You should definetely read about method lookups.
Anyway, I would do it this way:
module Foo
def name
:foo
end
end
module Bar
def name
:bar
end
end
class MyClass
include Foo
include Bar
def foo_name
Foo.instance_method(:name).bind(self).call
end
def bar_name
Bar.instance_method(:name).bind(self).call
end
#
# or even like this: obj.name(Foo)
#
def name(mod)
mod.instance_method(:name).bind(self).call
end
end
BTW if you are using Module#instance_method and UnboundMethod#bind you don't really need to include specific module. This code works:
Foo.instance_method(:name).bind('any object (e.g. string)').call
Is there a concise way to limit method visibility within the module while including it? In other words, I'd like to limit polluting the class with helper methods only used in the included module.
module Bar
def call
hide_me
end
private
# make this method only callable within this module
def hide_me
'visible'
end
end
class Foo
include Bar
def unrelated_method
hide_me
end
end
# that's ok
Foo.new.call #=> 'visible'
# that's not
Foo.new.unrelated_method #=> 'visible'
I'm ok with calling it via Bar.instance_method(:hide_me).bind(self).call, I just don't want to worry about accessing or redefining a helper method from some module.
You can wrap a class into the module and use private methods within the class, like so:
module Bar
def call
BarClass.new.call
end
class BarClass
def call
hide_me
end
private
def hide_me
puts "invisible"
end
end
end
class Foo
include Bar
def call_bar
call
end
def this_method_fails
hide_me
end
end
One can do what you want by including the module to the class and then undefining the unwanted included methods.
First construct a module.
module M
def cat
puts "meow"
end
def dog
puts "woof"
end
def owl
puts "who?"
end
private
def frog
puts "ribbit"
end
def pig
puts "oink"
end
end
Confirm the methods now exist.
M.instance_methods(false)
#=> [:cat, :dog, :owl]
M.private_instance_methods(false)
#=> [:frog, :pig]
Create the class, including the module M.
class C
def froggie
frog
end
def cat
puts "meow-meow"
end
include M
end
Check the instance methods.
C.instance_methods & [:cat, :dog, :owl, :froggie]
#=> [:cat, :froggie, :dog, :owl]
C.private_instance_methods & [:frog, :pig]
#=> [:frog, :pig]
and confirm :cat is owned by C and not by M.
C.instance_method(:cat).owner
#=> C
Now use the method Module#undef_method to undefine the unwanted methods from the module.
class C
[:cat, :owl, :pig].each { |m|
undef_method(m) unless instance_method(m).owner == self }
end
The unless... clause is needed so that the instance method :cat defined in C is not undefined.
Confirm the methods were undefined.
C.instance_methods & [[:cat, :dog, :owl, :froggie]
#=> [:cat, :froggie, :dog]
C.private_instance_methods & [:frog, :pig]
#=> [:frog]
Execute the methods.
c = C.new
c.cat
#=> "meow-meow"
c.dog
#=> "woof"
c.froggie
#=> "ribbit"
Is it possible to temporarily apply certain methods to a class for tests? I want to be able to run specs depending on many ways to apply it. While I could make a bunch of fixtures with different settings, I find it easier to just class_eval the model in the tests. For example:
describe "some context"
before do
Page.class_eval do
my_applying_method :some => :option
end
end
it "should..."
end
Then in another context block:
describe "another context without the gem applied"
before do
Page.class_eval do
# nothing here since I want to page to be as is
end
end
it "should do something else..."
end
But the problem with the last context block is that it has a modified class (modified in the context block above). Is it possible to reset a class after class_eval? How?
Thanks!
I hope there is a better way to do that but you can use this(and it's with a warning at the Foo = Foo_old line):
module Bar
def baz
end
end
class Foo
end
puts Foo.method_defined? :baz #=> false
Foo_old = Foo.dup # create a copy of our class
Foo.class_eval do
include Bar
end
puts Foo.method_defined? :baz #=> true
Foo = Foo_old
puts Foo.method_defined? :baz #=> false
You haven't clarified how you are modifying the class.
The remix library allows you to temporarily include a module and properly uninclude it later.
In general, it's probably safest to just duplicate the class and test the duplicate:
irb(main):001:0> class Foo; end
#=> nil
irb(main):002:0> Foo.dup.class_eval{ #x = 42 }
#=> 42
irb(main):003:0> Foo.class_eval{ #x }
#=> nil
I am trying to define some classes in Ruby that have an inheritance hierarchy, but I want to use one of the methods in the base class in the derived class. The twist is that I don't want to call the exact method I'm in, I want to call a different one. The following doesn't work, but it's what I want to do (basically).
class A
def foo
puts 'A::foo'
end
end
class B < A
def foo
puts 'B::foo'
end
def bar
super.foo
end
end
Probably, this is what you want?
class A
def foo
puts 'A::foo'
end
end
class B < A
alias bar :foo
def foo
puts 'B::foo'
end
end
B.new.foo # => B::foo
B.new.bar # => A::foo
A more general solution.
class A
def foo
puts "A::foo"
end
end
class B < A
def foo
puts "B::foo"
end
def bar
# slightly oddly ancestors includes the class itself
puts self.class.ancestors[1].instance_method(:foo).bind(self).call
end
end
B.new.foo # => B::foo
B.new.bar # => A::foo