object_identifier 0.4.1 → 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: c0a0e09332282b492a3c9e83f9ab59a319a421c9ed3e83c2bfa87f6bfe20e6bd
4
- data.tar.gz: 7b27cf091a8ecfd1c747fb75918f4b88aabd725dba3221489021aa2f5d24138a
3
+ metadata.gz: 77c88ad5dd6256732f3cb586a72a2a6eb4c0c36e9042af8be0176599ba16d2d2
4
+ data.tar.gz: 69874ab0c4790cc824e664394327132fac01c2ac4873b85b42c50792367ea1f9
5
5
  SHA512:
6
- metadata.gz: 6b1150c07800795094a4f15e475ba180187c51e248f0a5cfb385030b9720c4b2e3000692974f89eecc211b223778159b3041b358957265ae7e2e55b2835f8e47
7
- data.tar.gz: 75b8bc577271cfe6b8a58ec5ec8a7d65c1e308aee3dfef8481e65869ac4e749ff9725f7f0d4fa752b391638c0e4b0003cafd2cd91ff352514fa03301df7931e8
6
+ metadata.gz: 0f2a040a4fbf0becb441abe51af4d2dc1bd74b2f4b4f33a29ddbe0e68df38600c54c44e3d1b989d99e921d5a41b62d8ca4dac488ecfe6e7e123c299f91951af5
7
+ data.tar.gz: 57f3ed43f09b46f84d3982a36c4822b1ae86d659ed9f6ccbf928b939257e29ab2d0bd4891d702b43f37825b410baa90063945f59f0f08cde8ad061b52662dfc1
@@ -0,0 +1,38 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ "master" ]
13
+ pull_request:
14
+ branches: [ "master" ]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ test:
21
+ runs-on: ubuntu-latest
22
+ strategy:
23
+ matrix:
24
+ ruby-version: ['2.7', '3.0', '3.1', '3.2']
25
+
26
+ steps:
27
+ - uses: actions/checkout@v3
28
+ - name: Set up Ruby
29
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
30
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
31
+ # uses: ruby/setup-ruby@v1
32
+ uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4 # v1.127.0
33
+ with:
34
+ ruby-version: ${{ matrix.ruby-version }}
35
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
36
+
37
+ - name: Run tests
38
+ run: bundle exec rake
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/.rubocop.yml CHANGED
@@ -24,6 +24,18 @@ Layout/FirstHashElementIndentation:
24
24
  Layout/FirstParameterIndentation:
25
25
  Enabled: false # Revisit if more settings become available.
26
26
 
27
+ Layout/LineContinuationSpacing:
28
+ EnforcedStyle: no_space
29
+
30
+ Layout/LineEndStringConcatenationIndentation:
31
+ EnforcedStyle: aligned
32
+
33
+ Layout/LineLength:
34
+ Max: 80
35
+ Exclude:
36
+ - "object_identifier.gemspec"
37
+ - "scripts/benchmarking/**/*"
38
+
27
39
  Layout/MultilineMethodCallBraceLayout:
28
40
  EnforcedStyle: same_line
29
41
 
@@ -42,11 +54,15 @@ Lint/AmbiguousOperator:
42
54
  Lint/AmbiguousRegexpLiteral:
43
55
  Enabled: false # Conflicts with other rules.
44
56
 
57
+ Lint/OrAssignmentToConstant:
58
+ Exclude:
59
+ - "scripts/**/*"
60
+
45
61
  Lint/Void:
46
62
  CheckForMethodsWithNoSideEffects: true
47
63
 
48
64
  Metrics/BlockLength:
49
- ExcludedMethods:
65
+ AllowedMethods:
50
66
  - describe
51
67
  - context
52
68
  - ips # Benchmarking
@@ -55,11 +71,6 @@ Metrics/ClassLength:
55
71
  Exclude:
56
72
  - "test/**/*"
57
73
 
58
- Metrics/LineLength:
59
- Max: 80
60
- Exclude:
61
- - "object_identifier.gemspec"
62
-
63
74
  Naming/MethodParameterName:
64
75
  AllowedNames:
65
76
  - a
@@ -71,6 +82,10 @@ Style/Alias:
71
82
  Style/BlockDelimiters:
72
83
  Enabled: false # Reconsider later.
