When I call self.class.instance_variable_set("#var", ...) from inside a class method, where is that variable actually stored? Is it on the class itself? On the instance of that class? I can't seem to find it with any of the following:
e = Example.new
e.instance_variables
e.class.class_variables
I even tried using the (class << self; self; end) trick but I can't find anything (http://ruby-metaprogramming.rubylearning.com/html/seeingMetaclassesClearly.html).
Here is the code snippet (which works as I need) but I'm not sure why it works :)
module HandyModule
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def use_template(template_name)
self.class.instance_variable_set("#_template", template_name)
end
def build(attributes = {})
template_name = self.class.instance_variable_get("#_template")
# do stuff with the template
end
end
end
class Example
include HandyModule
use_template :contact_form
end
Essentially I can include this handy module, and then I have a class method called use_template with which I can specify which template to be used by my build method.
When you call use_template inside the class definition, the self is the class Example. When you call self.class, it is Example.class, or Class. You define the instance variable to the class of the classes.
class Class
p #_template
end
# prints :contact_form
You probably should use just self instead of self.class.
Related
Before voting for closing due to question duplication I want to say that my question is really simple one (not asked in above mentioned questions).
There are two modules, one defines module method using extend self, another defines mixin method.
module A
extend self
def module_a_meth
"Called module_a_meth"
end
end
module B
def module_b_meth
"Called module_b_meth"
end
end
There is a class, where I both include and extend these modules:
class Test
include A
extend A
include B
extend B
end
When we includeing module, its methods become class' instance methods, when extending - class methods.
Question:
it doesn't matter for class, if methods in module defined as module methods or mixin methods, right? I mean, when included - EVERY method (either module methods or mixin methods) become instance methods, and when extended - either become class methods.
If I'm wrong - where is the difference?
obj = Test.new
puts obj.module_a_meth
puts obj.module_b_meth
puts Test.module_a_meth
puts Test.module_b_meth
#=> Called module_a_meth
#=> Called module_b_meth
#=> Called module_a_meth
#=> Called module_b_meth
EDIT
Please start your answer with Yes or No, since my question implies this type of answer :).
Regardless of whether you are using extend or include you are always copying over instance methods. The difference is where those instance methods live.
When you call Class#include you are "copying" all of the instance methods in the module to be instance methods in the class. It's similar to how inheritance work, and if you call Class#ancestors you'll see the module there.
When you call Object#extend you are copying all of the instance methods of the module to the object's singleton class. This is a class reserved just for this object instance that is created at runtime. This is how you get "class methods" (e.g. MyClass.hello_world); by adding them to the class's singleton. You can also do things like extend a particular object instance (e.g. s = String.new; s.extend(SomeModule); s.hello_world)
There are some other differences too. The context binding is different depending on whether you use extend or include. extend doesn't cause the module to show up in the ancestor chain while include does.
When trying to add both "class" and instance methods, one common pattern you'll see is doing things like this which uses the included callback to extend the base class with a ClassMethods module:
module MyModule
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def hello_world
end
end
end
ActiveSupport::Concerns also abstracts this pattern allowing you to add both instance and "class" methods in one call.
I personally prefer having modules only work with instance methods and using singleton methods (e.g. def self.my_method) to have scoped methods (sort of like how you would use private methods). This allows consumers to use either extend or include however they want and have it work as expected.
I'm not sure if that answers your question or not, but there's some info for you
Let's look at this in steps.
module A
puts "self = #{self}"
extend self
def module_a_meth
"Called module_a_meth"
end
end
class Test
end
Test.include A
#-> self = Test
Test.instance_methods.include?(:module_a_meth)
#=> true
Test.methods.include?(:module_a_meth)
#=> false - no class method
So include includes :module_a_meth as an instance method. As self is Test, the line:
extend self
is equivalent to:
extend Test
which of course makes no reference to the module. Now we extend and obtain the expected result:
Test.extend A
#=> true
Test.methods.include?(:module_a_meth)
#=> true
including and extending B is normal:
module B
def module_b_meth
"Called module_b_meth"
end
end
Test.include B
Test.instance_methods.include?(:module_b_meth)
#=> true
Test.extend B
Test.methods.include?(:module_b_meth)
#=> true
First of all, regarding the actual question: No :).
Class (or any other object) cares how methods are defined in a module you're including. Basically, method's in a module you've described are defined as mixin methods. extend self doesn't redefine methods to be a module methods, but, basically, duplicates them to both contexts.
It's pretty much a question about how does extend work, it's just a tricky case.
First of all, think of extend as an include in object's singleton class context. Those two definitions are equal:
module SomeModule
def hi
'hi'
end
end
class SomeClass
extend SomeModule
end
class SomeClass
class << self
include SomeModule
end
end
Given that, by using extend self in a module you're saying: Take all of the mixin methods I've defined and extend module's singleton class with them. This magic is a result of ruby's nature: an ability to re-open any definition. Here's how a verbose version of extend self would look like:
module Module1
def hi
'hi'
end
end
module Module1
extend Module1 # which is self
#### now "hi" is both here:
# def hi; end
#### and here:
# class << self; def hi; end
end
Module1.hi # => 'hi'
class SomeClass; include Module1; end;
SomeClass.new.hi # => 'hi'
__ EDIT __
Just a quick proof that object cares about how methods in a module are defined:
module SomeModule
def self.hi
'hi'
end
end
object = 'some string'
class << object
include SomeModule
end
object.hi # => NoMethodError: undefined method
In my point of view, For instance_eval, the class scope becomes the singleton class also know as eigenclass of the target object. So Instance methods created on the singleton class for an object become singleton methods for that object. I mean the following two code snippets produces expected results
class TestClass; end
Using eigen class
class TestClass
class << self
def class_method
"class_method of TestClass"
end
end
end
Using instance_eval
TestClass.instance_eval do
def class_method2
"class_method of TestClass"
end
end
So we can call TestClass.class_method and TestClass.class_method2 and get the corresponding results.
Now let's assume we have a module TestModule
module TestModule
def instance_method
" instance_method from TestModule"
end
end
Now if we include this module in the eigen class then we can access the instance_method as a class method of the TestClass
class TestClass
class << self
include TestModule
end
end
So TestClass.instance_method will works as expected.
But if we do the same thing using instance_eval it fails.Please check the following snippet
TestClass.instance_eval do
include TestModule
end
When I tried to call TestClass.instance_method then I am getting the following error.
ArgumentError: wrong number of arguments(0 for 1)
Would anybody explain what is the issue and what is the internal logic behind this. I really appreciate any help you can provide.
It's because if you use include, it doesn't matter what a current class is, but what does self point to (include is a method called on self if you don't specify explicit receiver), and in your example, self points to TestClass, so TestModule#instance_method becomes instance method of TestClass, like in following example:
class TestClass
end
module TestModule
def test_method
'test'
end
end
TestClass.instance_eval { include TestModule }
TestClass.new.test_method
# => "test"
I am still trying to clearly understand Module/Class/Instance variables ...
My code currently looks something like this ...
module Foo
##var1 ={}
##var2 =[]
##var3 = nil
def m1(value)
##var2 << value
end
def m2(value)
##var1[##var3]=value
end
end
class Bar
include Foo
p ##var1
end
class Bar2
include Foo
p #var1
end
I am trying to create a module that contains a class-wide configuration for how each class will behave. The configuration is stored in ##var1 and ##var2. Using this code the variables are shared across ALL classes that include the module. This is not the desire result, I want each class to have it's own behavior configuration.
I have also tried creating a single class that includes the module and also creates the variables but then the variables are not accessible by the module.
module Foo
def m1(value)
##var2 << value
end
def m2(value)
##var1[##var3]=value
end
end
class T
##var1 ={}
##var2 =[]
##var3 = nil
include foo
end
class Bar < T
p ##var1
end
class Bar2 < T
p #var1
end
I have also read that having modules with class variables is not good coding style but I cannot think of a way to achieve my functionality with this ...
Thanks in advance for any help
Firstly - class variables are evil and should be avoided (because they are also inherited by all subclasses and usually causes more harm than good.
You want to create a class instance variable (not class variable) on a class or module which is including given module. It is easy to do with included method:
module Foo
#default_settings = {}
module ClassMethods
def foo_settings
#foo_settings
end
end
def self.included(target)
target.instance_variable_set('#foo_settings', #default_settings.dup)
target.extend ClassMethods
end
end
You can call include to mixin a module with a class in ruby, but it must be done at the beginning of the class definition. Why can't it be done inside a class function? Is there an alternate syntax?
EX:
module UsefulThings
def a() puts "a" end
end
class IncludeTester
include UsefulThings
def initialize
end
end
n = IncludeTester.new
n.a()
^^ This works, but if I change IncludeTester to the following, I get the error "undefined method `include'"
class IncludeTester
def initialize
include UsefulThings
end
end
It can be done in a class method.
This works:
module UsefulThings
def a
puts "a"
end
end
class IncludeTester
def self.mix_in_useful_things
include UsefulThings
end
end
x = IncludeTester.new
IncludeTester.mix_in_useful_things
x.a # => a
But "initialize" is not a class method, it's an instance method.
"new" is a class method. You can think of new as allocating a new object and then calling initialize on it, passing initialize whatever arguments were passed to new.
You can't call include directly in initialize because include is a private method of Class (inherited from Module), not of the newly created IncludeTester instance.
If you want to include a module into a class from an instance method, you have to do something like this:
class IncludeTester
def initialize
self.class.send(:include, UsefulThings)
end
end
It's necessary to use "send" here because include is private method, which means it can only be directly invoked with an implicit receiver (of self).
When you call initialize normally in a class definition, you're actually calling it with an implicit receiver of "self", referring to the class being defined.
This is what is actually happening when you do this:
class IncludeTester
include UsefulThings
end
include is a method from Module, Module is the superclass of Class and so include is a method on Class and that makes it a class method in your IncludeTester. When you do this:
class IncludeTester
def initialize
include UsefulThings
end
end
you're trying to call a class method inside an instance method and Ruby says
`initialize': undefined method `include'
because there is no instance method called include. If you want to call a class method inside an instance method (such as initialize), you'd do this:
def initialize
self.class.include UsefulThings
end
But that won't work because include is a private method; you can get around that with class_eval though:
def initialize
self.class.class_eval {
include UsefulThings
}
end
You would be doing include UsefulThings every single time you instantiated an IncludeTester, aside from not making much sense, it could cause problems if UsefulThings had an included method.
It's actually fully possible to include a module from a class method, like so:
module Stuff
def say_hello
puts "hello"
end
end
class Foo
def self.i_am_a_class_method
include Stuff
end
def i_am_an_instance_method
end
end
You cannot however do that from an instance method, because the include method is only available as a private class method, and therefore not accessible from a Foo.new instance.
You want the extend method:
class IncludeTester
def initialize
extend UsefulThings
end
end
This need not be done within the a method either:
IncludeTester.new.tap { |newTester| newTester.extend(UsefulThings) }
I want to be able to make an option passed to my class method (auditable) available to instance methods. I'm mixing in both the class and instance methods using a Module.
The obvious choice is to use a class variable, but I get an error when trying access it:
uninitialized class variable ##auditable_only_once in Auditable
class Document
include Auditable
auditable :only_once => true
end
# The mixin
module Auditable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def auditable(options = {})
options[:only_once] ||= false
class_eval do
# SET THE OPTION HERE!!
##auditable_only_once = options[:only_once]
end
end
end
private
def audit(action)
# AND READ IT BACK LATER HERE
return if ##auditable_only_once && self.audit_item
AuditItem.create(:auditable => self, :tag => "#{self.class.to_s}_#{action}".downcase, :user => self.student)
end
end
I've stripped out some of the code to make this a bit easier to read, the full code is here: https://gist.github.com/1004399 (EDIT: Gist now includes the solution)
Using ## class instance variables is irregular and the number of of occasions when they're strictly required is exceedingly rare. Most of the time they just seem to cause trouble or confusion. Generally you can use regular instance variables in the class context without issue.
What you might want to do is use a different template for this sort of thing. If you have mattr_accessor, which is provided by ActiveSupport, you may want to use that instead of that variable, or you can always write your own equivalent in your ClassMethods component.
One approach I've used is to break up your extension into two modules, a hook and an implementation. The hook only adds the methods to the base class that can be used to add the rest of the methods if required, but otherwise doesn't pollute the namespace:
module ClassExtender
def self.included(base)
base.send(:extend, self)
end
def engage(options = { })
extend ClassExtenderMethods::ClassMethods
include ClassExtenderMethods::InstanceMethods
self.class_extender_options.merge!(options)
end
end
This engage method can be called anything you like, as in your example it is auditable.
Next you create a container module for the class and instance methods that the extension adds when it is exercised:
module ClassExtenderMethods
module ClassMethods
def class_extender_options
#class_extender_options ||= {
:default_false => false
}
end
end
module InstanceMethods
def instance_method_example
:example
end
end
end
In this case there is a simple method class_extender_options that can be used to query or modify the options for a particular class. This avoids having to use the instance variable directly. An example instance method is also added.
You can define a simple example:
class Foo
include ClassExtender
engage(:true => true)
end
Then test that it is working properly:
Foo.class_extender_options
# => {:default_false=>false, :true=>true}
foo = Foo.new
foo.instance_method_example
# => :example