Defining attr_accessor for class instance variables - Ruby - ruby

I am trying to create an accessor for a class instance variable. I am calling the attr_accessor method from a module which is included in the class. See the code below:
module Persistence
def self.included(mod)
mod.extend ClassMethods
# Add accessor for class instance variable
class << mod
attr_accessor :persistent_data
end
end
module ClassMethods
def X
persistent_data = 'data'
end
end
end
The above code works. However when I change the code which calls attr_accessor, to this:
mod.instance_eval do
attr_accessor :persistent_data
end
I get NoMethodError: undefined method `persistent_data='
Shouldn't both ways work the same or is my understanding wrong here? I am using REE 1.8.7

Related

Ruby scope at the bottom level of a module

I couldn't find any explanation that made sense for this.
I'm trying to figure out how to access a variable I'm creating in a module, but not inside any class, i.e.:
module Moddy
attr_accessor :var
var = "wibble"
class Squiggy
def Class_Method ()
end
end
end
I'm trying to figure out how (if it is at all possible) to access 'var' from inside the scope of 'Squiggy' as well as from the scope of the script requiring Moddy.
A module is kind of like a class with no instances. So instance methods defined in a module are useless on their own; you need to include that module in a class in order to use them.
That's important because attr_accessor :var essentially defines two instance methods:
def var
#var
end
def var= v
#var = v
end
If you want var = "wibble" to call the instance method you just created, you need self to be an instance of Moddy, but in this context self is just Moddy so you're actually just creating a local variable called var.
If you want Moddy to own var, you need to call attr_accessor from the singleton class (since Moddy is an instance of its singleton class).
module Moddy
class << self
attr_accessor :var
end
self.var = "wibble"
end
Moddy.var
# "wibble"
module Moddy
attr_accessor :action
def initialize
#action= "wibble"
end
class Squiggy
include Moddy
def initialize
super
end
def put_var
puts #action
end
end
end
reference: Accessing instance variables declared in ruby modules

Using helper methods in Ruby modules

I'm having some trouble understanding how to incorporate my own helper methods into a Ruby module.
My code:
module MyModule
def self.foo
bar
end
def bar
# helper for MyModule.foo
end
end
MyModule.foo
#=> NameError: undefined local variable or method `bar' for MyModule:Module
I'm not sure why MyModule cannot recognize the bar method. What aspect of scope in Ruby am I being oblivious to?
Modules can be integrated into classes as mixins. So, you need to include it in a class so it can be used with instance of that class.
As of now, you can make bar as your module method so it can be accessed as is.
module MyModule
def self.foo
bar
end
def self.bar
puts "Now it works"
end
end
MyModule.foo #=> Now it works
Ruby Docs
A Module is a collection of methods and constants. The methods in a
module may be instance methods or module methods. Instance methods
appear as methods in a class when the module is included, module
methods do not.
You are trying to call an instance method from a class method. You would have to write
module MyModule
def MyModule.foo
MyModule.bar
end
# Or you can have it this way
def MyModule::bar
# helper for MyModule.foo
end
end
MyModule.foo
to get what you want.
You're missing the scope of a method (module as well as instance) and basically it's lifecycle..
Module method way: -
Following is how you define the module with module methods.
module MyModule
def self.foo
puts "called self.foo"
bar
end
def self.bar
puts "self.bar got called"
# helper for MyModule.foo
end
end
Now, This way, you do not have to instantiate any object to call those methods.. Here is how you would call the methods (one inside the other)
MyModule.foo
Using a class to instantiate the Module and calling methods will not work as they are not instance methods.
Output -
called foo
bar got called
Instance method way: - Following is how you'll define the module with instance methods so that they will work between classes and objects..
module MyModule
def foo
puts "called foo"
bar
end
def bar
puts "bar got called"
# helper for MyModule.foo
end
end
class TestModule
include MyModule
end
Choosing to use the module methods this way you have to call the methods inside module as per below -
#instantiating module MyModule via class
myinstance = TestModule.new
myinstance.foo
Output -
called foo
bar got called

Module variable not accessable from all files

