Ruby metaprogramming: adding #variables to existing 'initialize' method (using class_eval) [duplicate] - ruby

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Ruby.Metaprogramming. class_eval
I have this little project, the goal is to create a 'attr_accessor_with_history' method, that will keep a record of every single value assigned to variables created by it. Here's the code :
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s # make sure it's a string
attr_reader attr_name # create the attribute's getter
attr_reader attr_name+"_history" # create bar_history getter
a = %Q{
def initialize
##{attr_name}_history = [nil]
end
def #{attr_name}
##{attr_name}
end
def #{attr_name}=(new_value)
##{attr_name}=new_value
##{attr_name}_history.push(new_value)
end }
puts a
class_eval(a)
end
end
Now, when I test the script for one variable. It works fine. But when I try to create two or more variables (like this) ....
class Foo
attr_accessor_with_history :bar
attr_accessor_with_history :lab
end
a = Foo.new
a.bar = 45
a.bar = 5
a.bar = 'taat'
puts a.bar_history
b = Foo.new
b.lab = 4
b.lab = 145
b.lab = 'tatu'
puts b.lab_history
....Ruby gives out a "no-existing 'push' method for (class_eval) bar_history.push(new_value)". I think that 'initialize' method gets overriden on the second call of attr_accessor_with_history, so the record for the first variable gets destroyed.
I have no idea how to get around this. I already tried calling 'super' . Any clue ?

In your setter method just check if the the history instance variable is already initialized:
def #{attr_name}=(new_value)
##{attr_name}=new_value
##{attr_name}_history ||= [nil]
##{attr_name}_history.push(new_value)
end
You'll need another getter for your history variable that sets your default value if it was not set before:
def #{attr_name}_history
##{attr_name}_history ||= [nil]
end
Then you could remove your initialize method, that was btw vulnerable to be overwritten.

Related

Variables in class methods