73
84
 
85
+ Style/ClassAndModuleChildren:
86
+ AutoCorrect: true
87
+ EnforcedStyle: compact
88
+
74
89
  Style/EmptyElse:
75
90
  # It"s helpful to show intent by including a comment in an else block.
76
91
  Enabled: false
@@ -88,6 +103,13 @@ Style/FormatString:
88
103
  Style/Lambda:
89
104
  EnforcedStyle: literal
90
105
 
106
+ Style/LambdaCall:
107
+ Enabled: false # Allow ServiceObject.(*). Only use on classes, not instances.
108
+
109
+ Style/OpenStructUse:
110
+ Exclude:
111
+ - "test/**/*"
112
+
91
113
  Style/RegexpLiteral:
92
114
  EnforcedStyle: mixed
93
115
 
data/CHANGELOG.md CHANGED
@@ -1,4 +1,15 @@
1
- ### 0.4.1 - 2022-12-?
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
+
7
+ ### 0.5.0 - 2023-01-04
8
+ - Add support for defining customer Formatters.
9
+ - Add ObjectInspector::Configuration#formatter_class setting for overriding the default Formatter. See the README for more.
10
+ - Add a benchmarking script for comparing performance of formatters. See the README for more.
11
+
12
+ ### 0.4.1 - 2022-12-30
2
13
  - Make compatible with Ruby 3.2 (and likely Ruby 3.0 and 3.1 as well).
3
14
  - Update development dependencies.
4
15
 
