object_identifier 0.4.1 → 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: 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