dry-equalizer 0.1.0 → 0.3.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.
data/Rakefile CHANGED
@@ -1,5 +1,8 @@
1
1
  require 'bundler'
2
- require 'devtools'
3
2
 
4
3
  Bundler::GemHelper.install_tasks
5
- Devtools.init_rake_tasks
4
+
5
+ require "rspec/core/rake_task"
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+ task default: [:spec]
@@ -0,0 +1,86 @@
1
+ ---
2
+ title: Introduction & Usage
3
+ description: Simple mixin providing equality, equivalence and inspection methods
4
+ layout: gem-single
5
+ order: 5
6
+ type: gem
7
+ name: dry-equalizer
8
+ ---
9
+
10
+ `dry-equalizer` is a simple mixin that can be used to add instance variable based equality, equivalence and inspection methods to your objects.
11
+
12
+ ### Usage
13
+
14
+ ```ruby
15
+ require 'dry-equalizer'
16
+
17
+ class GeoLocation
18
+ include Dry::Equalizer(:latitude, :longitude)
19
+
20
+ attr_reader :latitude, :longitude
21
+
22
+ def initialize(latitude, longitude)
23
+ @latitude, @longitude = latitude, longitude
24
+ end
25
+ end
26
+
27
+ point_a = GeoLocation.new(1, 2)
28
+ point_b = GeoLocation.new(1, 2)
29
+ point_c = GeoLocation.new(2, 2)
30
+
31
+ point_a.inspect # => "#<GeoLocation latitude=1 longitude=2>"
32
+
33
+ point_a == point_b # => true
34
+ point_a.hash == point_b.hash # => true
35
+ point_a.eql?(point_b) # => true
36
+ point_a.equal?(point_b) # => false
37
+
38
+ point_a == point_c # => false
39
+ point_a.hash == point_c.hash # => false
40
+ point_a.eql?(point_c) # => false
41
+ point_a.equal?(point_c) # => false
42
+ ```
43
+
44
+ ### Configuration options
45
+
46
+ #### `inspect`
47
+
48
+ Use `inspect` option to skip `#inspect` method overloading:
49
+
50
+ ```ruby
51
+ class Foo
52
+ include Dry::Equalizer(:a, inspect: false)
53
+
54
+ attr_reader :a, :b
55
+
56
+ def initialize(a, b)
57
+ @a, @b = a, b
58
+ end
59
+ end
60
+
61
+ Foo.new(1, 2).inspect
62
+ # => "#<Foo:0x00007fbc9c0487f0 @a=1, @b=2>"
63
+ ```
64
+
65
+ #### `immutable`
66
+
67
+ For objects that are immutable it doesn't make sense to calculate `#hash` every time it's called. To memoize hash use `immutable` option:
68
+
69
+ ```ruby
70
+ class ImmutableHash
71
+ include Dry::Equalizer(:foo, :bar, immutable: true)
72
+
73
+ attr_accessor :foo, :bar
74
+
75
+ def initialize(foo, bar)
76
+ @foo, @bar = foo, bar
77
+ end
78
+ end
79
+
80
+ obj = ImmutableHash.new('foo', 'bar')
81
+ old_hash = obj.hash
82
+ obj.foo = 'changed'
83
+ old_hash == obj.hash
84
+ # => true
85
+ ```
86
+
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  require File.expand_path('../lib/dry/equalizer/version', __FILE__)
4
2
 
5
3
  Gem::Specification.new do |gem|
@@ -9,7 +7,7 @@ Gem::Specification.new do |gem|
9
7
  gem.email = %w[dan.kubb@gmail.com mbj@schirp-dso.com]
10
8
  gem.description = 'Module to define equality, equivalence and inspection methods'
11
9
  gem.summary = gem.description
12
- gem.homepage = 'https://github.com/dkubb/equalizer'
10
+ gem.homepage = 'https://github.com/dry-rb/dry-equalizer'
13
11
  gem.licenses = 'MIT'
14
12
 
15
13
  gem.require_paths = %w[lib]
@@ -17,7 +15,5 @@ Gem::Specification.new do |gem|
17
15
  gem.test_files = `git ls-files -- spec/{unit,integration}`.split("\n")
18
16
  gem.extra_rdoc_files = %w[LICENSE README.md CONTRIBUTING.md]
19
17
 
20
- gem.required_ruby_version = '>= 2.1.0'
21
-
22
- gem.add_development_dependency('bundler', '~> 1.3', '>= 1.3.5')
18
+ gem.required_ruby_version = '>= 2.4.0'
23
19
  end
@@ -1,11 +1,9 @@
1
- # encoding: utf-8
2
-
3
1
  module Dry
4
2
  # Build an equalizer module for the inclusion in other class
5
3
  #
6
4
  # @api public
7
- def self.Equalizer(*keys)
8
- Dry::Equalizer.new(*keys)
5
+ def self.Equalizer(*keys, **options)
6
+ Dry::Equalizer.new(*keys, **options)
9
7
  end
10
8
 
11
9
  # Define equality, equivalence and inspection methods
@@ -16,13 +14,16 @@ module Dry
16
14
  # #hash, and #inspect
17
15
  #
18
16
  # @param [Array<Symbol>] keys
17
+ # @param [Hash] options
18
+ # @option options [Boolean] :inspect whether to define #inspect method
19
+ # @option options [Boolean] :immutable whether to memoize #hash method
19
20
  #
20
21
  # @return [undefined]
21
22
  #
22
23
  # @api private
23
- def initialize(*keys)
24
- @keys = keys
25
- define_methods
24
+ def initialize(*keys, **options)
25
+ @keys = keys.uniq
26
+ define_methods(**options)
26
27
  freeze
27
28
  end
28
29
 
@@ -43,13 +44,16 @@ module Dry
43
44
 
44
45
  # Define the equalizer methods based on #keys
45
46
  #
47
+ # @param [Boolean] inspect whether to define #inspect method
48
+ # @param [Boolean] immutable whether to memoize #hash method
49
+ #
46
50
  # @return [undefined]
47
51
  #
48
52
  # @api private
49
- def define_methods
53
+ def define_methods(inspect: true, immutable: false)
50
54
  define_cmp_method
51
- define_hash_method
52
- define_inspect_method
55
+ define_hash_method(immutable: immutable)
56
+ define_inspect_method if inspect
53
57
  end
54
58
 
55
59
  # Define an #cmp? method based on the instance's values identified by #keys
@@ -72,10 +76,20 @@ module Dry
72
76
  # @return [undefined]
73
77
  #
74
78
  # @api private
75
- def define_hash_method
76
- keys = @keys
77
- define_method(:hash) do | |
78
- keys.map(&method(:send)).push(self.class).hash
79
+ def define_hash_method(immutable:)
80
+ calculate_hash = ->(obj) { @keys.map { |key| obj.__send__(key) }.push(obj.class).hash }
81
+ if immutable
82
+ define_method(:hash) do
83
+ @__hash__ ||= calculate_hash.call(self)
84
+ end
85
+ define_method(:freeze) do
86
+ hash
87
+ super()
88
+ end
89
+ else
90
+ define_method(:hash) do
91
+ calculate_hash.call(self)
92
+ end
79
93
  end
80
94
  end
81
95
 
@@ -86,10 +100,10 @@ module Dry
86
100
  # @api private
87
101
  def define_inspect_method
88
102
  keys = @keys
89
- define_method(:inspect) do | |
103
+ define_method(:inspect) do
90
104
  klass = self.class
91
- name = klass.name || klass.inspect
92
- "#<#{name}#{keys.map { |key| " #{key}=#{__send__(key).inspect}" }.join}>"
105
+ name = klass.name || klass.inspect
106
+ "#<#{name}#{keys.map { |key| " #{key}=#{__send__(key).inspect}" }.join}>"
93
107
  end
94
108
  end
95
109
 
@@ -122,8 +136,8 @@ module Dry
122
136
  #
123
137
  # @api public
124
138
  def ==(other)
125
- other.kind_of?(self.class) && cmp?(__method__, other)
139
+ other.is_a?(self.class) && cmp?(__method__, other)
126
140
  end
127
- end # module Methods
128
- end # class Equalizer
141
+ end
142
+ end
129
143
  end
@@ -1,8 +1,6 @@
1
- # encoding: utf-8
2
-
3
1
  module Dry
4
2
  class Equalizer < Module
5
3
  # Gem version
6
- VERSION = '0.1.0'.freeze
7
- end # class Equalizer
4
+ VERSION = '0.3.0'.freeze
5
+ end
8
6
  end
@@ -1,26 +1,20 @@
1
- # encoding: utf-8
2
-
3
1
  if ENV['COVERAGE'] == 'true'
4
2
  require 'simplecov'
5
-
6
3
  SimpleCov.start do
7
- command_name 'spec:unit'
8
-
9
- add_filter 'config'
10
- add_filter 'spec'
11
- add_filter 'vendor'
12
-
13
- minimum_coverage 100
4
+ add_filter '/spec/'
14
5
  end
15
6
  end
16
7
 
17
- require 'devtools/spec_helper'
18
8
  require 'dry-equalizer'
19
9
 
20
10
  RSpec.configure do |config|
21
11
  config.raise_errors_for_deprecations!
22
12
 
