From 913a916856cf078f91325d120d544ba100e67141 Mon Sep 17 00:00:00 2001 From: J Smith Date: Tue, 5 Jan 2021 16:54:09 -0400 Subject: [PATCH 1/2] Simplify call counting --- test/memoist_test.rb | 167 +++++++++++++++++++------------------------ 1 file changed, 72 insertions(+), 95 deletions(-) diff --git a/test/memoist_test.rb b/test/memoist_test.rb index 433f119..28551a5 100644 --- a/test/memoist_test.rb +++ b/test/memoist_test.rb @@ -17,40 +17,29 @@ def count(method_name) end end - class Person - extend Memoist + module Countable + attr_reader :counter def initialize @counter = CallCounter.new end - def name_calls - @counter.count(:name) - end - - def student_name_calls - @counter.count(:student_name) - end - - def name_query_calls - @counter.count(:name?) - end - - def is_developer_calls - @counter.count(:is_developer?) + def calls(method_name) + counter.count(method_name) end + end - def age_calls - @counter.count(:age) - end + class Person + extend Memoist + include Countable def name - @counter.call(:name) + counter.call(:name) 'Josh' end def name? - @counter.call(:name?) + counter.call(:name?) true end memoize :name? @@ -61,30 +50,26 @@ def update(_name) memoize :update def age - @counter.call(:age) + counter.call(:age) nil end memoize :name, :age def age? - @counter.call(:age?) + counter.call(:age?) true end memoize 'age?' def sleep(hours = 8) - @counter.call(:sleep) + counter.call(:sleep) hours end memoize :sleep - def sleep_calls - @counter.count(:sleep) - end - def update_attributes(_options = {}) - @counter.call(:update_attributes) + counter.call(:update_attributes) true end memoize :update_attributes @@ -103,7 +88,7 @@ def memoize_protected_test private def is_developer? - @counter.call(:is_developer?) + counter.call(:is_developer?) 'Yes' end memoize :is_developer? @@ -111,7 +96,7 @@ def is_developer? class Student < Person def name - @counter.call(:student_name) + counter.call(:student_name) "Student #{super}" end memoize :name, identifier: :student @@ -125,24 +110,20 @@ def seniority end class Company - attr_reader :name_calls - def initialize - @name_calls = 0 - end + include Countable def name - @name_calls += 1 + counter.call(:name) '37signals' end end module Rates extend Memoist + include Countable - attr_reader :sales_tax_calls def sales_tax(price) - @sales_tax_calls ||= 0 - @sales_tax_calls += 1 + counter.call(:sales_tax) price * 0.1025 end memoize :sales_tax @@ -150,15 +131,11 @@ def sales_tax(price) class Calculator extend Memoist + include Countable include Rates - attr_reader :fib_calls - def initialize - @fib_calls = 0 - end - def fib(n) - @fib_calls += 1 + counter.call(:fib) if n == 0 || n == 1 n @@ -177,11 +154,11 @@ def add_or_subtract(i, j, add) end memoize :add_or_subtract - def counter - @count ||= 0 - @count += 1 + def incrementor + @incrementor ||= 0 + @incrementor += 1 end - memoize :counter + memoize :incrementor end class Book @@ -243,32 +220,32 @@ def setup def test_memoization assert_equal 'Josh', @person.name - assert_equal 1, @person.name_calls + assert_equal 1, @person.calls(:name) 3.times { assert_equal 'Josh', @person.name } - assert_equal 1, @person.name_calls + assert_equal 1, @person.calls(:name) end def test_memoize_with_optional_arguments assert_equal 4, @person.sleep(4) - assert_equal 1, @person.sleep_calls + assert_equal 1, @person.calls(:sleep) 3.times { assert_equal 4, @person.sleep(4) } - assert_equal 1, @person.sleep_calls + assert_equal 1, @person.calls(:sleep) 3.times { assert_equal 4, @person.sleep(4, :reload) } - assert_equal 4, @person.sleep_calls + assert_equal 4, @person.calls(:sleep) end def test_memoize_with_options_hash assert_equal true, @person.update_attributes(age: 21, name: 'James') - assert_equal 1, @person.update_attributes_calls + assert_equal 1, @person.calls(:update_attributes) 3.times { assert_equal true, @person.update_attributes(age: 21, name: 'James') } - assert_equal 1, @person.update_attributes_calls + assert_equal 1, @person.calls(:update_attributes) 3.times { assert_equal true, @person.update_attributes({ age: 21, name: 'James' }, :reload) } - assert_equal 4, @person.update_attributes_calls + assert_equal 4, @person.calls(:update_attributes) end def test_memoization_with_punctuation @@ -289,33 +266,33 @@ def test_memoization_flush_with_punctuation assert_equal true, @person.name? @person.flush_cache(:name?) 3.times { assert_equal true, @person.name? } - assert_equal 2, @person.name_query_calls + assert_equal 2, @person.calls(:name?) end def test_memoization_with_nil_value assert_nil @person.age - assert_equal 1, @person.age_calls + assert_equal 1, @person.calls(:age) 3.times { assert_nil @person.age } - assert_equal 1, @person.age_calls + assert_equal 1, @person.calls(:age) end def test_reloadable - assert_equal 1, @calculator.counter - assert_equal 2, @calculator.counter(:reload) - assert_equal 2, @calculator.counter - assert_equal 3, @calculator.counter(true) - assert_equal 3, @calculator.counter + assert_equal 1, @calculator.incrementor + assert_equal 2, @calculator.incrementor(:reload) + assert_equal 2, @calculator.incrementor + assert_equal 3, @calculator.incrementor(true) + assert_equal 3, @calculator.incrementor end def test_flush_cache - assert_equal 1, @calculator.counter + assert_equal 1, @calculator.incrementor - assert @calculator.instance_variable_get(:@_memoized_counter) - @calculator.flush_cache(:counter) - assert_equal false, @calculator.instance_variable_defined?(:@_memoized_counter) + assert @calculator.instance_variable_get(:@_memoized_incrementor) + @calculator.flush_cache(:incrementor) + assert_equal false, @calculator.instance_variable_defined?(:@_memoized_incrementor) - assert_equal 2, @calculator.counter + assert_equal 2, @calculator.incrementor end def test_class_flush_cache @@ -346,14 +323,14 @@ def test_flush_cache_in_child_class end def test_unmemoize_all - assert_equal 1, @calculator.counter + assert_equal 1, @calculator.incrementor - assert_equal true, @calculator.instance_variable_defined?(:@_memoized_counter) - assert @calculator.instance_variable_get(:@_memoized_counter) + assert_equal true, @calculator.instance_variable_defined?(:@_memoized_incrementor) + assert @calculator.instance_variable_get(:@_memoized_incrementor) @calculator.unmemoize_all - assert_equal false, @calculator.instance_variable_defined?(:@_memoized_counter) + assert_equal false, @calculator.instance_variable_defined?(:@_memoized_incrementor) - assert_equal 2, @calculator.counter + assert_equal 2, @calculator.incrementor end def test_all_memoized_structs @@ -403,7 +380,7 @@ def test_unmemoize_all_subclasses def test_memoize_all @calculator.memoize_all - assert_equal true, @calculator.instance_variable_defined?(:@_memoized_counter) + assert_equal true, @calculator.instance_variable_defined?(:@_memoized_incrementor) end def test_memoize_all_subclasses @@ -426,9 +403,9 @@ def test_memoize_all_subclasses end def test_memoization_cache_is_different_for_each_instance - assert_equal 1, @calculator.counter - assert_equal 2, @calculator.counter(:reload) - assert_equal 1, Calculator.new.counter + assert_equal 1, @calculator.incrementor + assert_equal 2, @calculator.incrementor(:reload) + assert_equal 1, Calculator.new.incrementor end def test_memoization_class_variables @@ -449,16 +426,16 @@ def test_memoized_is_not_affected_by_freeze def test_memoization_with_args assert_equal 55, @calculator.fib(10) - assert_equal 11, @calculator.fib_calls + assert_equal 11, @calculator.calls(:fib) end def test_reloadable_with_args assert_equal 55, @calculator.fib(10) - assert_equal 11, @calculator.fib_calls + assert_equal 11, @calculator.calls(:fib) assert_equal 55, @calculator.fib(10, :reload) - assert_equal 12, @calculator.fib_calls + assert_equal 12, @calculator.calls(:fib) assert_equal 55, @calculator.fib(10, true) - assert_equal 13, @calculator.fib_calls + assert_equal 13, @calculator.calls(:fib) end def test_memoization_with_boolean_arg @@ -472,19 +449,19 @@ def test_object_memoization company.memoize :name assert_equal '37signals', company.name - assert_equal 1, company.name_calls + assert_equal 1, company.calls(:name) assert_equal '37signals', company.name - assert_equal 1, company.name_calls + assert_equal 1, company.calls(:name) end end def test_memoized_module_methods assert_equal 1.025, @calculator.sales_tax(10) - assert_equal 1, @calculator.sales_tax_calls + assert_equal 1, @calculator.calls(:sales_tax) assert_equal 1.025, @calculator.sales_tax(10) - assert_equal 1, @calculator.sales_tax_calls + assert_equal 1, @calculator.calls(:sales_tax) assert_equal 2.5625, @calculator.sales_tax(25) - assert_equal 2, @calculator.sales_tax_calls + assert_equal 2, @calculator.calls(:sales_tax) end def test_object_memoized_module_methods @@ -492,11 +469,11 @@ def test_object_memoized_module_methods company.extend(Rates) assert_equal 1.025, company.sales_tax(10) - assert_equal 1, company.sales_tax_calls + assert_equal 1, company.calls(:sales_tax) assert_equal 1.025, company.sales_tax(10) - assert_equal 1, company.sales_tax_calls + assert_equal 1, company.calls(:sales_tax) assert_equal 2.5625, company.sales_tax(25) - assert_equal 2, company.sales_tax_calls + assert_equal 2, company.calls(:sales_tax) end def test_double_memoization_with_identifier @@ -529,8 +506,8 @@ def test_memoization_with_a_subclass student = Student.new student.name student.name - assert_equal 1, student.student_name_calls - assert_equal 1, student.name_calls + assert_equal 1, student.calls(:student_name) + assert_equal 1, student.calls(:name) end def test_memoization_is_chainable @@ -556,8 +533,8 @@ def test_private_method_memoization assert_raises(NoMethodError) { person.is_developer? } assert_equal 'Yes', person.send(:is_developer?) - assert_equal 1, person.is_developer_calls + assert_equal 1, person.calls(:is_developer?) assert_equal 'Yes', person.send(:is_developer?) - assert_equal 1, person.is_developer_calls + assert_equal 1, person.calls(:is_developer?) end end From 01ffd5d703a32103a3da0b2a614039e928ccaacf Mon Sep 17 00:00:00 2001 From: J Smith Date: Tue, 2 Feb 2021 13:48:04 -0400 Subject: [PATCH 2/2] Use Ruby 2-style keywords via the `ruby2_keywords` method flag --- lib/memoist.rb | 3 +- memoist.gemspec | 7 +++- test/memoist_test.rb | 82 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 87 insertions(+), 5 deletions(-) diff --git a/lib/memoist.rb b/lib/memoist.rb index c49e102..8c04cf3 100644 --- a/lib/memoist.rb +++ b/lib/memoist.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'memoist/version' +require 'ruby2_keywords' module Memoist def self.extended(extender) @@ -203,7 +204,7 @@ def #{method_name}(reload = false) # end module_eval <<-EOS, __FILE__, __LINE__ + 1 - def #{method_name}(*args) + ruby2_keywords def #{method_name}(*args) reload = Memoist.extract_reload!(method(#{unmemoized_method.inspect}), args) skip_cache = reload || !(instance_variable_defined?(#{memoized_ivar.inspect}) && #{memoized_ivar} && #{memoized_ivar}.has_key?(args)) diff --git a/memoist.gemspec b/memoist.gemspec index 3b6fb30..7474e24 100644 --- a/memoist.gemspec +++ b/memoist.gemspec @@ -37,10 +37,15 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'benchmark-ips' spec.add_development_dependency 'bundler' + spec.add_development_dependency 'minitest', '~> 5.10' + if RUBY_VERSION < '1.9.3' spec.add_development_dependency 'rake', '~> 10.4' else spec.add_development_dependency 'rake' end - spec.add_development_dependency 'minitest', '~> 5.10' + + if RUBY_VERSION < '3.0.0' + spec.add_dependency 'ruby2_keywords' + end end diff --git a/test/memoist_test.rb b/test/memoist_test.rb index 28551a5..0301ad8 100644 --- a/test/memoist_test.rb +++ b/test/memoist_test.rb @@ -74,9 +74,40 @@ def update_attributes(_options = {}) end memoize :update_attributes - def update_attributes_calls - @counter.count(:update_attributes) + def rest(*args) + counter.call(:rest) + + args.each_with_index.each_with_object({}) do |(arg, i), memo| + memo[i + 1] = arg + end + end + memoize :rest + + def rest_and_kwargs(*args, **options) + counter.call(:rest_and_kwargs) + + i = 0 + + rest = args.each_with_object({}) do |arg, memo| + memo[i += 1] = arg + end + + kwargs = options.each_with_object({}) do |(key, value), memo| + memo[i += 1] = [key, value] + end + + { rest: rest, kwargs: kwargs } + end + memoize :rest_and_kwargs + + def kwargs(**options) + counter.call(:kwargs) + + options.each_with_index.each_with_object({}) do |((key, value), i), memo| + memo[i += 1] = [key, value] + end end + memoize :kwargs protected @@ -338,7 +369,7 @@ def test_all_memoized_structs # Student < Person memoize :name, :identifier => :student # Teacher < Person memoize :seniority - expected = %w[age age? is_developer? memoize_protected_test name name? sleep update update_attributes] + expected = %w[age age? is_developer? memoize_protected_test name name? sleep update update_attributes rest rest_and_kwargs kwargs].sort structs = Person.all_memoized_structs assert_equal expected, structs.collect(&:memoized_method).collect(&:to_s).sort assert_equal '@_memoized_name', structs.detect { |s| s.memoized_method == :name }.ivar @@ -537,4 +568,49 @@ def test_private_method_memoization assert_equal 'Yes', person.send(:is_developer?) assert_equal 1, person.calls(:is_developer?) end + + def test_rest + person = Person.new + + 3.times do + assert_equal({ 1 => :one, 2 => :two, 3 => :three }, person.rest(:one, :two, :three)) + end + + assert_equal 1, person.calls(:rest) + end + + def test_rest_and_kwargs + person = Person.new + + 3.times do + assert_equal({ + rest: { + 1 => :one, + 2 => :two, + 3 => :three + }, + kwargs: { + 4 => [:four, :five], + 5 => [:six, :seven] + } + }, + person.rest_and_kwargs(:one, :two, :three, four: :five, six: :seven)) + end + + assert_equal 1, person.calls(:rest_and_kwargs) + end + + def test_kwargs + person = Person.new + + 3.times do + assert_equal({ + 1 => [:four, :five], + 2 => [:six, :seven] + }, + person.kwargs(four: :five, six: :seven)) + end + + assert_equal 1, person.calls(:kwargs) + end end