could you please explain me why the class variable cannot be accessed by attribute_accessors?
As i am trying here to have the list of all methods of all subclasses in one array it works a little different. It created array #method_names in every subclass with specific methods for every class ... so i do need to do a loop through subclasses.
What kind of variable/attribute is #method_names?
Thanks!
module First
class First_class
class << self
def info
puts "First_class method info."
puts #subclasses
puts #method_names
end
def inherited(subclass)
puts "#{subclass} located ..."
subclasses << subclass
end
def subclasses
#subclasses ||= []
end
def method_added(method_name)
puts "Method located #{method_name} ..."
method_names << method_name
end
def method_names
#method_names ||= []
end
end
def initialize
puts "Instance of First_class is created."
end
def first_method
end
end
class Second_class < First_class
def self.info
puts "Second_class method info."
puts #subclasses
puts #method_names
end
def second_method
end
def initialize
puts "Instance of Second_class is created."
end
end
class Third_class < First_class
def third_method
end
def initialize
puts "Instance of Third_class is created."
end
end
end
First::First_class.subclasses.each {
|subclass| puts subclass
subclass.method_names.each {
|methodn| puts methodn
}
}
#################UPDATE#########
Ok, maybe I put the question incorrectly.
Basically what is the difference for ##method_names(class variable) and #method_names (instance variable) if i do not create the instance of object? After inserting more inputs into #method_names it still inserts into the same object_id. So what is benefit of ##method_names?
updated to answer updated question.
Classes in ruby can have class variables. However if you modify the class level variable, ALL instances will be modified. This is not recommended but will illustrate the point. But also see this answer
class Foo
##bar = 'bar'
attr_accessor :bar
def initialize
#bar = 'bar'
end
def class_bar
##bar
end
def change_class_bar string
raise ArgumentError unless string.is_a?(String)
##bar = string
end
end
a = Foo.new
b = Foo.new
# change the class variable ##bar
b.change_class_bar 'wtf?'
# see both instances are changed because objects are passed by referrence
print 'a.class_bar is: '
puts a.class_bar
print 'b.class_bar is: '
puts b.class_bar
# change one instance only
a.bar = 'only a has changed'
print 'a.bar is: '
puts a.bar
print 'b.bar is still: '
puts b.bar
run this and you should get output:
a.class_bar is: wtf?
b.class_bar is: wtf?
a.bar is: only a has changed
b.bar is still: bar
original answer left here
#method_names is an instance variable of an instance of the class from which it was instantiated. However it cannot be accessed for read/write unless those attributes are defined with getter or setter methods defined.
ff = First::First_class.new
Instance of First_class is created.
=> #<First::First_class:0x00007fde5a6867b8>
ff.method_names
NoMethodError: undefined method `method_names' for #<First::First_class:0x00007fde5a6867b8>
Did you mean? methods
Now if you call ff.methods you will see all methods defined through standard Ruby inheritance.
As a side note, class names in Ruby conventionally use PascalCase see PascalCase. Mixed_case is discouraged.

Why are my instance variables not existing across methods of the same class in ruby?

I am doing the ruby koans and I am on the DiceSet project. I've made the DiceSet class but my instance variables don't seem to persist with the instance like I thought they would. My code is
class DiceSet
attr_reader :values
#values = []
puts #values.class
def roll(number_of_rolls)
(1..number_of_rolls).each do |roll|
puts #values.class
#values << (1..6).to_a.sample
end
return #values
end
end
The koan then uses my DiceSet class with
dice = DiceSet.new
dice.roll(5)
puts dice.values.class
assert dice.values.is?(Array)
I put the puts commands in there to follow whats happening with the #values instance variable and only the first puts #values.class says its an Array class. All the others are returning NilClass. Am I using instance variables incorrectly or is there something else I am missing? Do instance variables get deallocated after a method call?
EDIT: My class works correctly now that I have put #values = [] in the roll method as suggested below. My only question now, is why the roll method thinks that #values is a NilClass instead of an array when I put #values = [] in an initialize method
In Ruby everything are objects. The Ruby interpreter assumes that all instance variables belong to the current object self. This is also true in a class definition. The role of self belongs to the class itself, so the instance variable #values belongs to the class. Don’t get confused! Instance variables of the class are different from instance
variables of that class’s objects. Also you don't need specify return keyword explicitly Try this:
class DiceSet
attr_accessor :values
def roll(number_of_rolls)
#values = []
(1..number_of_rolls).each do |roll|
#values << (1..6).to_a.sample
end
#values
end
end
dice = DiceSet.new
dice.roll(5)
puts dice.values.class
assert dice.values.is_a?(Array)
Each DiceSet instance has its own #values, and furthermore, the class DiceSet also has its own #values. They are all different from one another. If you want the instances and the class to share the same variable, you should use a class variable ##values.
Just put the declaration of #values = [] in the initialized method and your code should work as expected.
class DiceSet
attr_reader :values
def initialize()
#values = []
end
def roll(number_of_rolls)
(1..number_of_rolls).each do |roll|
puts #values.class
#values << (1..6).to_a.sample
end
return #values
end
end
Try this:
class Cat
attr_accessor :age
def initialize
#age = 12
end
#age = 6
def meow
puts "I'm #{#age}"
end
def self.meow
puts "I'm #{#age}, going on #{#age+1}"
end
end
Cat.age = 4 # => NoMethodError: undefined method `age=' for Cat:Class
p Cat.age # =? NoMethodError: undefined method `age' for Cat:Class
Cat.meow # => I'm 6, going on 7
cat = Cat.new
p cat.age # => 12
cat.meow # => I'm 12
cat.age = 20 # => 20
cat.meow # => I'm 20
Were I to add
class << self
attr_accessor :age
end
the first three lines of output would become:
Cat.age = 4 # => 4
p Cat.age # => 4
Cat.meow # => I'm 4, going on 5

In Ruby, when should you use self. in your classes? [duplicate]