13
+ config.disable_monkey_patching!
14
+
23
15
  config.expect_with :rspec do |expect_with|
24
16
  expect_with.syntax = :expect
25
17
  end
18
+
19
+ config.warnings = true
26
20
  end
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  require 'rbconfig'
4
2
 
5
3
  ::Config = RbConfig unless defined?(::Config)
@@ -1,8 +1,6 @@
1
- # encoding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
- describe Dry::Equalizer, '#included' do
3
+ RSpec.describe Dry::Equalizer, '#included' do
6
4
  subject { descendant.instance_exec(object) { |mod| include mod } }
7
5
 
8
6
  let(:object) { described_class.new }
@@ -1,8 +1,6 @@
1
- # encoding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
- describe Dry::Equalizer::Methods, '#eql?' do
3
+ RSpec.describe Dry::Equalizer::Methods, '#eql?' do
6
4
  subject { object.eql?(other) }
7
5
 
8
6
  let(:object) { described_class.new(true) }
@@ -1,8 +1,6 @@
1
- # encoding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
- describe Dry::Equalizer::Methods, '#==' do
3
+ RSpec.describe Dry::Equalizer::Methods, '#==' do
6
4
  subject { object == other }
7
5
 
8
6
  let(:object) { described_class.new(true) }
@@ -1,9 +1,6 @@
1
- # encoding: utf-8
2
-
3
1
  require 'spec_helper'
4
2
 
5
- describe Dry::Equalizer do
6
- let(:object) { described_class }
3
+ RSpec.describe Dry::Equalizer do
7
4
  let(:name) { 'User' }
8
5
  let(:klass) { ::Class.new }
9
6
 
@@ -13,12 +10,12 @@ describe Dry::Equalizer do
13
10
  before do
14
11
  # specify the class #name method
15
12
  allow(klass).to receive(:name).and_return(name)
16
- klass.send(:include, subject)
13
+ klass.include(subject)
17
14
  end
18
15
 
19
16
  let(:instance) { klass.new }
20
17
 
21
- it { should be_instance_of(object) }
18
+ it { should be_instance_of(described_class) }
22
19
 
23
20
  it { should be_frozen }
24
21
 
@@ -83,6 +80,7 @@ describe Dry::Equalizer do
83
80
  let(:klass) do
84
81
  ::Class.new do
85
82
  attr_reader :firstname, :lastname
83
+ attr_writer :firstname
86
84
  private :firstname, :lastname
87
85
 
88
86
  def initialize(firstname, lastname)
@@ -95,10 +93,10 @@ describe Dry::Equalizer do
95
93
  before do
96
94
  # specify the class #inspect method
97
95
  allow(klass).to receive_messages(name: nil, inspect: name)
98
- klass.send(:include, subject)
96
+ klass.include(subject)
99
97
  end
100
98
 
101
- it { should be_instance_of(object) }
99
+ it { should be_instance_of(described_class) }
102
100
 
103
101
  it { should be_frozen }
104
102
 
@@ -154,5 +152,87 @@ describe Dry::Equalizer do
154
152
  .to eql('#<User firstname="John" lastname="Doe">')
155
153
  end
156
154
  end
155
+
156
+ context 'when immutable' do
157
+ describe '#hash' do
158
+
159
+ subject { Dry::Equalizer(*keys, immutable: true) }
160
+
161
+ it 'returns memoized hash' do
162
+ expect { instance.firstname = 'Changed' }.not_to(change { instance.hash })
163
+ end
164
+
165
+ context 'when frozen' do
166
+ it 'returns memoized hash' do
167
+ instance.freeze
168
+
169
+ expect(instance.hash)
170
+ .to eql([firstname, lastname, klass].hash)
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ context 'with duplicate keys' do
178
+ subject { Dry::Equalizer(*keys) }
179
+
180
+ let(:keys) { %i[firstname firstname lastname].freeze }
181
+ let(:firstname) { 'John' }
182
+ let(:lastname) { 'Doe' }
183
+ let(:instance) { klass.new(firstname, lastname) }
184
+
185
+ let(:klass) do
186
+ ::Class.new do
187
+ attr_reader :firstname, :lastname
188
+ private :firstname, :lastname
189
+
190
+ def initialize(firstname, lastname)
191
+ @firstname = firstname
192
+ @lastname = lastname
193
+ end
194
+ end
195
+ end
196
+
197
+ before do
198
+ # specify the class #inspect method
199
+ allow(klass).to receive_messages(name: nil, inspect: name)
200
+ klass.include(subject)
201
+ end
202
+
203
+ it { should be_instance_of(described_class) }
204
+
205
+ it { should be_frozen }
206
+
207
+ describe '#inspect' do
208
+ it 'returns the expected string' do
209
+ expect(instance.inspect)
210
+ .to eql('#<User firstname="John" lastname="Doe">')
211
+ end
212
+ end
213
+ end
214
+
215
+ context 'with options' do
216
+ context 'w/o inspect' do
217
+ subject { Dry::Equalizer(*keys, inspect: false) }
218
+
219
+ let(:keys) { %i[firstname lastname].freeze }
220
+ let(:firstname) { 'John' }
221
+ let(:lastname) { 'Doe' }
222
+ let(:instance) { klass.new(firstname, lastname) }
223
+
224
+ let(:klass) do
225
+ ::Struct.new(:firstname, :lastname)
226
+ end
227
+
228
+ before { klass.include(subject) }
229
+
230
+ describe '#inspect' do
231
+ it 'returns the default string' do
232
+ expect(instance.inspect).to eql('#<struct firstname="John", lastname="Doe">')
233
+ expect(instance.to_s).to eql('#<struct firstname="John", lastname="Doe">')
234
+ end
235
+ end
236
+ end
157
237
  end
