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
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
Well, I can do this:
class Person
attr_accessor :name
def greeting
"Hello #{#name}"
end
end
p = Person.new
p.name = 'Dave'
p.greeting # "Hello Dave"
but when I decide to assign the property in the class itself it doesnt work:
class Person
attr_accessor :name
#name = "Dave"
def greeting
"Hello #{#name}"
end
end
p = Person.new
p.greeting # "Hello"
This is the default behavior, albeit a confusing one (especially if you're used to other languages in the OOP region).
Instance variables in Ruby starts being available when it is assigned to and normally this happens in the initialize method of your class.
class Person
def initialize(name)
#name = name
end
end
In your examples you're using attr_accessor, this magical method produces a getter and a setter for the property name. A Person#name and Person#name=, method is created which overrides your "inline" instance variable (that's why your first example works and the second one doesn't).
So the proper way to write your expected behaviour would be with the use of a initialize method.
class Person
def initialize(name)
#name = name
end
def greeting
"Hello, #{#name}"
end
end
Edit
After a bit of searching I found this awesome answer, all rep should go to that question.
Think of a Person class as a blueprint that you can create single person instances with. As not all of these person instances will have the name "Dave", you should set this name on the instance itself.
class Person
def initialize(name)
#name = name
end
attr_accessor :name
def greeting
"Hello #{#name}"
end
end
david = Person.new("David")
p david.greeting
# => "Hello David"
mike = Person.new("Mike")
p mike.greeting
# => "Hello Mike"
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!
For example:
class Animal
def make_noise
print NOISE
end
end
class Dog < Animal
NOISE = "bark"
end
d = Dog.new
d.make_noise # I want this to print "bark"
How do I accomplish the above? Currently it says
uninitialized constant Animal::NOISE
I think that you don't really want a constant; I think that you want an instance variable on the class:
class Animal
#noise = "whaargarble"
class << self
attr_accessor :noise
end
def make_noise
puts self.class.noise
end
end
class Dog < Animal
#noise = "bark"
end
a = Animal.new
d = Dog.new
a.make_noise #=> "whaargarble"
d.make_noise #=> "bark"
Dog.noise = "WOOF"
d.make_noise #=> "WOOF"
a.make_noise #=> "whaargarble"
However, if you are sure that you want a constant:
class Animal
def make_noise
puts self.class::NOISE
# or self.class.const_get(:NOISE)
end
end
one way to do it without class instance variables:
class Animal
def make_noise
print self.class::NOISE
end
end
class Dog < Animal
NOISE = "bark"
end
d = Dog.new
d.make_noise # prints bark
I think you have the wrong concept here. Classes in Ruby are similar to classes in Java, Smalltalk, C#, ... and all are templates for their instances. So the class defines the structure and the behavior if its instances, and the parts of the structure and behavior of the instances of its subclasses but not vice versae.
So direct access from a superclass to a constant in a subclass is not possible at all, and that is a good thing. See below how to fix it. For your classes defined, the following things are true:
class Animal defines the method make_noise.
instances of class Animal may call the method make_noise.
class Dogdefines the constant NOISE with its value.
instances of Dog and the class Dog itself may use the constant NOISE.
What is not possible:
Instances of Animal or the class Animal itself have access to constants of the class Dog.
You may fix that by the following change:
class Animal
def make_noise
print Dog::NOISE
end
end
But this is bad style, because now, your superclass (which is an abstraction about Dog and other animals) knows now something that belongs to Dog.
A better solution would be:
Define an abstract method in class Animal which defines that make_noise should be defined. See the answer https://stackoverflow.com/a/6792499/41540.
Define in your concrete classes the method again, but now with the reference to the constant.
If you're doing this to configure your sub classes so that the base class has access to the constants then you can create a DSL for them like this:
module KlassConfig
def attr_config(attribute)
define_singleton_method(attribute) do |*args|
method_name = "config_#{attribute}"
define_singleton_method method_name do
args.first
end
define_method method_name do
args.first
end
end
end
end
class Animal
extend KlassConfig
attr_config :noise
def make_noise
puts config_noise
end
end
class Dog < Animal
noise 'bark'
end
This way is a bit more performant as on each method call you don't have to introspect the class to reach back (or is it forward?) for the constant.
If you want it the Object-Oriented Way (TM), then I guess you want:
class Animal
# abstract animals cannot make a noise
end
class Dog < Animal
def make_noise
print "bark"
end
end
class Cat < Animal
def make_noise
print "meow"
end
end
d = Dog.new
d.make_noise # prints bark
c = Cat.new
c.make_noise # prints meow
If you want to refactor to prevent duplicating the code for print:
class Animal
def make_noise
print noise
end
end
class Dog < Animal
def noise
"bark"
end
end
class Cat < Animal
def noise
if friendly
"meow"
else
"hiss"
end
end
end
d = Dog.new
d.make_noise # prints bark
c = Cat.new
c.make_noise # prints meow or hiss