redis_model 0.1.0.pre3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +187 -0
  6. data/Rakefile +15 -0
  7. data/lib/redis_model/adapters/paperclip.rb +51 -0
  8. data/lib/redis_model/attribute.rb +124 -0
  9. data/lib/redis_model/base.rb +67 -0
  10. data/lib/redis_model/belonged_to.rb +27 -0
  11. data/lib/redis_model/class_attribute.rb +50 -0
  12. data/lib/redis_model/configurations.rb +15 -0
  13. data/lib/redis_model/helpers/sorted_set_paginator.rb +80 -0
  14. data/lib/redis_model/intersected.rb +17 -0
  15. data/lib/redis_model/schema.rb +114 -0
  16. data/lib/redis_model/types/base.rb +32 -0
  17. data/lib/redis_model/types/base_value.rb +26 -0
  18. data/lib/redis_model/types/counter.rb +25 -0
  19. data/lib/redis_model/types/float.rb +17 -0
  20. data/lib/redis_model/types/hash.rb +53 -0
  21. data/lib/redis_model/types/integer.rb +17 -0
  22. data/lib/redis_model/types/list.rb +40 -0
  23. data/lib/redis_model/types/set.rb +59 -0
  24. data/lib/redis_model/types/sorted_set.rb +184 -0
  25. data/lib/redis_model/types/string.rb +11 -0
  26. data/lib/redis_model/types/timestamp.rb +26 -0
  27. data/lib/redis_model/version.rb +3 -0
  28. data/lib/redis_model.rb +37 -0
  29. data/redis_model.gemspec +28 -0
  30. data/spec/redis_model/attribute_spec.rb +77 -0
  31. data/spec/redis_model/base_spec.rb +34 -0
  32. data/spec/redis_model/class_attribute_spec.rb +16 -0
  33. data/spec/redis_model/helpers/sorted_set_paginator_spec.rb +33 -0
  34. data/spec/redis_model/schema_spec.rb +118 -0
  35. data/spec/redis_model/types/base_spec.rb +28 -0
  36. data/spec/redis_model/types/counter_spec.rb +32 -0
  37. data/spec/redis_model/types/float_spec.rb +20 -0
  38. data/spec/redis_model/types/hash_spec.rb +55 -0
  39. data/spec/redis_model/types/integer_spec.rb +22 -0
  40. data/spec/redis_model/types/list_spec.rb +55 -0
  41. data/spec/redis_model/types/set_spec.rb +62 -0
  42. data/spec/redis_model/types/sorted_set_spec.rb +303 -0
  43. data/spec/redis_model/types/string_spec.rb +28 -0
  44. data/spec/redis_model/types/timestamp_spec.rb +22 -0
  45. data/spec/spec_helper.rb +13 -0
  46. data/spec/support/dynamic_class.rb +5 -0
  47. metadata +190 -0
@@ -0,0 +1,303 @@
1
+ require 'spec_helper'
2
+
3
+ describe RedisModel::Types::SortedSet do
4
+ let(:klass) { dynamic_class(RedisModel::Base) }
5
+ let(:object) { klass.new }
6
+ let(:members) { { one: 1, two: 2, three: 3 } }
7
+ let(:populate) { members.each { |value, score| RedisModel::Base.connection.zadd object.key_label, score, value } }
8
+
9
+ before { klass.data_type :sorted_set }
10
+
11
+ describe '#to_a' do
12
+ context 'when sorted set does not exist' do
13
+ it { expect(object.to_a).to eq([]) }
14
+ end
15
+
16
+ context 'when sorted set has been populated before' do
17
+ before { populate }
18
+
19
+ it { expect(object.to_a.sort).to eq(members.keys.map(&:to_s).sort) }
20
+ end
21
+ end
22
+
23
+ describe '#count_range' do
24
+ context 'when sorted set does not exist' do
25
+ it { expect(object.count_range(0, 4)).to eq(0) }
26
+ end
27
+
28
+ context 'when sorted set has been populated before' do
29
+ before { populate }
30
+
31
+ it { expect(object.count_range(0, 4)).to eq(members.length) }
32
+ end
33
+ end
34
+
35
+ describe '#include?' do
36
+ context 'when sorted set does not exist' do
37
+ it { expect(object.include?(members.keys.first)).to be_false }
38
+ end
39
+
40
+ context 'when sorted set has been populated before' do
41
+ before { populate }
42
+
43
+ it { expect(object.include?(members.keys.first)).to be_true }
44
+ it { expect(object.include?('unknown key')).to be_false }
45
+ end
46
+ end
47
+
48
+ describe '#get_range_by_rank' do
49
+ context 'when sorted set does not exist' do
50
+ it { expect(object.get_range_by_rank(0, 1)).to eq([]) }
51
+ end
52
+
53
+ context 'when sorted set has been populated before' do
54
+ before { populate }
55
+
56
+ it { expect(object.get_range_by_rank(0, 0)).to eq(members.sort_by(&:last).reverse.slice(0, 1).map(&:first).map(&:to_s)) }
57
+ it { expect(object.get_range_by_rank(0, 1)).to eq(members.sort_by(&:last).reverse.slice(0, 2).map(&:first).map(&:to_s)) }
58
+ it { expect(object.get_range_by_rank(0, 2)).to eq(members.sort_by(&:last).reverse.slice(0, 3).map(&:first).map(&:to_s)) }
59
+ end
60
+ end
61
+
62
+ describe '#get_range_by_reverse_rank' do
63
+ context 'when sorted set does not exist' do
64
+ it { expect(object.get_range_by_reverse_rank(0, 1)).to eq([]) }
65
+ end
66
+
67
+ context 'when sorted set has been populated before' do
68
+ before { populate }
69
+
70
+ it { expect(object.get_range_by_reverse_rank(0, 0)).to eq(members.sort_by(&:last).slice(0, 1).map(&:first).map(&:to_s)) }
71
+ it { expect(object.get_range_by_reverse_rank(0, 1)).to eq(members.sort_by(&:last).slice(0, 2).map(&:first).map(&:to_s)) }
72
+ it { expect(object.get_range_by_reverse_rank(0, 2)).to eq(members.sort_by(&:last).slice(0, 3).map(&:first).map(&:to_s)) }
73
+ end
74
+ end
75
+
76
+ describe '#get_rank' do
77
+ context 'when sorted set does not exist' do
78
+ it { expect(object.get_rank(members.keys.first)).to be_nil }
79
+ end
80
+
81
+ context 'when sorted set has been populated before' do
82
+ before { populate }
83
+
84
+ it do
85
+ members.each do |key, value|
86
+ expect(object.get_rank(key)).to eq(members.sort_by(&:last).reverse.map(&:first).index(key))
87
+ end
88
+ end
89
+
90
+ it { expect(object.get_rank('unknown')).to be_nil }
91
+ end
92
+ end
93
+
94
+ describe '#score' do
95
+ context 'when sorted set does not exist' do
96
+ it { expect(object.score(members.keys.first)).to be_nil }
97
+ end
98
+
99
+ context 'when sorted set has been populated before' do
100
+ before { populate }
101
+
102
+ it do
103
+ members.each do |key, value|
104
+ expect(object.score(key)).to eq(members[key])
105
+ end
106
+ end
107
+
108
+ it { expect(object.score('unknown')).to be_nil }
109
+ end
110
+ end
111
+
112
+ describe '#count' do
113
+ context 'when sorted set does not exist' do
114
+ it { expect(object.count).to eq(0) }
115
+ end
116
+
117
+ context 'when sorted set has been populated before' do
118
+ before { populate }
119
+
120
+ it { expect(object.count).to eq(members.count) }
121
+ end
122
+ end
123
+
124
+ describe '#get_range' do
125
+ context 'when sorted set does not exist' do
126
+ it { expect(object.get_range(0, 4)).to eq([]) }
127
+ end
128
+
129
+ context 'when sorted set has been populated before' do
130
+ before { populate }
131
+
132
+ it { expect(object.get_range(members.values.min, members.values.max)).to eq(members.reject { |k, v| [members.values.min, members.values.max].include?(v) }.keys.map(&:to_s)) }
133
+ it { expect(object.get_range(members.values.min, members.values.max, include_boundaries: true)).to eq(members.sort_by(&:last).reverse.map(&:first).map(&:to_s)) }
134
+ end
135
+ end
136
+
137
+ describe '#put' do
138
+ it { expect { object.put(0, 'zero') }.to change { object.count }.by(1) }
139
+ end
140
+
141
+ describe '#remove' do
142
+ context 'when sorted set does not exist' do
143
+ it { expect { object.remove('one') }.not_to change { object.count } }
144
+ end
145
+
146
+ context 'when sorted set has been populated before' do
147
+ before { populate }
148
+
149
+ it { expect { object.remove(members.keys.first) }.to change { object.count } }
150
+ it { expect { object.remove('unknown') }.not_to change { object.count } }
151
+ end
152
+ end
153
+
154
+ describe '#remove_range' do
155
+ context 'when sorted set does not exist' do
156
+ it { expect { object.remove_range }.not_to change { object.count } }
157
+ end
158
+
159
+ context 'when sorted set has been populated before' do
160
+ before { populate }
161
+
162
+ it { expect { object.remove_range }.to change { object.count }.to(0) }
163
+ it { expect { object.remove_range(members.values.min, members.values.max) }.to change { object.count }.to(0) }
164
+ it { expect { object.remove_range(members.values.min + 1, members.values.max) }.to change { object.count }.to(1) }
165
+ end
166
+ end
167
+
168
+ describe '#duplicate' do
169
+ context 'when sorted set does not exist' do
170
+ it { expect { object.duplicate('new') }.not_to change { RedisModel::Base.connection.keys } }
171
+ end
172
+
173
+ context 'when sorted set has been populated before' do
174
+ before { populate }
175
+
176
+ it { expect { object.duplicate('new') }.to change { RedisModel::Base.connection.keys } }
177
+ end
178
+ end
179
+
180
+ describe '#intersect' do
181
+ before { populate }
182
+
183
+ context 'when operand is a sorted set' do
184
+ let(:operand_klass) { dynamic_class(RedisModel::Base) }
185
+ let(:operand) { operand_klass.new }
186
+ let(:operand_members) { { three: 3, four: 4, five: 5 } }
187
+
188
+ before { operand_klass.data_type :sorted_set }
189
+ before { operand_members.each { |key, value| operand.put(value, key) } }
190
+
191
+ context 'when block is not given' do
192
+ it { expect(object.intersect(operand)).to be_kind_of(RedisModel::Intersected) }
193
+ it { expect(object.intersect(operand).key_label).to be_include(object.key_label) }
194
+ it { expect(object.intersect(operand).key_label).to be_include(operand.key_label) }
195
+
196
+ context 'when seed is given' do
197
+ let(:seed) { 123 }
198
+
199
+ it { expect(object.intersect(operand, seed: seed).key_label).to be_include(seed.to_s) }
200
+ end
201
+
202
+ context 'when generated' do
203
+ before { object.intersect(operand).generate }
204
+
205
+ it { expect(object.intersect(operand)).to be_exists }
206
+ it { expect(object.intersect(operand).to_a).to eq((members.keys & operand_members.keys).map(&:to_s)) }
207
+ end
208
+ end
209
+
210
+ context 'when block is given' do
211
+ it 'yields an instance of RedisModel::Intersected' do
212
+ object.intersect(operand) do |intersected|
213
+ expect(intersected).to be_kind_of(RedisModel::Intersected)
214
+ end
215
+ end
216
+
217
+ it 'returns evaluation result of block' do
218
+ expect(object.intersect(operand) do |intersected|
219
+ intersected.to_a
220
+ end).to eq((members.keys & operand_members.keys).map(&:to_s))
221
+ end
222
+
223
+ it 'the yielded instance has key_label which starts with caller\'s key_label' do
224
+ object.intersect(operand) do |intersected|
225
+ expect(intersected.key_label).to be_include(object.key_label)
226
+ expect(intersected.key_label).to be_include(operand.key_label)
227
+ end
228
+ end
229
+
230
+ it 'clears up intersected set' do
231
+ key_label = nil
232
+
233
+ object.intersect(operand) do |intersected|
234
+ key_label = intersected.key_label
235
+ end
236
+
237
+ expect(RedisModel::Base.connection.exists(key_label)).to be_false
238
+ end
239
+
240
+ context 'when seed is given' do
241
+ let(:seed) { 123 }
242
+
243
+ it 'adds seed string to key_label' do
244
+ key_label = nil
245
+
246
+ object.intersect(operand, seed: seed) do |intersected|
247
+ key_label = intersected.key_label
248
+ end
249
+
250
+ expect(key_label).to be_include(seed.to_s)
251
+ end
252
+ end
253
+ end
254
+ end
255
+
256
+ context 'when operand is a set' do
257
+ let(:operand_klass) { dynamic_class(RedisModel::Base) }
258
+ let(:operand) { operand_klass.new }
259
+ let(:operand_members) { [:three, :four, :five] }
260
+
261
+ before { operand_klass.data_type :set }
262
+ before { operand_members.each { |value| operand << value } }
263
+
264
+ context 'when block is not given' do
265
+ it { expect(object.intersect(operand)).to be_kind_of(RedisModel::Intersected) }
266
+ it { expect(object.intersect(operand).key_label).to be_include(object.key_label) }
267
+ it { expect(object.intersect(operand).key_label).to be_include(operand.key_label) }
268
+
269
+ context 'when generated' do
270
+ before { object.intersect(operand).generate }
271
+
272
+ it { expect(object.intersect(operand)).to be_exists }
273
+ it { expect(object.intersect(operand).to_a).to eq((members.keys & operand_members).map(&:to_s)) }
274
+ end
275
+ end
276
+
277
+ context 'when block is given' do
278
+ it 'yields an instance of RedisModel::Intersected' do
279
+ object.intersect(operand) do |intersected|
280
+ expect(intersected).to be_kind_of(RedisModel::Intersected)
281
+ end
282
+ end
283
+
284
+ it 'the yielded instance has key_label which starts with caller\'s key_label' do
285
+ object.intersect(operand) do |intersected|
286
+ expect(intersected.key_label).to be_include(object.key_label)
287
+ expect(intersected.key_label).to be_include(operand.key_label)
288
+ end
289
+ end
290
+
291
+ it 'clears up intersected set' do
292
+ key_label = nil
293
+
294
+ object.intersect(operand) do |intersected|
295
+ key_label = intersected.key_label
296
+ end
297
+
298
+ expect(RedisModel::Base.connection.exists(key_label)).to be_false
299
+ end
300
+ end
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe RedisModel::Types::String do
4
+ let(:klass) { dynamic_class(RedisModel::Base) }
5
+ let(:object) { klass.new }
6
+
7
+ before { klass.data_type :string }
8
+
9
+ describe '#get' do
10
+ context 'when no value was set before' do
11
+ it { expect(object.get).to be_nil }
12
+ end
13
+
14
+ context 'when value was set before' do
15
+ let(:value) { 'hi' }
16
+
17
+ before { RedisModel::Base.connection.set(object.key_label, value) }
18
+
19
+ it { expect(object.get).to eq(value) }
20
+ end
21
+ end
22
+
23
+ describe '#set' do
24
+ context 'when value was set before' do
25
+ it { expect { object.set('value') }.to change { RedisModel::Base.connection.get object.key_label } }
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe RedisModel::Types::Timestamp do
4
+ let(:klass) { dynamic_class(RedisModel::Base) }
5
+ let(:object) { klass.new }
6
+
7
+ before { klass.data_type :timestamp }
8
+
9
+ describe '#to_time' do
10
+ context 'when no value was set before' do
11
+ it { expect(object.to_time).to be_nil }
12
+ end
13
+
14
+ context 'when value was set before' do
15
+ let(:value) { DateTime.current }
16
+
17
+ before { RedisModel::Base.connection.set(object.key_label, value) }
18
+
19
+ it { expect(object.to_time.to_i).to eq(value.to_i) }
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ require 'redis_model'
2
+ Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require f }
3
+
4
+ RSpec.configure do |config|
5
+ config.treat_symbols_as_metadata_keys_with_true_values = true
6
+ config.run_all_when_everything_filtered = true
7
+ config.filter_run :focus
8
+ config.order = 'random'
9
+
10
+ config.before(:each) do
11
+ RedisModel::Base.connection.flushall
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ def dynamic_class(parent, name = 'Klass')
2
+ random_string = SecureRandom.base64(4).tr('+/=lIO0', 'pqrsxyz')
3
+
4
+ Class.new(parent).tap { |k| Object.const_set("#{name}#{random_string}", k) }
5
+ end
metadata ADDED
@@ -0,0 +1,190 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redis_model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre3
5
+ platform: ruby
6
+ authors:
7
+ - Inbeom Hwang
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: kaminari
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'
97
+ description: RedisModel provides various types of interfaces to handle values on Redis
98
+ email:
99
+ - hwanginbeom@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - Gemfile
106
+ - LICENSE.txt
107
+ - README.md
108
+ - Rakefile
109
+ - lib/redis_model.rb
110
+ - lib/redis_model/adapters/paperclip.rb
111
+ - lib/redis_model/attribute.rb
112
+ - lib/redis_model/base.rb
113
+ - lib/redis_model/belonged_to.rb
114
+ - lib/redis_model/class_attribute.rb
115
+ - lib/redis_model/configurations.rb
116
+ - lib/redis_model/helpers/sorted_set_paginator.rb
117
+ - lib/redis_model/intersected.rb
118
+ - lib/redis_model/schema.rb
119
+ - lib/redis_model/types/base.rb
120
+ - lib/redis_model/types/base_value.rb
121
+ - lib/redis_model/types/counter.rb
122
+ - lib/redis_model/types/float.rb
123
+ - lib/redis_model/types/hash.rb
124
+ - lib/redis_model/types/integer.rb
125
+ - lib/redis_model/types/list.rb
126
+ - lib/redis_model/types/set.rb
127
+ - lib/redis_model/types/sorted_set.rb
128
+ - lib/redis_model/types/string.rb
129
+ - lib/redis_model/types/timestamp.rb
130
+ - lib/redis_model/version.rb
131
+ - redis_model.gemspec
132
+ - spec/redis_model/attribute_spec.rb
133
+ - spec/redis_model/base_spec.rb
134
+ - spec/redis_model/class_attribute_spec.rb
135
+ - spec/redis_model/helpers/sorted_set_paginator_spec.rb
136
+ - spec/redis_model/schema_spec.rb
137
+ - spec/redis_model/types/base_spec.rb
138
+ - spec/redis_model/types/counter_spec.rb
139
+ - spec/redis_model/types/float_spec.rb
140
+ - spec/redis_model/types/hash_spec.rb
141
+ - spec/redis_model/types/integer_spec.rb
142
+ - spec/redis_model/types/list_spec.rb
143
+ - spec/redis_model/types/set_spec.rb
144
+ - spec/redis_model/types/sorted_set_spec.rb
145
+ - spec/redis_model/types/string_spec.rb
146
+ - spec/redis_model/types/timestamp_spec.rb
147
+ - spec/spec_helper.rb
148
+ - spec/support/dynamic_class.rb
149
+ homepage: http://gitlab.ultracaption.net/inbeom/redis_model
150
+ licenses:
151
+ - MIT
152
+ metadata: {}
153
+ post_install_message:
154
+ rdoc_options: []
155
+ require_paths:
156
+ - lib
157
+ required_ruby_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ required_rubygems_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">"
165
+ - !ruby/object:Gem::Version
166
+ version: 1.3.1
167
+ requirements: []
168
+ rubyforge_project:
169
+ rubygems_version: 2.2.2
170
+ signing_key:
171
+ specification_version: 4
172
+ summary: Interfaces for Redis values.
173
+ test_files:
174
+ - spec/redis_model/attribute_spec.rb
175
+ - spec/redis_model/base_spec.rb
176
+ - spec/redis_model/class_attribute_spec.rb
177
+ - spec/redis_model/helpers/sorted_set_paginator_spec.rb
178
+ - spec/redis_model/schema_spec.rb
179
+ - spec/redis_model/types/base_spec.rb
180
+ - spec/redis_model/types/counter_spec.rb
181
+ - spec/redis_model/types/float_spec.rb
182
+ - spec/redis_model/types/hash_spec.rb
183
+ - spec/redis_model/types/integer_spec.rb
184
+ - spec/redis_model/types/list_spec.rb
185
+ - spec/redis_model/types/set_spec.rb
186
+ - spec/redis_model/types/sorted_set_spec.rb
187
+ - spec/redis_model/types/string_spec.rb
188
+ - spec/redis_model/types/timestamp_spec.rb
189
+ - spec/spec_helper.rb
190
+ - spec/support/dynamic_class.rb