I have classes A and B, each with methods a and b respectively, like below.
class A
def a
#I want to get the object which calls this method
#caller = B
end
end
class B
def initialize
#to_call = A.new
end
def b
#to_call.a
end
end
B.new.b
How can I return B that calls a so that I can use methods of B inside A?
I have a class Board, which Game classes use to play. The board class has a method interact that gets user input either with gets.chomp or STDIN.getc to simulate guessing games or games which use arrow keys. The game class calls the interact method to begin playing the game, and sends it a block that handles the way the game is played. Each game has its own set of rules, therefore each game class has a method that displays its rule book to the user. Within interact, when the user enters "-rules", I want the board class to return the class that called its interact method and store it in a variable caller. With the caller variable defined, I want to use caller.rule_book to display the rules of the game class that called the board's interact method.
I don't know a way you you could do it, besides using the source's location (caller_locations), but I'd advise not to follow this path. Too much indirection often leads to code extremely hard to debug. It's much simpler and readable to simply pass the context (in this case, self, or the properties needed for the computation) as an argument:
class A
def initialize(context)
#context = context
end
def a
# Do something with #context
end
end
class B
def initialize
#to_call = A.new(self)
end
def b
#to_call.a
end
end
B.new.b
Related
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 working on a Blackjack game. My Game object contains a deck object that gets shuffled after the deck reaches a certain level of penetration. Many of my methods depend on this deck object. I don't see any reason for the deck object to be accessible through setter methods. I'm having trouble testing the methods on my Game class, since they depend on the order of the deck, which is random.
For instance, I have the deal_hand method.
def deal_hand(player)
reset_deck if #deck.size < 2
player.hands.push(Hand.new(*#deck.pop(2)))
end
How should I test a method like this? I was thinking I could just manually create a Deck object that gets used in the #deck instance variable. Unfortunately, I can't set the instance variable, and I don't really want to add a setter, since there's no reason for that to be "settable" except to test. Should I monkey patch the class from my test-file and add a setter?
As an aside--I mostly write scripts--I decided I needed to start writing tests after this project got out of hand. Is there any canonical resource for "testing patterns?"
edit:
I am using MiniTest, which supports stubbing/mocking. Though as far as I can tell, it only lets you set expected return values for method calls on the mock object. If I made a Mock deck, the actual deck object also depends on an internal array. None of the code that calls the deck accesses the array directly.
Use a mock library. RSpec comes built with one, but I dislike it, so I'll show you what it might look like with Surrogate, the one I wrote:
class Deck
def pop(n) end
def reset() end
def size() end
end
class Game
def initialize(deck)
#deck = deck
end
def deal_hand(player)
reset_deck if #deck.size < 2
player.hands.push(Hand.new(*#deck.pop(2)))
end
def reset_deck
#deck.reset
end
end
Hand = Struct.new :card1, :card2
class Player
def hands
#hands ||= []
end
end
require 'surrogate/rspec'
class MockDeck
Surrogate.endow self
define(:reset)
define(:pop) { |n| n.times.to_a }
define(:size) { 1 }
end
describe Game, 'deal_hand' do
let(:deck) { MockDeck.new }
let(:player) { Player.new }
let(:game) { Game.new deck }
it 'resets the deck if there are less than 2 cards' do
deck.will_have_size 2 # set the return value of deck.size
game.deal_hand player
deck.was_not told_to :reset # assert what happened to the deck
deck.will_have_size 1
game.deal_hand player
deck.was told_to :reset
end
it 'deals the top 2 cards to the player' do
deck.will_pop [:card1, :card2]
game.deal_hand player
deck.was told_to(:pop).with(2)
player.hands.last.should == Hand.new(:card1, :card2)
end
end
describe Deck do
it 'is substitutable for the mock' do
# because we use the mock in tests
# we want to make sure its interface matches the real deck
Deck.should substitute_for MockDeck
end
end
Have you considered using mocha?
That would allow you to stub or mock the Deck to ensure that it has the expected cards for your test runs.
In your test use the method instance_variable_set, which is a ruby method on object.
So I am assuming your method is in the Game class, so you if you're setting up something like
#test_deck = something_that_sets_up_state_of_test_deck
#game = Game.new
#game.instance_variable_set(:deck, #test_deck
That will set your instance variable within Game, without the need for attr_accessible or getters and setters being explicitly built.
I met strange difficulty developing one project. I dont have enough experience with classes so this is why I ask here. I have one class which is initialized with one parameter and I need other classes to call that class functions but I cant until that class is initialized so I asking how I could do that.
Here is some example what I talking about:
class AVR
def initialize(device)
#device = device
#avr_conf = YAML::load(File.open("./devices/#{device}.yaml"))
end
def avr_conf
return #avr_conf
end
end
class IO
def setMode(a,b)
"#{AVR.avr_conf[a]} = #{b}"
end
end
You either: need an instance, or to make avr_conf a class method (and initialize differently).
With an instance:
avr = AVR.new(a_device)
avr.avr_conf[a]
With a config singleton (roughly):
class AVR
def self.class_initialize(device)
##avr_conf ... etc ...
end
def self.avr_conf
return ##avr_conf
end
end
Then class IO would need to use the updated version, however that's appropriate.
If IO isn't going to/can't get an instance, the class/singleton-config might make more sense, although that approach always makes me a little nervous.
You could have the dependent method simply throw an exception if the independent class is not yet initialized:
class IO
def setMode(a, b)
raise StandardError.new("AVR device is not initialized") unless AVR.avr_conf
AVR.avr_conf[a] = b
end
end
Note however that this solution requires the AVR class to be a singleton (or module) since the IO extension methods will need to know which instance should be modified.
There are two kinds of methods in Ruby classes - actually class and instance methods.
Class methods defines with self. prefix and there's no need to initialize class:
class AVR
def self.avr_conf
'something here'
end
end
So, in this case you must call: AVR.avr_conf to get 'something here' result.
But, in your case, #avr_conf variable defines in initialize method and you have to create a class instance with:
avr = AVR.new('something')
avr.avr_conf[a] = b
Is it possible to attach my own methods to another class, in limited region ?
If so, could anyone show me a better practice, or is it supposed to use something like deligate to do this?
The situation is like this : In class A that receive, generate and pass out instance of class B,
I want to attach some method onto those bs, without leaving those new methods accessible outside class A.
These are called singleton methods. You can add methods to any object, only affecting that instance, not the entire class it was created from.
some_object = Whatever.new
other_object = Whatever.new
class << some_object
def another_method
...
end
end
some_object.another_method # works
other_object.another_method # error: no such method another_method
You can also use define_singleton_method:
some_object.define_singleton_method(:foo) do |arg1, arg2|
puts "Called with #{arg1} and #{arg2}"
end
some_object.foo(7, 8)
There's also instance_eval, but you get the idea ;)
You can write a private method in class B that takes an A object as an argument and uses instance_variable_get, instance_variable_set, and send to access whatever data from the object you want. This is pretty ugly though.
I'm really new to Ruby. And by new - less than 16 hours, but my boss gave me some Ruby code to add to. However, I found it was one giant file and not modular at all, so I decided to clean it up. Now that I've broken it up into several files/classes (generally speaking, 1 class per file,) I'm having problems piecing it together for it to work again. Originally everything was part of the same class, so the calls worked, but it looked ugly and it took an entire work day just to figure it out. I want to avoid that for the future as this code will grow much larger before it is done.
My main issue looks like the following (simplified, obviously):
class TestDevice
def initialize
#loghash = { }
....
end
end
class Log
def self.msg(identifier, level, section, message)
...
#loghash[identifier] = { level => { section => message }}
...
end
end
device = TestDevice.new
After that, it calls out to other class methods, and those class methods reference back to the class Log for their logging needs. Of course, Log needs to access "device.loghash" somehow to log the information in that hash. But I can't figure out how to make that happen outside of passing the contents of "loghash" to every method, so that they, in turn, can pass it, and then return the value back to the origination point and then logging it at the end, but that seems really clumsy and awkward.
I'm hoping I am really just missing something.
To create accessors for instance variables the simple way, use attr_accessor.
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
....
end
end
You can also manually define an accessor.
class TestDevice
def loghash
#loghash
end
def loghash=(val)
#loghash = val
end
end
This is effectively what attr_accessor does behind the scenes.
how about passing the device object as a parameter to the msg function? (I'm assuming that there can be many devices in your program, otherwise you can use singleton pattern).
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
....
end
end
class Log
def self.msg(device, identifier, level, section, message)
...
device.loghash[identifier] = { level => { section => message }}
...
end
end
So you need to learn the rules of ruby scoping.
Ruby variables have different scope, depending on their prefix:
$global_variables start with a $, and are available to everyone.
#instance_variables start with a single #, and are stored with the current value of self. If two
scopes share the same value of self (they're both instance methods, for example),
then both share the same instance variables
##class_variable start with ##, and are stored with the class. They're
shared between all instances of a class - and all instances of subclasses
of that class.
Constants start with a capital letter, and may be all caps. Like class
variables, they're stored with the current self.class, but they also
trickle up the hierarchy - so if you have a class defined in a module,
the instances of the class can access the module's constants as well.
Constants defined outside of a class have global scope.
Note that a constant variable means that which object is bound to the constant
won't change, not that the object itself won't change internal state.
local_variables start with a lowercase letter
You can read more about scope here.
Local variables scoping rules are mainly standard - they're available in
all subscopes of the one in which they are defined except when we move into
a module, class, or method definition. So if we look at your code from your
answer
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
end
end
device = TestDevice.new
class Somethingelse
def self.something
device.loghash='something here' # doesn't work
end
end
The scope of the device local variable defined at the toplevel does not include the Somethingelse.something
method definition. So the device local variable used in the Somethingelse.something method definition is a different (empty) variable. If you want the scoping to work that way, you should use a constant or a global variable.
class TestDevice
attr_accessor :loghash
def initialize
#loghash = { }
end
end
DEVICE = TestDevice.new
$has_logged = false
class Somethingelse
def self.something
DEVICE.loghash='something here'
$has_logged = true
end
end
p DEVICE.loghash # prints `{}`
p $has_logged # prints `false`
Somethingelse.something
p DEVICE.loghash # prints `"something here"`
p $has_logged # prints `true`