What is the cleanest way to calculate instance variables in initialize? - ruby

Say I have a class that defines a collection of my days and how wacky they are. Is it better to initialize my #scores variable in the initialize function like so:
class WackyDayScorer
attr_reader :scores
def initialize(start = Time.now, max = 90.days)
#start = start
#limit = start - max
#scores = get_scores
end
private
def get_scores
result = []
t = #start.clone
until t < max
result << score_wackiness(t)
t -= 1.day
end
result
end
end
or initialize it in the get_scores method like so:
class WackyDayScorer
attr_reader :scores
def initialize(start = Time.now, max = 90.days)
#start = start
#limit = start - max
get_scores
end
private
def get_scores
#scores = []
t = #start.clone
until t < max
#scores << score_wackiness(t)
t -= 1.day
end
end
end

The first one is just plain wrong. You are saying
#scores = get_scores
But get_scores does not return a useful value, so this is nuts. Instead, get_scores sets #scores directly and internally as a side effect.
The second one is at least coherent.
Personally, though, I would not do either of the things you do. I would say what you say the first time:
#scores = get_scores
But my implementation of get_scores would not touch any ivar. It would return an actual value:
def get_scores
scores = []
# do stuff to scores
return scores # or simply, scores
end

If you want to handle something in initialization, create a private method to handle the calculation and then set the instance variable in #initialize. I wouldn't set it in the method in case you want to reuse that calculation elsewhere in the class without wiping out your instance variable.
class WackyDayScorer
attr_accessor :scores
def initialize(start = DateTime.now, max = 90.days)
#start = start
#limit = start - max
#scores = calculate_scores
end
private
def calculate_scores
(#limit..#start).to_a.map { |date_time| score_wackiness(date_time) }
end
end
In #calculate_scores, we are creating a range between the limit date and the start (counting backwards, as you were doing) and sending each result to the #score_wackiness method.

I'd recommend explicitly defining the getter for scores, memoizing the calculation and skipping all the ceremony:
class WackyDayScorer
def initialize(start = Time.now, max = 90.days)
#start = start
#limit = start - max
end
def scores
#scores ||= begin
result = []
t = #start.clone
until t < max
result << score_wackiness(t)
t -= 1.day
end
result
end
end
end
This is basically what you are doing anyway, but it's more direct.

Related

Design pattern for a calculation that has to output a report in Ruby

I have a class that does calculations. Most of the times it is used in the code to output a single value:
Use: value = Calculator.new(1234).output
This is an example of the class definition:
class Calculator
def initialize(parameter_1)
#parameter_1 = parameter_1
end
def output
op_1_result = operation_1(#parameter_1)
op_2_result = operation_2(op_1_result)
operation_3(op_2_result)
end
def operation_1(param)
...
end
But sometimes the user has to print a report of the calculation, showing many of the variables from inside the calculations.
The first solution I implemented was to pass a parameter at initialization telling the class that it should save some internal variables for the report, like this:
class Calculator
attr_reader :report
def initialize(parameter_1, reporting=false)
#parameter_1 = parameter_1
#reporting = reporting
#report = {}
end
def output
op_1_result = operation_1(#parameter_1)
#report[:op_1_result] = op_1_result if #reporting
op_2_result = operation_2(op_1_result)
#report[:op_2_result] = op_2_result if #reporting
operation_3(op_2_result)
end
def operation_1(param)
...
end
Then, when I want to get those intermediate variables, I would:
calculator = Calculator.new(1234, true) # reporting activated
report = calculator.report
report[:op_1_result] # one of the intermediate variables of the calculation
Does this break the single responsibility principle, as the class is now calculating the value and reporting at the same time?
Is there a better way to do this, a design pattern where I could have a fast calculation of the output result where it is needed and show the report where needed without all those ifs?
Any light on this and comments will be really appreciated!
Obs (another discussion): I've read that a more functional approach to just outputting a value would be a great thing. But that kept me wondering about how to show those internal intermediate values when needed. How do more functional programmers would do it...?
I guess "builder pattern" is suitable and "report pad" should be injected from outside.
class Calculator
def initialize(*parameters)
#parameters = parameters
end
def report_to(report_pad)
#report_pad = report_pad
self
end
def output()
ret = #parameters[0].to_i + #parameters[1].to_i
report('Did p0 + p1')
ret
end
private
def report(message)
#report_pad << "\n".prepend(message) if #report_pad.respond_to? '<<'
end
end
####
reports = ""
result = Calculator
.new(1, 2)
.report_to(reports)
.output()
puts result, reports
Why don't you just make all intermediate results public methods and chain the results in the final output?
Perhaps something like this:
class Calculator
def initialize(parameter)
#parameter = parameter
end
def output
op_3_result
end
def op_1_result
#op_1_result ||= operation_1(parameter)
end
def op_2_result
#op_2_result ||= operation2(op_1_result)
end
def op_3_result
#op_3_result ||= operation3(op_2_result)
end
private
def operation1(arg)
# ...
end
def operation2(arg)
# ...
end
def operation3(arg)
# ...
end
attr_reader :parameter
end
That would allow you to call whatever you need on the same instance:
calculator = Calculator.new(1234)
calculator.output #=> the final result
calculator.op_2_result #=> the intermediate result of step 2
You could use a different pattern with Report being its own class and allow it to just pass through when reporting is turned off. Here is a simple example:
class Calculator
attr_reader :report
def initialize(parameter_1, reporting=false)
#parameter_1 = parameter_1
#report = Report.new(reporting)
end
def output
op1 = operation_1(report.capture(:param1,#parameter_1))
report.capture(:op1,op1)
op2 = report.capture(:op2) { operation_2(op1) }
operation_3(op2)
end
def operation_1(param);
param + 7
end
def operation_2(param);
param - 3
end
def operation_3(param);
param * 2
end
end
class Report
attr_reader :reporting, :reportables
def initialize(reporting)
#reporting = reporting
#reportables = {}
end
def capture(key, val=nil,&block)
warn "Block supercedes value" if val && block_given?
result = block_given? ? block.call : val
#reportables[key] = result if reporting
result
end
def [](key)
return 'No Reporting' unless reporting
#reportables[key]
end
end
Then you can use like so
c = Calculator.new(12)
c.output
#=> 32
c.report[:op1]
#=> "No Reporting"
c = Calculator.new(12, true)
c.output
#=> 32
c.report[:op1]
#=> 19
c.report[:param1]
#=> 12
This way each step can use a block for more complicated items where the result should be captured or just pass a value if you choose and intermediate steps like operation_3 (in this case) that do not need to be "captured" can just flow through.
If reporting is off then everything just flows through and the captures are ignored.
Your #output method could also look like this (no intermediate locals at all although it does hurt the readability a bit)
def output
operation_3 (
report.capture(:op2,
operation_2(
report.capture(:op1) do
operation_1(report.capture(:param1,#parameter_1))
end
)
)
)
end
You can use dependency injection like this:
class Calculator
def initialize(parameter_1, reporter = nil)
#parameter_1 = parameter_1
#reporter = reporter
end
def output
op_1_result = call_with_reporting :operation_1, #parameter_1
op_2_result = call_with_reporting :operation_2, op_1_result
operation_3(op_2_result)
end
def operation_1(param)
...
end
def call_with_reporting(operation, *args)
result = self.send(operation, *args)
#reporter.report(operation, result) if #reporter
result
end
end
class ConsoleReporter
def initialize
#results = {}
end
def report(operation, result)
#results[operation] = result
end
def run_report
puts #operations
end
end
Now you can use Calculator like this:
reporter = ConsoleReporter.new
Calculator.new(12, reporter).output
reporter.run_report
Later you can use Calculator with other format reporter (like ToFileReporter)

Call method of an object in an array

When trying to call the closePrice method on the stock object, nothing is being printed out. However, the currently commented code works. How can I iterate through the StockList array and call the closePrice method of my Stock objects?
class Stock
attr_reader :date, :open, :high, :low, :close, :adjClose, :volume
def initialize(date, open, high, low, close, adjClose, volume)
#date = date
#open = open
#high = high
#low = low
#close = close
#adjClose = adjClose
#volume = volume
end
def closePrice
"Close price: #{#close}"
end
end
class StockList < Array
def initialize()
#stockList = []
end
def addStock(stock)
#stockList.push(stock)
end
end
stocks = Array.new
stockList = StockList.new()
CSV.foreach(fileName) do |stock|
entry = Stock.new(stock[0], stock[1], stock[2], stock[3], stock[4], stock[5], stock[6])
stocks.push(entry)
stockList.addStock(entry)
end
# stocks.each do |stock|
# puts stock.closePrice
# end
stockList.each do |stock|
puts stock.closePrice
end
Could it be that there are missing links? Only beginning with Ruby. Thanks.
So the problem is in your implementation of StockList. You have extended the class array but are setting up a new instance variable #stockList. When you call addStock you are adding an instance to the #stockList variable. But when you call each on the StockList instance, it is not iterating over the StockList.
Conceivably, you could add a method each to StockList like:
def each(&block)
#stockList.each(&block)
end
and it should work.
But really I would recommend rethinking your data structures. StockList should really not extend Array.

Problems testing simple random behaviour in rspec

Im having a bit of trouble testing some random behaviour in rspec. I have a method on a class that should change one of the classes instance variables if a randomly generated number equals 10. I cant find anyway to correctly test this with rspec.
Here is the code for the class
class Airport
DEFAULT_CAPACITY = 20
attr_reader :landed_planes, :capacity
attr_accessor :weather
def initialize(capacity=DEFAULT_CAPACITY,weather = "clear")
#landed_planes = []
#capacity = capacity
#weather = weather
end
def stormy
if rand(10) == 10 then #weather = "stormy" end
end
end
does anyone know of a way i could write test for the stormy method?
One option is start rspec with rspec --seed 123 this will ensure that your random numbers are always predictable. But this will effect all subsequent calls to rand, shuffle, sample and so on.
Another way is to change your class to inject the randnumber generator:
class Airport
DEFAULT_CAPACITY = 20
attr_reader :landed_planes, :capacity
attr_accessor :weather
def initialize(capacity=DEFAULT_CAPACITY,weather = "clear", randomizer = ->(n) { rand(n)})
#landed_planes = []
#capacity = capacity
#weather = weather
#randomizer = randomizer
end
def stormy
if #randomizer.call(10) == 10 then #weather = "stormy" end
end
end

Initialize class object variable in Ruby

I have created a class for example
class Result
##min = 0
##max = 0
def initialize(min, max)
##max.min = min
##max.max = max
end
end
result = Result.new(1, 10)
result.max
Same as other lang. like php, C# etc I have created a class and pass a value and since it has initialize method it will should now contains the object values but when I try to print out
puts result.min
puts result.max
it says undefined method min
In Ruby, ## before a variable means it's a class variable. What you need is the single # before the variable to create an instance variable. When you do Result.new(..), you are creating an instance of the class Result.
You don't need to create default values like this:
##min = 0
##max = 0
You can do it in the initialize method
def initialize(min = 0, max = 0)
This will initialize min and max to be zero if no values are passed in.
So now, your initialize method should like something like
def initialize(min=0, max=0)
#min = min
#max = max
end
Now, if you want to be able to call .min or .max methods on the instance of the class, you need to create those methods (called setters and getters)
def min # getter method
#min
end
def min=(val) # setter method
#min = val
end
Now, you can do this:
result.min #=> 1
result.min = 5 #=> 5
Ruby has shortcuts for these setters and getters:
attr_accessor: creates the setter and getter methods.
attr_reader: create the getter method.
attr_writer: create the setter method.
To use those, you just need to do attr_accessor :min. This will create both methods for min, so you can call and set min values directly via the instance object.
Now, you code should look like this
class Result
attr_accessor :min, :max
def initialize(min=0, max=0)
#min = min
#max = max
end
end
result = Result.new(1, 10)
result.max #=> 10
Without knowing the context here it's hard to say exactly what you're looking to do. I suspect what you actually want is an instance variable. In which case you would do:
class Result
attr_accessor :min, :max
def initialize(min, max)
#max = min
#max = max
end
end
Class variables in Ruby and are best avoided unless you really need them. If you actually do you could do this:
class Result
##min = 0
##max = 0
def self.min
##min
end
def self.min=(new_min)
##min = new_min
end
def self.max
##max
end
def self.max=(new_max)
##max = new_max
end
def initialize(min, max)
##min = min
##max = max
end
def min
##min
end
def max
##max
end
end
puts Result.min
puts Result.max
result = Result.new(1, 10)
puts result.min
puts result.max
puts Result.min
puts Result.max
Be warned though, class variables are tricky.

Ruby Check Class Owner From Other Inheritance With Default Library

I wondering of how to check the owner of certain method/class from other class.
For example:
class Value
attr_accessor :money
def initialize
#money = 0.0
end
def get_money
return self.money
end
def transfer_money(target, amount)
self.money -= amount
target.money += amount
end
end
class Nation
attr_accessor :value
def initialize
#value = Value.new
end
end
class Nation_A < Nation
def initialize
super
end
def pay_tribute_to_decendant_country
value.transfer_money(Nation_B.value, 500)
end
end
class Nation_B < Nation
def initialize
super
end
def pay_tribute_to_decendant_country
value.transfer_money(Nation_C.value, 200)
end
end
class Nation_C < Nation
def initialize
super
end
def pay_tribute_to_decendant_country
value.transfer_money(Nation_A.value, 300)
end
end
Yea, makes no sense how the decendant goes in a circle, but I'd like to implement the idea that different subclass has different argument.
The list is pretty long (I've installed at least 40 of these already with much more complex desendant branches and much more methods that call transfer_money from Value class). Then I have some idea to implement to the structure. I'd like to add currency, but to override all transfer_money method call would be a tremendous task for me to apply. Therefore I create a hash table that generate the call for me.
class Nation
def self.get_descendants
ObjectSpace.each_object(Class).select { |klass| klass < self }
end
end
module Additional_Value
currency_table = {}
min = 50
max = 100
def self.range (min, max)
rand * (max-min) + min
end
Nation.get_descendants.each do |derived_classes|
currency_table[derived_classes] == self.range min, max
end
end
class Value
attr_accessor :currency
def initialize
#money = 0
#currency = Additional_Value::currency_table
end
def transfer_money(target, amount)
self.money -= amount
amount = amount * #currency[self.class.owner] / #currency[target.class.owner]
target.money += amount
end
end
and I need to figure out how to define owner class. I tried using the caller, but it returns me string / array of string instead of object, method or calle work only for the same method, the 'sender' gem gives me an idea, but it's written in C, and I need to use the default library due to my circumstances.
Greatly appreciated.
Edit:
I'll rewrite the problem in a shorter way:
class Slave
def who_is_the_owner_of_me
return self.class.owner unless self.class.owner.nil?
end
end
class Test
attr_accessor :testing
def initialize
#testing = Slave.new
end
end
class Test2 < Test1
end
a = Test.new
b = Test2.new
c = Slave.new
a.testing.who_is_the_owner_of_me #=> Test
b.testing.who_is_the_owner_of_me #=> Test2
c.who_is_the_owner_of_me #=> main

Resources