object_identifier 0.5.0 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4553678db77484aa04ce8bf00e3238310bb253632fb8867cae4e948a35bfda6c
4
- data.tar.gz: afae645239815d3a9a011b56892127eccbf0dbc9558fc59ed6d7c15349fdbbe7
3
+ metadata.gz: 77c88ad5dd6256732f3cb586a72a2a6eb4c0c36e9042af8be0176599ba16d2d2
4
+ data.tar.gz: 69874ab0c4790cc824e664394327132fac01c2ac4873b85b42c50792367ea1f9
5
5
  SHA512:
6
- metadata.gz: 639af898d48d62cfdf69c325986fab12147ddad81219b0e60557a9e52288597d5dddd3f4473ac94df3bbfd5ade304bb55c3f1a49b9918f4c959941ba4899f773
7
- data.tar.gz: 9f715f65e2afa4ac783db81cd8cab09b137b206dae15d4b05e757dc2d0c82e04848dbd2551e02b8a7c6127aed3b5cb5257c920e8d79f027cf42d1b61ae238679
6
+ metadata.gz: 0f2a040a4fbf0becb441abe51af4d2dc1bd74b2f4b4f33a29ddbe0e68df38600c54c44e3d1b989d99e921d5a41b62d8ca4dac488ecfe6e7e123c299f91951af5
7
+ data.tar.gz: 57f3ed43f09b46f84d3982a36c4822b1ae86d659ed9f6ccbf928b939257e29ab2d0bd4891d702b43f37825b410baa90063945f59f0f08cde8ad061b52662dfc1
data/.reek.yml ADDED
@@ -0,0 +1,11 @@
1
+ directories:
2
+ "test":
3
+ IrresponsibleModule:
4
+ enabled: false
5
+ UtilityFunction:
6
+ enabled: false
7
+ "scripts":
8
+ IrresponsibleModule:
9
+ enabled: false
10
+ UtilityFunction:
11
+ enabled: false
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ### 0.6.0 - 2023-01-09
2
+ - Internal refactoring for more Object-Oriented goodness.
3
+
4
+ #### BREAKING
5
+ - Refactor `ObjectIdentifier::Identifier` to just `ObjectIdentifier`. This has no effect on instance method usage (e.g. `<my_object>.identify(...)`). But if any manual invocations were made (e.g. `ObjectIdentifier::Identifier.call(...)`) then they will need to be updated to `ObjectIdentifier.call(...)` (or just `ObjectIdentifier.(...)`, per your own style guide).
6
+
1
7
  ### 0.5.0 - 2023-01-04
2
8
  - Add support for defining customer Formatters.
3
9
  - Add ObjectInspector::Configuration#formatter_class setting for overriding the default Formatter. See the README for more.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- object_identifier (0.5.0)
