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.
Related
I am trying to develop a simple card game console application in ruby as a pet project. One of the interactions I would like to add to this card game is something along the lines of "During your turn, you can buy cards as if they cost X less". Now, I thought about modelling this behaviour with the help of mixin decorators. Assuming the following class:
class Card
attr_reader :cost
def initialize(cost)
self.cost = cost
end
def play(*_args)
raise NotImplementedError, "Override me!"
end
private
attr_writer :cost
end
I thought a simple solution would be something like this:
class CardThatDiscounts < Card
def play(*_args)
#mod = Module.new do
def cost
super - 1
end
end
Card.prepend(#mod)
end
end
This would give me the flexibility to target either the class itself, or specific instances of the cards. However, I am unsure of how to reverse this effect, at the end of the turn. I saw a few questions similar to this one, the answers of which said something along the lines of:
Remove the defined method manually.
This approach won't really work for me, since I am decorating existing methods instead of adding new ones. I could maybe rename the existing method when I prepend the module, and then return it back, but what happens in case I want to chain multiple such decorators?
Use gem mixology
Mixology is an old gem, and seems no longer maintained. Though, according to the documentation, it is exactly what I need. However, the implementation mostly deals with ruby internals, that I unfortunately do not understand. Is it not possible to modify a class ancestor chain from within Ruby, or do I need c/Java extension for this?
I considered other alternatives, such as SimpleDelegator or DelegateClass, but both of these require that I instantiate new objects, and then somehow replace references to existing Card objects with these new wrapped objects, and back again, at the end of turn. Which seemed a bit more involved than modifying the ancestry chain directly.
I suppose my question has 2 parts. Is it possible to remove a specific ancestor from a class's ancestor chain using pure ruby (since mixology gem already suggests it is possible to do with c/Java extensions)? If not, what would be a suitable workaround to get a similar behaviour?
What you are trying to achieve is a very bad pattern. You should almost never use prepend or include dynamically, but model your code around the concepts you (and people possibly reading your code) do understand.
What you probably want to do is to create (some kind of) a Delegator called CardAffectedByEnvironment - and then instead of doing Card.new(x), you will always do CardAffectedByEnvironment.new(Card.new(x), env), where env will keep all your state changes, or add a method real_cost that would calculate things based on your cost and environment and use this method.
Below is a code with CardAffectedByEnvironment that would maybe describe better how I would assume it would work:
class Environment
def initialize
#modifiers = []
end
attr_reader :modifiers
def next_round
#modifiers = []
end
def modifiers_for(method)
#modifiers.select { |i| i.first == method }
end
end
class CardAffectedByEnvironment
def initialize(card, env)
#card, #env = card, env
end
# Since Ruby 3.0 you can write `...` instead of `*args, **kwargs, &block`
def method_missing(method, *args, **kwargs, &block)
value = #card.public_send(method, *args, **kwargs, &block)
#env.modifiers_for(method).each do |_, modifier|
value = modifier.call(value)
end
value
end
end
class Joker
def cost
10
end
end
env = Environment.new
card = CardAffectedByEnvironment.new(Joker.new, env)
p card.cost # => 10
env.modifiers << [:cost, ->(i) { i - 1 }]
p card.cost # => 9
env.next_round
p card.cost # => 10
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 need to make the following code functional by building a "Car" class. I feel I must be overlooking something simple. any help would be appreciated. The # indicates the expected output
# Make the following code functional by building a Car class
c = Car.new("blue")
puts c.color # blue
puts c.repaint_count # 0
c.paint("red")
c.paint("green")
puts c.repaint_count # 2
puts c.color # green
here is what I have done:
class Car
##repaint_count = 0
def initialize(color)
#color = color
end
def self.paint(color)
#color = color
##repaint_color += 1
end
def self.color
#color
end
end
I guess I am being thrown by the c.color / c.paint: should I be defining these methods and setting them equal to class or something else ? I think I am missing something about classes and inheritance.
I guess I am being thrown by the c.color / c.paint: should I be
defining these methods and setting them equal to class or something
else ? I think I am missing something about classes and inheritance.
I think in fact you are over-complicating it by worrying about these things at this stage. The question is not about inheritance. Although in some ways it is poorly specified in that it is possible to mis-interpret the question text and assign some properties to the class other than the instance.
So first things, you have got that the question expects you to implement a Car class, and that there is internal state to track for the current color and the number of times it has changed. You have partly mis-understood the repaint count and made it a class variable. It needs to be an instance variable - it is intended to be the number of times a specific car has been re-painted, not the number of times any car has been re-painted. Although the example numbers would be the same, the difference is that the question asks for c.repaint_count not Car.repaint_count, and c is an instance of Car, hence you want to store the count as an instance variable - set it to 0 in the constructor.
Similar confusion in your accessor code. Ruby's use of self is a little confusing - it changes meaning on context in the code. If you changed your def self.paint to just def paint and similarly for color then with the change from last paragraph, you are pretty much done.
One last thing, you need to implement repaint_count accessor similar to how you have done with color (and again, without the self. which would make it a class method)
You seem to be confusing classes and instances. c is an instance of Car, and is not the class Car itself. Unless you want to count the total repaint_count throughout the Car class, you should not use a class variable ##repaint_count, but should use an instance variable. Your paint method is a class method, and is not well defined. In addition the definition body looks like you randomly put something.
class Car
attr_reader :color, :repaint_count
def initialize color
#color = color
#repaint_count = 0
end
def paint color
#color = color
#repaint_count += 1
end
end
Well, this looks like a homework question, which I'm not going to write for you. However I'll give you some pointers.
You create/ open up a class like this.
class Foo
end
When you open up a class like this you can set it up to accept arguments immediately, like so:
class Foo
attr_accessor :bar, :bar_counter
def initialize(arg_1)
#bar = arg_1
#bar_counter = 0
end
# And add methods with any name like so.
def increase_bar
#bar_counter += 1
end
def change_bar(arg)
#bar = arg
end
end
This will explain the differences between attr_accessor, attr_reader, attr_writer https://stackoverflow.com/a/4371458/2167965
People have various opinions on Codecademy, but in my opinion it's perfect for teaching basic syntax like this. There's also Ruby Koans, and Ruby Test First.
My recommendations would be to start with codecademy to learn the syntax, and move to Test First to flesh out those concepts.
I'm writing a program like this:
module Filter
def self.included?(klass)
#count = 0
end
end
class Object
include Filter
end
class Person
def get_count
puts #count
end
end
I want to define an instance variable #count through mixing Filter, hoping this mixin is accessible to all the Object.
However, my way doesn't work.
Can anyone tell me how to achieve this?
Don't use a variable, use an attribute:
module Counter
attr_accessor :count
end
class Person
include Counter
end
person = Person.new
person.count = 50
puts person.count
Also, don't include stuff into Object, only include in your classes. Polluting the main namespace with non-general methods is bad and could lead to method name clashes in other classes, causing hard-to-find bugs.
Only include modules where you think they're needed, not everywhere.
#HanXu, The solution from MaurĂcio Linhares (as well as the suggestion to not pollute the main namespace) is the ideal way to go.
However, if you are really looking to change every instance of Object, then you might be able to just open the Object class and add the specific functionality you are looking for:
class Object
attr_accessor :count
end
...
p = Person.new
p.count = 10
puts p.count # => 10
Not recommended, but will work (in any case, you are opening Object to include the module now.)
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.