Say I have the following class:
class Cashier
def purchase(amount)
(#purchases ||= []) << amount
end
def total_cash
(#purchases || []).inject(0) {|sum,amount| sum + amount}
end
end
This is for learning purposes only, please ignore how unrealistic this may be.
Now in general, the total_cash could be an expensive call to loop through all the items.
I want to know how I can call .inject ONLY if the #purchases variable is dirty i.e. there was something modified.
How would my class be modified to do this?
The simplest approach would be to maintain another variable to indicate whether or not #purchases is dirty. For example:
class Cashier
def initialize(*args)
# init #purchases and #total_cash
#is_purchases_dirty = false
end
def purchase(amount)
(#purchases ||= []) << amount
#is_purchases_dirty = true
end
def total_cash
return #total_cash unless #is_purchases_dirty
#is_purchases_dirty = false
#total_cash = (#purchases || []).inject(0) {|sum,amount| sum + amount}
return #total_cash
end
end
A cleaner/simpler approach may be to calculate #total_cash each time the setter is called for purchases. However, this means that you need to always use the setter, even within your class. It also means that you will be "hiding" an expensive operation inside of a setter method. You can decide which one you like better.
class Cashier
def purchase(amount)
(#purchases ||= []) << amount
#total_cash = (#purchases || []).inject(0) {|sum,amount| sum + amount}
end
def total_cash
#total_cash
end
end
I would also recommend against your naming scheme for an expensive operation. I would rename total_cash to something like calc_total_cash in order to tell users of your API that this is a relatively expensive call as opposed to a simple getter/setter.
You can take this a step further than the other answers if you wanted. Rather than changing your code to only recalculate when necessary, you can write the code that changes your code. Everybody loves a bit of metaprogramming.
Here's some code that takes the name of a method that performs a potentially long calculation, and a list of names of methods that when called invalidate any previous calculation, and writes the code to wrap the methods and only perform the calculation if necessary, returning the stored value if not.
module ExpensiveCalculation
def recalc_only_if_necessary(meth, *mutators)
aliased_method_name = "__#{meth.object_id}__"
value = "#__#{meth.object_id}_value__"
dirty_flag = "#__#{meth.object_id}_dirty__"
module_eval <<-EOE
alias_method :#{aliased_method_name}, :#{meth}
private :#{aliased_method_name}
def #{meth}(*args, &blk)
#{dirty_flag} = true unless defined? #{dirty_flag}
return #{value} unless #{dirty_flag}
#{value} = #{aliased_method_name}(*args, &blk)
#{dirty_flag} = false
#{value}
end
EOE
mutators.each do |mutator|
aliased_mutator = "__#{meth.object_id}_#{mutator.object_id}__"
module_eval <<-EOE
alias_method :#{aliased_mutator}, :#{mutator}
private :#{aliased_mutator}
def #{mutator}(*args, &blk)
#{dirty_flag} = true
#{aliased_mutator}(*args, &blk)
end
EOE
end
end
# this hook is used to make the new method
# private to the extended class.
def self.extend_object(obj)
super
obj.private_class_method :recalc_only_if_necessary
end
end
By making this available inside your class definition, you can wrap one or many methods easily without changing your existing code:
class Cashier
extend ExpensiveCalculation
def purchase(amount)
(#purchases ||= []) << amount
end
def total_cash
(#purchases || []).inject(0) {|sum,amount| sum + amount}
end
recalc_only_if_necessary :total_cash, :purchase
end
It might not make sense to do something like this if you just want to change one method, but if you have several that you want to change some way techniques like this can be pretty useful.
In the simplest case, you could define an instance variable for the thing you want to mark as dirty. Set it to true when the variable is modified (in your purchase method).
Check for the value in total_cash; if so, use a cached version of the total. Otherwise, compute the new value and store it in the cache.
class Cashier
def purchase(amount)
#purchases_dirty = true
(#purchases ||= []) << amount
end
def total_cash
#total_cash = (#purchases || []).inject(0) do |sum,amount|
sum + amount
end if (#purchases_dirty || #total_cash.nil?)
#purchases_dirty = false
#total_cash
end
end
Related
I'm trying to wrap my head around delegation vs. inheritance so I'm manually delegating a version of Array. One of the specific reasons I read to do this is because when you use things like enumerables, your returned value on the inherited methods reverts back to the parent class (i.e. Array). So I did this:
module PeepData
# A list of Peeps
class Peeps
include Enumerable
def initialize(list = [])
#list = list
end
def [](index)
#list[index]
end
def each(...)
#list.each(...)
end
def reverse
Peeps.new(#list.reverse)
end
def last
#list.last
end
def join(...)
#list.join(...)
end
def from_csv(csv_table)
#list = []
csv_table.each { |row| #list << Peep.new(row.to_h) }
end
def include(field, value)
Peeps.new(select { |row| row[field] == value })
end
def exclude(field, value)
Peeps.new(select { |row| row[field] != value })
end
def count_by_field(field)
result = {}
#list.each do |row|
result[row[field]] = result[row[field]].to_i + 1
end
result
end
protected
attr_reader :list
end
end
When I instantiate this, my include and exclude function great and return a Peeps class but when using an enumerable like select, it returns Array, which prevents me from chaining further Peeps specific methods after the select. This is exactly what I'm trying to avoid with learning about delegation.
p = Peeps.new
p.from_csv(csv_generated_array_of_hashes)
p.select(&:certified?).class
returns Array
If I override select, wrapping it in Peeps.new(), I get a "SystemStackError: stack level too deep". It seems to be recursively burying the list deeper into the list during the select enumeration.
def select(...)
Peeps.new(#list.select(...))
end
Any help and THANKS!
I would recommend using both Forwardable and Enumerable. Use Forwardable to delegate the each method to your list (to satisfy the Enumerable interface requirement), and also forward any Array methods you might want to include that are not part of the Enumerable module, such as size. I would also suggest not overriding the behavior of select as it is supposed to return an array and would at the very least lead to confusion. I would suggest something like the subset provided below to implement the behavior you are looking for.
require 'forwardable'
class Peeps
include Enumerable
extend Forwardable
def_delegators :#list, :each, :size
def initialize(list = [])
#list = list
end
def subset(&block)
selected = #list.select(&block)
Peeps.new(selected)
end
protected
attr_reader :list
end
Example usage:
peeps = Peeps.new([:a,:b,:c])
subset = peeps.subset {|s| s != :b}
puts subset.class
peeps.each do |peep|
puts peep
end
puts peeps.size
puts subset.size
produces:
Peeps
a
b
c
3
2
I think that if Peeps#select will return an Array, then it is OK to include Enumerable. But, you want Peeps#select to return a Peeps. I don't think you should include Enumerable. It's misleading to claim to be an Enumerable if you don't conform to its interface. This is just my opinion. There is no clear consensus on this in the ecosystem. See "Examples from the ecosystem" below.
If we accept that we cannot include Enumerable, here's the first implementation that comes to my mind.
require 'minitest/autorun'
class Peeps
ARRAY_METHODS = %i[flat_map map reject select]
ELEMENT_METHODS = %i[first include? last]
def initialize(list)
#list = list
end
def inspect
#list.join(', ')
end
def method_missing(mth, *args, &block)
if ARRAY_METHODS.include?(mth)
self.class.new(#list.send(mth, *args, &block))
elsif ELEMENT_METHODS.include?(mth)
#list.send(mth, *args, &block)
else
super
end
end
end
class PeepsTest < Minitest::Test
def test_first
assert_equal('alice', Peeps.new(%w[alice bob charlie]).first)
end
def test_include?
assert Peeps.new(%w[alice bob charlie]).include?('bob')
end
def test_select
peeps = Peeps.new(%w[alice bob charlie]).select { |i| i < 'c' }
assert_instance_of(Peeps, peeps)
assert_equal('alice, bob', peeps.inspect)
end
end
I don't normally use method_missing, but it seemed convenient.
Examples from the ecosystem
There doesn't seem to be a consensus on how strictly to follow interfaces.
ActionController::Parameters used to inherit Hash. Inheritance ceased in Rails 5.1.
ActiveSupport::HashWithIndifferentAccess still inherits Hash.
As mentioned in the other answer, this isn't really proper usage of Enumerable. That said, you could still include Enumerable and use some meta-programming to override the methods that you want to be peep-chainable:
module PeepData
class Peeps
include Enumerable
PEEP_CHAINABLES = [:map, :select]
PEEP_CHAINABLES.each do |method_name|
define_method(method_name) do |&block|
self.class.new(super(&block))
end
end
# solution for select without meta-programming looks like this:
# def select
# Peeps.new(super)
# end
end
end
Just so you know, this really has nothing to do with inheritance vs delegation. If Peeps extended Array, you would have the exact same issue, and the exact solution above would still work.
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
I have an empty object that accepts calculated values for each factor. The method is readable but long and ugly. What is a DRY way of doing this?
class ReadingScore
def initialize(reading, score)
#reading = reading
#score = score
end
def assign_scoring_factors
#score.heart_rate_factor = heart_rate_factor
#score.systolic_pressure_factor = systolic_pressure_factor
#score.diastolic_pressure_factor = diastolic_pressure_factor
#score.mean_pressure_factor = mean_pressure_factor
#score.signal_minimum_factor = signal_minimum_factor
#score.signal_average_factor = signal_average_factor
…
end
def heart_rate_factor
#reading.heart_rate && (1..10).include?(#reading.heart_rate) ? 0 : 10
end
…
end
Update
The overall purpose of this class is to calculate a score of a reading. I can’t provide all the code because it is a proprietary algorithm for a medical device.
But basically there are n factors of a #reading that are calculated and then saved to a #score object associated with the #reading. The sum of the factors is also calculated as a total on the #score object. The #score object looks like the following:
#score=
#<Score:0x007faa0b33ec50
#attributes=
{"id"=>42,
"reading_id"=>42,
"sum_of_factors"=>10,
"heart_rate_factor"=>10,
"another_factor"=>0,
"another_factor"=>0}
This seems to be the best option so far. The first answer to the question started me on this route, but the poster seems to have removed it…
def assign_factors_to_score
factors.each do |factor|
#score.public_send("#{factor}=", self.public_send(factor))
end
end
def factors
%i{factor_a factor_b factor_c factor_d}
end
You can automatically populate an array of factors using method_added. This combines nicely with dynamic assignment of factors as in your answer.
class ReadingScore
#factors = []
def self.method_added meth
#factors << meth if meth =~ /_factor\Z/
end
def self.factors
#factors
end
end
Note that these are class methods, so you would need to use self.class.factors when using this in an instance method.
Here is a full implementation in case you do not see how to integrate this code.
You could use delegate
class ReadingScore
extend Forwardable
delegate [:heart_rate_factor=, :systolic_pressure_factor=,:diastolic_pressure_factor=,
:mean_pressure_factor=,:signal_minimum_factor=,:signal_average_factor=] => :#score
def initialize
#score = Score.new
end
def assign_scoring_factors
%w(heart_rate_factor systolic_pressure_factor diastolic_pressure_factor mean_pressure_factor signal_minimum_factor signal_average_factor).each do |meth|
self.send("#{meth}=",self.send(meth))
end
self
end
end
but I agree with others that rethinking the whole design might be better here.
You could also use tap but the code will look fairly similar to what you have now.
Also I have no idea what a Score looks like internally because it seems like it would be better to place this logic inside the Score or Reading and pass all of this to a method or initializer of Score. e.g.
class ReadingScore
def intialize(reading)
#reading = Reading.new(reading)
#score = Score.new(#reading)
end
end
class Reading
#...
def heart_rate_score
heart_rate && (1..10).include?(#reading.heart_rate) ? 0 : 10
end
def systolic_pressure_score
#logic
end
def diastolic_pressure_score
#logic
end
def mean_pressure_score
#logic
end
def signal_minimum_score
#logic
end
def signal_average_score
#logic
end
end
class Score
attr_accessor :heart_rate_factor,:systolic_pressure_factor,:diastolic_pressure_factor,
:mean_pressure_factor,:signal_minimum_factor,:signal_average_factor
def initialize(reading)
factorialize(reading)
self
end
private
def factorialize(reading)
%w(heart_rate systolic_pressure diastolic_pressure mean_pressure signal_minimum signal_average) do |meth|
self.send("#{meth}_factor=",reading.send("#{meth}_score")
end
end
end
This way your logic is centralized in Score and Reading and can be avoided in ReadingScore. This will make the code easier to trace and will clean up the original class.
you can do it like this, if you insist:
def assign_scoring_factors
%w(heart_rate systolic_pressure diastolic_pressure mean_pressure signal_minimum signal_average).each |f| do
eval("#score.#{f}.factor = #{f}.factor")
end
end
but this isn't what I'd do. I'd either leave it moist, or just use a map.
Some code that I had that used attr_accessor_with_default in a rails model is now giving me a deprecation warning, telling me to "Use Ruby instead!"
So, thinking that maybe there was a new bit in ruby 1.9.2 that made attr_accessor handle defaults, I googled it, but I don't see that. I did see a bunch of methods to override attr_accessor to handle defaults though.
Is that what they mean when they tell me to "Use Ruby?" Or am I supposed to write full getters/setters now? Or is there some new way I can't find?
This apidock page suggests to just do it in the initialize method.
class Something
attr_accessor :pancakes
def initialize
#pancakes = true
super
end
end
Don't forget to call super especially when using ActiveRecord or similar.
attr_accessor :pancakes
def after_initialize
return unless new_record?
self.pancakes = 11
end
This ensures that the value is initialized to some default for new record only.
Since you probably know your data quite well, it can be quite acceptable to assume nil is not a valid value.
This means you can do away with an after_initialize, as this will be executed for every object you create. As several people have pointed out, this is (potentially) disastrous for performance. Also, inlining the method as in the example is deprecated in Rails 3.1 anyway.
To 'use Ruby instead' I would take this approach:
attr_writer :pancakes
def pancakes
return 12 if #pancakes.nil?
#pancakes
end
So trim down the Ruby magic just a little bit and write your own getter. After all this does exactly what you are trying to accomplish, and it's nice and simple enough for anyone to wrap his/her head around.
This is an ooooold question, but the general problem still crops up - and I found myself here.
The other answers are varied and interesting, but I found problems with all of them when initializing arrays (especially as I wanted to be able to use them at a class level before initialize was called on the instance). I had success with:
attr_writer :pancakes
def pancakes
#pancakes ||= []
end
If you use = instead of ||= you will find that the << operator fails for adding the first element to the array. (An anonymous array is created, a value is assigned to it, but it's never assigned back to #pancakes.)
For example:
obj.pancakes
#=> []
obj.pancakes << 'foo'
#=> ['foo']
obj.pancakes
#=> []
#???#!%$##%FRAK!!!
As this is quite a subtle problem and could cause a few head scratches, I thought it was worth mentioning here.
This pattern will need to be altered for a bool, for example if you want to default to false:
attr_writer :pancakes
def pancakes
#pancakes.nil? ? #pancakes = false : #pancakes
end
Although you could argue that the assignment isn't strictly necessary when dealing with a bool.
There's nothing magical in 1.9.2 for initializing instance variables that you set up with attr_accessor. But there is the after_initialize callback:
The after_initialize callback will be called whenever an Active Record object is instantiated, either by directly using new or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record initialize method.
So:
attr_accessor :pancakes
after_initialize :init
protected
def init
#pancakes = 11
end
This is safer than something like this:
def pancakes
#pancakes ||= 11
end
because nil or false might be perfectly valid values after initialization and assuming that they're not can cause some interesting bugs.
I'm wondering if just using Rails implementation would work for you:
http://apidock.com/rails/Module/attr_accessor_with_default
def attr_accessor_with_default(sym, default = nil, &block)
raise 'Default value or block required' unless !default.nil? || block
define_method(sym, block_given? ? block : Proc.new { default })
module_eval( def #{sym}=(value) # def age=(value) class << self; attr_reader :#{sym} end # class << self; attr_reader :age end ##{sym} = value # #age = value end # end, __FILE__, __LINE__ + 1)
end
You can specify default values for instances of any class (not only ActiveRecords) after applying patch to Module:
class Zaloop
attr_accessor var1: :default_value, var2: 2
def initialize
self.initialize_default_values
end
end
puts Zaloop.new.var1 # :default_value
Patch for module:
Module.module_eval do
alias _original_attr_accessor attr_accessor
def attr_accessor(*args)
attr_names = extract_default_values args
_original_attr_accessor *attr_names
end
alias _original_attr_reader attr_reader
def attr_reader(*args)
attr_names = extract_default_values args
_original_attr_reader *attr_names
end
def extract_default_values(args)
#default_values ||= {}
attr_names = []
args.map do |arg|
if arg.is_a? Hash
arg.each do |key, value|
define_default_initializer if #default_values.empty?
#default_values[key] = value
attr_names << key
end
else
attr_names << arg
end
end
attr_names
end
def define_default_initializer
default_values = #default_values
self.send :define_method, :initialize_default_values do
default_values.each do |key, value|
instance_variable_set("##{key}".to_sym, value)
end
end
end
def initialize_default_values
# Helper for autocomplete and syntax highlighters
end
end
Using Ruby I know you can get pretty creative with how you name your methods. For instance in rails you have .find_by_this_and_that.
How can I do this?
Example:
def get_persons_with_5_things
res = []
persons.each do |person|
if person.number_of_things == %MAGICALLY GET THE NUMBER 5 FROM FUNCTION NAME%
res << person
end
end
return res
end
I'm not even sure how you call this kind of things so any pointers would be appreciated.
I'm a little confused by your example. If you define the method with the hardcoded 5 in the method name, then you don't need to magically figure it out inside the body of the method. If you want to do something dynamic with method missing, it would be something like this:
def method_missing(name, *args)
if name.to_s =~ /get_persons_with_(\d+)_things/
number_of_things = $1.to_i
res = []
persons.each do |person|
if person.number_of_things == number_of_things
res << person
end
end
return res
else
return super(name, *args)
end
end
[EDIT (Jörg W Mittag)]: This is a more Rubyish way of implementing that same method:
def method_missing(name, *args)
return super unless name.to_s =~ /get_persons_with_(\d+)_things/
number_of_things = $1.to_i
return persons.select {|person| person.number_of_things == number_of_things }
end
super without any arguments just passes the original arguments along, no need to pass them explicitly
an early return guarded by a trailing if or unless expression greatly clears up control flow
all the each iterator does, is select items according to a predicate; however, there already is an iterator for selecting items: select
Ruby has different meta programming techniches to do this kind of stuff.
First we need our variable method
class DB
def get_persons_with_x_things(x)
res = []
persons.each do |person|
if person.number_of_things == x
res << person
end
end
return res
end
end
define_method
If there is a finite number of x's. We could use define_method to create all this methods. define_method creates a method. The first argument is the name of the method, the seccond argument or the given block is the stuff, which get's executed when the method is called.
This way, you don't realy create such method's, but It will look for the user if he calls it, as if it existed. But if the user relies on Object#methods and such, he will never see your inifinite number of fake methods.
class DB
99.times do |i|
define_method("get_persons_with_#{i}_things") do
get_persons_with_x_things(i)
end
end
end
method_missing
If there is an infinite numbor of x's method_missing would be better suited for this Task. If someone tries to call a method which does not exist, method_missing is executed instead. The first argument for method_missing is the method name as symbol, the following arguments are the original arguments.
class DB
def method_missing(name, *args)
case name.to_s
when /^get_persons_with_(\d+)_things$/
get_persons_with_x_things($1.to_i)
else
super(name, *args)
end
end
end
method_missing and send
To not use static regexe would be even cooler. But this could have some security implications. The method send I use here, calls a method by it's name.
class DB
def method_missing(name, *args)
name.to_s=~ /\d+/
# always be carefull with $ variables, they are global for this thread, so save everything as fast as you can
new_name= "#{$`}x#{$'}"
number= $1.to_i
if method_defined?(new_name)
send(new_name, number)
else
super(name, *args)
end
end
end
you can do a lot of things like this with method missing:
Ruby Docs
StackOveflow method_missing
Have a look at Ruby's callbacks specially method_missing.