attr_memoizer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ *.idea
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - jruby-18mode # JRuby in 1.8 mode
6
+ - jruby-19mode # JRuby in 1.9 mode
7
+ - rbx-18mode
8
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Matthew McEachen
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,99 @@
1
+ # AttrMemoizer [![Build Status](https://api.travis-ci.org/mceachen/attr_memoizer.png?branch=master)](https://travis-ci.org/mceachen/attr_memoizer)
2
+
3
+ The common ruby idiom for attribute [memoization](http://en.wikipedia.org/wiki/Memoization)
4
+ looks like this:
5
+
6
+ ``` ruby
7
+ class Example
8
+ def field
9
+ @field ||= some_expensive_task()
10
+ end
11
+ end
12
+ ```
13
+
14
+ If ```some_expensive_task``` can return a "falsy" value (like ```nil``` or ```false```), this
15
+ doesn't work correctly—the prior memoized value of
16
+ ```some_expensive_task``` will be ignored, and every subsequent call to ```field``` will result
17
+ in another call to ```some_expensive_task```.
18
+
19
+ <strong>AttrMemoizer aims to usurp your misbegotten love of ```||=```.</strong>
20
+
21
+ ## Usage
22
+
23
+ 1. ```include AttrMemoizer```
24
+ 2. Call ```attr_memoizer``` with the attributes you want memoized
25
+ 3. Throw your ```@something ||=``` **on the [ground](http://en.wikipedia.org/wiki/Threw_It_on_the_Ground)**.
26
+
27
+ ``` ruby
28
+ class Example
29
+ include AttrMemoizer
30
+ attr_memoizer :field, :another_field
31
+
32
+ def field
33
+ # code that computes field
34
+ end
35
+
36
+ def another_field
37
+ # code that computes another_field
38
+ end
39
+ end
40
+ ```
41
+
42
+ Calling ```Example.new.field``` will call your definition of ```field```, memoize the result
43
+ for subsequent calls to an ivar called ```@field```, and return that value.
44
+
45
+ Note that caching method results does **not** span instances:
46
+
47
+ ``` ruby
48
+ class TimeHolder
49
+ include AttrMemoizer
50
+ attr_memoizer :a_time
51
+
52
+ def a_time
53
+ Time.now
54
+ end
55
+ end
56
+
57
+ t = TimeHolder.new
58
+ t.a_time
59
+ #> 2013-01-01 00:00:00 -0800
60
+ sleep 1
61
+ t.a_time
62
+ #> 2013-01-01 00:00:00 -0800 # < this is the memoized value
63
+
64
+ # But with a new instance, we get a new value:
65
+ TimeHolder.new.a_time
66
+ #> 2013-01-01 20:26:41 -0800
67
+ ```
68
+
69
+ ### Gotcha
70
+
71
+ To keep the metaprogramming demons at bay, we're using ```alias_method``` to rename the method—if
72
+ you rename the attribute or look for consumers of the attribute, you at least has a chance of
73
+ finding the attribute and their consumers, as opposed to how deferred_attribute worked, which made
74
+ you call a method that only existed after the attr_memoizer method ran.
75
+
76
+ The problem with using ```alias_method``` at the time that ```attr_memoizer``` is called, is that
77
+ the method may not be defined in the class yet. To get around this issue, we implement the
78
+ class-level ```method_added``` hook, and set up the memoization after the method is defined.
79
+
80
+ If you are also using ```method_added```, remember to call ```super``` at the end of your
81
+ implementation. See the test cases for examples, and proof that this nonsense all works.
82
+
83
+ ## Installation
84
+
85
+ Add this line to your application's Gemfile:
86
+
87
+ ``` ruby
88
+ gem 'attr_memoizer'
89
+ ```
90
+
91
+ And then execute:
92
+
93
+ $ bundle
94
+
95
+ ## Changelog
96
+
97
+ ### 0.0.1
98
+
99
+ * There were previous names for this library. We won't speak of them again.
@@ -0,0 +1,17 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'yard'
4
+ YARD::Rake::YardocTask.new do |t|
5
+ t.files = ['lib/**/*.rb', 'README.md']
6
+ end
7
+
8
+ require 'rake/testtask'
9
+
10
+ Rake::TestTask.new do |t|
11
+ t.libs.push "lib"
12
+ t.libs.push "test"
13
+ t.pattern = 'test/**/*_test.rb'
14
+ t.verbose = true
15
+ end
16
+
17
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'attr_memoizer/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "attr_memoizer"
8
+ gem.version = AttrMemoizer::VERSION
9
+ gem.authors = ["Matthew McEachen"]
10
+ gem.email = ["matthew+github@mceachen.org"]
11
+ gem.description = %q{Correct attribute memoization for ruby, made easy}
12
+ gem.summary = gem.description
13
+ gem.homepage = "https://github.com/mceachen/attr_memoizer"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^test/})
18
+ gem.require_paths = ["lib"]
19
+ gem.add_development_dependency 'rake'
20
+ gem.add_development_dependency 'yard'
21
+ gem.add_development_dependency 'minitest'
22
+ gem.add_development_dependency 'minitest-great_expectations'
23
+ end
@@ -0,0 +1,33 @@
1
+ require "attr_memoizer/version"
2
+
3
+ module AttrMemoizer
4
+ def self.included(klass)
5
+ klass.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def attr_memoizer(*attrs)
10
+ attrs_to_memoize.concat(attrs.collect { |ea| ea.to_sym }).uniq!
11
+ # OMG 1.8.7 RETURNS STRINGS
12
+ existing_methods = self.instance_methods(true).collect { |ea| ea.to_sym }
13
+ (existing_methods & attrs_to_memoize).each do |name|
14
+ attrs_to_memoize.delete(name)
15
+ class_eval <<-RUBY
16
+ alias_method :_#{name}, :#{name}
17
+ def #{name}
18
+ defined?(@#{name}) ? @#{name} : @#{name} = _#{name}()
19
+ end
20
+ RUBY
21
+ end
22
+ end
23
+
24
+ def attrs_to_memoize
25
+ @attrs_to_memoize ||= []
26
+ end
27
+
28
+ def method_added(method_name)
29
+ attr_memoizer(method_name) if attrs_to_memoize.include?(method_name)
30
+ super
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module AttrMemoizer
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,106 @@
1
+ require 'minitest_helper'
2
+
3
+ describe AttrMemoizer do
4
+
5
+ before :each do
6
+ Target.call_count = 0
7
+ end
8
+
9
+ class Target
10
+ class << self
11
+ attr_accessor :call_count
12
+ attr_accessor :methods_added
13
+ end
14
+
15
+ self.methods_added = []
16
+ self.call_count = 0
17
+
18
+ def self.method_added(method_name)
19
+ self.methods_added << method_name
20
+ super
21
+ end
22
+
23
+ def already_defined_field
24
+ "prething#{self.class.call_count += 1}"
25
+ end
26
+
27
+ include AttrMemoizer
28
+ attr_memoizer :thing, :already_defined_field
29
+ attr_memoizer :one_more_thing
30
+
31
+ def thing
32
+ "thing#{self.class.call_count += 1}"
33
+ end
34
+
35
+ def one_more_thing
36
+ "iThing#{self.class.call_count += 1}"
37
+ end
38
+ end
39
+
40
+ class TargetWithMethodsAddedAfter
41
+ class << self
42
+ attr_accessor :methods_added
43
+ attr_accessor :call_count
44
+ end
45
+
46
+ self.methods_added = []
47
+ self.call_count = 0
48
+
49
+ include AttrMemoizer
50
+ attr_memoizer :thing
51
+
52
+ def self.method_added(method_name)
53
+ self.methods_added << method_name
54
+ super
55
+ end
56
+
57
+ def thing
58
+ "thing#{self.class.call_count += 1}"
59
+ end
60
+ end
61
+
62
+ it "doesn't call on initialize" do
63
+ Target.new
64
+ Target.call_count.must_equal 0
65
+ end
66
+
67
+ it "doesn't hide prior-defined method_added" do
68
+ Target.methods_added.must_include_all [:already_defined_field, :thing, :one_more_thing]
69
+ end
70
+
71
+ it "doesn't hide post-defined method_added" do
72
+ TargetWithMethodsAddedAfter.methods_added.must_include_all [:thing]
73
+ end
74
+
75
+ it "works for post-defined method_added classes" do
76
+ t = TargetWithMethodsAddedAfter.new
77
+ t.thing.must_equal "thing1"
78
+ t.thing.must_equal "thing1"
79
+ end
80
+
81
+ it "works for already-defined fields" do
82
+ t = Target.new
83
+ t.already_defined_field.must_equal "prething1"
84
+ t.already_defined_field.must_equal "prething1"
85
+ end
86
+
87
+ it "works for fields defined after attr_memoizer" do
88
+ t = Target.new
89
+ t.thing.must_equal "thing1"
90
+ t.thing.must_equal "thing1"
91
+ end
92
+
93
+ it "works for multiple calls to attr_memoizer" do
94
+ t = Target.new
95
+ t.one_more_thing.must_equal "iThing1"
96
+ t.one_more_thing.must_equal "iThing1"
97
+ Target.new.one_more_thing.must_equal "iThing2"
98
+ end
99
+
100
+ it "doesn't pollute other instances" do
101
+ targets = 3.times.collect { Target.new }
102
+ targets.collect { |ea| ea.thing }.must_equal %w(thing1 thing2 thing3)
103
+ Target.new.thing.must_equal "thing4"
104
+ targets.collect { |ea| ea.thing }.must_equal %w(thing1 thing2 thing3)
105
+ end
106
+ end
@@ -0,0 +1,3 @@
1
+ require 'minitest/great_expectations'
2
+ require 'minitest/autorun'
3
+ require 'attr_memoizer'
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: attr_memoizer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Matthew McEachen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: yard
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: minitest
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: minitest-great_expectations
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: Correct attribute memoization for ruby, made easy
79
+ email:
80
+ - matthew+github@mceachen.org
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - .travis.yml
87
+ - Gemfile
88
+ - LICENSE.txt
89
+ - README.md
90
+ - Rakefile
91
+ - attr_memoizer.gemspec
92
+ - lib/attr_memoizer.rb
93
+ - lib/attr_memoizer/version.rb
94
+ - test/attr_memoizer_test.rb
95
+ - test/minitest_helper.rb
96
+ homepage: https://github.com/mceachen/attr_memoizer
97
+ licenses: []
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ segments:
109
+ - 0
110
+ hash: 4020524827246179430
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ segments:
118
+ - 0
119
+ hash: 4020524827246179430
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 1.8.23
123
+ signing_key:
124
+ specification_version: 3
125
+ summary: Correct attribute memoization for ruby, made easy
126
+ test_files:
127
+ - test/attr_memoizer_test.rb
128
+ - test/minitest_helper.rb
129
+ has_rdoc: