translated_collection 0.1.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.
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