4
+ object_identifier (0.6.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -38,10 +38,12 @@ Or install it yourself as:
38
38
  ## Compatibility
39
39
 
40
40
  Tested MRI Ruby Versions:
41
- * 3.2.0
42
- * 2.4.9
43
- * 2.5.8
44
- * 2.6.6
41
+ * 2.4 (not recently tested)
42
+ * 2.5 (not recently tested)
43
+ * 2.6 (not recently tested)
44
+ * 2.7
45
+ * 3.1
46
+ * 3.2
45
47
 
46
48
 
47
49
  ## Configuration
@@ -168,7 +170,7 @@ ObjectIdentifier works great with the [ObjectInspector](https://github.com/pdobb
168
170
 
169
171
  ### Benchmarking Formatters
170
172
 
171
- Performance of Formatters can be tested by playing the [Formatters Benchmarking Scripts](https://github.com/pdobb/object_identifier/tree/master/scripts/benchmarking) in the pry console for this gem.
173
+ Performance of Formatters can be tested by playing the [Formatters Benchmarking Scripts](https://github.com/pdobb/object_identifier/blob/master/scripts/benchmarking/formatters.rb) in the pry console for this gem.
172
174
 
173
175
  Custom Formatters may be similarly gauged for comparison by adding them to the `custom_formatter_klasses` array before playing the script.
174
176
 
@@ -40,6 +40,6 @@ class Object
40
40
  # (1..10).to_a.identify(:to_f, limit: 2)
41
41
  # # => "Integer[to_f:1.0], Integer[to_f:2.0], ... (8 more)"
42
42
  def identify(*args, **kargs)
43
- ObjectIdentifier::Identifier.(self, *args, **kargs)
43
+ ObjectIdentifier.(self, *args, **kargs)
44
44
  end
45
45
  end
@@ -4,29 +4,17 @@
4
4
  # given object(s).
5
5
  class ObjectIdentifier::StringFormatter
6
6
  NO_OBJECTS_INDICATOR = "[no objects]"
7
- KLASS_NOT_GIVEN = "NOT_GIVEN"
8
7
 
9
- def self.call(objects, *attributes, **kargs)
10
- new(objects, *attributes, **kargs).call
8
+ def self.call(objects, parameters = ObjectIdentifier.buid_parameters)
9
+ new(objects, parameters).call
11
10
  end
12
11
 
13
12
  # @param objects [Object, [Object, ...]] the object(s) to be interrogated for
14
13
  # String values to be added to the output String
15
- # @param attributes [Array, *args] a list of method calls to interrogate the
16
- # given object(s) with
17
- # @param limit [Integer, nil] a given limit on the number of objects to
18
- # interrogate
19
- # @param klass [String, Symbol] a preferred type name for identifying the
20
- # given object(s) as
21
- def initialize(
22
- objects,
23
- attributes = ObjectIdentifier::Identifier.default_attributes,
24
- limit: nil,
25
- klass: KLASS_NOT_GIVEN)
14
+ # @param parameters [ObjectIdentifier::Parameters]
15
+ def initialize(objects, parameters = ObjectIdentifier.buid_parameters)
26
16
  @objects = ObjectIdentifier::ArrayWrap.(objects)
27
- @attributes = ObjectIdentifier::ArrayWrap.(attributes)
28
- @limit = (limit || @objects.size).to_i
29
- @klass = klass.to_s
17
+ @parameters = parameters
30
18
  end
31
19
 
32
20
  # Output the self-identifying string for the given object(s). Will either
@@ -35,69 +23,138 @@ class ObjectIdentifier::StringFormatter
35
23
  #
36
24
  # @return [String] a string that identifies the object(s)
37
25
  def call
38
- return NO_OBJECTS_INDICATOR if @objects.empty?
39
-
40
- output_strings = @objects.first(@limit).map { |obj| format(obj) }
41
- output_strings << "... (#{truncated_objects_count} more)" if truncated?
42
- output_strings.join(", ")
26
+ if @objects.none?
27
+ NO_OBJECTS_INDICATOR
28
+ elsif @objects.one?
29
+ format_single_object
30
+ else # @objects.size > 1
31
+ format_multiple_objects
32
+ end
43
33
  end
44
34
 
45
35
  private
46
36
 
47
- def format(object)
48
- return NO_OBJECTS_INDICATOR if blank?(object)
49
-
50
- "#{class_name(object)}[#{format_attributes(evaluate_attributes(object))}]"
37
+ def format_single_object(object = @objects.first)
38
+ SingleObject.(object, @parameters)
51
39
  end
52
40
 
53
- # Simple version of Rails' Object#blank? method.
54
- def blank?(object)
55
- object.nil? || object == [] || object == {}
41
+ def format_multiple_objects
42
+ Collection.(@objects, @parameters)
56
43
  end
57
44
 
58
- def class_name(object)
59
- klass_given? ? @klass : object.class.name
60
- end
45
+ # ObjectIdentifier::StringFormatter::Collection formats a collection-specific
46
+ # identification String, which will also necessarily be composed of
47
+ # {ObjectIdentifier::StringFormatter::SingleObject} identification Strings.
48
+ class Collection
49
+ # @return [String] the self-identifying String for the passed in object.
50
+ def self.call(*args)
51
+ new(*args).call
52
+ end
61
53
 
62
- def klass_given?
63
- @klass != KLASS_NOT_GIVEN
64
- end
54
+ # @param objects [Object, [Object, ...]] the object(s) to be interrogated
55
+ # for String values to be added to the output String
56
+ # @param parameters [ObjectIdentifier::Parameters]
57
+ def initialize(objects, parameters)
58
+ @objects = objects
59
+ @parameters = parameters
60
+ end
65
61
 
66
- def format_attributes(attributes_hash)
67
- return if attributes_hash.empty?
62
+ def call
63
+ output_strings =
64
+ @objects.first(limit).map { |obj| format_single_object(obj) }
65
+ output_strings << "... (#{truncated_objects_count} more)" if truncated?
66
+ output_strings.join(", ")
67
+ end
68
68
 
69
- attributes_formatter = determine_attributes_formatter(attributes_hash)
70
- attributes_hash.map(&attributes_formatter).join(", ")
71
- end
69
+ private
72
70
 
73
- def determine_attributes_formatter(attributes_hash)
74
- if attributes_hash.one?
75
- ->(_key, value) { value.inspect_lit }
76
- else
77
- ->(key, value) { "#{key}:#{value.inspect_lit}" }
71
+ def format_single_object(object = @objects.first)
72
+ SingleObject.(object, @parameters)
78
73
  end
79
- end
80
74
 
81
- # @return [Hash]
82
- def evaluate_attributes(object)
83
- @attributes.each_with_object({}) { |key, acc|
84
- if object.respond_to?(key, :include_private)
85
- acc[key] = object.send(key)
86
- elsif key.to_s.start_with?("@")
87
- acc[key] = object.instance_variable_get(key)
88
- end
89
- }
90
- end
75
+ def limit
76
+ @parameters.limit { objects_count }
77
+ end
91
78
 
92
- def truncated_objects_count
93
- @truncated_objects_count ||= objects_count - @limit
94
- end
79
+ def truncated_objects_count
80
+ @truncated_objects_count ||= objects_count - limit
81
+ end
95
82
 
96
- def objects_count
97
- @objects_count ||= @objects.size
83
+ def objects_count
84
+ @objects_count ||= @objects.size
85
+ end
86
+
87
+ def truncated?
88
+ truncated_objects_count.positive?
89
+ end
98
90
  end
99
91
 
100
- def truncated?
101
- truncated_objects_count.positive?
92
+ # ObjectIdentifier::StringFormatter::SingleObject formats a
93
+ # single-object-specific identification String.
94
+ class SingleObject
95
+ # @return [String] the self-identifying String for the passed in object.
96
+ def self.call(*args)
97
+ new(*args).call
98
+ end
99
+
100
+ # @param object [Object] the object to be interrogated for String values to
101
+ # be added to the output String
102
+ # @param parameters [ObjectIdentifier::Parameters]
103
+ def initialize(object, parameters)
104
+ @object = object
105
+ @parameters = parameters
106
+ end
107
+
108
+ # @return [String] the self-identifying String for {@object}.
109
+ def call
110
+ return NO_OBJECTS_INDICATOR if blank?
111
+
112
+ "#{class_name}[#{formatted_attributes}]"
113
+ end
114
+
115
+ private
116
+
117
+ # Simple version of Rails' Object#blank? method.
118
+ # :reek:NilCheck
119
+ def blank?
120
+ @object.nil? || @object == [] || @object == {}
121
+ end
122
+
123
+ def class_name
124
+ @parameters.klass { @object.class.name }
125
+ end
126
+
127
+ def formatted_attributes
128
+ return if attributes_hash.empty?
129
+
130
+ attributes_hash.map(&attributes_formatter).join(", ")
131
+ end
132
+
133
+ # :reek:DuplicateMethodCall
134
+ def attributes_formatter
135
+ @attributes_formatter ||=
136
+ if attributes_hash.one?
137
+ ->(_key, value) { value.inspect_lit }
138
+ else # attributes_hash.size > 1
139
+ ->(key, value) { "#{key}:#{value.inspect_lit}" }
140
+ end
141
+ end
142
+
143
+ # @return [Hash]
144
+ # :reek:ManualDispatch
145
+ def attributes_hash
146
+ @attributes_hash ||=
147
+ attributes.each_with_object({}) { |key, acc|
148
+ if @object.respond_to?(key, :include_private)
149
+ acc[key] = @object.__send__(key)
150
+ elsif key.to_s.start_with?("@")
151
+ acc[key] = @object.instance_variable_get(key)
152
+ end
153
+ }
154
+ end
155
+
156
+ def attributes
157
+ @parameters.attributes
158
+ end
102
159
  end
103
160
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ObjectIdentifier
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -3,20 +3,41 @@
3
3
  # ObjectIdentifier is the base namespace for all modules/classes related to the
4
4
  # object_identifier gem.
5
5
  module ObjectIdentifier
6
- # ObjectIdentifier::ArrayWrap mirrors the implementation of Rails'
7
- # {Array.wrap} method. This allows us to get around objects that respond to
8
- # `to_a` (such as Struct) and, instead, either utilize `to_ary` or just
9
- # actually wrap the object in an Array ourselves.
10
- class ArrayWrap
11
- def self.call(object)
12
- if object.nil?
13
- []
14
- elsif object.respond_to?(:to_ary)
15
- object.to_ary || [object]
16
- else
17
- [object]
18
- end
19
- end
6
+ # ObjectIdentifier.call is the main entry point for use of this gem. In
7
+ # typical usage, however, this method will almost exclusively just be called
8
+ # by {Object#identify}, as defined in lib/core_ext/object.rb.
9
+ # :reek:LongParameterList
10
+ def self.call(
11
+ objects,
12
+ *attributes,
13
+ formatter_class: default_formatter_class,
14
+ **formatter_options)
15
+
16
+ parameters =
17
+ buid_parameters(
18
+ attributes: attributes,
19
+ formatter_options: formatter_options)
20
+
21
+ formatter_class.(objects, parameters)
22
+ end
23
+
24
+ # Factory method for building an {ObjectIdentifier::Parameters} object.
25
+ def self.buid_parameters(attributes: [], formatter_options: {})
26
+ attrs = ObjectIdentifier::ArrayWrap.(attributes)
27
+ attrs = default_attributes if attrs.empty?
28
+ attrs.flatten!
29
+
30
+ Parameters.new(
31
+ attributes: attrs,
32
+ formatter_options: formatter_options.to_h)
33
+ end
34
+
35
+ def self.default_formatter_class
36
+ configuration.formatter_class
37
+ end
38
+
39
+ def self.default_attributes
40
+ configuration.default_attributes
20
41
  end
21
42
 
22
43
  def self.configuration
@@ -55,10 +76,72 @@ module ObjectIdentifier
55
76
  @default_attributes = value.to_a.map!(&:to_sym)
56
77
  end
57
78
  end
79
+
80
+ # ObjectIdentifier::Parameters encapsulates the attributes list and
81
+ # formatter options that may be needed for custom formatting during object
82
+ # identification.
83
+ class Parameters
84
+ KLASS_NOT_GIVEN = "NOT_GIVEN"
85
+
86
+ attr_reader :attributes
87
+
88
+ # @param attributes [Array, *args] a list of method calls to interrogate the
89
+ # given object(s) with
90
+ # @param formatter_options[:limit] [Integer, nil] (nil) a given limit on the
91
+ # number of objects to interrogate
92
+ # @param formatter_options[:klass] [#to_s] a preferred type name for
93
+ # identifying the given object(s) as
94
+ def initialize(
95
+ attributes: [],
96
+ formatter_options: {})
97
+ @attributes = attributes
98
+ @limit = formatter_options.fetch(:limit, nil)
99
+ @klass = formatter_options.fetch(:klass, KLASS_NOT_GIVEN)
100
+ end
101
+
102
+ # NOTE: Expects a block if a value wasn't supplied on initialization.
103
+ def limit
104
+ @limit || (yield if block_given?)
105
+ end
106
+
107
+ # NOTE: Expects a block if a value wasn't supplied on initialization.
108
+ def klass
109
+ if klass_given?
110
+ @klass.to_s
111
+ elsif block_given?
112
+ yield.to_s
113
+ else
114
+ nil
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def klass_given?
121
+ @klass != KLASS_NOT_GIVEN
122
+ end
123
+ end
124
+
125
+ # ObjectIdentifier::ArrayWrap mirrors the implementation of Rails'
126
+ # {Array.wrap} method. This allows us to get around objects that respond to
127
+ # `to_a` (such as Struct) and, instead, either utilize `to_ary` or just
128
+ # actually wrap the object in an Array ourselves.
129
+ class ArrayWrap
130
+ # :reek:NilCheck
131
+ # :reek:ManualDispatch
132
+ def self.call(object)
133
+ if object.nil?
134
+ []
135
+ elsif object.respond_to?(:to_ary)
136
+ object.to_ary || [object]
137
+ else
138
+ [object]
139
+ end
140
+ end
141
+ end
58
142
  end
59
143
 
60
144
  require "object_identifier/version"
61
- require "object_identifier/identifier"
62
145
  require "object_identifier/formatters/string_formatter"
63
146
  require "core_ext/object"
64
147
  require "core_ext/string"
@@ -7,7 +7,7 @@ require "object_identifier/version"
7
7
  Gem::Specification.new do |spec|
8
8
  spec.name = "object_identifier"
9
9
  spec.version = ObjectIdentifier::VERSION
10
- spec.authors = ["Paul DobbinSchmaltz", "Evan Sherwood"]
10
+ spec.authors = ["Paul DobbinSchmaltz"]
11
11
  spec.email = ["p.dobbinschmaltz@icloud.com"]
12
12
  spec.required_ruby_version = ">= 2.4.0"
13
13
  spec.metadata = { "rubygems_mfa_required" => "true" }
@@ -20,16 +20,22 @@ objects = [
20
20
  MyObject.new(id: 3, name: "NAME3")
21
21
  ].freeze
22
22
 
23
+ def parameterize(attributes = [], **formatter_options)
24
+ ObjectIdentifier.buid_parameters(
25
+ attributes: attributes,
26
+ formatter_options: formatter_options)
27
+ end
28
+
23
29
  puts "== Averaged ============================================================="
24
30
  Benchmark.ips { |x|
25
31
  formatter_klasses.each do |formatter_klass|
26
32
  x.report(formatter_klass) {
27
33
  formatter_klass.new(objects[0]).call
28
- formatter_klass.new(objects[0], %i[id name]).call
29
- formatter_klass.new(objects[0], klass: "CustomClass").call
30
- formatter_klass.new(objects[0], %i[id name], klass: "CustomClass").call
31
- formatter_klass.new(objects, limit: 2).call
32
- formatter_klass.new(objects, %i[id name], klass: "CustomClass", limit: 2).call
34
+ formatter_klass.new(objects[0], parameterize(%i[id name])).call
35
+ formatter_klass.new(objects[0], parameterize(klass: "CustomClass")).call
36
+ formatter_klass.new(objects[0], parameterize(%i[id name], klass: "CustomClass")).call
37
+ formatter_klass.new(objects, parameterize(limit: 2)).call
38
+ formatter_klass.new(objects, parameterize(%i[id name], klass: "CustomClass", limit: 2)).call
33
39
  }
34
40
  end
35
41
 
@@ -47,27 +53,27 @@ Benchmark.ips { |x|
47
53
  end
48
54
  formatter_klasses.each do |formatter_klass|
49
55
  x.report("#{formatter_klass} - Custom Attributes") {
50
- formatter_klass.new(objects[0], %i[id name]).call
56
+ formatter_klass.new(objects[0], parameterize(%i[id name])).call
51
57
  }
52
58
  end
53
59
  formatter_klasses.each do |formatter_klass|
54
60
  x.report("#{formatter_klass} - Custom Class") {
55
- formatter_klass.new(objects[0], klass: "CustomClass").call
61
+ formatter_klass.new(objects[0], parameterize(klass: "CustomClass")).call
56
62
  }
57
63
  end
58
64
  formatter_klasses.each do |formatter_klass|
59
65
  x.report("#{formatter_klass} - Custom Attributes & Custom Class") {
60
- formatter_klass.new(objects[0], %i[id name], klass: "CustomClass").call
66
+ formatter_klass.new(objects[0], parameterize(%i[id name], klass: "CustomClass")).call
61
67
  }
62
68
  end
63
69
  formatter_klasses.each do |formatter_klass|
64
70
  x.report("#{formatter_klass} - Limit 2") {
65
- formatter_klass.new(objects, limit: 2).call
71
+ formatter_klass.new(objects, parameterize(limit: 2)).call
66
72
  }
67
73
  end
68
74
  formatter_klasses.each do |formatter_klass|
69
75
  x.report("#{formatter_klass} - Custom Attributes & Custom Class & Limit 2") {
70
- formatter_klass.new(objects, %i[id name], klass: "CustomClass", limit: 2).call
76
+ formatter_klass.new(objects, parameterize(%i[id name], klass: "CustomClass", limit: 2)).call
71
77
  }
72
78
  end
73
79
  # rubocop:enable Style/CombinableLoops
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: object_identifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul DobbinSchmaltz
8
- - Evan Sherwood
9
8
  autorequire:
10
9
  bindir: exe
11
10
  cert_chain: []
12
- date: 2023-01-05 00:00:00.000000000 Z
11
+ date: 2023-01-09 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: benchmark-ips
@@ -190,6 +189,7 @@ extra_rdoc_files: []
190
189
  files:
191
190
  - ".github/workflows/ci.yml"
192
191
  - ".gitignore"
192
+ - ".reek.yml"
193
193
  - ".rubocop"
194
194
  - ".rubocop.yml"
195
195
  - CHANGELOG.md
@@ -206,7 +206,6 @@ files:
206
206
  - lib/core_ext/symbol.rb
207
207
  - lib/object_identifier.rb
208
208
  - lib/object_identifier/formatters/string_formatter.rb
209
- - lib/object_identifier/identifier.rb
210
209
  - lib/object_identifier/version.rb
211
210
  - object_identifier.gemspec
212
211
  - scripts/benchmarking/formatters.rb
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # ObjectIdentifier::Identifier manages construction of identification outputs
4
- # using the passed in formatter_class.
5
- class ObjectIdentifier::Identifier
6
- # NOTE: `kargs` may be specific to the Formatter being called.
7
- def self.call(
8
- objects,
9
- *attributes,
10
- formatter_class: default_formatter_class,
11
- **formatter_options)
12
- formatter_class.(objects, *attributes, **formatter_options)
13
- end
14
-
15
- def self.default_formatter_class
16
- ObjectIdentifier.configuration.formatter_class
17
- end
18
-
19
- def self.default_attributes
20
- ObjectIdentifier.configuration.default_attributes
21
- end
22
- end