attr_accessor or custom methods duplicate method names confusion - ruby

I do understand that Ruby supports short-hand style of calling methods i.e: 1.+(2) is same as 1+2 (and I still think if it related to my situation), but I got really confused why attr_accessor methods are neither duplicate (well, they should not be, as writer differs with = in its name) nor they differ with anything (except writer needs an argument) while accessing them outside active object.
My question is included in this code (in the second comment)
class Test
def initialize(number)
#number = number
end
def number
#number
end
def number=(n)
#number = n
end
end
t = Test.new(12)
puts t.number # => 12
t.number = 13 # Why does it do what t.number=(13) should do
puts t.number # => 13
I wonder why t.number = 13 works, when it points to a method which should only return a number and moreover how does it set a new value when t.number=(13) is not called instead.

t.number = 13 is just a shorthand for t.number=(13), they are effectively the same statement in Ruby.
attr_accessor :b creates the equivalent of the following two methods:
def b
#b
end
def b=(new_val)
#b = new_val
end
So in your code example, you could replace the two methods #number and #number= with attr_accessor :number

Related

Create hash of instance methods references

I have a class that can parse different types of messages and what I want to do is to create a hash that will use the msg type id as the keys and different instance methods as the values.
Something like this:
class Parser
def initialize(msg_id)
#my_methods = {1 => method_1, 2 => method_2, 3 => method_3}
#my_methods[msg_id]()
end
def method_1
end
def method_2
end
def method_3
end end
I know it's possible, but I am not sure how to do it. I tried using the self.method(:method_1) as a value but I got an error saying that method_1 is not defined.
Thank you
The simplest possible changes to fix your code are like this:
class Parser
def initialize(msg_id)
#my_methods = { 1 => method(:method_1), 2 => method(:method_2), 3 => method(:method_3) }
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
I.e. use the Object#method method to get a Method object, and use the Method#call method to execute it.
However, there are a few improvements we could make. For one, your Hash associates Integers with values. But there is a better data structure which already does that: an Array. (Note: if your message IDs are not assigned sequentially, then a Hash is probably the right choice, but from the looks of your example, they are just Integers counting up from 1.)
And secondly, hardcoding the methods inside the Parser#initialize method is probably not a good idea. There should be a declarative description of the protocol, i.e. the message IDs and their corresponding method names somewhere.
class Parser
# this will make your message IDs start at 0, though
PROTOCOL_MAPPING = [:method_1, :method_2, :method_3].freeze
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
def method_1; end
def method_2; end
def method_3; end
end
Another possibility would be something like this:
class Parser
PROTOCOL_MAPPING = []
private_class_method def self.parser(name)
PROTOCOL_MAPPING << name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map(&method(:method))
#my_methods[msg_id].()
end
parser def method_1; end
parser def method_2; end
parser def method_3; end
end
Or maybe this:
class Parser
PROTOCOL_MAPPING = {}
private_class_method def self.parser(msg_id, name)
PROTOCOL_MAPPING[msg_id] = name
end
def initialize(msg_id)
#my_methods = PROTOCOL_MAPPING.map {|msg_id, name| [msg_id, method(name)] }.to_h.freeze
#my_methods[msg_id].()
end
parser 1, def method_1; end
parser 2, def method_2; end
parser 3, def method_3; end
end
While provided answer would work fine, there are few "minor" issues with it:
If there'd be tons of methods, hardcoding such hash would take time, and since it is not dynamic (because you have to update the hash manually each time new method is added to the class body) it is very error prone.
Even though you are within the class, and technically have access to all methods defined with any visibility scope with implicit receiver (including private and protected), it is still a good practice to only rely on public interface, thus, I'd recommend to use Object#public_send.
So here is what I would suggest (despite the fact I do not see how the idea of having such map would work in real life):
class Parser
def initialize(msg_id)
# generate a dynamic hash with keys starting with 1
# and ending with the size of the methods count
methods_map = Hash[(1..instance_methods.size).zip(instance_methods)]
# Use public_send to ensure, only public methods are accessed
public_send(methods_map[msg_id])
end
# create a method, which holds a list of all instance methods defined in the class
def instance_methods
self.class.instance_methods(false)
end
end
After a quick thought I refactored it a bit, so that we hide the implementation of the mapping to private methods:
class Parser
def initialize(msg_id)
public_send(methods_map[msg_id])
end
# methods omitted
private
def methods_map # not methods_hash, because what we do is mapping
Hash[(1..instance_methods.size).zip(instance_methods)]
# or
# Hash[instance_methods.each.with_index(1).map(&:reverse)]
end
def instance_methods
self.class.instance_methods(false)
end
end
The method you're looking for is send.
Note that the values in your hash need to be symbols to be passed to send.
class Parser
def initialize(msg_id)
#my_methods = {1 => :method_1, 2 => :method_2, 3 => :method_3}
send(#my_methods[msg_id])
end
def method_1
end
def method_2
end
def method_3
end
end
Documentation here

Making a Yhatzee game, array won't show up on screen

Ok so I just started learning ruby and I'm making a Yhatzee game, now this is where I'm currently at:
class Yhatzee
def dices
#dices.to_a= [
dice1=rand(1..6),
dice2=rand(1..6),
dice3=rand(1..6),
dice4=rand(1..6),
dice5=rand(1..6)
]
end
def roll_dice
#dices.to_a.each do |dice|
puts dice
end
end
end
x = Yhatzee.new
puts x.roll_dice
Now the reason i typed .to_a after the array is i kept getting a "uninitialized variable #dices" error, and that seemed to fix it, i have no idea why.
anyways on to my question, i currently don't get any errors but my program still won't print anything to the screen. I expected it to print out the value of each dice in the array... any idea what I'm doing wrong? It seems to work when i do it in a procedural style without using classes or methods so i assumed it might work if i made the 'dices' method public. But no luck.
There are a few issues here. Firstly #dices is nil because it is not set anywhere. Thus when you call #dices.to_a you will get []. Also the dices method will not work either because nil does not have a to_a= method and the local variables you are assigning in the array will be ignored.
It seems a little reading is in order but I would do something like the following: (Not the whole game just refactor of your code)
class Yhatzee
def dice
#dice = Array.new(5){rand(1..6)}
end
def roll_dice
puts dice
end
end
x = Yhatzee.new
puts x.roll_dice
There are alot of additional considerations that need to be made here but this should at least get you started. Small Example of how I would recommend expanding your logic: (I did not handle many scenarios here so don't copy paste. Just wanted to give you a more in depth look)
require 'forwardable'
module Yahtzee
module Display
def show_with_index(arr)
print arr.each_index.to_a
print "\n"
print arr
end
end
class Roll
include Display
extend Forwardable
def_delegator :#dice, :values_at
attr_reader :dice
def initialize(dice=5)
#dice = Array.new(dice){rand(1..6)}
end
def show
show_with_index(#dice)
end
end
class Turn
class << self
def start
t = Turn.new
t.show
t
end
end
attr_reader :rolls
include Display
def initialize
#roll = Roll.new
#rolls = 1
#kept = []
end
def show
#roll.show
end
def roll_again
if available_rolls_and_dice
#rolls += 1
#roll = Roll.new(5-#kept.count)
puts "Hand => #{#kept.inspect}"
show
else
puts "No Rolls left" if #rolls == 3
puts "Remove a Die to keep rolling" if #kept.count == 5
show_hand
end
end
def keep(*indices)
#kept += #roll.values_at(*indices)
end
def show_hand
show_with_index(#kept)
end
def remove(*indices)
indices.each do |idx|
#kept.delete_at(idx)
end
show_hand
end
private
def available_rolls_and_dice
#rolls < 3 && #kept.count < 5
end
end
end
The main problem with this code is that you are trying to use the #dices instance variable inside of the roll_dice method, however you are not defining the instance variable anywhere (anywhere that is being used). You have created the dices method but you are not actually instantiating it anywhere. I have outlined a fix below:
class Yhatzee
def initialize
create_dices
end
def roll_dice
#dices.each do |dice|
puts dice
end
end
private
def create_dices
#dices = Array.new(5){rand(1..6)}
end
end
x = Yhatzee.new
x.roll_dice
I have done some simple refactoring:
Created an initialize method, which creates the #dice instance variable on the class initialization.
Made the 'dices' method more descriptive and changed the method visibility to private so only the class itself is able to create the #dice.
Cleaned up the creation of the dices inside of the #dice instance variable
I have omitted the .to_a from the roll_dice method, now that we create the variable from within the class and we know that it is an array and it will be unless we explicitly redefine it.
UPDATE
Although I cleaned up the implementation of the class, it was kindly pointed out by #engineersmnky that I oversaw that the roll would return the same results each time I called the roll_dice function, I have therefore written two functions which will achieve this, one that defines an instance variable for later use and one that literally just returns the results.
class Yhatzee
def roll_dice
#dice = Array.new(5){rand(1..6)} # You will have access to this in other methods defined on the class
#dice.each {|dice| puts dice }
end
def roll_dice_two
Array.new(5){rand(1..6)}.each {|dice| puts dice } # This will return the results but will not be stored for later use
end
end
x = Yhatzee.new
x.roll_dice
x.roll_dice # Will now return a new result

Is it possible to temporarily alter an instance variable for a chained method call?

I have a class that lazily loads data from a database into an instance variable, they are events in an array in numeric order. The class has several methods that analyse this array, here is an example of how I use it.
class Foo
def initialize
#a = [1,2,3,4,5] # data from database
end
def analyse
#a.reduce(:+)
end
end
d = Foo.new
result = d.analyse
I wanted to be able to apply these methods to the data after a very basic filter (eg: <= 3) and I imagined being able to call it like so:
d.at(3).analyse
and that the at method only affected the instance variable for the chained analyse call. i.e.
d = Foo.new # data loaded into instance var [1,2,3,4,5]
d.analyse # 15
d.at(3).analyse # 6
d.analyse # 15
I'm not sure how I can do this without re-creating a completely new object within the at call and this feels inefficient. I have a work around which would change how I call the at method - not the end of the world but I wondered if what I want is feasible whilst remaining efficient.
I'm not sure how I can do this without re-creating a completely new object within the at call and this feels inefficient.
I don't think creating a new object is too expensive. You could return a new Foo from at, initialized with a subset of the original data. You'd have another Foo instance and another Array instance, but the array would contain the very same objects:
class Foo
def initialize(a = nil)
#a = a || [1,2,3,4,5] # use a or fetch data from database
end
def analyse
#a.reduce(:+)
end
def at(max)
Foo.new(#a.take_while { |x| x <= max })
end
end
Example:
d = Foo.new
d.analyse #=> 15
d.at(3).analyse #=> 6
d.analyse #=> 15
You can prepare another instance variable which is set by at, overrides #a when defined, and is reset by analyse.
class Foo
def initialize
#a = [1,2,3,4,5]
end
def at i
#b = #a.select{|e| e <= i}
self
end
def array
if instance_variable_defined?(:#b)
#b.tap{remove_instance_variable(:#b)}
else
#a
end
end
def analyse
array.reduce(:+)
end
end

How should I define an instance variable in Ruby whose processing algorithm is defined in a super class?

In short: I want to define the algorithm in the superclass which inherits into all subclasses, but I want to define the data (on which the algorithm operates) in the subclasses as instance variables, which come into being when I call the "new" method of the given classes. What is the standard way for doing this in Ruby?
My solution is (but I am not exactly sure this is the right way):
class A
attr_accessor :var
def initialize
#var=nil #I dont know the actual value, it will be defined only in the more specific subclasses.
end
def process_data
puts #var #simply puts it out
end
end
#in my program all further classes are inherited form class A, the processing facility is inherited, only the data varies.
class B < A
attr_accessor :var
def initialize
#var=10 #specific value for class B which is always 10, no need for example b=B.new(20)
end
end
class C < A
attr_accessor :var
def initialize
#var=20 #specific value for class C which is always 20, no need for example c=C.new(20)
end
end
b=B.new
b.process_data #needs to print 10
c=C.new
c.process_data #needs to print 20
What you have works. There's just some unneeded stuff in there:
Instance variables evaluate to nil if they are uninitialized and they spring into existence as soon as they are used, so your A#initialize method is unnecessary.
You override the A#var and A#var= methods in B and C with methods that do the exact same thing. There is no need for that, just get rid of the calls to attr_accessor in the definition of B and C.
You create var and var= accessor methods but you never use them. Either get rid of the call to attr_accessor or (preferably) use the accessor methods, i.e. use self.var = in initialize and puts var in process_data.
class A
attr_accessor :var
def process_data
puts var #simply puts it out
end
end
#in my program all further classes are inherited form class A, the processing facility is inherited, only the data varies.
class B < A
def initialize
self.var = 10 #specific value for class B which is always 10, no need for example b=B.new(20)
end
end
class C < A
def initialize
self.var = 20 #specific value for class C which is always 20, no need for example c=C.new(20)
end
end
b = B.new
b.process_data #needs to print 10
c = C.new
c.process_data #needs to print 20
[Note: your coding style was also off. Indentation is 2 spaces in Ruby, not 3.]
However, if the value of #var is always the same for all instances of B, then why do you need multiple instances of B?
Why not something like this:
class A
attr_accessor :var
def initialize(val)
self.var = val
end
def process_data
puts var #simply puts it out
end
end
b = A.new(10)
b.process_data #needs to print 10
c = A.new(20)
c.process_data #needs to print 20
You are doing it in almost the right way. A minor point is that your A::initialize definition is redundant for two reasons:
An instance variable is initialized to nil automatically.
The initialize methods in the subclasses will override A::initialize when the subclass instances are created.
Also, the attr_accessor calls in the subclasses are redundant.
Now I seem to get what you wanted. You can use class-level instance variables.
class A
def initialize
#var=self.class.instance_variable_get(:#var)
end
def process_data
puts #var
end
end
class B < A
#var = 10
end
class C < A
#var = 20
end
B.new.process_data # => 10
C.new.process_data # => 20

What is the difference between def func(var) and def func=(var)?

In a class definition, what is the difference between these two methods?
def func(var)
...
end
def func=(var)
...
end
Is there any, or is one of them not valid?
Both of them are valid method definitions. But the second one is defining a 'setter' method - you can call this method with the following syntax:
obj.func = 123
This statement will be translated into
obj.func=(123)
You can take a look at this answer where I explain this syntax in a bit more detail.
To explain some things about reader/writer AKA getter/setter methods in Ruby:
Ruby doesn't force us to use = in the method definition for a setter. We get to choose whether the method has one.
Consider this:
class Foo
# automagically creates:
# .v
# .v=
attr_accessor :v
def initialize(v)
puts "inside initialize(#{ v })"
#v = v
end
def setter(v)
puts "inside setter(#{ v })"
#v = v
end
def setter=(v)
puts "inside setter=(#{ v })"
#v = v
end
end
f = Foo.new(1)
puts f.v
f.setter(2)
puts f.v
f.setter = 3
puts f.v
f.setter=(4)
puts f.v
f.v = 5
puts f.v
f.v=(6)
puts f.v
Running the code outputs:
inside initialize(1)
1
inside setter(2)
2
inside setter=(3)
3
inside setter=(4)
4
5
6
The = is simply another letter in the method name because Ruby is smart enough to know if it sees f.setter = 3 it should use the setter=(v) method.
Ruby doesn't force using = to set a variable, you can decide if it makes more sense to you when you define the method. It is idiomatic that we use = because it helps make a setter look like an assignment, removing the urge to name all the setters something like set_v(v).
These are defining the getter and setter methods if you will. Say you have a Person class with a phone attribute.
class Person
def phone
#phone
end
def phone=(number)
#phone = number
end
end
Now you could change the phone attribute (managed internally in the #phone) by simply setting the property which will invoke the phone= method.
john = Person.new
john.phone = "123-456-7890"
It looks like a property assignment on the outside. Other characters that you can stack at the end of a method name are ? for boolean getters, ! for destructive operations. Again, these are just conventions and you're free to use these three characters as you want. However, code simply looks more natural with these symbols around. For example,
question.closed?
document.destroy!

Resources