This question already has answers here:
When to use `self.foo` instead of `foo` in Ruby methods
(3 answers)
Closed 9 years ago.
When do you use self.property_name in Ruby?
Use self when calling a class's mutator. For example, this won't work:
class Foo
attr_writer :bar
def do_something
bar = 2
end
end
The problem is that 'bar = 2' creates a local variable named 'bar', rather than calling the method 'bar=' which was created by attr_writer. However, a little self will fix it:
class Foo
attr_writer :bar
def do_something
self.bar = 2
end
end
self.bar = 2 calls the method bar=, as desired.
You may also use self to call a reader with the same name as a local variable:
class Foo
attr_reader :bar
def do_something
bar = 123
puts self.bar
end
end
But it's usually better to avoid giving a local variable the same name as an accessor.
self references the current object. This lends itself to many uses:
calling a method on the current object
class A
def initialize val
#val = val
end
def method1
1 + self.method2()
end
def method2
#val*2
end
end
Here running A.new(1).method1() will return 3. The use of self is optional here - the following code is equivalent:
class A
def initialize val
#val = val
end
def method1
1 + method2()
end
def method2
#val*2
end
end
self is not redundant for this purpose though - operator overloading makes it neccessary:
class A
def initialize val
#val = val
end
def [] x
#val + x
end
def method1 y
[y] #returns an array!
end
def method2 y
self.[y] #executes the [] method
end
end
This shows how self must be used if you want to call the current object's [] method.
referencing attributes
You can generate the methods to read and write to instance variables using attr_accessor and co.
class A
attr_accessor :val
def initialize val
#val = val
end
def increment!
self.val += 1
end
end
Using self is redundant here because you can just reference the variable directly, eg. #val.
Using the previous class, A.new(1).increment! would return 2.
method chaining
You can return self to provide a form of syntactical sugar known as chaining:
class A
attr_reader :val
def initialize val
#val = val
end
def increment!
#val += 1
self
end
end
Here, because we are returning the current object, methods can be chained:
A.new(1).increment!.increment!.increment!.val #returns 4
creating class methods
You can define class methods using self:
class A
def self.double x
x*2
end
def self.quadruple x
self.double(self.double(x))
end
end
This will enable you to call A.double(2) #= 4 and A.quadruple(2) #=8. Note that in a class method, self references that class because the class is the current object.
how the value of self is determined
The current value of self in a particular method is set to the object that that method was called upon. Normally this uses the '.' notation. When you run some_object.some_method(), self is bound to some_object for the duration of some_method, meaning that some_method can use self in one of the ways mentioned above.
Using self is used will reference the current object accessible within a program. Therefore, self.property is used when accessing a variable through a attr_accessor of some sort. In must cases, it can be used in place of #property from within an object.

How can a class method (inside a module) update an instance variable?

How can a class method (inside a module) update an instance variable? Consider the code bellow:
module Test
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
def update_instance_variable
#temp = "It won't work, bc we are calling this on the class, not on the instance."
puts "How can I update the instance variable from here??"
end
end
end
class MyClass
include Test
attr_accessor :temp
update_instance_variable
end
m = MyClass.new # => How can I update the instance variable from here??
puts m.temp # => nil
You'd have to pass your object instance to the class method as a parameter, and then return the updated object from the method.
That does nto quite make sense.
You use the initialize method to set default values.
class MyClass
attr_accessor :temp
def initialize
#temp = "initial value"
end
end
The initialize method is automatically run for you when you create a new object.
When your class declaration is run, there are no, and cannot be any, instances of the class yet.
If you want to be able to change the default values later you can do something like this:
class MyClass
attr_accessor :temp
##default_temp = "initial value"
def initialize
#temp = ##default_temp
end
def self.update_temp_default value
##default_temp = value
end
end
a = MyClass.new
puts a.temp
MyClass.update_temp_default "hej"
b = MyClass.new
puts b.temp
prints
initial value
hej
If you also want that to change already created instances' variables you need additional magic. Please explain exactly what you wish to accomplish. You are probably doing it wrong :)

Ruby - How to use the method parameter as the name of the variable?

How would I use the parameter value as the instance variable name of an object?
This is the object
Class MyClass
def initialize(ex,ey)
#myvar = ex
#myothervar = ey
end
end
I have the following method
def test(element)
instanceofMyClass.element #this obviously doesnt work
end
How can I have the test method return either myvar or myothervar value depending on the element parameter. I don't want to write an if condition though, I want to pass myvar or myother var via element to the object instance if possible.
def test(element)
instanceofMyClass.send(element.to_sym)
end
You'll get a missing method error if instanceofMyClass doesn't respond to element.
def test(element)
instanceofmyclass.instance_variable_get element
end
test :#myvar # => ex
test :#myothervar # => ey
I like the simplicity of send(), though one bad thing with it is that it can be used to access privates. The issue is still remains solution below, but at least then it's explicitly specified, and reader can see which methods are to be forwarded. The first one just uses delegation, while the second one uses more dynamic way to define methods on the fly.
require 'forwardable'
class A
extend Forwardable
def_delegators :#myinstance, :foo, :bar
class B
def foo
puts 'foo called'
end
def bar
puts 'bar called'
end
def quux
puts 'quux called'
end
def bif
puts 'bif called'
end
end
def initialize
#myinstance = B.new
end
%i(quux bif).each do |meth| # note that only A#quux and A#bif are defined dynamically
define_method meth do |*args_but_we_do_not_have_any|
#myinstance.send(meth)
end
end
end
a = A.new
a.foo
a.bar
a.quux
a.bif

Resources