158
238
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-equalizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Kubb
@@ -9,28 +9,8 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-11-12 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: bundler
16
- requirement: !ruby/object:Gem::Requirement
17
- requirements:
18
- - - "~>"
19
- - !ruby/object:Gem::Version
20
- version: '1.3'
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: 1.3.5
24
- type: :development
25
- prerelease: false
26
- version_requirements: !ruby/object:Gem::Requirement
27
- requirements:
28
- - - "~>"
29
- - !ruby/object:Gem::Version
30
- version: '1.3'
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: 1.3.5
12
+ date: 2019-11-07 00:00:00.000000000 Z
13
+ dependencies: []
34
14
  description: Module to define equality, equivalence and inspection methods
35
15
  email:
36
16
  - dan.kubb@gmail.com
@@ -42,24 +22,24 @@ extra_rdoc_files:
42
22
  - README.md
43
23
  - CONTRIBUTING.md
44
24
  files:
25
+ - ".codeclimate.yml"
26
+ - ".github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md"
27
+ - ".github/ISSUE_TEMPLATE/---bug-report.md"
28
+ - ".github/ISSUE_TEMPLATE/---feature-request.md"
29
+ - ".github/workflows/docsite.yml"
30
+ - ".github/workflows/sync_configs.yml"
45
31
  - ".gitignore"
46
32
  - ".rspec"
47
33
  - ".rubocop.yml"
48
- - ".travis.yml"
49
34
  - ".yardstick.yml"
50
35
  - CHANGELOG.md
36
+ - CODE_OF_CONDUCT.md
51
37
  - CONTRIBUTING.md
52
38
  - Gemfile
53
39
  - LICENSE
54
40
  - README.md
55
41
  - Rakefile
56
- - config/devtools.yml
57
- - config/flay.yml
58
- - config/flog.yml
59
- - config/mutant.yml
60
- - config/reek.yml
61
- - config/rubocop.yml
62
- - config/yardstick.yml
42
+ - docsite/source/index.html.md
63
43
  - dry-equalizer.gemspec
64
44
  - lib/dry-equalizer.rb
65
45
  - lib/dry/equalizer.rb
@@ -70,7 +50,7 @@ files:
70
50
  - spec/unit/equalizer/methods/eql_predicate_spec.rb
71
51
  - spec/unit/equalizer/methods/equality_operator_spec.rb
72
52
  - spec/unit/equalizer/universal_spec.rb
73
- homepage: https://github.com/dkubb/equalizer
53
+ homepage: https://github.com/dry-rb/dry-equalizer
74
54
  licenses:
75
55
  - MIT
76
56
  metadata: {}
@@ -82,15 +62,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
62
  requirements:
83
63
  - - ">="
84
64
  - !ruby/object:Gem::Version
85
- version: 2.1.0
65
+ version: 2.4.0
86
66
  required_rubygems_version: !ruby/object:Gem::Requirement
87
67
  requirements:
88
68
  - - ">="
89
69
  - !ruby/object:Gem::Version
90
70
  version: '0'
91
71
  requirements: []
92
- rubyforge_project:
93
- rubygems_version: 2.4.5
72
+ rubygems_version: 3.0.6
94
73
  signing_key:
95
74
  specification_version: 4
96
75
  summary: Module to define equality, equivalence and inspection methods
@@ -99,4 +78,3 @@ test_files:
99
78
  - spec/unit/equalizer/methods/eql_predicate_spec.rb
100
79
  - spec/unit/equalizer/methods/equality_operator_spec.rb
101
80
  - spec/unit/equalizer/universal_spec.rb
102
- has_rdoc: