mixins 0.1.0.pre.20250527171116
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/History.md +7 -0
- data/LICENSE.txt +23 -0
- data/README.md +89 -0
- data/lib/mixins/data_utilities.rb +88 -0
- data/lib/mixins/datadir.rb +59 -0
- data/lib/mixins/delegation.rb +95 -0
- data/lib/mixins/hooks.rb +84 -0
- data/lib/mixins/inspection.rb +28 -0
- data/lib/mixins/method_utilities.rb +92 -0
- data/lib/mixins.rb +20 -0
- data/spec/mixins/data_utilities_spec.rb +266 -0
- data/spec/mixins/datadir_spec.rb +86 -0
- data/spec/mixins/delegation_spec.rb +186 -0
- data/spec/mixins/hooks_spec.rb +86 -0
- data/spec/mixins/inspection_spec.rb +41 -0
- data/spec/mixins/method_utilities_spec.rb +98 -0
- data/spec/mixins_spec.rb +32 -0
- data/spec/spec_helper.rb +25 -0
- data.tar.gz.sig +0 -0
- metadata +106 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,266 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'mixins'
|
6
|
+
|
7
|
+
|
8
|
+
RSpec.describe( Mixins::DataUtilities ) do
|
9
|
+
|
10
|
+
it "doesn't try to dup immediate objects" do
|
11
|
+
expect( Mixins::DataUtilities.deep_copy( nil ) ).to be( nil )
|
12
|
+
expect( Mixins::DataUtilities.deep_copy( 112 ) ).to be( 112 )
|
13
|
+
expect( Mixins::DataUtilities.deep_copy( true ) ).to be( true )
|
14
|
+
expect( Mixins::DataUtilities.deep_copy( false ) ).to be( false )
|
15
|
+
expect( Mixins::DataUtilities.deep_copy( :a_symbol ) ).to be( :a_symbol )
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
it "doesn't try to dup modules/classes" do
|
20
|
+
klass = Class.new
|
21
|
+
expect( Mixins::DataUtilities.deep_copy( klass ) ).to be( klass )
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
it "doesn't try to dup IOs" do
|
26
|
+
data = [ $stdin ]
|
27
|
+
expect( Mixins::DataUtilities.deep_copy( data[0] ) ).to be( $stdin )
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
it "doesn't try to dup Tempfiles" do
|
32
|
+
data = Tempfile.new( 'ravn_deepcopy.XXXXX' )
|
33
|
+
expect( Mixins::DataUtilities.deep_copy( data ) ).to be( data )
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
it "makes distinct copies of arrays and their members" do
|
38
|
+
original = [ 'foom', Set.new([ 1,2 ]), :a_symbol ]
|
39
|
+
|
40
|
+
copy = Mixins::DataUtilities.deep_copy( original )
|
41
|
+
|
42
|
+
expect( copy ).to eq( original )
|
43
|
+
expect( copy ).to_not be( original )
|
44
|
+
expect( copy[0] ).to eq( original[0] )
|
45
|
+
expect( copy[0] ).to_not be( original[0] )
|
46
|
+
expect( copy[1] ).to eq( original[1] )
|
47
|
+
expect( copy[1] ).to_not be( original[1] )
|
48
|
+
expect( copy[2] ).to eq( original[2] )
|
49
|
+
expect( copy[2] ).to be( original[2] ) # Immediate
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
it "makes recursive copies of deeply-nested Arrays" do
|
54
|
+
original = [ 1, [ 2, 3, [4], 5], 6, [7, [8, 9], 0] ]
|
55
|
+
|
56
|
+
copy = Mixins::DataUtilities.deep_copy( original )
|
57
|
+
|
58
|
+
expect( copy ).to eq( original )
|
59
|
+
expect( copy ).to_not be( original )
|
60
|
+
expect( copy[1] ).to_not be( original[1] )
|
61
|
+
expect( copy[1][2] ).to_not be( original[1][2] )
|
62
|
+
expect( copy[3] ).to_not be( original[3] )
|
63
|
+
expect( copy[3][1] ).to_not be( original[3][1] )
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
it "makes distinct copies of Hashes and their members" do
|
68
|
+
original = {
|
69
|
+
:a => 1,
|
70
|
+
'b' => 2,
|
71
|
+
3 => 'c',
|
72
|
+
}
|
73
|
+
|
74
|
+
copy = Mixins::DataUtilities.deep_copy( original )
|
75
|
+
|
76
|
+
expect( copy ).to eq( original )
|
77
|
+
expect( copy ).to_not be( original )
|
78
|
+
expect( copy[:a] ).to eq( 1 )
|
79
|
+
expect( copy.key( 2 ) ).to eq( 'b' )
|
80
|
+
expect( copy.key( 2 ) ).to_not be( original.key(2) )
|
81
|
+
expect( copy[3] ).to eq( 'c' )
|
82
|
+
expect( copy[3] ).to_not be( original[3] )
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
it "makes distinct copies of deeply-nested Hashes" do
|
87
|
+
original = {
|
88
|
+
:a => {
|
89
|
+
:b => {
|
90
|
+
:c => 'd',
|
91
|
+
:e => 'f',
|
92
|
+
},
|
93
|
+
:g => 'h',
|
94
|
+
},
|
95
|
+
:i => 'j',
|
96
|
+
}
|
97
|
+
|
98
|
+
copy = Mixins::DataUtilities.deep_copy( original )
|
99
|
+
|
100
|
+
expect( copy ).to eq( original )
|
101
|
+
expect( copy[:a][:b][:c] ).to eq( 'd' )
|
102
|
+
expect( copy[:a][:b][:c] ).to_not be( original[:a][:b][:c] )
|
103
|
+
expect( copy[:a][:b][:e] ).to eq( 'f' )
|
104
|
+
expect( copy[:a][:b][:e] ).to_not be( original[:a][:b][:e] )
|
105
|
+
expect( copy[:a][:g] ).to eq( 'h' )
|
106
|
+
expect( copy[:a][:g] ).to_not be( original[:a][:g] )
|
107
|
+
expect( copy[:i] ).to eq( 'j' )
|
108
|
+
expect( copy[:i] ).to_not be( original[:i] )
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
it "copies the default proc of copied Hashes" do
|
113
|
+
original = Hash.new {|h,k| h[ k ] = Set.new }
|
114
|
+
|
115
|
+
copy = Mixins::DataUtilities.deep_copy( original )
|
116
|
+
|
117
|
+
expect( copy.default_proc ).to eq( original.default_proc )
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
it "preserves frozen-ness of copied objects" do
|
122
|
+
original = Object.new
|
123
|
+
original.freeze
|
124
|
+
|
125
|
+
copy = Mixins::DataUtilities.deep_copy( original )
|
126
|
+
|
127
|
+
expect( copy ).to_not be( original )
|
128
|
+
expect( copy ).to be_frozen()
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
it "can recursively transform Hash keys into Symbols" do
|
133
|
+
original = {
|
134
|
+
'id' => 'a8fd4d6f-5c0f-45b2-8732-8b8a90b595de',
|
135
|
+
'time' => Time.now.to_f,
|
136
|
+
'type' => 'sparrow.order.turning',
|
137
|
+
'data' => {
|
138
|
+
'response_type' => 'receipt',
|
139
|
+
'response' => 1
|
140
|
+
}
|
141
|
+
}
|
142
|
+
|
143
|
+
result = Mixins::DataUtilities.symbolify_keys( original )
|
144
|
+
|
145
|
+
expect( result.keys ).to all( be_a Symbol )
|
146
|
+
expect( result[:data].keys ).to all( be_a Symbol )
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
it "doesn't try to turn keys other than Strings into Symbols" do
|
151
|
+
original = {
|
152
|
+
'foo' => {
|
153
|
+
'bar' => 3,
|
154
|
+
3 => 'bar',
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
result = Mixins::DataUtilities.symbolify_keys( original )
|
159
|
+
|
160
|
+
expect( result[:foo].keys ).to contain_exactly( :bar, 3 )
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
it "doesn't try to turn String keys that aren't identifiers into Symbols" do
|
165
|
+
original = {
|
166
|
+
'foo' => {
|
167
|
+
'an arbitrary string' => 3,
|
168
|
+
'$punctuation_string' => 8,
|
169
|
+
'_underscore_string' => 4,
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
result = Mixins::DataUtilities.symbolify_keys( original )
|
174
|
+
|
175
|
+
expect( result[:foo].keys ).
|
176
|
+
to contain_exactly( 'an arbitrary string', '$punctuation_string', :_underscore_string )
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
it "recurses into Arrays when transforming Hash keys into Symbols" do
|
181
|
+
original = {
|
182
|
+
'type' => 'Vic Checkin',
|
183
|
+
'text' => 'Vehicles check in',
|
184
|
+
'ontological_suffix' => 'conversation.headcount',
|
185
|
+
'components' => {
|
186
|
+
'recipients' => {
|
187
|
+
'to' => 'Vic Commanders',
|
188
|
+
},
|
189
|
+
'responses' => {
|
190
|
+
'responses_from' => 'Vic Commanders',
|
191
|
+
'send_label' => 'Send UP',
|
192
|
+
'steps' => [
|
193
|
+
{ 'type' => 'integer', 'label' => 'PAX' },
|
194
|
+
{ 'type' => 'select', 'label' => 'Ready', 'values' => ['yes', 'no'] },
|
195
|
+
],
|
196
|
+
}
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
result = Mixins::DataUtilities.symbolify_keys( original )
|
201
|
+
|
202
|
+
expect( result.keys ).to all( be_a Symbol )
|
203
|
+
expect( result.dig(:components, :responses, :steps) ).to all( include(:type, :label) )
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
it "can recursively transform Hash keys into Strings" do
|
208
|
+
original = {
|
209
|
+
:id => '4cc37025-fb34-47ef-b762-6abac23e0792',
|
210
|
+
:time => Time.now.to_f,
|
211
|
+
:type => 'acme.widget.model1',
|
212
|
+
1 => 'something',
|
213
|
+
:data => {
|
214
|
+
:response_type => 'receipt',
|
215
|
+
:response_id => 1
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
result = Mixins::DataUtilities.stringify_keys( original )
|
220
|
+
|
221
|
+
expect( result.keys ).to contain_exactly( 'id', 'time', 'type', 1, 'data' )
|
222
|
+
expect( result['data'].keys ).to contain_exactly( 'response_type', 'response_id' )
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
it "doesn't stringify non-Symbols when stringifying Hash keys" do
|
227
|
+
original = {
|
228
|
+
:foo => "bar",
|
229
|
+
18 => "something",
|
230
|
+
}
|
231
|
+
|
232
|
+
result = Mixins::DataUtilities.stringify_keys( original )
|
233
|
+
|
234
|
+
expect( result.keys ).to contain_exactly( 'foo', 18 )
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
it "recurses into Arrays when transforming Hash keys into Strings" do
|
239
|
+
original = {
|
240
|
+
type: 'Teddy Bear',
|
241
|
+
text: "What's your name?",
|
242
|
+
sort: 'toy.plush.animal',
|
243
|
+
components: {
|
244
|
+
route: {
|
245
|
+
to: 'receiving@acme.com',
|
246
|
+
},
|
247
|
+
responses: {
|
248
|
+
responses_from: 'warehouse1@acme.com',
|
249
|
+
send_label: 'BZ5556',
|
250
|
+
steps: [
|
251
|
+
{ type: 'integer', label: 'QC' },
|
252
|
+
{ type: 'select', label: 'conv1', values: ['yes', 'no'] },
|
253
|
+
],
|
254
|
+
}
|
255
|
+
}
|
256
|
+
}
|
257
|
+
|
258
|
+
result = Mixins::DataUtilities.stringify_keys( original )
|
259
|
+
|
260
|
+
expect( result.keys ).to all( be_a String )
|
261
|
+
expect( result.dig('components', 'responses', 'steps') ).to all( include('type', 'label') )
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'mixins'
|
6
|
+
|
7
|
+
|
8
|
+
RSpec.describe( Mixins::Datadir ) do
|
9
|
+
|
10
|
+
let( :zebra_gemspec ) do
|
11
|
+
Gem::Specification.new do |s|
|
12
|
+
s.name = "zebra"
|
13
|
+
s.version = Gem::Version.new("0.2.1")
|
14
|
+
s.installed_by_version = Gem::Version.new("0")
|
15
|
+
s.authors = ["Zaphod Beeblebrox"]
|
16
|
+
s.date = Time.utc(2024, 6, 12)
|
17
|
+
s.description = "So many zebras."
|
18
|
+
s.email = ["zaph@example.com"]
|
19
|
+
s.files = ["zebras.rb"]
|
20
|
+
s.homepage = "https://github.com/zaph/zebras"
|
21
|
+
s.licenses = ["Ruby", "BSD-2-Clause"]
|
22
|
+
s.metadata = {
|
23
|
+
"homepage_uri"=>"https://github.com/zaph/zebras",
|
24
|
+
"source_code_uri"=>"https://github.com/zaph/zebras"
|
25
|
+
}
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
s.required_ruby_version = Gem::Requirement.new([">= 2.5.0"])
|
28
|
+
s.rubygems_version = "3.5.11"
|
29
|
+
s.specification_version = 4
|
30
|
+
s.summary = "All the zebras."
|
31
|
+
end
|
32
|
+
end
|
33
|
+
let( :zebra_datadir ) { '/path/to/installed/gem/datadir' }
|
34
|
+
let( :loaded_gemspecs ) { { 'zebra' => zebra_gemspec } }
|
35
|
+
|
36
|
+
|
37
|
+
before( :each ) do
|
38
|
+
@original_env = ENV.to_h
|
39
|
+
end
|
40
|
+
after( :each ) do
|
41
|
+
ENV.replace( @original_env )
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
it "uses the currently-loaded gem's data directory if there is one" do
|
46
|
+
expect( Gem ).to receive( :loaded_specs ).
|
47
|
+
and_return( loaded_gemspecs ).at_least( :once )
|
48
|
+
expect( zebra_gemspec ).to receive( :datadir ).
|
49
|
+
and_return( zebra_datadir ).at_least( :once )
|
50
|
+
expect( File ).to receive( :exist? ).with( zebra_datadir ).and_return( true )
|
51
|
+
|
52
|
+
target_class = Class.new do
|
53
|
+
def self::name; 'Zebra'; end
|
54
|
+
end
|
55
|
+
target_class.extend( described_class )
|
56
|
+
|
57
|
+
expect( target_class.data_dir ).to eq( Pathname(zebra_datadir) )
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
it "uses the directory at ../../data/<gemname> if no gem is loaded" do
|
62
|
+
target_class = Class.new do
|
63
|
+
def self::name; 'Panda'; end
|
64
|
+
end
|
65
|
+
target_class.extend( described_class )
|
66
|
+
|
67
|
+
local_datadir = Pathname( __FILE__ ).parent.parent.parent / 'data' / 'panda'
|
68
|
+
|
69
|
+
expect( target_class.data_dir ).to eq( local_datadir )
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
it "allows the data dir to be overridden using an environment variable" do
|
74
|
+
ocelot_data = '/path/to/ocelot/data'
|
75
|
+
ENV['OCELOT_DATADIR'] = ocelot_data
|
76
|
+
|
77
|
+
target_class = Class.new do
|
78
|
+
def self::name; 'Ocelot'; end
|
79
|
+
end
|
80
|
+
target_class.extend( described_class )
|
81
|
+
|
82
|
+
expect( target_class.data_dir ).to eq( Pathname(ocelot_data) )
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,186 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'mixins'
|
6
|
+
|
7
|
+
|
8
|
+
RSpec.describe( Mixins::Delegation ) do
|
9
|
+
|
10
|
+
let( :testclass ) do
|
11
|
+
Class.new do
|
12
|
+
extend Mixins::Delegation
|
13
|
+
|
14
|
+
@data_dir = nil
|
15
|
+
class << self
|
16
|
+
attr_accessor :data_dir
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize( obj=nil )
|
20
|
+
@obj = obj
|
21
|
+
end
|
22
|
+
|
23
|
+
def demand_loaded_object
|
24
|
+
return @load_on_demand ||= @obj
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
let( :subobj ) { double( "delegate" ) }
|
30
|
+
let( :obj ) { testclass.new(subobj) }
|
31
|
+
|
32
|
+
|
33
|
+
describe "method delegation" do
|
34
|
+
|
35
|
+
it "can be used to set up delegation through a method" do
|
36
|
+
testclass.def_method_delegators( :demand_loaded_object, :delegated_method )
|
37
|
+
|
38
|
+
expect( subobj ).to receive( :delegated_method )
|
39
|
+
|
40
|
+
obj.delegated_method
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
it "passes any arguments through to the delegate object's method" do
|
45
|
+
testclass.def_method_delegators( :demand_loaded_object, :delegated_method )
|
46
|
+
|
47
|
+
expect( subobj ).to receive( :delegated_method ).with( :arg1, :arg2 )
|
48
|
+
|
49
|
+
obj.delegated_method( :arg1, :arg2 )
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
it "allows delegation to the delegate object's method with a block" do
|
54
|
+
testclass.def_method_delegators :demand_loaded_object, :delegated_method
|
55
|
+
|
56
|
+
expect( subobj ).to receive( :delegated_method ).with( :arg1 ).
|
57
|
+
and_yield( :the_block_argument )
|
58
|
+
|
59
|
+
blockarg = nil
|
60
|
+
obj.delegated_method( :arg1 ) {|arg| blockarg = arg }
|
61
|
+
|
62
|
+
expect( blockarg ).to eq( :the_block_argument )
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
it "reports errors from its caller's perspective", :ruby_1_8_only => true do
|
67
|
+
testclass.def_method_delegators( :nonexistant_method, :erroring_delegated_method )
|
68
|
+
|
69
|
+
begin
|
70
|
+
obj.erroring_delegated_method
|
71
|
+
rescue NoMethodError => err
|
72
|
+
expect( err.message ).to match( /nonexistant_method/ )
|
73
|
+
expect( err.backtrace.first ).to match( /#{__FILE__}/ )
|
74
|
+
rescue ::Exception => err
|
75
|
+
fail "Expected a NoMethodError, but got a %p (%s)" % [ err.class, err.message ]
|
76
|
+
else
|
77
|
+
fail "Expected a NoMethodError, but no exception was raised."
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
it "delegates setters correctly" do
|
83
|
+
testclass.def_method_delegators :demand_loaded_object, :delegated_setter=
|
84
|
+
|
85
|
+
expect( subobj ).to receive( :delegated_setter= ).with( 1 )
|
86
|
+
|
87
|
+
obj.delegated_setter = 1
|
88
|
+
|
89
|
+
expect( subobj ).to receive( :delegated_setter= ).with( [1, 2] )
|
90
|
+
|
91
|
+
obj.delegated_setter = 1, 2
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
describe "instance variable delegation (ala Forwardable)" do
|
98
|
+
|
99
|
+
let( :testclass ) do
|
100
|
+
Class.new do
|
101
|
+
extend Mixins::Delegation
|
102
|
+
|
103
|
+
def initialize( obj )
|
104
|
+
@obj = obj
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
it "can be used to set up delegation through a method" do
|
111
|
+
testclass.def_ivar_delegators( :@obj, :delegated_method )
|
112
|
+
|
113
|
+
expect( subobj ).to receive( :delegated_method )
|
114
|
+
|
115
|
+
obj.delegated_method
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
it "passes any arguments through to the delegate's method" do
|
120
|
+
testclass.def_ivar_delegators( :@obj, :delegated_method )
|
121
|
+
|
122
|
+
expect( subobj ).to receive( :delegated_method ).with( :arg1, :arg2 )
|
123
|
+
|
124
|
+
obj.delegated_method( :arg1, :arg2 )
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
it "allows delegation to the delegate's method with a block" do
|
129
|
+
testclass.def_ivar_delegators( :@obj, :delegated_method )
|
130
|
+
|
131
|
+
expect( subobj ).to receive( :delegated_method ).with( :arg1 ).
|
132
|
+
and_yield( :the_block_argument )
|
133
|
+
|
134
|
+
blockarg = nil
|
135
|
+
obj.delegated_method( :arg1 ) {|arg| blockarg = arg }
|
136
|
+
|
137
|
+
expect( blockarg ).to eq( :the_block_argument )
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
it "reports errors from its caller's perspective", :ruby_1_8_only => true do
|
142
|
+
testclass.def_ivar_delegators( :@glong, :erroring_delegated_method )
|
143
|
+
|
144
|
+
begin
|
145
|
+
obj.erroring_delegated_method
|
146
|
+
rescue NoMethodError => err
|
147
|
+
expect( err.message ).to match( /['`]erroring_delegated_method' for nil/ )
|
148
|
+
expect( err.backtrace.first ).to match( /#{__FILE__}/ )
|
149
|
+
rescue ::Exception => err
|
150
|
+
fail "Expected a NoMethodError, but got a %p (%s)" % [ err.class, err.message ]
|
151
|
+
else
|
152
|
+
fail "Expected a NoMethodError, but no exception was raised."
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
it "delegates setters correctly" do
|
158
|
+
testclass.def_ivar_delegators( :@obj, :delegated_setter= )
|
159
|
+
|
160
|
+
expect( subobj ).to receive( :delegated_setter= ).with( 1 )
|
161
|
+
|
162
|
+
obj.delegated_setter = 1
|
163
|
+
|
164
|
+
expect( subobj ).to receive( :delegated_setter= ).with( [1, 2] )
|
165
|
+
|
166
|
+
obj.delegated_setter = 1, 2
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
describe "class-method delegation" do
|
173
|
+
|
174
|
+
it "can be used to set up delegation through a method" do
|
175
|
+
testclass.def_class_delegators :data_dir
|
176
|
+
|
177
|
+
testclass.data_dir = '/path/to/the/data'
|
178
|
+
obj = testclass.new
|
179
|
+
|
180
|
+
expect( obj.data_dir ).to eq( '/path/to/the/data' )
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'mixins'
|
6
|
+
|
7
|
+
|
8
|
+
RSpec.describe( Mixins::Hooks ) do
|
9
|
+
|
10
|
+
let( :extended_object ) do
|
11
|
+
obj = Object.new
|
12
|
+
obj.extend( described_class )
|
13
|
+
return obj
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
it "allows a set of hooks to be declared" do
|
18
|
+
extended_object.define_hook( :after_fork )
|
19
|
+
|
20
|
+
hook1_called = false
|
21
|
+
hook2_called = false
|
22
|
+
|
23
|
+
extended_object.after_fork do
|
24
|
+
hook1_called = true
|
25
|
+
end
|
26
|
+
extended_object.after_fork do
|
27
|
+
hook2_called = true
|
28
|
+
end
|
29
|
+
|
30
|
+
expect {
|
31
|
+
extended_object.call_after_fork_hook
|
32
|
+
}.to change { hook1_called }.to( true ).and \
|
33
|
+
change { hook2_called }.to( true ).and \
|
34
|
+
change { extended_object.after_fork_callbacks_run? }.to( true )
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
it "ensures declared hooks are run at least once" do
|
39
|
+
extended_object.define_hook( :after_fork )
|
40
|
+
|
41
|
+
hook1_called = false
|
42
|
+
hook2_called = false
|
43
|
+
|
44
|
+
extended_object.after_fork do
|
45
|
+
hook1_called = true
|
46
|
+
end
|
47
|
+
extended_object.call_after_fork_hook
|
48
|
+
extended_object.after_fork do
|
49
|
+
hook2_called = true
|
50
|
+
end
|
51
|
+
|
52
|
+
expect( hook1_called ).to be_truthy
|
53
|
+
expect( hook2_called ).to be_truthy
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
it "doesn't re-register a hook callback that already exists" do
|
58
|
+
extended_object.define_hook( :before_fork )
|
59
|
+
|
60
|
+
callback = Proc.new {}
|
61
|
+
extended_object.before_fork( &callback )
|
62
|
+
extended_object.before_fork( &callback )
|
63
|
+
|
64
|
+
expect( extended_object.before_fork_callbacks.length ).to eq( 1 )
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
it "can declare a hook that passes arguments to its callbacks" do
|
69
|
+
extended_object.define_hook( :on_event )
|
70
|
+
|
71
|
+
callback_args = []
|
72
|
+
extended_object.on_event do |ev, *args|
|
73
|
+
callback_args << [ev, args]
|
74
|
+
end
|
75
|
+
|
76
|
+
extended_object.call_on_event_hook( :slip, 4 )
|
77
|
+
extended_object.call_on_event_hook( :traverse, 16 )
|
78
|
+
|
79
|
+
expect( callback_args ).to contain_exactly(
|
80
|
+
[:slip, [4]],
|
81
|
+
[:traverse, [16]]
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
require 'mixins'
|
6
|
+
|
7
|
+
|
8
|
+
RSpec.describe( Mixins::Inspection ) do
|
9
|
+
|
10
|
+
it "handles empty details" do
|
11
|
+
oclass = Class.new do
|
12
|
+
def initialize( serial )
|
13
|
+
@serial = serial
|
14
|
+
end
|
15
|
+
end
|
16
|
+
oclass.include( described_class )
|
17
|
+
instance = oclass.new( 11 )
|
18
|
+
|
19
|
+
expect( instance.inspect ).to match( /#<#{oclass.inspect}:#\h+/ )
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
it "allows the inspection contents to be overridden" do
|
24
|
+
oclass = Class.new do
|
25
|
+
def initialize( serial )
|
26
|
+
@serial = serial
|
27
|
+
end
|
28
|
+
attr_reader :serial
|
29
|
+
def inspect_details
|
30
|
+
return "serial: %d" % [ self.serial ]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
oclass.include( described_class )
|
34
|
+
|
35
|
+
instance = oclass.new( 13 )
|
36
|
+
|
37
|
+
expect( instance.inspect ).to match( /#<\S+ serial: 13>/ )
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|