wholable 0.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 926e2edcbc46f6bc80713ffcabfbcfcc7e0863ecfca039bb43aae8d79aca7727
4
- data.tar.gz: 1b3bee9c9eeaaf4f3e3addcde40f1e49912533cb50ca0f7348bc988050da75f3
3
+ metadata.gz: 72a28b3df96aec9ab54375e96583424bc919c9659b2f701ab600b341d82d61b9
4
+ data.tar.gz: 898361fe5766c92fc629b7ea3d459576a902369f09fe438f3c7e5cc76ed93bf3
5
5
  SHA512:
6
- metadata.gz: 46e3d952fed95ffc62afc158c93c802e38445b1bea62c8ff80c0edc28c4b699435bd41081d48a047d93e84d746e3a094596f58055e0207bdfdf8cfaf9292452f
7
- data.tar.gz: 9baf38439f8ad831639f29ce8722bcda20b941a14b78330917c3efd6b1a33c713001562cbf5cc3643025fb4a5305d4b3742fe9444b57fa34a779d65b9d546dca
6
+ metadata.gz: a66ba09ec485373c4b3789203bf5a5d72d4d5438d33668f4981de9269b665ccbd81a5969f45da443149a377775762090d9926e826648b9a5f5fb5c7f72fc3d36
7
+ data.tar.gz: e0407f928f8310c4350a512ee087ab8c542ca66482cd2f2fc67c9cd996b4991aa53ac3c8ab2209c57429108b1ed743afb5f76b9ec43fc63249e0e7055e3bef0a
checksums.yaml.gz.sig CHANGED
Binary file
data/README.adoc CHANGED
@@ -3,12 +3,14 @@
3
3
  :figure-caption!:
4
4
 
5
5
  :data_link: link:https://alchemists.io/articles/ruby_data[Data]
6
+ :pattern_matching_link: link:https://alchemists.io/articles/ruby_pattern_matching[pattern matching]
6
7
  :ruby_link: link:https://www.ruby-lang.org[Ruby]
8
+ :data_link: link:https://alchemists.io/articles/ruby_data[Data]
7
9
  :structs_link: link:https://alchemists.io/articles/ruby_structs[Structs]
8
10
 
9
11
  = Wholable
10
12
 
11
- Wholable is a mixin that allows you to turn your object into a _whole value object_ by ensuring object equality is determined by the values of the object instead of by identity. Whole value objects -- or value objects in general -- have the following traits as also noted via link:https://en.wikipedia.org/wiki/Value_object[Wikipedia]:
13
+ Wholable allows you to turn your object into a _whole value object_ by ensuring object equality is determined by the values of the object instead of by identity. Whole value objects -- or value objects in general -- have the following traits as also noted via link:https://en.wikipedia.org/wiki/Value_object[Wikipedia]:
12
14
 
13
15
  * Equality is determined by the values that make up an object and not by link:https://en.wikipedia.org/wiki/Identity_(object-oriented_programming)[identity] (i.e. memory address) which is the default behavior for all {ruby_link} objects except for {data_link} and {structs_link}.
14
16
  * Identity remains unique since two objects can have the same values but different identity. This means `BasicObject#equal?` is never overwritten -- which is strongly discouraged -- as per link:https://rubyapi.org/o/basicobject#method-i-3D-3D[BasicObject] documentation.
@@ -19,6 +21,8 @@ toc::[]
19
21
  == Features
20
22
 
21
23
  * Ensures equality (i.e. `#==` and `#eql?`) is determined by attribute values and not object identity (i.e. `#equal?`).
24
+ * Allows you to compare two objects of same or different types and see their differences.
25
+ * Provides {pattern_matching_link}.
22
26
  * Automatically defines public attribute readers (i.e. `.attr_reader`) based on provided keys.
23
27
  * Ensures object inspection (i.e. `#inspect`) shows all registered attributes.
24
28
  * Ensures object is frozen upon initialization.
@@ -78,38 +82,56 @@ jill = Person.new name: "Jill Smith", email: "jill@example.com"
78
82
  jill_two = Person.new name: "Jill Smith", email: "jill@example.com"
79
83
  jack = Person.new name: "Jack Smith", email: "jack@example.com"
80
84
 
81
- jill.name # "Jill Smith"
82
- jill.email # "jill@example.com"
85
+ jill.name # "Jill Smith"
86
+ jill.email # "jill@example.com"
87
+
88
+ jill.frozen? # true
89
+ jill_two.frozen? # true
90
+ jack.frozen? # true
91
+
92
+ jill.inspect # "#<Person @name=\"Jill Smith\", @email=\"jill@example.com\">"
93
+ jill_two.inspect # "#<Person @name=\"Jill Smith\", @email=\"jill@example.com\">"
94
+ jack.inspect # "#<Person @name=\"Jack Smith\", @email=\"jack@example.com\">"
95
+
96
+ jill == jill # true
97
+ jill == jill_two # true
98
+ jill == jack # false
83
99
 
