I'm trying to get the following code to work.
class Animal
end
class Tiger < Animal
#hunger = 100
def self.hunger
#hunger
end
def run
puts "The Tiger runs"
#hunger += 10
end
end
class Statistics
puts "This tigers hunger is: #{Tiger.hunger}"
end
tiger = Tiger.new()
tiger.run
So the tiger has a variable called hunger which is by itself at the top of the Tiger class. I want to make it so this variable can be changed throughout the class methods. For example in run the hunger is set to hunger += 10, but when I run this code I get an undefined method '+' for nil:NilClass (NoMethodError). What do I do to make this program work so the variable can be changed and then displayed in the Statistics class?
The problem is that you have completely mushed class and instance variables/methods together.
If you want to use instances (which you should):
class Animal; end
class Tiger < Animal
attr_reader :hunger
def initialize
#hunger = 100
end
def run
puts "The Tiger runs"
#hunger += 10
end
end
class Statistics
def self.show(tiger)
puts "This tigers hunger is: #{tiger.hunger}"
end
end
tiger = Tiger.new
tiger.run
Statistics.show(tiger)
If you want to use class methods/variables:
class Animal; end
class Tiger < Animal
#hunger = 100
def self.hunger
#hunger
end
def self.run
puts "The Tiger runs"
#hunger += 10
end
end
class Statistics
def self.show
puts "This tigers hunger is: #{Tiger.hunger}"
end
end
Tiger.run
Statistics.show
Note that #hunger is now a class instance variable of Tiger. The difference between class instance variables and class variables (ones defined with ##) is that the latter is shared by all descendants of the said class, while the former is only tied to the class where it was defined.
I do not agree with your descision about Statistic in either case though.
Don't you need an initialize method in your Tiger class?
def initialize()
#hunger = 100
end
I am confused about your Statistics class. Shouldn't this be a method in the Tiger class? How does it know what Tiger to access? I would add that puts in a method taking your tiger as a parameter if you want to do that.
class Statistics
def tiger_stat(tiger_name)
puts "This tigers hunger is: #{tiger_name.hunger}"
end
end
Edit* this is the code I would use:
class Animal
def initialize(animal_type, hunger_start)
#animal_type = animal_type
#hunger = hunger_start
end
def hunger_print
puts "This #{#animal_type}'s hunger is: #{#hunger}."
end
end
class Tiger < Animal
def initialize
super("Tiger", 100)
end
def run
puts "The Tiger runs"
#hunger += 10
end
end
This way you can call hunger_print on any animal without worrying about the type or making an extra class. You could do something like.
tiger = Tiger.new
tiger.run
tiger.hunger_print
You can use class variable that is access from both instance and class methods.
class Animal
##hunger = 100
def self.hunger
##hunger
end
def self.increase_hunger
##hunger +=1
end
def hunger
##hunger
end
def increase_hunger
##hunger += 1
end
end
Animal.increase_hunger
puts Animal.hunger # => 101
animal = Animal.new
animal.increase_hunger
puts animal.hunger # => 102
Related
I am studying the adapter pattern implementation in ruby. I want to access an instance variable within the adapter module definition. Take a look at the following code:
module Adapter
module Dog
def self.speak
# I want to access the #name instance variable from my Animal instance
puts "#{name} says: woof!"
end
end
module Cat
def self.speak
# I want to access the #name instance variable from my Animal instance
puts "#{name} says: meow!"
end
end
end
class Animal
attr_accessor :name
def initialize(name)
#name = name
end
def speak
self.adapter.speak
end
def adapter
return #adapter if #adapter
self.adapter = :dog
#adapter
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
end
end
To test it out I did the following:
animal = Animal.new("catdog")
animal.adapter = :cat
animal.speak
I want it to return the following:
catdog says: meow!
Instead it says:
Adapter::Cat says: meow!
Any tips on how I can get access to the Animal#name instance method from the adapter module? I think the issue is that my adapter methods are class-level methods.
Thanks!
You need to use your Module as a mixin and provide a way to keep track of which module is active, the methods don't seem to be overwritten by reincluding or reextending so I took the extend and remove methods I found here.
module Adapter
module Dog
def speak
puts "#{name} says: woof!"
end
end
module Cat
def speak
puts "#{name} says: meow!"
end
end
def extend mod
#ancestors ||= {}
return if #ancestors[mod]
mod_clone = mod.clone
#ancestors[mod] = mod_clone
super mod_clone
end
def remove mod
mod_clone = #ancestors[mod]
mod_clone.instance_methods.each {|m| mod_clone.module_eval {remove_method m } }
#ancestors[mod] = nil
end
end
class Animal
include Adapter
attr_accessor :name, :adapter
def initialize(name)
#name = name
#adapter = Adapter::Dog
extend Adapter::Dog
end
def adapter=(adapter)
remove #adapter
extend Adapter::const_get(adapter.capitalize)
#adapter = Adapter.const_get(adapter.capitalize)
end
end
animal = Animal.new("catdog")
animal.speak # catdog says: woof!
animal.adapter = :cat
animal.speak # catdog says: meow!
animal.adapter = :dog
animal.speak # catdog says: woof!
This is because name inside of the module context refers to something entirely different than the name you're expecting. The Animal class and the Cat module do not share data, they have no relationship. Coincidentally you're calling Module#name which happens to return Adapter::Cat as that's the name of the module.
In order to get around this you need to do one of two things. Either make your module a mix-in (remove self, then include it as necessary) or share the necessary data by passing it in as an argument to speak.
The first method looks like this:
module Adapter
module Dog
def self.speak(name)
puts "#{name} says: woof!"
end
end
end
class Animal
attr_accessor :name
attr_reader :adapter
def initialize(name)
#name = name
self.adapter = :dog
end
def speak
self.adapter.speak(#name)
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
end
end
That doesn't seem as simple as it could be as they basically live in two different worlds. A more Ruby-esque way is this:
module Adapter
module Dog
def speak
puts "#{name} says: woof!"
end
end
end
class Animal
attr_accessor :name
attr_reader :adapter
def initialize(name)
#name = name
self.adapter = :dog
end
def adapter=(adapter)
#adapter = Adapter.const_get(adapter.to_s.capitalize)
extend(#adapter)
end
end
Sandi Metz says in SOLID OOPS concepts from GORUCO that presence of if..else blocks in Ruby can be considered to be a deviation from Open-Close Principle. What all methods can be used to avoid not-urgent if..else conditions? I tried the following code:
class Fun
def park(s=String.new)
puts s
end
def park(i=Fixnum.new)
i=i+2
end
end
and found out that function overloading does not work in Ruby. What are other methods through which the code can be made to obey OCP?
I could have simply gone for:
class Fun
def park(i)
i=i+2 if i.class==1.class
puts i if i.class=="asd".class
end
end
but this is in violation to OCP.
With your current example, and wanting to avoid type detection, I would use Ruby's capability to re-open classes to add functionality you need to Integer and String:
class Integer
def park
puts self + 2
end
end
class String
def park
puts self
end
end
This would work more cleanly when altering your own classes. But maybe it doesn't fit your conceptual model (it depends what Fun represents, and why it can take those two different classes in a single method).
An equivalent but keeping your Fun class might be:
class Fun
def park_fixnum i
puts i + 2
end
def park_string s
puts s
end
def park param
send("park_#{param.class.to_s.downcase}", param)
end
end
As an opinion, I am not sure you will gain much writing Ruby in this way. The principles you are learning may be good ones (I don't know), but applying them forcefully "against the grain" of the language may create less readable code, regardless of whether it meets a well-intentioned design.
So what I would probably do in practice is this:
class Fun
def park param
case param
when Integer
puts param + 2
when String
puts param
end
end
end
This does not meet your principles, but is idiomatic Ruby and slightly easier to read and maintain than an if block (where the conditions could be far more complex so take longer for a human to parse).
You could just create handled classes for Fun like so
class Fun
def park(obj)
#parker ||= Object.const_get("#{obj.class}Park").new(obj)
#parker.park
rescue NameError => e
raise ArgumentError, "expected String or Fixnum but recieved #{obj.class.name}"
end
end
class Park
def initialize(p)
#park = p
end
def park
#park
end
end
class FixnumPark < Park
def park
#park += 2
end
end
class StringPark < Park
end
Then things like this will work
f = Fun.new
f.park("string")
#=> "string"
f.instance_variable_get("#parker")
#=> #<StringPark:0x1e04b48 #park="string">
f = Fun.new
f.park(2)
#=> 4
f.instance_variable_get("#parker")
#=> #<FixnumPark:0x1e04b48 #park=4>
f.park(22)
#=> 6 because the instance is already loaded and 4 + 2 = 6
Fun.new.park(12.3)
#=> ArgumentError: expected String or Fixnum but received Float
You could do something like this:
class Parent
attr_reader :s
def initialize(s='')
#s = s
end
def park
puts s
end
end
class Child1 < Parent
attr_reader :x
def initialize(s, x)
super(s)
#x = x
end
def park
puts x
end
end
class Child2 < Parent
attr_reader :y
def initialize(s, y)
super(s)
#y = y
end
def park
puts y
end
end
objects = [
Parent.new('hello'),
Child1.new('goodbye', 1),
Child2.new('adios', 2),
]
objects.each do |obj|
obj.park
end
--output:--
hello
1
2
Or, maybe I overlooked one of your twists:
class Parent
attr_reader :x
def initialize(s='')
#x = s
end
def park
puts x
end
end
class Child1 < Parent
def initialize(x)
super
end
def park
x + 2
end
end
class Child2 < Parent
def initialize(x)
super
end
def park
x * 2
end
end
objects = [
Parent.new('hello'),
Child1.new(2),
Child2.new(100),
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
[nil, 4, 200]
And another example using blocks, which are like anonymous functions. You can pass in the desired behavior to park() as a function:
class Function
attr_reader :block
def initialize(&park)
#block = park
end
def park
raise "Not implemented"
end
end
class StringFunction < Function
def initialize(&park)
super
end
def park
block.call
end
end
class AdditionFunction < Function
def initialize(&park)
super
end
def park
block.call 1
end
end
class DogFunction < Function
class Dog
def bark
puts 'woof, woof'
end
end
def initialize(&park)
super
end
def park
block.call Dog.new
end
end
objects = [
StringFunction.new {puts 'hello'},
AdditionFunction.new {|i| i+2},
DogFunction.new {|dog| dog.bark},
]
results = objects.map do |obj|
obj.park
end
p results
--output:--
hello
woof, woof
[nil, 3, nil]
Look at the is_a? method
def park(i)
i.is_a?(Fixnum) ? (i + 2) : i
end
But even better not to check a type, but use duck typing:
def park(i)
i.respond_to?(:+) ? (i + 2) : i
end
UPD: After reading comments. Yes, both examples above don't solve the OCP problem. That is how I would do it:
class Fun
# The method doesn't know how to pluck data. But it knows a guy
# who knows the trick
def pluck(i)
return __pluck_string__(i) if i.is_a? String
__pluck_fixnum__(i) if i.is_a? Fixnum
end
private
# Every method is responsible for plucking data in some special way
# Only one cause of possible changes for each of them
def __pluck_string__(i)
puts i
end
def __pluck_fixnum__(i)
i + 2
end
end
I understand or equal to operation in ruby but can you explain what
you have done with:
Object.const_get("#{obj.class}Park").new(obj)
In ruby, something that starts with a capital letter is a constant. Here is a simpler example of how const_get() works:
class Dog
def bark
puts 'woof'
end
end
dog_class = Object.const_get("Dog")
dog_class.new.bark
--output:--
woof
Of course, you can also pass arguments to dog_class.new:
class Dog
attr_reader :name
def initialize(name)
#name = name
end
def bark
puts "#{name} says woof!"
end
end
dog_class = Object.const_get("Dog")
dog_class.new('Ralph').bark
--output:--
Ralph says woof!
And the following line is just a variation of the above:
Object.const_get("#{obj.class}Park").new(obj)
If obj = 'hello', the first portion:
Object.const_get("#{obj.class}Park")
is equivalent to:
Object.const_get("#{String}Park")
And when the String class object is interpolated into a string, it is simply converted to the string "String", giving you:
Object.const_get("StringPark")
And that line retrieves the StringPark class, giving you:
Object.const_get("StringPark")
|
V
StringPark
Then, adding the second portion of the original line gives you:
StringPark.new(obj)
And because obj = 'hello', that is equivalent to:
StringPark.new('hello')
Capice?
I came across some interesting Ruby code today.
class MeetingReminderJob < Struct.new(:user, :meeting)
def perform
send_reminder(user, meeting)
end
end
What is the purpose of the < Struct.new(:user, :meeting)?
Struct is a ruby class, it created a Class object that contains attributes and accessors, you don't need define a class explicitly. In the api ,you can find more details : http://www.ruby-doc.org/core-1.9.3/Struct.html.
In your case , it create a class that contains 2 attributes named "user" and "meeting", then class MeetingReminderJob inherits it.
Here's another example:
class Animal
def greet
puts "Hi. I'm an animal"
end
end
def get_class
return Animal
end
class Dog < get_class
def warn
puts "Woof."
end
end
Dog.new.greet
Dog.new.warn
--output:--
Hi. I'm an animal
Woof.
And another:
class Dog < Class.new { def greet; puts "Hi"; end }
def warn
puts "Woof."
end
end
Dog.new.greet
Dog.new.warn
--output:--
Hi
Woof.
I'd appreciate knowing why the instance of "Cat" below isn't also putting the "This animal can:" text before its specific instance attributes. I'm expecting an output like this:
This animal can:
Say it's name: 'Rover'
Bark
This animal can:
Say its name: 'Satan'
Meow
Here's the code:
class Animal
puts "This animal can:"
end
class Dog < Animal
def initialize(name)
#name = name
puts "Say its #name: '%s'" % [name]
end
def bark
puts "Bark"
end
end
class Cat < Animal
def initialize(name)
#name = name
puts "Say its #name: '%s'" % [name]
end
def meow
puts "Meow"
end
end
rover = Dog.new("Rover").bark
satan = Cat.new("Satan").meow
What I'm seeing is this:
This animal can:
Say it's name: 'Rover'
Bark
Say its name: 'Satan'
Meow
Doesn't "cat" also inherit from the Animal class? Shouldn't its output also begin with "This animal can:"?
The problem in your code is that:
puts "This animal can:"
is run when the Animal class gets defined. It seems like you want this in the initializer:
class Animal
def initialize(name)
puts "This animal can:"
end
end
You'll then need to call super in the other initializers for the result you're expecting.
The "This Animal can:" line only occurs when the class is defined, since it doesn't live in a method. Since both animals have common behavior in their initializers, you might want to promote the initializer to the Animal class.
class Animal
def introduce_myself
puts "Hello! My name is #{#name} and I know how to: "
end
def initialize(name)
#name = name
introduce_myself
end
end
class Dog < Animal
def bark
puts "Woof!"
end
end
class Cat < Animal
def meow
puts "Meow!"
end
end
Dog.new("Fido").bark
Cat.new("Sparky").meow
Output:
Hello! My name is Fido and I know how to:
Woof!
Hello! My name is Sparky and I know how to:
Meow!
Your Animal class doesn't define a constructor, nor is it called by inheritors:
class Animal
def initialize
puts "This animal can:"
end
end
class Dog < Animal
def initialize(name)
super()
#name = name
puts "Say its #name: '%s'" % [name]
end
def bark
puts "Bark"
end
end
Exactly!
class Animal
def initialize(name)
puts "This animal can:"
end
end
def initialize(name)
#name = name
puts "Say its #name: '%s'" % [name]
super # initializer from parent class
end
why that don't work ?
class Animal
puts "This animal can:"
end
when ruby parser read code it evaluate everything on way. I mean even if you will not create any object it will display "This animal can:" That's why you saw that for the first time and had impression that Dog class worked correctly
Install name_magic (gem install y_support) and have fun
require 'y_support/name_magic';
class Animal
include NameMagic
def initialize; puts "This animal #{sound}s." end
def sound; "growl" end
def speak; puts "#{sound.capitalize}!" end
end
class Dog < Animal
def sound; "bark" end
end
class Cat < Animal
def sound; "meow" end
end
def hullo( animal )
Animal.instance( animal ).speak
end
Rover = Dog.new
#=> This animal barks.
Satan = Cat.new
#=> This animal meows.
hullo :Rover
#=> Bark!
Animal.instances.each( &:speak )
#=> Bark!
#=> Meow!
I would like to create a base class to handle all the methods. How would I achieve this using ruby in this scenario?
class Dog
def initialize
#breed = "good breed"
end
def show_dog
puts "#{self.class.first_name} of breed #{#breed}"
end
end
class Lab < Dog
attr_reader :first_name
def initialize
#first_name = "good dog"
end
end
lb = Lab.new()
lb.show_dog
The expected result would be "good dog of breed good breed"
Thank you in advance :)
self.class.first_name doesn't do what you probably wanted to do. You need to use #first_name directly.
You need to call the parent class' constructor from the child class; it's not called automatically.
This works:
class Dog
def initialize
#breed = "good breed"
end
def show_dog
puts "#{#first_name} of breed #{#breed}" ### <- Changed
end
end
class Lab < Dog
attr_reader :first_name
def initialize
super ### <- Added
#first_name = "good dog"
end
end
lb = Lab.new()
lb.show_dog
self.class.first_name means Lab.first_name, not Lab's instance lb.first_name