data/Gemfile.lock CHANGED
@@ -1,13 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- object_identifier (0.4.1)
4
+ object_identifier (0.6.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  ansi (1.5.0)
10
10
  ast (2.4.2)
11
+ benchmark-ips (2.10.0)
11
12
  builder (3.2.4)
12
13
  byebug (11.1.3)
13
14
  coderay (1.1.3)
@@ -15,12 +16,13 @@ GEM
15
16
  json (2.6.3)
16
17
  kwalify (0.7.2)
17
18
  method_source (1.0.0)
18
- minitest (5.16.3)
19
+ minitest (5.17.0)
19
20
  minitest-reporters (1.5.0)
20
21
  ansi
21
22
  builder
22
23
  minitest (>= 5.0)
23
24
  ruby-progressbar
25
+ much-stub (0.1.10)
24
26
  parallel (1.22.1)
25
27
  parser (3.1.3.0)
26
28
  ast (~> 2.4.1)
@@ -38,14 +40,14 @@ GEM
38
40
  rainbow (>= 2.0, < 4.0)
39
41
  regexp_parser (2.6.1)
40
42
  rexml (3.2.5)
41
- rubocop (1.41.1)
43
+ rubocop (1.42.0)
42
44
  json (~> 2.3)
43
45
  parallel (~> 1.10)
44
46
  parser (>= 3.1.2.1)
45
47
  rainbow (>= 2.2.2, < 4.0)
46
48
  regexp_parser (>= 1.8, < 3.0)
47
49
  rexml (>= 3.2.5, < 4.0)
48
- rubocop-ast (>= 1.23.0, < 2.0)
50
+ rubocop-ast (>= 1.24.1, < 2.0)
49
51
  ruby-progressbar (~> 1.7)
50
52
  unicode-display_width (>= 1.4.0, < 3.0)
51
53
  rubocop-ast (1.24.1)
@@ -63,10 +65,12 @@ PLATFORMS
63
65
  ruby
64
66
 
65
67
  DEPENDENCIES
68
+ benchmark-ips
66
69
  bundler
67
70
  byebug
68
71
  minitest
69
72
  minitest-reporters
73
+ much-stub
70
74
  object_identifier!
71
75
  pry
72
76
  pry-byebug
data/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Object Identifier
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/object_identifier.svg)](https://badge.fury.io/rb/object_identifier)
4
- [![Test Coverage](https://api.codeclimate.com/v1/badges/0b737a72d16ec755c1ff/test_coverage)](https://codeclimate.com/github/pdobb/object_identifier/test_coverage)
5
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/0b737a72d16ec755c1ff/maintainability)](https://codeclimate.com/github/pdobb/object_identifier/maintainability)
6
5
 
7
6
  Object Identifier allows quick, easy, and uniform identification of an object by inspecting its class name and outputting any desirable attributes/methods. It is great for logging, sending descriptive notification messages, etc.
@@ -39,10 +38,27 @@ Or install it yourself as:
39
38
  ## Compatibility
40
39
 
41
40
  Tested MRI Ruby Versions:
42
- * 3.2.0
43
- * 2.4.9
44
- * 2.5.8
45
- * 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
47
+
48
+
49
+ ## Configuration
50
+
51
+ Global/default values for ObjectIdentifier can be configured via the ObjectIdentifier::Configuration object.
52
+
53
+ _Note: In a Rails app, the following would go in e.g. `config/initializers/object_identifier.rb`_
54
+
55
+ ```ruby
56
+ # Default values are shown.
57
+ ObjectIdentifier.configure do |config|
58
+ config.formatter_class = ObjectIdentifier::StringFormatter
59
+ config.default_attributes = %i[id]
60
+ end
61
+ ```
46
62
 
47
63
 
48
64
  ## Usage
@@ -152,6 +168,23 @@ OpenStruct.new(my_value: my_value_object).identify(:my_value)
152
168
  ObjectIdentifier works great with the [ObjectInspector](https://github.com/pdobb/object_inspector) gem.
153
169
 
154
170
 
171
+ ### Benchmarking Formatters
172
+
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.
174
+
175
+ Custom Formatters may be similarly gauged for comparison by adding them to the `custom_formatter_klasses` array before playing the script.
176
+
177
+ ```ruby
178
+ custom_formatter_klasses = [MyCustomFormatter]
179
+
180
+ play scripts/benchmarking/formatters.rb
181
+ # ObjectIdentifier::StringFormatter
182
+ # 58.478k (± 0.8%) i/s - 295.776k in 5.058178s
183
+ # MyCustomFormatter
184
+ # ...
185
+ ```
186
+
187
+
155
188
  ## Development
156
189
 
157
190
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -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.identify(self, *args, **kargs)
43
+ ObjectIdentifier.(self, *args, **kargs)
44
44
  end
45
45
  end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ObjectIdentifier::StringFormatter builds a String to identify the
4
+ # given object(s).
5
+ class ObjectIdentifier::StringFormatter
6
+ NO_OBJECTS_INDICATOR = "[no objects]"
7
+
8
+ def self.call(objects, parameters = ObjectIdentifier.buid_parameters)
9
+ new(objects, parameters).call
10
+ end
11
+
12
+ # @param objects [Object, [Object, ...]] the object(s) to be interrogated for
13
+ # String values to be added to the output String
14
+ # @param parameters [ObjectIdentifier::Parameters]
15
+ def initialize(objects, parameters = ObjectIdentifier.buid_parameters)
16
+ @objects = ObjectIdentifier::ArrayWrap.(objects)
17
+ @parameters = parameters
18
+ end
19
+
20
+ # Output the self-identifying string for the given object(s). Will either
21
+ # return a single object representation or a list of object
22
+ # representations, based on the number of objects we're identifying.
23
+ #
24
+ # @return [String] a string that identifies the object(s)
25
+ def call
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
33
+ end
34
+
35
+ private
36
+
37
+ def format_single_object(object = @objects.first)
38
+ SingleObject.(object, @parameters)
39
+ end
40
+
41
+ def format_multiple_objects
42
+ Collection.(@objects, @parameters)
43
+ end
44
+
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
53
+
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
61
+
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
+
69
+ private
70
+
71
+ def format_single_object(object = @objects.first)
72
+ SingleObject.(object, @parameters)
73
+ end
74
+
75
+ def limit
76
+ @parameters.limit { objects_count }
77
+ end
78
+
79
+ def truncated_objects_count
80
+ @truncated_objects_count ||= objects_count - limit
81
+ end
82
+
83
+ def objects_count
84
+ @objects_count ||= @objects.size
85
+ end
86
+
87
+ def truncated?
88
+ truncated_objects_count.positive?
89
+ end
90
+ end
91
+
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
159
+ end
160
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ObjectIdentifier
4
- VERSION = "0.4.1"
4
+ VERSION = "0.6.0"
5
5
  end
@@ -3,10 +3,146 @@
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.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
41
+ end
42
+
43
+ def self.configuration
44
+ @configuration ||= Configuration.new
45
+ end
46
+
47
+ def self.configure
48
+ yield(configuration)
49
+ end
50
+
51
+ def self.reset_configuration
52
+ @configuration = Configuration.new
53
+ end
54
+
55
+ # ObjectIdentifier::Configuration stores the default configuration options for
56
+ # the ObjectIdentifier gem. Modification of attributes is possible at any
57
+ # time, and values will persist for the duration of the running process.
58
+ class Configuration
59
+ attr_reader :formatter_class,
60
+ :default_attributes
61
+
62
+ def initialize
63
+ @formatter_class = ObjectIdentifier::StringFormatter
64
+ @default_attributes = %i[id]
65
+ end
66
+
67
+ def formatter_class=(value)
68
+ unless value.is_a?(Class)
69
+ raise TypeError, "Formatter must be a Class constant"
70
+ end
71
+
72
+ @formatter_class = value
73
+ end
74
+
75
+ def default_attributes=(value)
76
+ @default_attributes = value.to_a.map!(&:to_sym)
77
+ end
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
6
142
  end
7
143
 
8
144
  require "object_identifier/version"
9
- require "object_identifier/identifier"
145
+ require "object_identifier/formatters/string_formatter"
10
146
  require "core_ext/object"
11
147
  require "core_ext/string"
12
148
  require "core_ext/symbol"
@@ -7,9 +7,10 @@ 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
+ spec.metadata = { "rubygems_mfa_required" => "true" }
13
14
 
14
15
  spec.summary = "ObjectIdentifier identifies an object by its class name and attributes."
15
16
  spec.description = "Object Identifier allows quick, easy, and uniform identification of an object by inspecting its class name and outputting any desirable attributes/methods. It is great for logging, sending descriptive notification messages, etc."
@@ -32,10 +33,12 @@ Gem::Specification.new do |spec|
32
33
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
34
  spec.require_paths = ["lib"]
34
35
 
36
+ spec.add_development_dependency "benchmark-ips"
35
37
  spec.add_development_dependency "bundler"
36
38
  spec.add_development_dependency "byebug"
37
39
  spec.add_development_dependency "minitest"
38
40
  spec.add_development_dependency "minitest-reporters"
41
+ spec.add_development_dependency "much-stub"
39
42
  spec.add_development_dependency "pry"
40
43
  spec.add_development_dependency "pry-byebug"
41
44
  spec.add_development_dependency "rake"
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Play from the pry console with:
4
+ # play scripts/benchmarking/formatters.rb
5
+
6
+ require "benchmark/ips"
7
+
8
+ custom_formatter_klasses ||= []
9
+
10
+ formatter_klasses = [
11
+ ObjectIdentifier::StringFormatter,
12
+ *Array(custom_formatter_klasses)
13
+ ].freeze
14
+
15
+ MyObject ||= Struct.new(:id, :name)
16
+
17
+ objects = [
18
+ MyObject.new(id: 1, name: "NAME1"),
19
+ MyObject.new(id: 2, name: "NAME2"),
20
+ MyObject.new(id: 3, name: "NAME3")
21
+ ].freeze
22
+
23
+ def parameterize(attributes = [], **formatter_options)
24
+ ObjectIdentifier.buid_parameters(
25
+ attributes: attributes,
26
+ formatter_options: formatter_options)
27
+ end
28
+
29
+ puts "== Averaged ============================================================="
30
+ Benchmark.ips { |x|
31
+ formatter_klasses.each do |formatter_klass|
32
+ x.report(formatter_klass) {
33
+ formatter_klass.new(objects[0]).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
39
+ }
40
+ end
41
+
42
+ x.compare!
43
+ }
44
+ puts "== Done"
45
+
46
+ puts "== Individualized ======================================================="
47
+ Benchmark.ips { |x|
48
+ # rubocop:disable Style/CombinableLoops
49
+ formatter_klasses.each do |formatter_klass|
50
+ x.report("#{formatter_klass} - Default Attributes") {
51
+ formatter_klass.new(objects[0]).call
52
+ }
53
+ end
54
+ formatter_klasses.each do |formatter_klass|
55
+ x.report("#{formatter_klass} - Custom Attributes") {
56
+ formatter_klass.new(objects[0], parameterize(%i[id name])).call
57
+ }
58
+ end
59
+ formatter_klasses.each do |formatter_klass|
60
+ x.report("#{formatter_klass} - Custom Class") {
61
+ formatter_klass.new(objects[0], parameterize(klass: "CustomClass")).call
62
+ }
63
+ end
64
+ formatter_klasses.each do |formatter_klass|
65
+ x.report("#{formatter_klass} - Custom Attributes & Custom Class") {
66
+ formatter_klass.new(objects[0], parameterize(%i[id name], klass: "CustomClass")).call
67
+ }
68
+ end
69
+ formatter_klasses.each do |formatter_klass|
70
+ x.report("#{formatter_klass} - Limit 2") {
71
+ formatter_klass.new(objects, parameterize(limit: 2)).call
72
+ }
73
+ end
74
+ formatter_klasses.each do |formatter_klass|
75
+ x.report("#{formatter_klass} - Custom Attributes & Custom Class & Limit 2") {
76
+ formatter_klass.new(objects, parameterize(%i[id name], klass: "CustomClass", limit: 2)).call
77
+ }
78
+ end
79
+ # rubocop:enable Style/CombinableLoops
80
+
81
+ x.compare!
82
+ }
83
+ puts "== Done"
metadata CHANGED
@@ -1,16 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: object_identifier
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
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: 2022-12-30 00:00:00.000000000 Z
11
+ date: 2023-01-09 00:00:00.000000000 Z
13
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: benchmark-ips
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
14
27
  - !ruby/object:Gem::Dependency
15
28
  name: bundler
16
29
  requirement: !ruby/object:Gem::Requirement
@@ -67,6 +80,20 @@ dependencies:
67
80
  - - ">="
68
81
  - !ruby/object:Gem::Version
69
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: much-stub
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
70
97
  - !ruby/object:Gem::Dependency
71
98
  name: pry
72
99
  requirement: !ruby/object:Gem::Requirement
@@ -160,7 +187,9 @@ executables: []
160
187
  extensions: []
161
188
  extra_rdoc_files: []
162
189
  files:
190
+ - ".github/workflows/ci.yml"
163
191
  - ".gitignore"
192
+ - ".reek.yml"
164
193
  - ".rubocop"
165
194
  - ".rubocop.yml"
166
195
  - CHANGELOG.md
@@ -176,13 +205,15 @@ files:
176
205
  - lib/core_ext/string.rb
177
206
  - lib/core_ext/symbol.rb
178
207
  - lib/object_identifier.rb
179
- - lib/object_identifier/identifier.rb
208
+ - lib/object_identifier/formatters/string_formatter.rb
180
209
  - lib/object_identifier/version.rb
181
210
  - object_identifier.gemspec
211
+ - scripts/benchmarking/formatters.rb
182
212
  homepage: https://github.com/pdobb/object_identifier
183
213
  licenses:
184
214
  - MIT
185
- metadata: {}
215
+ metadata:
216
+ rubygems_mfa_required: 'true'
186
217
  post_install_message:
187
218
  rdoc_options: []
188
219
  require_paths:
@@ -1,161 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ObjectIdentifier
4
- # ObjectIdentifier::Identifier manages construction of the inspect String.
5
- class Identifier
6
- NO_OBJECTS_INDICATOR = "[no objects]"
7
-
8
- # Class method for constructing a self-identifying string for any given
9
- # object or collection of objects.
10
- #
11
- # @overload self.identify(obj, *args)
12
- # @param obj [Object] the object to identify
13
- # @param args [*] (optional) a list of arguments to identify for this
14
- # object or for each object in this collection
15
- # @overload self.identify(obj, *args, options)
16
- # @param obj [Object] the object to identify
17
- # @param args [*] (optional) (default :id) a list of arguments to identify
18
- # for this object
19
- # @param [Hash] options the options for building a customized
20
- # self-identifier
21
- # @option options [String, nil] :klass object class name override
22
- # @option options [Integer] :limit maximum number of objects to display
23
- # from a collection
24
- #
25
- # @return [String] a self-identifying string like `Class[id:1, name:'temp']`
26
- #
27
- # @example
28
- # ObjectIdentifier::Identifier.identify(
29
- # OpenStruct.new(a: 1, b: '2', c: :"3"), :a, :b, :c)
30
- # # => "OpenStruct[a:1, b:\"2\", c::\"3\"]"
31
- #
32
- # ObjectIdentifier::Identifier.identify(1, :to_s)
33
- # # => "Integer[to_s:\"1\"]"
34
- # ObjectIdentifier::Identifier.identify(nil)
35
- # # => "[no objects]"
36
- #
37
- # ObjectIdentifier::Identifier.identify(%w(1 2), :to_i, :to_f)
38
- # # => "String[to_i:1, to_f:1.0], String[to_i:2, to_f:2.0]"
39
- #
40
- # ObjectIdentifier::Identifier.identify((1..10).to_a, :to_f, limit: 2)
41
- # # => "Integer[to_f:1.0], Integer[to_f:2.0], ... (8 more)"
42
- def self.identify(*args, **kargs)
43
- new(*args, **kargs).to_s
44
- end
45
-
46
- def initialize(objects, *args, limit: nil, klass: :not_given)
47
- @objects = ArrayWrap.call(objects)
48
- @attributes = args.empty? ? [:id] : args
49
- @limit = (limit || @objects.size).to_i
50
- @klass = klass
51
- end
52
-
53
- # Output the self-identifying string for an instance of
54
- # ObjectIdentifier::Identifier. Will either return a single object
55
- # representation or a list of object representations, based on the number of
56
- # objects we're identifying.
57
- #
58
- # @return [String] a string representing the object or list of objects
59
- def to_s
60
- if many?
61
- format_multiple_objects
62
- else
63
- format_single_object
64
- end
65
- end
66
-
67
- private
68
-
69
- def format_multiple_objects
70
- objects =
71
- @objects.first(@limit).map { |obj| format(obj) }
72
-
73
- objects << "... (#{truncated_objects_count} more)" if truncated?
74
-
75
- objects.join(", ")
76
- end
77
-
78
- def format_single_object
79
- object = @objects.first if @objects.respond_to?(:first)
80
-
81
- format(object)
82
- end
83
-
84
- def format(object)
85
- return NO_OBJECTS_INDICATOR if blank?(object)
86
-
87
- "#{class_name(object)}[#{format_attributes(evaluate_attributes(object))}]"
88
- end
89
-
90
- def format_attributes(attributes_hash)
91
- return if attributes_hash.empty?
92
-
93
- attributes_hash.
94
- map(&format_attributes_map_block(attributes_hash)).
95
- join(", ")
96
- end
97
-
98
- def format_attributes_map_block(attributes_hash)
99
- if attributes_hash.one?
100
- ->(_key, value) { value.inspect_lit }
101
- else
102
- ->(key, value) { "#{key}:#{value.inspect_lit}" }
103
- end
104
- end
105
-
106
- # @return [Hash]
107
- def evaluate_attributes(object)
108
- @attributes.each_with_object({}) { |key, acc|
109
- if object.respond_to?(key, :include_private)
110
- acc[key] = object.send(key)
111
- elsif key.to_s.start_with?("@")
112
- acc[key] = object.instance_variable_get(key)
113
- end
114
- }
115
- end
116
-
117
- def class_name(object)
118
- klass_given? ? @klass : object.class.name
119
- end
120
-
121
- def truncated_objects_count
122
- objects_count - @limit
123
- end
124
-
125
- def objects_count
126
- @objects_count ||= @objects.size
127
- end
128
-
129
- def klass_given?
130
- @klass != :not_given
131
- end
132
-
133
- def blank?(object)
134
- object.nil? || object == [] || object == {}
135
- end
136
-
137
- def many?
138
- objects_count > 1
139
- end
140
-
141
- def truncated?
142
- truncated_objects_count.positive?
143
- end
144
-
145
- # ObjectIdentifier::Identifier::ArrayWrap mirrors the implementation of
146
- # Rails' {Array.wrap} method. This allows us to get around objects that
147
- # respond to `to_a` (such as Struct) and, instead, utilize either `to_ary`
148
- # or just actually wrapping the object in an Array.
149
- class ArrayWrap
150
- def self.call(object)
151
- if object.nil?
152
- []
153
- elsif object.respond_to?(:to_ary)
154
- object.to_ary || [object]
155
- else
156
- [object]
157
- end
158
- end
159
- end
160
- end
161
- end