84
- jill.frozen? # true
85
- jill_two.frozen? # true
86
- jack.frozen? # true
100
+ jill.diff(jill) # {}
101
+ jill.diff(jack) # {
102
+ # name: ["Jill Smith", "Jack Smith"],
103
+ # email: ["jill@example.com", "jack@example.com"]
104
+ # }
105
+ jill.diff(Object.new) # {:name=>["Jill Smith", nil], :email=>["jill@example.com", nil]}
87
106
 
88
- jill.inspect # "#<Person @name=\"Jill Smith\", @email=\"jill@example.com\">"
89
- jill_two.inspect # "#<Person @name=\"Jill Smith\", @email=\"jill@example.com\">"
90
- jack.inspect # "#<Person @name=\"Jack Smith\", @email=\"jack@example.com\">"
107
+ jill.eql? jill # true
108
+ jill.eql? jill_two # true
109
+ jill.eql? jack # false
91
110
 
92
- jill == jill # true
93
- jill == jill_two # true
94
- jill == jack # false
111
+ jill.equal? jill # true
112
+ jill.equal? jill_two # false
113
+ jill.equal? jack # false
95
114
 
96
- jill.eql? jill # true
97
- jill.eql? jill_two # true
98
- jill.eql? jack # false
115
+ jill.hash # 3650965837788801745
116
+ jill_two.hash # 3650965837788801745
117
+ jack.hash # 4460658980509842640
99
118
 
100
- jill.equal? jill # true
101
- jill.equal? jill_two # false
102
- jill.equal? jack # false
119
+ jill.to_a # ["Jill Smith", "jill@example.com"]
120
+ jack.to_a # ["Jack Smith", "jack@example.com"]
103
121
 
104
- jill.hash # 3650965837788801745
105
- jill_two.hash # 3650965837788801745
106
- jack.hash # 4460658980509842640
122
+ jill.to_h # {:name=>"Jill Smith", :email=>"jill@example.com"}
123
+ jack.to_h # {:name=>"Jack Smith", :email=>"jack@example.com"}
124
+
125
+ jill.with name: "Sue" # #<Person @name="Sue", @email="jill@example.com">
126
+ jill.with bad: "!" # unknown keyword: :bad (ArgumentError)
107
127
  ----
108
128
 
109
129
  As you can see, object equality is determined by the object's values and _not_ by the object's identity. When you include `Wholable` along with a list of keys, the following happens:
110
130
 
111
- . The corresponding _public_ `attr_reader` for each key is created which saves you time and reduces double entry.
112
- . Custom `#==`, `#eql?`, `#hash`, and `#inspect` methods are added to provide whole value behavior.
131
+ . The corresponding _public_ `attr_reader` for each key is created which saves you time and reduces double entry when implementing your whole value object.
132
+ . The `#to_a` and `#to_h` methods are added for convenience in order to play nice with {data_link} and {structs_link}.
133
+ . The `#deconstruct` and `#deconstruct_keys` aliases are created for you so you can leverage {pattern_matching_link}.
134
+ . Custom `#==`, `#eql?`, `#hash`, `#inspect`, `#to_a`, `#to_h`, and `#with` methods are added to provide whole value behavior.
113
135
  . The object is immediately frozen after initialization to ensure your instance is _immutable_ by default.
114
136
 
115
137
  == Caveats
@@ -117,18 +139,9 @@ As you can see, object equality is determined by the object's values and _not_ b
117
139
  Whole values can be broken via the following:
118
140
 
119
141
  * *Duplication*: Sending the `#dup` message will cause your whole value object to be unfrozen. This might be desired in certain situations but make sure to refreeze when able.
120
- * *Post Attributes*: Adding additional attributes after what is defined when including `Wholable` will break your whole value object. To prevent this, let Wholable manage this for you (easiest). Otherwise (harder), you can manually override `#==`, `#eql?`, `#hash`, and `#inspect` behavior at which point you probably don't need Wholable anymore.
142
+ * *Post Attributes*: Adding additional attributes after what is defined when including `Wholable` will break your whole value object. To prevent this, let Wholable manage this for you (easiest). Otherwise (harder), you can manually override `#==`, `#eql?`, `#hash`, `#inspect`, `#to_a`, and `#to_h` behavior at which point you don't need Wholable anymore.
121
143
  * *Deep Freezing*: The automatic freezing of your instances is shallow and will not deeply freeze nested attributes. This behavior mimics the behavior of {data_link} objects.
122
144
 
123
- == Influences
124
-
125
- This implementation is based upon these original designs:
126
-
127
- - link:https://github.com/dkubb/equalizer[Equalizer]: One of the first implementations that is over a decade old and no longer maintained.
128
- - link:https://github.com/dry-rb/dry-equalizer[Dry Equalizer]: Deprecated and no longer maintained but was based upon the above implementation and has now moved into Dry Core.
129
- - link:https://dry-rb.org/gems/dry-core[Dry Core]: Includes the link:https://dry-rb.org/gems/dry-core/equalizer[Dry::Core::Equalizer] module which is officially supported and actively maintained by the Dry RB team. A good alternative to this gem.
130
- - link:https://github.com/piotrmurach/equatable/tree/master[Equatable]: A similar implementation to the above but is based off what you define via your `.attr_reader`. The project hasn't been maintained or updated in several years.
131
-
132
145
  == Development
133
146
 
134
147
  To contribute, run:
@@ -6,5 +6,14 @@ module Wholable
6
6
  def eql?(other) = instance_of?(other.class) && hash == other.hash
7
7
 
8
8
  def ==(other) = other.is_a?(self.class) && hash == other.hash
9
+
10
+ def diff other
11
+ if other.is_a? self.class
12
+ to_h.merge(other.to_h) { |_, one, two| [one, two].uniq }
13
+ .select { |_, diff| diff.size == 2 }
14
+ else
15
+ to_h.each.with_object({}) { |(key, value), diff| diff[key] = [value, nil] }
16
+ end
17
+ end
9
18
  end
10
19
  end
@@ -6,14 +6,13 @@ module Wholable
6
6
  def initialize *keys
7
7
  super()
8
8
  @keys = keys.uniq
9
- define_hash
10
- define_inspect
9
+ define_instance_methods
11
10
  freeze
12
11
  end
13
12
 
14
13
  def included descendant
15
14
  super
16
- define_readers descendant
15
+ define_class_methods descendant
17
16
  descendant.include Comparable
18
17
  descendant.prepend Freezable
19
18
  end
@@ -22,12 +21,42 @@ module Wholable
22
21
 
23
22
  attr_reader :keys
24
23
 
24
+ def define_instance_methods
25
+ define_hash
26
+ define_inspect
27
+ define_with
28
+ define_to_a
29
+ define_to_h
30
+ end
31
+
32
+ def define_class_methods descendant
33
+ define_readers descendant
34
+ define_deconstruct descendant
35
+ define_deconstruct_keys descendant
36
+ end
37
+
25
38
  def define_readers descendant
26
39
  descendant.class_eval <<-READERS, __FILE__, __LINE__ + 1
27
40
  attr_reader #{keys.map(&:inspect).join ", "}
28
41
  READERS
29
42
  end
30
43
 
44
+ def define_deconstruct descendant
45
+ descendant.class_eval <<-READERS, __FILE__, __LINE__ + 1
46
+ alias deconstruct to_a
47
+ READERS
48
+ end
49
+
50
+ def define_deconstruct_keys descendant
51
+ descendant.class_eval <<-READERS, __FILE__, __LINE__ + 1
52
+ alias deconstruct_keys to_h
53
+ READERS
54
+ end
55
+
56
+ def define_with
57
+ define_method(:with) { |**attributes| self.class.new(**to_h.merge!(attributes)) }
58
+ end
59
+
31
60
  def define_hash
32
61
  local_keys = keys
33
62
 
@@ -50,5 +79,21 @@ module Wholable
50
79
  .then { |pairs| "#<#{name} #{pairs}>" }
51
80
  end
52
81
  end
82
+
83
+ def define_to_a
84
+ local_keys = keys
85
+
86
+ define_method :to_a do
87
+ local_keys.reduce([]) { |array, key| array.append public_send(key) }
88
+ end
89
+ end
90
+
91
+ def define_to_h
92
+ local_keys = keys
93
+
94
+ define_method :to_h do
95
+ local_keys.each.with_object({}) { |key, dictionary| dictionary[key] = public_send key }
96
+ end
97
+ end
53
98
  end
54
99
  end
data/wholable.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "wholable"
5
- spec.version = "0.0.1"
5
+ spec.version = "0.1.1"
6
6
  spec.authors = ["Brooke Kuhlmann"]
7
7
  spec.email = ["brooke@alchemists.io"]
8
8
  spec.homepage = "https://alchemists.io/projects/wholable"
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wholable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brooke Kuhlmann
@@ -35,7 +35,7 @@ cert_chain:
35
35
  3n5C8/6Zh9DYTkpcwPSuIfAga6wf4nXc9m6JAw8AuMLaiWN/r/2s4zJsUHYERJEu
36
36
  gZGm4JqtuSg8pYjPeIJxS960owq+SfuC+jxqmRA54BisFCv/0VOJi7tiJVY=
37
37
  -----END CERTIFICATE-----
38
- date: 2023-08-06 00:00:00.000000000 Z
38
+ date: 2023-10-26 00:00:00.000000000 Z
39
39
  dependencies: []
40
40
  description:
41
41
  email:
@@ -79,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
79
  - !ruby/object:Gem::Version
80
80
  version: '0'
81
81
  requirements: []
82
- rubygems_version: 3.4.18
82
+ rubygems_version: 3.4.21
83
83
  signing_key:
84
84
  specification_version: 4
85
85
  summary: A whole value object mixin.
metadata.gz.sig CHANGED
Binary file