memonic 1.0.0

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: de009766f0ddc455437e5cf5530965053056e7b3
4
+ data.tar.gz: 59f9760f45a6df85cba1f57502fe31d5070ea47f
5
+ SHA512:
6
+ metadata.gz: 1fa36f7e0d6685ddbb228d45f177a4ad4a95967bbb9fec410caf63b04af7df119bbd3e072299edb5c9b29af9ad1244f15d7b0926028eb2c297db9db6d3d0bcc8
7
+ data.tar.gz: fd671c71357aed5cd141a792a59f043d1b8644334abccd74faca9aafaced5b1317cc5560d5a90ae865f63c7d8a5193e7e0880dbfa0849ce8264455fdf6607e99
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .ruby-version
19
+ .ruby-gemset
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,14 @@
1
+ language: ruby
2
+ cache: bundler
3
+
4
+ rvm:
5
+ - 2.1.0
6
+
7
+ script: 'bundle exec rake'
8
+
9
+ notifications:
10
+ email:
11
+ recipients:
12
+ - john+memonic@carney.id.au
13
+ on_failure: change
14
+ on_success: never
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pikelet.gemspec
4
+ gemspec
@@ -0,0 +1,128 @@
1
+ # Memonic
2
+
3
+ [![Build status][build-badge]][build]
4
+
5
+ ## Introduction
6
+
7
+ Memonic is a very simple, lightweight memoization helper. The simplest way to
8
+ use it is with the `memoize` class method.
9
+
10
+ class MyClass
11
+ include Memonic
12
+
13
+ memoize :value do
14
+ an_expensive_computation
15
+ end
16
+ end
17
+
18
+ This defines an instance method named `value` on `MyClass` that is the
19
+ equivalent of:
20
+
21
+ def value
22
+ unless defined? @value
23
+ @value = an_expensive_computation
24
+ end
25
+ @value
26
+ end
27
+
28
+ Note that unlike the more usual `@value ||= computation` pattern, Memonic
29
+ guarantees that the computation is only executed once, even if it returns
30
+ `nil` or `false`.
31
+
32
+ `memoize` is also available as an instance method. I'm not entirely sure why
33
+ you would want to use it, but if you do, here's how:
34
+
35
+ class MyClass
36
+ include Memonic
37
+
38
+ def value
39
+ memoize(:@value) { an_expensive_computation }
40
+ end
41
+ end
42
+
43
+ Note that the '@' prefix **is** necessary.
44
+
45
+ ## Background
46
+
47
+ Memoization is a common optimization technique in which the result of a
48
+ potentially expensive computation is captured the first time a function is
49
+ invoked and the cached result is used for subsequent invocations. In Ruby it's
50
+ usually expressed as follows:
51
+
52
+ class MyClass
53
+ def value
54
+ @value ||= an_expensive_computation
55
+ end
56
+ end
57
+
58
+ This is simple and well-understood, but suffers from a fairly serious flaw: if
59
+ the computation result is `nil` or `false`, then the full computation will be
60
+ performed on every call to `value`. In most cases this is not an issue -
61
+ either the computation in question never yields a "falsey" result, or it's not
62
+ so expensive that it matters if it's repeated a few times. For cases where
63
+ these issues are a concern, the usual solution is to first check whether the
64
+ cached result actually exists:
65
+
66
+ def value
67
+ unless defined?(@value)
68
+ @value = an_expensive_computation
69
+ end
70
+ @value
71
+ end
72
+
73
+ This does the job, but it's verbose and not very idiomatic. Memonic does
74
+ pretty much exactly this internally, but dresses it in a convenient, idiomatic
75
+ syntax.
76
+
77
+ ## Alternatives
78
+
79
+ There are a couple of gems that offer similar functionality. Most of these
80
+ are intended to replace `ActiveSupport::Memoizable`, which was deprecated
81
+ way back in Rails 3.2 for being an overly complex solution to a relatively
82
+ simple problem. The Memoizable-style gems use a slightly different syntax from
83
+ Memonic. You define your method, then mark it for memoization:
84
+
85
+ class MyClass
86
+ extend Memoist
87
+
88
+ def value
89
+ an_expensive_computation
90
+ end
91
+ memoize :value
92
+ end
93
+
94
+ If you prefer this approach, then you should totally use something like
95
+ [Memoist][memoist] or [Memoizable][memoizable].
96
+
97
+ ## Installation
98
+
99
+ Add this line to your application's Gemfile:
100
+
101
+ gem 'memonic'
102
+
103
+ And then execute:
104
+
105
+ $ bundle
106
+
107
+ Or install it yourself as:
108
+
109
+ $ gem install memonic
110
+
111
+ ## Contributing
112
+
113
+ 1. Fork it ([http://github.com/johncarney/memonic/fork][fork])
114
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
115
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
116
+ 4. Push to the branch (`git push origin my-new-feature`)
117
+ 5. Create new Pull Request
118
+
119
+ [memoist]: https://github.com/matthewrudy/memoist
120
+ [memoizable]: https://github.com/dkubb/memoizable
121
+
122
+ [gem-badge]: https://badge.fury.io/rb/memonic.svg
123
+ [gem]: http://badge.fury.io/rb/memonic
124
+ [build-badge]: https://travis-ci.org/johncarney/memonic.svg?branch=master
125
+ [build]: https://travis-ci.org/johncarney/memonic
126
+ [coverage-badge]: https://img.shields.io/coveralls/johncarney/memonic.svg
127
+ [coverage]: https://coveralls.io/r/johncarney/memonic?branch=master
128
+ [fork]: http://github.com/johncarney/memonic/fork
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ desc "Run specs"
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ desc 'Default: run specs.'
8
+ task :default => :spec
@@ -0,0 +1,28 @@
1
+ require "memonic/version"
2
+
3
+ module Memonic
4
+ if defined?(ActiveSupport::Concern)
5
+ extend ActiveSupport::Concern
6
+ else
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def memoize(variable, &block)
15
+ if instance_variable_defined?(variable)
16
+ instance_variable_get(variable)
17
+ else
18
+ instance_variable_set(variable, instance_exec(&block))
19
+ end
20
+ end
21
+
22
+ module ClassMethods
23
+ def memoize(name, &block)
24
+ variable = "@#{name}"
25
+ define_method(name) { memoize(variable, &block) }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,3 @@
1
+ module Memonic
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'memonic/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "memonic"
8
+ spec.version = Memonic::VERSION
9
+ spec.authors = ["John Carney"]
10
+ spec.email = ["john@carney.id.au"]
11
+ spec.summary = %q{A simple, lightweight memoization helper.}
12
+ spec.description = %q{Memonic is a simple, lightweight memoization helper.}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.required_ruby_version = '>= 2.1.0'
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.5"
24
+ spec.add_development_dependency "rake"
25
+ spec.add_development_dependency "rspec"
26
+ end
@@ -0,0 +1,118 @@
1
+ require "spec_helper"
2
+ require "memonic"
3
+
4
+ describe Memonic do
5
+ describe ".memoize" do
6
+ let(:klass) do
7
+ Struct.new(:computation) do
8
+ include Memonic
9
+
10
+ memoize :value do
11
+ computation
12
+ end
13
+ end
14
+ end
15
+
16
+ let(:instance) { klass.new(result) }
17
+
18
+ before do
19
+ allow(instance).to receive(:computation).and_call_original
20
+ end
21
+
22
+ context "with a truthy result" do
23
+ let(:result) { Object.new }
24
+
25
+ it "returns the computation result" do
26
+ expect(instance.value).to be result
27
+ end
28
+
29
+ it "invokes the computation only once" do
30
+ 2.times { instance.value }
31
+ expect(instance).to have_received(:computation).once
32
+ end
33
+ end
34
+
35
+ context "with a nil result" do
36
+ let(:result) { nil }
37
+
38
+ it "returns the computation result" do
39
+ expect(instance.value).to be result
40
+ end
41
+
42
+ it "invokes the computation only once" do
43
+ 2.times { instance.value }
44
+ expect(instance).to have_received(:computation).once
45
+ end
46
+ end
47
+
48
+ context "with a false result" do
49
+ let(:result) { false }
50
+
51
+ it "returns the computation result" do
52
+ expect(instance.value).to be result
53
+ end
54
+
55
+ it "invokes the computation only once" do
56
+ 2.times { instance.value }
57
+ expect(instance).to have_received(:computation).once
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "#memoize" do
63
+ let(:klass) do
64
+ Struct.new(:computation) do
65
+ include Memonic
66
+
67
+ def value
68
+ memoize(:@value) { computation }
69
+ end
70
+ end
71
+ end
72
+
73
+ let(:instance) { klass.new(result) }
74
+
75
+ before do
76
+ allow(instance).to receive(:computation).and_call_original
77
+ end
78
+
79
+ context "with a truthy result" do
80
+ let(:result) { Object.new }
81
+
82
+ it "returns the computation result" do
83
+ expect(instance.value).to be result
84
+ end
85
+
86
+ it "invokes the computation only once" do
87
+ 2.times { instance.value }
88
+ expect(instance).to have_received(:computation).once
89
+ end
90
+ end
91
+
92
+ context "with a nil result" do
93
+ let(:result) { nil }
94
+
95
+ it "returns the computation result" do
96
+ expect(instance.value).to be result
97
+ end
98
+
99
+ it "invokes the computation only once" do
100
+ 2.times { instance.value }
101
+ expect(instance).to have_received(:computation).once
102
+ end
103
+ end
104
+
105
+ context "with a false result" do
106
+ let(:result) { false }
107
+
108
+ it "returns the computation result" do
109
+ expect(instance.value).to be result
110
+ end
111
+
112
+ it "invokes the computation only once" do
113
+ 2.times { instance.value }
114
+ expect(instance).to have_received(:computation).once
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,96 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+ RSpec.configure do |config|
20
+ # rspec-expectations config goes here. You can use an alternate
21
+ # assertion/expectation library such as wrong or the stdlib/minitest
22
+ # assertions if you prefer.
23
+ config.expect_with :rspec do |expectations|
24
+ # This option will default to `true` in RSpec 4. It makes the `description`
25
+ # and `failure_message` of custom matchers include text for helper methods
26
+ # defined using `chain`, e.g.:
27
+ # be_bigger_than(2).and_smaller_than(4).description
28
+ # # => "be bigger than 2 and smaller than 4"
29
+ # ...rather than:
30
+ # # => "be bigger than 2"
31
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
32
+ end
33
+
34
+ # rspec-mocks config goes here. You can use an alternate test double
35
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
36
+ config.mock_with :rspec do |mocks|
37
+ # Prevents you from mocking or stubbing a method that does not exist on
38
+ # a real object. This is generally recommended, and will default to
39
+ # `true` in RSpec 4.
40
+ mocks.verify_partial_doubles = true
41
+ end
42
+
43
+ # The settings below are suggested to provide a good initial experience
44
+ # with RSpec, but feel free to customize to your heart's content.
45
+ =begin
46
+ # These two settings work together to allow you to limit a spec run
47
+ # to individual examples or groups you care about by tagging them with
48
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
49
+ # get run.
50
+ config.filter_run :focus
51
+ config.run_all_when_everything_filtered = true
52
+
53
+ # Allows RSpec to persist some state between runs in order to support
54
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
55
+ # you configure your source control system to ignore this file.
56
+ config.example_status_persistence_file_path = "spec/examples.txt"
57
+
58
+ # Limits the available syntax to the non-monkey patched syntax that is
59
+ # recommended. For more details, see:
60
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
61
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
62
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
63
+ config.disable_monkey_patching!
64
+
65
+ # This setting enables warnings. It's recommended, but in some cases may
66
+ # be too noisy due to issues in dependencies.
67
+ config.warnings = true
68
+
69
+ # Many RSpec users commonly either run the entire suite or an individual
70
+ # file, and it's useful to allow more verbose output when running an
71
+ # individual spec file.
72
+ if config.files_to_run.one?
73
+ # Use the documentation formatter for detailed output,
74
+ # unless a formatter has already been configured
75
+ # (e.g. via a command-line flag).
76
+ config.default_formatter = 'doc'
77
+ end
78
+
79
+ # Print the 10 slowest examples and example groups at the
80
+ # end of the spec run, to help surface which specs are running
81
+ # particularly slow.
82
+ config.profile_examples = 10
83
+
84
+ # Run specs in random order to surface order dependencies. If you find an
85
+ # order dependency and want to debug it, you can fix the order by providing
86
+ # the seed, which is printed after each run.
87
+ # --seed 1234
88
+ config.order = :random
89
+
90
+ # Seed global randomization in this process using the `--seed` CLI option.
91
+ # Setting this allows you to use `--seed` to deterministically reproduce
92
+ # test failures related to randomization by passing the same `--seed` value
93
+ # as the one that triggered the failure.
94
+ Kernel.srand config.seed
95
+ =end
96
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: memonic
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - John Carney
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Memonic is a simple, lightweight memoization helper.
56
+ email:
57
+ - john@carney.id.au
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - README.md
67
+ - Rakefile
68
+ - lib/memonic.rb
69
+ - lib/memonic/version.rb
70
+ - memonic.gemspec
71
+ - spec/memonic_spec.rb
72
+ - spec/spec_helper.rb
73
+ homepage: ''
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 2.1.0
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.4.4
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: A simple, lightweight memoization helper.
97
+ test_files:
98
+ - spec/memonic_spec.rb
99
+ - spec/spec_helper.rb