translated_collection 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/script/shell ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'yaml'
6
+ require 'active_record'
7
+ configs = YAML.load(File.read("config/database.yml.example"))
8
+ ActiveRecord::Base.establish_connection(configs['development'])
9
+
10
+ require 'translated_collection'
11
+
12
+ def conn
13
+ ActiveRecord::Base.connection
14
+ end
15
+
16
+ def upperfn
17
+ lambda {|elt| elt.upcase }
18
+ end
19
+
20
+ def lowerfn
21
+ lambda {|elt| elt.downcase }
22
+ end
23
+
24
+ def collection
25
+ %w[a b c d e f g h i j k l]
26
+ end
27
+
28
+ def wrapone
29
+ TranslatedCollection::Wrapper.new(collection, lowerfn, upperfn)
30
+ end
31
+
32
+ def wrapped
33
+ @wrapped ||= wrapone
34
+ end
35
+
36
+ begin
37
+ require 'pry'
38
+ Pry.start
39
+ rescue LoadError
40
+ require 'irb'
41
+ IRB.start
42
+ end
43
+
@@ -0,0 +1,439 @@
1
+ require 'spec_helper'
2
+ require 'translated_collection/wrapper'
3
+
4
+ describe TranslatedCollection::Wrapper do
5
+ let :upperfn do
6
+ lambda {|elt| elt.upcase }
7
+ end
8
+
9
+ let :lowerfn do
10
+ lambda {|elt| elt.downcase }
11
+ end
12
+
13
+ subject { TranslatedCollection::Wrapper.new(collection, lowerfn, upperfn) }
14
+
15
+ let :collection do
16
+ %w[a b c]
17
+ end
18
+
19
+ let :xlated_collection do
20
+ collection.map {|elt| upperfn.call(elt)}
21
+ end
22
+
23
+ context 'creation' do
24
+ let :collection do
25
+ %w[a b C]
26
+ end
27
+
28
+ context '#new' do
29
+ it 'should assign the same collection internally' do
30
+ subject.instance_variable_get("@collection").__id__.should == collection.__id__
31
+ end
32
+
33
+ it 'should not check conformity by default' do
34
+ expect { subject }.not_to raise_error
35
+ end
36
+
37
+ it 'should raise on creation if conformity check requested and failed' do
38
+ expect { TranslatedCollection::Wrapper.new(collection, lowerfn, upperfn, true) }.
39
+ to raise_error(ArgumentError)
40
+ end
41
+ end
42
+
43
+ it 'should make the same collection available via #collection' do
44
+ subject.collection.__id__.should == collection.__id__
45
+ end
46
+ end
47
+
48
+ context 'validation' do
49
+ context '#_conforming?' do
50
+ it 'should be true for conforming collection' do
51
+ subject._conforming?.should be_true
52
+ end
53
+
54
+ it 'should be false for non-conforming collection' do
55
+ TranslatedCollection::Wrapper.new(%w[a B C], lowerfn, upperfn)._conforming?.should be_false
56
+ end
57
+ end
58
+
59
+ context '#_make_conforming!' do
60
+ it 'should leave a conforming collection unaltered' do
61
+ oldcoll = subject.collection.dup
62
+ subject._make_conforming!
63
+ subject.collection.should == oldcoll
64
+ end
65
+
66
+ it 'should make a non-conforming collection conform' do
67
+ tcw = TranslatedCollection::Wrapper.new(%w[a B C], lowerfn, upperfn)
68
+ tcw._make_conforming!
69
+ tcw.collection.should == %w[a b c]
70
+ end
71
+
72
+ it 'should preserve collection type' do
73
+ tcw = TranslatedCollection::Wrapper.new(Set.new(%w[a B C]), lowerfn, upperfn)
74
+ tcw._make_conforming!
75
+ tcw.collection.should == Set.new(%w[a b c])
76
+ end
77
+ end
78
+ end
79
+
80
+ context 'reading' do
81
+ context '#[]' do
82
+ it 'should return xlated element at index' do
83
+ subject[0].should == 'A'
84
+ end
85
+ end
86
+
87
+ context '#fetch' do
88
+ it 'should return xlated element on hit' do
89
+ subject.fetch(1).should == 'B'
90
+ end
91
+
92
+ it 'should return xlated default element on miss' do
93
+ subject.fetch(99, 'x').should == 'X'
94
+ end
95
+ end
96
+
97
+ context '#each' do
98
+ it 'should yield each element in order' do
99
+ res = []
100
+ subject.each {|elt| res << elt}
101
+ res.should == xlated_collection
102
+ end
103
+
104
+ end
105
+
106
+ context '#map' do
107
+ it 'should yield each element in order, and return all' do
108
+ subject.map {|elt| elt+elt}.to_a.should == %w[AA BB CC]
109
+ end
110
+ end
111
+
112
+ context '#include?' do
113
+ it 'should indicate that the translated form is present' do
114
+ subject.should include('A')
115
+ end
116
+
117
+ it 'should indicate that the un-translated form is NOT present' do
118
+ subject.should_not include('a')
119
+ end
120
+ end
121
+
122
+
123
+ end
124
+
125
+ context 'updating and returning a new collection' do
126
+ context '#reject' do
127
+ let :postreject do
128
+ subject.reject {|x| x.to_i % 2 == 0 }
129
+ end
130
+
131
+ it 'should remove elements from new collection' do
132
+ postreject.to_a.should_not == subject.to_a
133
+ end
134
+
135
+ it 'should return a new collection' do
136
+ postreject.__id__.should_not == subject.__id__
137
+ end
138
+
139
+ it 'should not alter old collection' do
140
+ expect { postreject }.not_to change { subject.to_a }
141
+ end
142
+ end
143
+ end
144
+
145
+ context 'destructive updates' do
146
+ context '#clear' do
147
+ it 'should return same class on clear' do
148
+ subject.clear.should be_a(described_class)
149
+ end
150
+
151
+ it 'should be empty after clear' do
152
+ expect { subject.clear }.not_to change { subject.__id__ }
153
+ end
154
+ end
155
+
156
+ context '#reject!' do
157
+ let :postreject do
158
+ subject.reject! {|x| x.to_i % 2 == 0 }
159
+ end
160
+
161
+ it 'should remove elements from original' do
162
+ expect { postreject }.to change { subject.to_a }
163
+ end
164
+
165
+ it 'should not return a new collection' do
166
+ postreject.__id__.should == subject.__id__
167
+ end
168
+
169
+ it 'should remove the correct elements' do
170
+ postreject.to_a.should == []
171
+ end
172
+
173
+ it 'should return nil if no changes were made' do
174
+ subject.reject! {|x| x == Object.new }.should be_nil
175
+ end
176
+ end
177
+ end
178
+
179
+ context 'Enumerable' do
180
+ let :collection do
181
+ %w[a b c d e f g h i j k l]
182
+ end
183
+
184
+ context 'methods with optional block' do
185
+ context '#drop_while' do
186
+ it 'should invoke condition block on translated-out values' do
187
+ subject.drop_while {|x| x < 'C'}.to_a.should == %w[C D E F G H I J K L]
188
+ end
189
+ end
190
+
191
+ context '#map' do
192
+ it 'should invoke map block on translated-out values' do
193
+ subject.map {|x| x * 3}.to_a.should == %w[AAA BBB CCC DDD EEE FFF GGG HHH III JJJ KKK LLL]
194
+ end
195
+ end
196
+
197
+ context '#collect' do
198
+ it 'should invoke collect block on translated-out values' do
199
+ subject.collect {|x| x * 3}.to_a.should == %w[AAA BBB CCC DDD EEE FFF GGG HHH III JJJ KKK LLL]
200
+ end
201
+ end
202
+
203
+ context '#find_all' do
204
+ it 'should invoke condition block on translated-out values' do
205
+ subject.find_all {|x| x.to_i % 2 == 0 }.to_a.should == %w[A B C D E F G H I J K L]
206
+ end
207
+ end
208
+
209
+ context '#sort' do
210
+ let :collection do
211
+ (-5..0).to_a.reverse
212
+ end
213
+
214
+ subject do
215
+ TranslatedCollection::Wrapper.new(collection,
216
+ Proc.new {|x| -(x.abs)},
217
+ Proc.new {|x| x.abs})
218
+ end
219
+
220
+ it 'should return a wrapped array by default' do
221
+ subject.sort.should be_a_kind_of(TranslatedCollection::Wrapper)
222
+ end
223
+
224
+ it 'should return a bare array if configured' do
225
+ subject.wrap_results = false
226
+ subject.sort.class.should == Array
227
+ end
228
+
229
+ it 'should sort elements by comparison on translated-out elements' do
230
+ subject.sort.to_a.should == (0..5).to_a
231
+ end
232
+
233
+ it 'should sort elements by comparison on translated-out elements' do
234
+ subject.sort.collection.should == (-5..0).to_a.reverse
235
+ end
236
+ end
237
+
238
+ context '#sort_by' do
239
+ let :collection do
240
+ (-5..0).to_a.reverse
241
+ end
242
+
243
+ subject do
244
+ TranslatedCollection::Wrapper.new(collection,
245
+ Proc.new {|x| x-1},
246
+ Proc.new {|x| x+1})
247
+ end
248
+
249
+ it 'should sort elements by comparison on translated-out elements' do
250
+ subject.sort_by(&:abs).to_a.should == [0, 1, -1, -2, -3, -4]
251
+ end
252
+
253
+ it 'should sort elements by comparison on translated-out elements' do
254
+ subject.sort_by(&:abs).collection.should == [-1, 0, -2, -3, -4, -5]
255
+ end
256
+ end
257
+
258
+ context '#take_while' do
259
+ it 'should evaluate condition on translated-out elements' do
260
+ subject.take_while {|x| x <= 'C'}.to_a.should == %w[A B C]
261
+ end
262
+
263
+ it 'should internally store the non-translated elements' do
264
+ subject.take_while {|x| x <= 'C'}.collection.should == %w[a b c]
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+
271
+ context 'copying' do
272
+ context '#clone' do
273
+ it 'should return a new instance of itself'do
274
+ subject.clone.__id__.should_not == subject.__id__
275
+ end
276
+
277
+ it 'should wrap a copy of the collection' do
278
+ coll_id = subject.collection.__id__
279
+ subject.clone.collection.__id__.should_not == coll_id
280
+ end
281
+
282
+ it 'should keep the translated-in versions of elements in the copy' do
283
+ subject.clone.collection.should == subject.collection
284
+ end
285
+
286
+ it 'should preserve frozen status' do
287
+ subject.freeze
288
+ copy = subject.clone
289
+
290
+ copy.should be_frozen
291
+ copy.collection.should be_frozen
292
+ end
293
+ end
294
+
295
+
296
+ context '#dup' do
297
+ it 'should return a new instance of itself'do
298
+ subject.dup.__id__.should_not == subject.__id__
299
+ end
300
+
301
+ it 'should wrap a copy of the collection' do
302
+ coll_id = subject.collection.__id__
303
+ subject.dup.collection.__id__.should_not == coll_id
304
+ end
305
+
306
+ it 'should keep the translated-in versions of elements in the copy' do
307
+ subject.dup.collection.should == subject.collection
308
+ end
309
+
310
+ it 'should not preserve frozen status' do
311
+ subject.freeze
312
+ copy = subject.dup
313
+
314
+ copy.should_not be_frozen
315
+ copy.collection.should_not be_frozen
316
+ end
317
+ end
318
+ end
319
+
320
+ context 'introspection' do
321
+ it 'should claim to be an instance of proxied collection if Array' do
322
+ subject.should be_a_kind_of(Array)
323
+ subject.should_not be_a_kind_of(Set)
324
+ end
325
+
326
+ it 'should claim to be an instance of proxied collection if Set' do
327
+ wrapped = TranslatedCollection::Wrapper.new(Set.new(%w[a b c]), lowerfn, upperfn)
328
+ wrapped.should be_a_kind_of(Set)
329
+ wrapped.should_not be_a_kind_of(Array)
330
+ end
331
+ end
332
+
333
+ context 'observation' do
334
+ let :state do
335
+ Struct.new(:count, :events) do
336
+ def observeit(object, event, *args)
337
+ self.count += 1
338
+ self.events << event
339
+ end
340
+ end.new(0, [])
341
+ end
342
+
343
+ before do
344
+ subject.add_observer(state, :observeit)
345
+ end
346
+
347
+ context '#[]=' do
348
+ it 'should trigger update' do
349
+ expect { subject[5] = 'foo' }.
350
+ to change { state.count }.by(1)
351
+ end
352
+
353
+ it 'should add event to list' do
354
+ expect { subject[5] = 'foo' }.
355
+ to change { state.events }.from([]).to([:set])
356
+ end
357
+ end
358
+
359
+ context '#clear' do
360
+ it 'should trigger update' do
361
+ expect { subject.clear}.
362
+ to change { state.count }.by(1)
363
+ end
364
+
365
+ it 'should add event to list' do
366
+ expect { subject.clear }.
367
+ to change { state.events }.from([]).to([:clear])
368
+ end
369
+ end
370
+
371
+ context '#delete' do
372
+ it 'should trigger update' do
373
+ expect { subject.delete('a') }.
374
+ to change { state.count }.by(1)
375
+ end
376
+
377
+ it 'should add event to list' do
378
+ expect { subject.delete('a') }.
379
+ to change { state.events }.from([]).to([:delete])
380
+ end
381
+ end
382
+
383
+ context '#delete_at' do
384
+ it 'should trigger update' do
385
+ expect { subject.delete_at(0) }.
386
+ to change { state.count }.by(1)
387
+ end
388
+
389
+ it 'should add event to list' do
390
+ expect { subject.delete_at(0) }.
391
+ to change { state.events }.from([]).to([:delete])
392
+ end
393
+ end
394
+
395
+ context '#<<' do
396
+ it 'should trigger update' do
397
+ expect { subject << 'foo' }.
398
+ to change { state.count }.by(1)
399
+ end
400
+
401
+ it 'should add event to list' do
402
+ expect { subject << 'foo' }.
403
+ to change { state.events }.from([]).to([:push])
404
+ end
405
+ end
406
+
407
+ context '#push' do
408
+ it 'should trigger update' do
409
+ expect { subject.push 'foo' }.
410
+ to change { state.count }.by(1)
411
+ end
412
+
413
+ it 'should add event to list' do
414
+ expect { subject.push 'foo' }.
415
+ to change { state.events }.from([]).to([:push])
416
+ end
417
+ end
418
+
419
+ context '#pop' do
420
+ it 'should trigger update' do
421
+ expect { subject.pop }.
422
+ to change { state.count }.by(1)
423
+ end
424
+
425
+ it 'should add event to list' do
426
+ expect { subject.pop }.
427
+ to change { state.events }.from([]).to([:pop])
428
+ end
429
+ end
430
+
431
+ context '[]' do
432
+ it 'should not trigger update' do
433
+ expect { subject[0] }.
434
+ not_to change { state.count }.by(1)
435
+ end
436
+ end
437
+ end
438
+
439
+ end
@@ -0,0 +1,20 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ if ENV['COVERAGE'] == 'true'
5
+ require 'simplecov'
6
+ SimpleCov.start
7
+ end
8
+
9
+ require 'yaml'
10
+ require 'active_record'
11
+ configs = YAML.load(File.read("config/database.yml.example"))
12
+ ActiveRecord::Base.establish_connection(configs['test'])
13
+
14
+ require 'translated_collection'
15
+
16
+ begin
17
+ require 'pry'
18
+ rescue LoadError
19
+ #
20
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'translated_collection/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ # coding: utf-8
8
+ spec.name = "translated_collection"
9
+ spec.version = TranslatedCollection::VERSION
10
+ spec.authors = ["Robert Sanders"]
11
+ spec.email = ["robert@curioussquid.com"]
12
+ spec.summary = %q{Transparently wrap a collection with mapping functions applied at item insertion/retrieval.}
13
+ spec.description = %q{Utility class for (somewhat) transparently wrapping a collection with mapping functions applied as values are added to and read from the collection. Especially useful for ActiveRecord serialized or PG Array fields}
14
+ spec.homepage = "http://github.com/rsanders/translated_collection"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features|gemfiles|coverage)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ # for testing with ActiveRecord serialized and PG Array attributes
23
+ spec.add_development_dependency "activerecord", '~> 4.0', '>= 4.0.0'
24
+ spec.add_development_dependency "pg", '~> 0.17', '>= 0.17.1'
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.7"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "~> 2.14"
29
+ spec.add_development_dependency "simplecov", '~> 0.9'
30
+ spec.add_development_dependency "wwtd", '~> 0.7'
31
+ end