dry-equalizer 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: