Adapter Pattern in ruby: Accessing Your Instance Variables - ruby

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

Related

Ruby classes pass variables between

Is there any way to pass variables between classes?
I have the next code.
module Test
class Super
def initialize(name)
#name = name
end
end
class Upper
def test
puts #name
end
end
end
a=Test::Super.new('My name')
b=Test::Upper.new()
b.test()
Thank you!
No, because a is an instance of the class. Two answers for you;
1) It's better programming practice to have send a to b. So you'd do something like this; (assuming attr_reader :name)
class Upper
def test(s)
s.name
end
end
a = Test::Super.new('My Name')
u = Test::Upper.new
u.test(a)
or you could have it part of the setup; I won't give you all the code, but here's how it'd look
a = Test::Super.new('My name')
b = Test::Upper.new(a)
b.test
=> 'My name'
Neither of these examples is particularly good practice for classes but I imagine you have a more specific use case you're trying to achieve that has been anonymised for the purpose of this question :)
If for some reason instances of the class Upper need to have an access to the internals of instances of the class Super, it means you have a design flaw.
One possible way would be Super needs to expose the variable via a getter:
module Test
class Super
def initialize(name)
#name = name
end
def name
#name
end
end
end
Now you might get the name with Test::Super.new("my name").name.
Another possibility is Upper is actually a subclass of Super:
class Upper < Super
def test
puts #name
end
end
Now Test::Upper.new("my name").test will print "my name", because Upper derives the implementation from Super.
Also, one might use an instance variable on the enclosing module level:
module Test
def self.name=(name)
#name = name
end
def self.name
#name
end
class Super
def initialize(name)
Test.name = name
end
end
class Upper
def test
puts Test.name
end
end
end
This would print:
▶ Test::Super.new("my")
#⇒ #<Test::Super:0x0055dae57fe390>
▶ Test::Upper.new.test
#⇒ "my"
You can make use of class variables in module, which is supported natively by Ruby.
You can do:
module Test
##name = ''
class Super
def initialize(name)
##name = name
end
end
class Upper
def test
puts ##name
end
end
end
a=Test::Super.new('My name')
b=Test::Upper.new()
b.test()

I can not use in-class assigned property of a class in Ruby

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"

how to get child class variable back into parent class in ruby

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

Understanding Ruby class vs instance methods

I have the following code:
#!/usr/bin/ruby
class Person
def self.speak
p = self.new
puts "Hello"
p.chatter
end
private
def chatter
puts "Chattering"
end
end
p = Person.new
Person.speak
I'd like to make chatter private, accessible only within p.. but I want p to be able to access it within the class method. Is there a better way to design this so chatter isn't available to the public, but a "factory" method like self.speak can call chatter?
In Ruby 1.8.7, "send" bypasses the usual protections against calling private methods:
#!/usr/bin/ruby1.8
class Person
def self.speak
puts "Hello"
new.send(:chatter)
end
def speak
puts "Hello"
puts chatter
end
private
def chatter
puts "Chattering"
end
end
Person.speak # => Hello
# => Chattering
Person.new.speak # => Hello
# => Chattering
However, what you want can be achieved without any voodoo, by simply having the class method do all the work, and the instance method defer to the class method:
class Person
def self.speak
puts "Hello"
puts "Chatter"
end
def speak
self.class.speak
end
end
If you had more than a few of these forwarding methods, it might be convenient to make a helper method that makes them for you:
module DelegateToClass
def delegate_to_class(name)
define_method(name) do |*args|
self.class.send(name, *args)
end
end
end
class Person
extend DelegateToClass
def self.speak
puts "Hello"
puts "Chatter"
end
delegate_to_class :speak
end
The built-in module Forwardable can do this just as well:
require 'forwardable'
class Person
extend Forwardable
def self.speak
puts "Hello"
puts "Chatter"
end
def_delegator self, :speak
end
def_delegator also bypasses protections against private methods.
There's a number of ways to do this. One approach would be a class method which instantiates a new instance and calls the #speak method on the instance.
class Person
def self.speak
new.speak
end
def speak
puts "Hello"
chatter
end
private
def chatter
puts "Chattering"
end
end
Then you could call it in either the class or instance context:
p = Person.new
p.speak
# ...or...
Person.speak

Ruby methods similar to attr_reader

I'm trying to make a method similar to attr_reader but I can't seem to get the instance of the class that the method gets called in.
class Module
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
variables = symbols.collect { |sym| ("#" << sym.to_s).to_sym }
attr_reader *symbols
(class << ModifyMethods; self; end).instance_eval do
define_method(*symbols) do
mod.instance_variable_get(*variables)
end
end
end
end
class Object
module ModifyMethods; end
def modify(&block)
ModifyMethods.instance_eval(&block)
end
end
class Klass
modifiable_reader :readable
def initialize
#readable = "this"
end
end
my_klass = Klass.new
my_klass.modify do
puts "Readable: " << readable.to_s
end
I'm not sure what it is you're trying to do.
If it helps, the spell for attr_reader is something like this:
#!/usr/bin/ruby1.8
module Kernel
def my_attr_reader(symbol)
eval <<-EOS
def #{symbol}
##{symbol}
end
EOS
end
end
class Foo
my_attr_reader :foo
def initialize
#foo = 'foo'
end
end
p Foo.new.foo # => "foo"
What I can understand from your code is that you want to have the modify block to respond to the instance methods of Klass, that's as simple as:
class Klass
attr_reader :modifiable
alias_method :modify, :instance_eval
def initialize(m)
#modifiable = m
end
end
Klass.new('john').modify do
puts 'Readable %s' % modifiable
end
About this tidbit of code:
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
...
Probably this can give you a hint of what is going on:
Class.superclass # => Module
Klass.instance_of?(Class) # => true
Klass = Class.new do
def hello
'hello'
end
end
Klass.new.hello # => 'hello'
When you are adding methods to the Module class, you are also adding methods to the Class class, which will add an instance method to instances of Class (in this case your class Klass), at the end this means you are adding class methods on your Klass class

Resources