I have recently learned how to create classes, although I am not ENTIRELY sure where and why I should use them.
I'd use them to create objects, which have similar methods/properties.
I tried making a gag code, but I stumbled upon a question I can't find an answer to.
class Person
def initialize(name,health)
#name = name
#hp = health
end
def kick
#hp -= 1
if (#hp <= 0)
puts "#{#name} got REKT!"
end
end
end
#Friends
michael = Person.new("Michael", 10)
10.times { michael.kick }
Even though this code works, I'm wondering if it is possible to use/call mihchael's hp outside the class? Perhaps sort of like a hash? michael[#hp]? But this doesn't work, even if i set hp to be a global variable.
Should all if/else statements who check object's properties be inside the class?
Thank you very much
The standard way to do this in Ruby is to create an accessor:
class Person
attr_reader :name
attr_reader :hp
end
Then outside the class you can call things like:
puts "#{michael.name} has only #{michael.hp} HP left"
The reason you create objects is to organize your code and data into logical contexts and containers.
I am going through a ruby tutorial and trying to use understand how Virtual Attributes. This is the example shown in the tutorial.
class Spaceship
def destination
#autopilot.destination
end
def destination=(new_destination)
#autopilot.destination = new_destination
end
end
ship = Spaceship.new
ship.destination = "Earth"
puts ship.destination
As per tutorial, this code should ideally return Earth, but I am encountering the below error.
class.rb:7:in `destination=': undefined method `destination=' for nil:NilClass (NoMethodError) from class.rb:12:in `<main>'
I am sorry but unable to identify the missing part.
You need to assign your #autopilot variable something.
Something like this should work:
class Spaceship
def initialize
#autopilot = Struct.new(:destination).new(nil)
end
def destination
#autopilot.destination
end
def destination=(new_destination)
#autopilot.destination = new_destination
end
end
But if you want to add a virtual attribute, then keep the value as a simple instance variable, like so:
class Spaceship
def destination
#destination
end
def destination=(new_destination)
#destination = new_destination
end
end
As humza has pointed out, the code as written will not work.
I suspect the author meant to write something like the following, and wants to make the point that although destination looks like an attribute (and we may send the message destination to an object and get the expected response), there is no corresponding instance variable #destination. We may think of destination as being a virtual attribute.
class Spaceship
def destination
dosomething
end
def destination=(new_destination)
#autopilot = new_destination
end
def dosomething
#autopilot
end
end
ship = Spaceship.new
ship.destination ="Earth"
puts ship.destination
Objects may behave as if the class Spaceship was written as shown in the next example, that is the interface of both classes is the same (and in this case we do have an instance variable #destination).
class Spaceship
def destination
#destination
end
def destination=(new_destination)
#destination = new_destination
end
end
ship = Spaceship.new
ship.destination ="Earth"
puts ship.destination
A message that is send to an object of the Class Spaceship does not need to know (and does not know) about the internal implementation.
Virtual Attributes are treated well here, and a better example is given, where a method durationInMinutes is defined without any corresponding instance variable #durationInMinutes. The explanation given is very concise, and I'll quote it in full:
Here we've used attribute methods to create a virtual instance variable. To the outside world, durationInMinutes seems to be an attribute like any other. Internally, though, there is no corresponding instance variable.
The author(s) continue:
This is more than a curiosity. In his landmark book Object-Oriented Software Construction , Bertrand Meyer calls this the Uniform Access Principle. By hiding the difference between instance variables and calculated values, you are shielding the rest of the world from the implementation of your class. You're free to change how things work in the future without impacting the millions of lines of code that use your class. This is a big win.
I'm trying to fill in class variables for a class instance from a file, and the only way I've managed to figure how to do this is something like
a=Thing.new
File.read("filename.ext").each_line do |arguments| #this will => things like #variable=str\n
eval( "a.instance_eval {" + arguments.chop + "}") #hence the awkward eval eval chop
end
The only problem I've found is that in trying to impliment this in a class method (to do this for several instances in a go), I don't know how to make this happen:
class Thing
attr_accessor :variable
def self.method
File.read("filename.ext").each_line do |arguments|
eval("instance.instance_eval{" + arguments.chop + "}") #this line
end
end
end
namely, the reference to the instance calling the method. self will just just be Thing in this case, so is there any way to do this? More pertinent might be a better way to go about this overall. I only just learned ruby last night, so I haven't had an opportunity to see some of the neater tricks that are to be had, and my language maturity is a little fresh yet as a result.
For context, Thing is a character in a game, loading its base values from a savefile.
Well, first off, take a look at Marshal. It's specifically used for dumping data structures to serialized formats and loading them back.
The said, if you want to persist in your direction, then try something like this:
class Thing
attr_accessor :variable
def self.method
File.read("filename.ext").each_line do |arguments|
ivar, val = arguments.strip.split("=", 2)
instance.instance_variable_set(ivar, val)
end
end
end
#instance_variable_set allows you to...well, set instance variables on an object by name. No ugly eval necessary!
By way of demonstration:
class Foo
attr_accessor :bar
end
foo = Foo.new
foo.instance_variable_set("#bar", "whatzits")
puts foo.bar # => whatzits
Is there a practical application to the "crazy-ness" below?
It seems like this is a way for ted to always be able to return himself to the world and people will think they are talking to ted who they expect to act a certain way and be a certain age... but he isn't acting the way he portrays himself and is lying about his age to someone.
What 'trickery' is possible when an object is returned and you check on what that object represents and is capable of... but really that object was acting another way and capable of other things before returning.
class Person
def age
21
end
def who_am_i?
puts "I am #{self} / #{object_id} and I am #{age} years old"
self
end
end
ted = Person.new
def ted.singleton_who_am_i?
class << self
def age
0
end
end
puts "I am #{self} / #{object_id} and I am #{age} years old"
self
end
puts ted.who_am_i? == ted.singleton_who_am_i?
>> I am #<Person:0x100138340> / 2148123040 and I am 21 years old
>> I am #<Person:0x100138340> / 2148123040 and I am 0 years old
>> true
http://andrzejonsoftware.blogspot.ca/2011/02/dci-and-rails.html
in DCI, your data model gets different type of behavior based on the context it is used it. Usually it is done with object.extend, but it is pretty much what you are doing above -- taking advantage of the metaclass.
Another example (and probably why things work that way) is the way classes work in ruby. If you say
class Foo
end
that is the same thing as saying
Foo = Class.new
end
meaning that what you are doing is assigning a new instance of class Class to a constant. When you define a method on that class, you don't want it applied to all instance of class Class, you only want it on the class you are defining. So when you say
class Foo
def self.bar
end
end
it is the exact thing as saying
class Foo
end
def Foo.bar
end
which is exactly the same principal as you are talking about in your question
(sorry if that was unclear)
Ruby is a very dynamic language letting you inject code into objects at runtime. There are some good uses for it but it can also make code very hard to debug and understand.
It's totally counter-intuitive for a method that queries an object to modify that object. Nobody would expect a call to who_am_i to modify the object.
On the other hand replacing methods like that can make unit testing classes really straight forward.
If you want to test how the class behaves with different ages you can inject code like that before your tests.
I'm thinking in:
class X
def new()
#a = 1
end
def m( other )
#a == other.#a
end
end
x = X.new()
y = X.new()
x.m( y )
But it doesn't works.
The error message is:
syntax error, unexpected tIVAR
How can I compare two private attributes from the same class then?
There have already been several good answers to your immediate problem, but I have noticed some other pieces of your code that warrant a comment. (Most of them trivial, though.)
Here's four trivial ones, all of them related to coding style:
Indentation: you are mixing 4 spaces for indentation and 5 spaces. It is generally better to stick to just one style of indentation, and in Ruby that is generally 2 spaces.
If a method doesn't take any parameters, it is customary to leave off the parantheses in the method definition.
Likewise, if you send a message without arguments, the parantheses are left off.
No whitespace after an opening paranthesis and before a closing one, except in blocks.
Anyway, that's just the small stuff. The big stuff is this:
def new
#a = 1
end
This does not do what you think it does! This defines an instance method called X#new and not a class method called X.new!
What you are calling here:
x = X.new
is a class method called new, which you have inherited from the Class class. So, you never call your new method, which means #a = 1 never gets executed, which means #a is always undefined, which means it will always evaluate to nil which means the #a of self and the #a of other will always be the same which means m will always be true!
What you probably want to do is provide a constructor, except Ruby doesn't have constructors. Ruby only uses factory methods.
The method you really wanted to override is the instance method initialize. Now you are probably asking yourself: "why do I have to override an instance method called initialize when I'm actually calling a class method called new?"
Well, object construction in Ruby works like this: object construction is split into two phases, allocation and initialization. Allocation is done by a public class method called allocate, which is defined as an instance method of class Class and is generally never overriden. It just allocates the memory space for the object and sets up a few pointers, however, the object is not really usable at this point.
That's where the initializer comes in: it is an instance method called initialize, which sets up the object's internal state and brings it into a consistent, fully defined state which can be used by other objects.
So, in order to fully create a new object, what you need to do is this:
x = X.allocate
x.initialize
[Note: Objective-C programmers may recognize this.]
However, because it is too easy to forget to call initialize and as a general rule an object should be fully valid after construction, there is a convenience factory method called Class#new, which does all that work for you and looks something like this:
class Class
def new(*args, &block)
obj = alloc
obj.initialize(*args, &block)
return obj
end
end
[Note: actually, initialize is private, so reflection has to be used to circumvent the access restrictions like this: obj.send(:initialize, *args, &block)]
Lastly, let me explain what's going wrong in your m method. (The others have already explained how to solve it.)
In Ruby, there is no way (note: in Ruby, "there is no way" actually translates to "there is always a way involving reflection") to access an instance variable from outside the instance. That's why it's called an instance variable after all, because it belongs to the instance. This is a legacy from Smalltalk: in Smalltalk there are no visibility restrictions, all methods are public. Thus, instance variables are the only way to do encapsulation in Smalltalk, and, after all, encapsulation is one of the pillars of OO. In Ruby, there are visibility restrictions (as we have seen above, for example), so it is not strictly necessary to hide instance variables for that reason. There is another reason, however: the Uniform Access Principle.
The UAP states that how to use a feature should be independent from how the feature is implemented. So, accessing a feature should always be the same, i.e. uniform. The reason for this is that the author of the feature is free to change how the feature works internally, without breaking the users of the feature. In other words, it's basic modularity.
This means for example that getting the size of a collection should always be the same, regardless of whether the size is stored in a variable, computed dynamically every time, lazily computed the first time and then stored in a variable, memoized or whatever. Sounds obvious, but e.g. Java gets this wrong:
obj.size # stored in a field
vs.
obj.getSize() # computed
Ruby takes the easy way out. In Ruby, there is only one way to use a feature: sending a message. Since there is only one way, access is trivially uniform.
So, to make a long story short: you simply can't access another instance's instance variable. you can only interact with that instance via message sending. Which means that the other object has to either provide you with a method (in this case at least of protected visibility) to access its instance variable, or you have to violate that object's encapsulation (and thus lose Uniform Access, increase coupling and risk future breakage) by using reflection (in this case instance_variable_get).
Here it is, in all its glory:
#!/usr/bin/env ruby
class X
def initialize(a=1)
#a = a
end
def m(other)
#a == other.a
end
protected
attr_reader :a
end
require 'test/unit'
class TestX < Test::Unit::TestCase
def test_that_m_evaluates_to_true_when_passed_two_empty_xs
x, y = X.new, X.new
assert x.m(y)
end
def test_that_m_evaluates_to_true_when_passed_two_xs_with_equal_attributes
assert X.new('foo').m(X.new('foo'))
end
end
Or alternatively:
class X
def m(other)
#a == other.instance_variable_get(:#a)
end
end
Which one of those two you chose is a matter of personly taste, I would say. The Set class in the standard library uses the reflection version, although it uses instance_eval instead:
class X
def m(other)
#a == other.instance_eval { #a }
end
end
(I have no idea why. Maybe instance_variable_get simply didn't exist when Set was written. Ruby is going to be 17 years old in February, some of the stuff in the stdlib is from the very early days.)
There are several methods
Getter:
class X
attr_reader :a
def m( other )
a == other.a
end
end
instance_eval:
class X
def m( other )
#a == other.instance_eval { #a }
end
end
instance_variable_get:
class X
def m( other )
#a == other.instance_variable_get :#a
end
end
I don't think ruby has a concept of "friend" or "protected" access, and even "private" is easily hacked around. Using a getter creates a read-only property, and instance_eval means you have to know the name of the instance variable, so the connotation is similar.
If you don't use the instance_eval option (as #jleedev posted), and choose to use a getter method, you can still keep it protected
If you want a protected method in Ruby, just do the following to create a getter that can only be read from objects of the same class:
class X
def new()
#a = 1
end
def m( other )
#a == other.a
end
protected
def a
#a
end
end
x = X.new()
y = X.new()
x.m( y ) # Returns true
x.a # Throws error
Not sure, but this might help:
Outside of the class, it's a little bit harder:
# Doesn't work:
irb -> a.#foo
SyntaxError: compile error
(irb):9: syntax error, unexpected tIVAR
from (irb):9
# But you can access it this way:
irb -> a.instance_variable_get(:#foo)
=> []
http://whynotwiki.com/Ruby_/_Variables_and_constants#Variable_scope.2Faccessibility