object_identifier 0.5.0 → 0.6.0

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: 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