I have a ruby application that stretches across a module: call it Bacon
module Bacon
##foo = nil
def self.set_foo(info)
##foo = SomeClass.new(info)
end
def self.get_foo
return ##foo
end
class SomeClass
attr_accessor :info
def initialize(info)
#info = info
end
end
end
Bacon::set_foo("Secret!")
puts Bacon::get_foo.info
and that works all fine. However if I want to access Bacon::get_foo from a different file using require_relative and initiating an OtherClass instance as follows:
module Bacon
class OtherClass
def initialize
puts Bacon::get_foo.info
end
end
end
I get:
undefined method `info' for nil:NilClass
I was under the impression that, when using a module across multiple files, all the variables and values were shared between them. After all the OtherClass can see the method get_foo, just that there is no value stored in ##foo.
Can someone explain this? If there is a better way to do it (have a class instance accessible throughout a module).

Using a mixin method inside an instance method

Why does this not work:
class Myclass
include HTTParty
def dosomething
base_uri("some_url")
end
end
The base_uri method is a class method of HTTParty. It works fine if I call it from my class, outside of any instance methods, or from a class method, but when try to call it from an instance method I get "NoMethodError: undefined method `base_uri' for #"
Why? Shouldn't there be some way to refer to the HTTParty class from within my instance method so I can call that HTTParty class method?
I could change it to a class method, but then every instance of my class would have the same value for base_uri.
Why doesn't it work? Because that's not how Ruby works. Similarly, this doesn't work:
class Foo
def self.utility_method; ...; end
def inst_method
utility_method # Error! This instance has no method named "utility_method"
end
end
You could work around this by just doing:
class MyClass
include HTTParty
def dosomething
HTTParty.base_uri("some_url")
end
end
Let's look deeper at how method lookup works with modules. First, some code:
module M
def self.m1; end
def m2; end
end
class Foo
include M
end
p Foo.methods - Object.methods #=> []
p Foo.new.methods - Object.methods #=> [:m2]
class Bar
extend M
end
p Bar.methods - Object.methods #=> [:m2]
p Bar.new.methods - Object.methods #=> []
class Jim; end
j = Jim.new
j.extend M
p j.methods - Object.methods #=> [:m2]
As we see, you can use extend to cause an object (a class or instance) to use the 'instance' methods of a module for the object itself (instead of instances), but you cannot cause 'class methods' of the module to be inherited by anything. The closest you can get is this idiom:
module M2
module ClassMethods
def m1; end # Define as an instance method of this sub-module!
end
extend ClassMethods # Make all methods on the submodule also my own
def self.included(k)
k.extend(ClassMethods) # When included in a class, extend that class with
end # my special class methods
def m2; end
end
class Foo
include M2
end
p Foo.methods - Object.methods #=> [:m1]
p Foo.new.methods - Object.methods #=> [:m2]
If the HTTParty module used the above pattern, and so made the base_uri method available on your MyClass, then you could do this:
class MyClass
include HTTParty
def dosomething
self.class.base_uri("some_url")
end
end
...but that's more work than just directly referencing the module owning the method.
Finally, because this might help you, here's a diagram I made some years ago. (It's missing some core objects from Ruby 1.9, like BasicObject, but is otherwise still applicable. Click for a PDF version. Note #3 from the diagram is particularly applicable.)
(source: phrogz.net)
class Myclass
include HTTParty
def dosomething
Myclass.base_uri("some_url")
end
end

Dynamically add (pre-defined) instance method in Ruby

I see how to dynamically add a method to an instance in Ruby with def [instance].[methodname]; [...]; end.
However, I'm interested in attaching a method that exists in another location to a given instance. e.g.
def my_meth
puts self.foo
end
class MyCls
attr_accessor :foo
end
my_obj = MyCls.new
my_obj.my_meth
How could I simply attach my_meth to my_obj so that the method call in the final line of the foregoing code would work?
You could use include or extend to add a module to your class, eg. extend:
module Foo
def my_meth
puts self.foo
end
end
class MyCls
attr_accessor :foo
end
my_obj = MyCls.new
my_obj.extend(Foo)
my_obj.foo = "hello"
my_obj.my_meth
Unless you have a need to mix-in a module on the fly like this it's generally better to include your module like so:
class MyCls
include Foo
attr_accessor :foo
end

Resources