Can someone please help me understand why the class attribute is losing the value outside the initialize method?
2.0.0-p0 :031 > $arr = [1, 2, 3, 4]
=> [1, 2, 3, 4]
2.0.0-p0 :032 > class Class1
2.0.0-p0 :033?> def initialize
2.0.0-p0 :034?> val1 = $arr[0]
2.0.0-p0 :035?> puts val1
2.0.0-p0 :036?> end
2.0.0-p0 :037?> end
=> nil
2.0.0-p0 :038 > cl1 = Class1.new
1
=> #<Class1:0x007fe8ac16be70>
2.0.0-p0 :039 > puts cl1.val1
=> nil
2.0.0-p0 :040 >
In a lot of programming languages, including Ruby, variables are scoped, and in your code val1 is in the local scope. It is forgotten when the scope – in this case the function – ends. You probably wanted an instance variable.
A local variable has a name starting with a lower case letter or an underscore character (_).
Each object represents its own song, so we need each of our Song objects to carry around its own song name, artist, and duration. This means we need to store these values as instance variables within the object. In Ruby, an instance variable is simply a name preceded by an at sign (#).
One you have an instance variable, you can access it.
class Person
def name
#name # simply returning an instance variable #name
end
end
person = Person.new
person.name # => nil
But you won't be able to set a value:
person.name = "miku" # => no method error
So for read and write access you'll need provide writer methods or use attr_accessor. This answer explains it in examples: What is attr_accessor in Ruby?
Refs:
http://www.rubyist.net/~slagell/ruby/localvars.html
http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_classes.html
Continuing from #miku's answer you need to define var1 as instance variable to be able to be accessed outside of initialize
irb(main):013:0> class Class1
irb(main):014:1> attr_accessor :val1
irb(main):015:1> def initialize
irb(main):016:2> #val1 = $arr[0]
irb(main):017:2> puts #val1
irb(main):018:2> end
irb(main):019:1> end
=> nil
irb(main):020:0> c = Class1.new
1
=> #<Class1:0x8016f79e0 #val1=1>
irb(main):021:0> puts c.val1
1
=> nil
Probably what you are trying to do is:
class Class1
def initialize
#val1 = $arr[0]
puts #val1
end
end
Although maybe it would be better to pass the value you want to initialize in the constructor's argument:
class Class1
def initialize(val)
#val1 = val
puts #val1
end
end
cl1=Class1.new($arr[0])
Related
I'm learning ruby and my opinion about attr_reader is bad. Because you can change the value of particular instance variable from outside of class. For example:
class X
attr_reader :some_string
def initialize
#some_string = "abc"
end
end
tmp = X.new
puts tmp.some_string
tmp.some_string[0] = "aaaaaaaaa"
puts tmp.some_string
When you run this code you can see that the instance variable some_string has changed. So to avoid this I'm always making my own getter, and returning frozen object. For example:
class X
def initialize
#some_string = "abc"
end
def some_string
#some_string.freeze
end
end
tmp = X.new
puts tmp.some_string
tmp.some_string[0] = "aaaaaaaaa"
puts tmp.some_string
Now when you run the code, it throws an error saying can't modify frozen String: "abc", which is what I wanted. So my question is should I use attr_reader and is always returning frozen objects from getters bad practice?
attr_reader is not "bad", it simply does what it does, which is to return the value of the instance variable, which is a reference. Unlike attr_writer or attr_accessor it will not allow you to change the value of the instance variable (ie you can't change what the instance variable refers to)
The question is do you really want or need the reference. For example say you want to convert the value of some_string to upper case. You could use attr_reader to get the reference to some_string and then call upcase! on it like below. But having the reference allows you to call any method on the object including the []= method which maybe you don't want to allow. Note: []= is a method that manipulates the content of what some_string references it does not set #some_string to a new value, it still points to the same object, but the object it points to was manipulated.
class Foo
attr_reader :some_string
def initialize()
#some_string = "abc"
end
end
puts "Foo"
foo = Foo.new
some_string = foo.some_string
puts some_string #=> abc
some_string.upcase!
p foo # => #<Foo:0x0000563c38391ac8 #some_string="ABC">
puts some_string.object_id # => 60
some_string[0] = "x"
p foo # => #<Foo:0x0000563c38391ac8 #some_string="xBC">
puts some_string.object_id # => 60 ... ie same object different content
# foo.some_string = "ABC" is a runtime error
If you don't want to allow arbitrary methods to be called on an instance variable then you should not expose it using attr_reader, rather you should manipulate the instance variable via methods in your class. For example below I "delegate" the upcase! method to the instance variable #some_string, and I provide a string_value method to return a new string with the same value as the instance variable.
class Bar
def initialize()
#some_string = "abc"
end
def upcase!()
#some_string.upcase!
end
def string_value()
"#{#some_string}"
end
end
puts "Bar"
bar = Bar.new
p bar # => #<Bar:0x0000563c383915a0 #some_string="abc">
bar.upcase!
p bar # => #<Bar:0x0000563c383915a0 #some_string="ABC">
some_string = bar.string_value
p some_string # => "ABC"
some_string[0] = "x"
p bar # => #<Bar:0x0000563c383915a0 #some_string="ABC">
p some_string # => "xBC"
So I would say attr_reader is not bad, you might argue it is over used, I know I will often use it to "get" an instance variable when all I really need is some property on the instance variable.
A lot of developers try to use private attr_reader and use it inside the class or avoid using it at all
A good conversations about attr_reader are here and here
In the example below I am reopening the Module class and setting instance variables. Can this possibly "break" the class if it already uses those instance variables for something else and how can this be avoided?
class Module
def fields
#fields ||= []
end
def foo name
fields << name
end
end
So far I haven't run into any issues with something similar to the above. However my next example shows how this could be a problem.
class Foo
def bar
#test = 1
end
def print
puts #test
end
end
class Foo
def oops
#test = 2
end
end
obj = Foo.new
obj.bar
obj.print #=> 1
# method that we added later sets instance variable
obj.oops #=> 2
obj.print
This worries me.
Can this possibly "break" the class if it already uses those instance
variables for something else and how can this be avoided?
1.9.3-p545 :003 > Module.new.instance_variables
=> []
1.9.3-p545 :004 > Module.new.instance_variable_defined? "#fields" => false
Here is an example what will it change and what won't
module Helper
#fields = '#fields defined in Helper'
def self.print_helper_fields
p #fields
end
def print
p #fields
end
end
class A
include Helper
attr_accessor :fields
end
class Module
def foo
#fields = '#fields changed in Module#foo'
end
end
a = A.new
a.fields = [1, 2, 3]
a.print # => [1, 2, 3]
Helper.print_helper_fields # => '#fields defined in Helper'
Helper.foo
a.print # => [1, 2, 3]
Helper.print_helper_fields # => '#fields changed in Module#foo'
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
Question:
Using Ruby it is simple to add custom methods to existing classes, but how do you add custom properties? Here is an example of what I am trying to do:
myarray = Array.new();
myarray.concat([1,2,3]);
myarray._meta_ = Hash.new(); # obviously, this wont work
myarray._meta_['createdby'] = 'dreftymac';
myarray._meta_['lastupdate'] = '1993-12-12';
## desired result
puts myarray._meta_['createdby']; #=> 'dreftymac'
puts myarray.inspect() #=> [1,2,3]
The goal is to construct the class definition in such a way that the stuff that does not work in the example above will work as expected.
Update: (clarify question) One aspect that was left out of the original question: it is also a goal to add "default values" that would ordinarily be set-up in the initialize method of the class.
Update: (why do this) Normally, it is very simple to just create a custom class that inherits from Array (or whatever built-in class you want to emulate). This question derives from some "testing-only" code and is not an attempt to ignore this generally acceptable approach.
Isn't a property just a getter and a setter? If so, couldn't you just do:
class Array
# Define the setter
def _meta_=(value)
#_meta_ = value
end
# Define the getter
def _meta_
#_meta_
end
end
Then, you can do:
x = Array.new
x._meta_
# => nil
x._meta_ = {:name => 'Bob'}
x._meta_
# => {:name => 'Bob'}
Does that help?
Recall that in Ruby, you do not have access to attributes (instance variables) outside of that instance. You only have access to an instance's public methods.
You can use attr_accessor to create a method for a class that acts as a property as you describe:
irb(main):001:0> class Array
irb(main):002:1> attr_accessor :_meta_
irb(main):003:1> end
=> nil
irb(main):004:0>
irb(main):005:0* x = [1,2,3]
=> [1, 2, 3]
irb(main):006:0> x._meta_ = Hash.new
=> {}
irb(main):007:0> x._meta_[:key] = 'value'
=> "value"
irb(main):008:0>
For a simple way to do a default initialization for an accessor, we'll need to basically reimplement attr_accessor ourselves:
class Class
def attr_accessor_with_default accessor, default_value
define_method(accessor) do
name = "##{accessor}"
instance_variable_set(name, default_value) unless instance_variable_defined?(name)
instance_variable_get(name)
end
define_method("#{accessor}=") do |val|
instance_variable_set("##{accessor}", val)
end
end
end
class Array
attr_accessor_with_default :_meta_, {}
end
x = [1,2,3]
x._meta_[:key] = 'value'
p x._meta_
y = [4,5,6]
y._meta_[:foo] = 'bar'
p y._meta_
But wait! The output is incorrect:
{:key=>"value"}
{:foo=>"bar", :key=>"value"}
We've created a closure around the default value of a literal hash.
A better way might be to simply use a block:
class Class
def attr_accessor_with_default accessor, &default_value_block
define_method(accessor) do
name = "##{accessor}"
instance_variable_set(name, default_value_block.call) unless instance_variable_defined?(name)
instance_variable_get(name)
end
define_method("#{accessor}=") do |val|
instance_variable_set("##{accessor}", val)
end
end
end
class Array
attr_accessor_with_default :_meta_ do Hash.new end
end
x = [1,2,3]
x._meta_[:key] = 'value'
p x._meta_
y = [4,5,6]
y._meta_[:foo] = 'bar'
p y._meta_
Now the output is correct because Hash.new is called every time the default value is retrieved, as opposed to reusing the same literal hash every time.
{:key=>"value"}
{:foo=>"bar"}
I have a class like:
class Configuration
def self.files
##files ||= Array.new
end
end
Now instead of doing this:
irb(main):001:0> Configuration.files
=> [file, file, file]
I would like to be able to do this:
irb(main):001:0> Configuration
=> [file, file, file]
But I can't figure out how, any ideas?
#glenn jackman:
I was thinking of adding extra methods to a 'Configuration' constant that's a hash. So if I had...
Configuration = Hash.new
Configuration[:foo] = 'bar'
I wanted to be able to save this Configuration hash constant to be able to dump to and load from a YAML file I wanted to be able to use...
Configuration.load
Configuration.save
I wanted the Configuration class to look like
class Configuration
def self.save
open('config.yml', 'w') {|f| YAML.dump( self , f)}
end
def self.load
open('config.yml') {|f| YAML.load(f)}
end
end
You could possibly get the effect you are looking for by adding a method to the top level object which implicitly calls Configuration.files, but you can't really make a reference to a class invoke a method on it. You can alias the method to something shorter, but you will need to call something.
irb's top level just calls 'inspect' on the result, so by overriding it you can customize what you see in irb:
$ irb
>> class Configuration
>> def self.files
>> ##files ||= Array.new
>> end
>> def self.inspect
>> ##files.inspect
>> end
>> end
=> nil
>> Configuration.files << 1 << 2 << 3
=> [1, 2, 3]
>> Configuration
=> [1, 2, 3]
You could do something like this:
class Configuration
(class << self; self; end).module_eval do
def files
['foo','bar','baz']
end
def to_s
files
end
end
end
This would define the Configuration.files method and say that the to string conversion would return the result of that method. But I am really not sure why you would want to do this. It seems quite wrong.
wpc#wpc-laptop:~$ irb
irb(main):001:0> 'a'
=> a
irb(main):002:0> def A
irb(main):003:1> end
=> nil
irb(main):004:0> A
NameError: uninitialized constant A
from (irb):4
from :0
irb(main):005:0> class A
irb(main):006:1> end
=> nil
irb(main):007:0> A
=> A
irb(main):008:0> class A
irb(main):009:1> (class ['g']
irb(main):012:3> end
irb(main):013:2> def to_s
irb(main):014:3> g
irb(main):015:3> end
irb(main):016:2> end
irb(main):017:1> end
=> nil
irb(main):018:0> A
=> g
irb(main):019:0> class B
irb(main):020:1> class def to_s
irb(main):022:3> 'g'
irb(main):023:3> end
irb(main):024:2> end
irb(main):025:1> end
=> nil
irb(main):026:0> B
=> g
irb(main):027:0> class B
irb(main):028:1> class def files
irb(main):030:3> ['a','b','c']
irb(main):031:3> end
irb(main):032:2> def to_s
irb(main):033:3> files
irb(main):034:3> end
irb(main):035:2> end
irb(main):036:1> end
=> nil
irb(main):037:0> B
=> abc
The right solution is to using the class' method of to_s, but what it return is only the string; and then you can set the inspect for using. See the follows' detail for help.
class A
class<<self
def to_s
......
end
end
end