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"
Related
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
Consider that I have a module XYZ. When included in a class, it extends the base class and adds a class variable ##foo inside it. It also extends the class with the methods to do some get and set.
module XYZ
def self.included(base)
base.send :extend, ClassMethods
base.class_eval do
##foo ||= []
end
end
module ClassMethods
def foo
class_variable_get("##foo")
end
def foo=(arg)
class_variable_set("##foo", arg)
end
def dynamic_set(*args)
foo += args # This doesn't work
end
def dynamic_set_2(*args)
class_variable_set("##foo", class_variable_get("##foo") + args)
end
end
end
Now, consider the usage:
class A
include XYZ
end
A.foo #=> []
A.class_variable_get("##foo") #=> []
A.dynamic_set 1, 2 #=> NoMethodError: undefined method `+' for nil:NilClass
A.dynamic_set_2 1, 2 #=> [1,2]
A.foo #=> [1,2]
A.class_variable_get("##foo") #=> [1,2]
The snippet makes sense and gets the work done, but I'm not able to figure out why A.dynamic_set 1, 2 didn't work.
Coming to the main part of the question - If I define a new class B as:
class B
include XYZ
end
B.foo #=> [1,2] => Why? How did B.foo get these values?
B.class_variable_get("##foo") #=> [1,2] => Why?
B.dynamic_set_2 3, 4
B.foo #=> [1,2,3,4]
A.foo #=> [1,2,3,4]
Why are B and A sharing the same class variable when ##foo is defined on the class level (with class_eval)?
I understand about implications of using class variable and class instance variables. Just trying to figure out why this doesn't work as intended, to clear some concepts :)
I'm not able to figure out why A.dynamic_set 1, 2 didn't work.
Use an explicit receiver when calling setters:
def dynamic_set(*args)
foo += args # This doesn't work
self.foo += args # This DOES work
end
Coming to the main part of the question
TL;DR: Don’t use class variables. Use instance variables at class level.
module XYZ
def self.included(base)
base.send :extend, ClassMethods
base.class_eval do
#foo ||= []
end
end
module ClassMethods
def foo
instance_variable_get("#foo")
end
def foo=(arg)
instance_variable_set("#foo", arg)
end
def dynamic_set(*args)
self.foo += args # This doesn't work
end
def dynamic_set_2(*args)
class_variable_set("#foo", instance_variable_get("#foo") + args)
end
end
end
It’s worth to mention it in the answer.
module XYZ
def self.included(base)
base.class_eval { ##foo ||= [] }
end
end
The code above pollutes the XYZ class variables with ##foo because ##foo is hoisted in XYZ module.
base.class_variable_set(:##foo, []) instead would not pollute XYZ.
How can I dynamically and easily insert code into the beginning of each method of a class and subclasses without actually inserting it manually? I want something like a macros.
class C1
def m1
#i_am = __method__
end
def m2
#i_am = __method__
end
end
This is one of the examples where I want to avoid repetition.
I initially misinterpreted the question (but have left my original answer after the horizontal line below). I believe the following may be what you are looking for.
class C1
[:m1, :m2].each do |m|
define_method(m) do |name|
#i_am = __method__
puts "I'm #{name} from method #{#i_am}"
end
end
end
C1.instance_methods(false)
#=> [:m1, :m2]
c1 = C1.new
#=> #<C1:0x007f94a10c0b60>
c1.m1 "Bob"
# I'm Bob from method m1
c1.m2 "Lucy"
# I'm Lucy from method m2
My original solution follows.
class C1
def add_code_to_beginning(meth)
meth = meth.to_sym
self.class.send(:alias_method, "old_#{meth}".to_sym, meth)
self.class.send(:define_method, meth) do
yield
send("old_#{meth}".to_sym)
end
end
end
Module#alias_method
and Module#define_method are private; hence the need to use send.
c = C1.new
#=> #<C1:0x007ff5e3023650>
C1.instance_methods(false)
#=> [:m1, :m2, :add_code_to_beginning]
c.add_code_to_beginning(:m1) do
puts "hiya"
end
C1.instance_methods(false)
#=> [:m1, :m2, :add_code_to_beginning, :old_m1]
c.m1
# hiya
#=> :m1
You can use a rails like class decorators to that. The piece of code below is rendering a method called before_action defined in the Base class of the ActiveRecord module. The Test class is inherited from the ActiveRecord. The define_method is used if we want to call something explicitly from the Base class.
module ActiveRecord
class Base
def self.before_action(name)
puts "#{name}"
puts "inside before_action of class Base"
define_method(name) do
puts "Base: rendering code from Base class"
end
end
end
end
class Test < ActiveRecord::Base
before_action :hola
def render()
puts "inside render of class Test"
end
end
test = Test.new
test.render
test.hola
It has the output
hola
inside before_action of class Base
inside render of class Test
Base: rendering code from Base class
So, before running the render method it runs the before_action method in the Base class. It can be applied to all other methods in the Test class. This is a way of representing macros in ruby.
Assuming that the function is the same, you could create a module and include it in your classes.
Example:
module MyModule
def test_method
puts "abc"
end
end
class MyClass
include MyModule
def my_method
puts "my method"
end
end
inst = MyClass.new
inst.test_method # => should print "abc"
inst.my_method # => should print "my method"
I want to create a ruby module that, when included by a class, sets up a class variable with a default value that can be overridden in the class definition. I've been twisting myself in knots with metaprogramming trying to get it to work but I just can't get it right.
What I Want:
class A
include Nickname
end
A.nickname # => 'A'
A.new.nickname # => 'A'
class B
include Nickname
set_nickname "Bubba"
end
B.nickname # => 'Bubba'
B.new.nickname # => 'Bubba'
class BB < B
end
BB.nickname # => 'Bubba'
BB.new.nickname # => 'Bubba'
A.nickname # => 'A'
A.new.nickname # => 'A'
I tried
module Nickname
def self.included(klass)
klass.class_eval <<-EOS
##nickname = "#{klass.name}"
def self.nickname
##nickname
end
def nickname
##nickname
end
def self.set_nickname(new_nick)
##nickname = new_nick
end
EOS
end
end
but that set the nickname on the Nickname module, so every class that included the module would have the same Nickname (whatever was set last). Turns out the code was right. After 7stud pointed that out, I reviewed my work. It seems that because I was doing my testing in irb and had previously defined Nickname several other ways, I got the undesirable behavior. In fact, on a fresh load of irb, the above code works exactly like I want and expected. Sorry about that.
Then I tried using a class instance variable
module Nickname
def self.included(klass)
klass.send(:define_singleton_method, :nickname) do
#nickname
end
klass.send(:define_method, :nickname) do
klass.nickname
end
klass.send(:define_singleton_method, :set_nickname) do |new_nick|
#nickname = new_nick
end
klass.set_nickname klass.name
end
end
That almost worked, but it uses a Class Instance Variable, so BB.nickname is nil.
I've tried various combinations of class_eval and instance_eval inside self.included and I cannot get it all to work together. If I manage to get the value stored as a class variable in the including class, I can't get the class to be able to override the value and/or I cannot set a default value.
EDIT
Moral 1 of the story, be careful about doing metaprogramming in irb. It's quite possible your failed experiments will ruin your next experiments.
Moral 2 of the story is that if you think class variables are strange, look at class instance variables with inheritance and instance_variable_set/get. When a subclass inherits the superclass' class instance variable, the variable is still attached to the superclass part of the subclass instance, so you cannot access it via #var in the subclass, and you can't access from a superclass function that accesses #var, but, as 7stud illustrates, you can access it from a superclass function that calls instance_variable_get on itself.
...but that set the nickname on the Nickname module...
I'm not seeing that:
module Nickname
def self.included(klass)
klass.class_eval %Q{
##nickname = #{name}
def self.set_nickname(new_nick)
##nickname = new_nick
end
}
end
end
class A
include Nickname
end
p Nickname.class_variables(false)
p A.class_variables(false)
A.set_nickname("Joe")
p Nickname.class_variables(false)
p A.class_variables(false)
--output:--
[]
[:##nickname]
[]
[:##nickname]
every class
that included the module would have the same Nickname (whatever was
set last).
No, include isn't the culprit:
module Nickname
def self.included(includer)
includer.class_eval %Q{
##nickname = #{name}
def self.set_nickname(new_nick)
##nickname = new_nick
end
def self.nickname
##nickname
end
}
end
end
class A
include Nickname
end
A.set_nickname("Joe")
p A.nickname
class B
include Nickname
end
B.set_nickname("Sally")
p B.nickname
p A.nickname
--output:--
"Joe"
"Sally"
"Joe"
Rather, it's inheritance that's the problem:
module Nickname
def self.included(includer)
includer.class_eval %Q{
##nickname = #{name}
def self.set_nickname(new_nick)
##nickname = new_nick
end
def self.nickname
##nickname
end
}
end
end
class A
include Nickname
end
A.set_nickname("Joe")
p A.nickname
class B < A
end
B.set_nickname("Sally")
p B.nickname
p A.nickname
--output:--
"Joe"
"Sally"
"Sally"
An ##variable is shared by all the classes in an inheritance hierarchy, which is what Sergio Tulentsev was trying to warn you about in the comments. You probably don't want to use an ##variable (nobody does). Here it is with a class instance variable, which doesn't suffer that problem:
module Nickname
def self.included(includer)
includer.class_eval do
#nickname = includer.name #Instance variables attach themselves to whatever object is self, and class_eval sets self to the receiver, i.e. includer
define_method(:nickname) do #Using define_method() with a block makes the includer variable visible inside the nickname() method.
includer.instance_variable_get(:#nickname)
end
end
includer.singleton_class.class_eval do
define_method(:nickname) do
includer.instance_variable_get(:#nickname)
end
define_method(:set_nickname) do |new_val|
includer.instance_variable_set(:#nickname, new_val)
end
end
end
end
class A
include Nickname
end
p A.nickname
p A.new.nickname
class B
include Nickname
set_nickname "Bubba"
end
p B.nickname
p B.new.nickname
class BB < B
end
p BB.nickname
p BB.new.nickname
--output:--
"A"
"A"
"Bubba"
"Bubba"
"Bubba"
"Bubba"
Then you can set the class instance variable in BB, and it won't affect B's class instance variable:
...
...
class BB < B
include Nickname
set_nickname "Bubba Bubba"
end
p BB.nickname
p BB.new.nickname
p B.nickname
p B.new.nickname
--output:--
...
...
"Bubba Bubba"
"Bubba Bubba"
"Bubba"
"Bubba"
Module#class_variable_set comes to the rescue:
▶ module A
▷ def self.included base
▷ base.class_variable_set :##nickname, 'mudasobwa'
▷ class << base
▷ def nickname # extended class method
▷ class_variable_get :##nickname
▷ end
▷ end
▷ end
▷ end
▶ class B
▷ include A
▷ def nickname # instance method, might be put in `A`
▷ ##nickname
▷ end
▷ end
▶ B.nickname
#⇒ "mudasobwa"
▶ B.new.nickname
#⇒ "mudasobwa"
▶ B.class_variable_set :##nickname, 'google'
#⇒ "google"
▶ B.nickname
#⇒ "google"
▶ B.new.nickname
#⇒ "google"
Hope it helps.
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