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 +4 -4
- data/.github/workflows/ci.yml +38 -0
- data/.reek.yml +11 -0
- data/.rubocop.yml +28 -6
- data/CHANGELOG.md +12 -1
- data/Gemfile.lock +8 -4
- data/README.md +38 -5
- data/lib/core_ext/object.rb +1 -1
- data/lib/object_identifier/formatters/string_formatter.rb +160 -0
- data/lib/object_identifier/version.rb +1 -1
- data/lib/object_identifier.rb +137 -1
- data/object_identifier.gemspec +4 -1
- data/scripts/benchmarking/formatters.rb +83 -0
- metadata +36 -5
- data/lib/object_identifier/identifier.rb +0 -161
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 77c88ad5dd6256732f3cb586a72a2a6eb4c0c36e9042af8be0176599ba16d2d2
|
4
|
+
data.tar.gz: 69874ab0c4790cc824e664394327132fac01c2ac4873b85b42c50792367ea1f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
-
|
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.
|
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
|
+
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.
|
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.
|
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.
|
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
|
[](https://badge.fury.io/rb/object_identifier)
|
4
|
-
[](https://codeclimate.com/github/pdobb/object_identifier/test_coverage)
|
5
4
|
[](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
|
-
*
|
43
|
-
* 2.
|
44
|
-
* 2.
|
45
|
-
* 2.
|
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.
|
data/lib/core_ext/object.rb
CHANGED
@@ -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
|
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
|
data/lib/object_identifier.rb
CHANGED
@@ -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/
|
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"
|
data/object_identifier.gemspec
CHANGED
@@ -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"
|
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
|
+
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:
|
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/
|
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
|