active_hash 0.6.1

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.
@@ -0,0 +1,16 @@
1
+ module ActiveYaml
2
+
3
+ class Base < ActiveFile::Base
4
+ class << self
5
+ def load_file
6
+ YAML.load_file(full_path)
7
+ end
8
+
9
+ def extension
10
+ "yml"
11
+ end
12
+
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,265 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ActiveFile::Base do
4
+ before do
5
+ class Country < ActiveFile::Base
6
+ end
7
+ end
8
+
9
+ after do
10
+ Object.send :remove_const, :Country
11
+ end
12
+
13
+ describe ".reload_active_file" do
14
+
15
+ it "returns false by default" do
16
+ Country.reload_active_file.should be_nil
17
+ end
18
+
19
+ end
20
+
21
+ describe ".filename=" do
22
+ before do
23
+ Country.filename = "foo-izzle"
24
+
25
+ class Bar < ActiveFile::Base
26
+ self.filename = "bar-izzle"
27
+ end
28
+ end
29
+
30
+ it "sets the filename on a per-subclass basis" do
31
+ Country.filename.should == "foo-izzle"
32
+ Bar.filename.should == "bar-izzle"
33
+ end
34
+ end
35
+
36
+ describe ".set_filename" do
37
+ before do
38
+ Country.set_filename "foo-izzle"
39
+
40
+ class Bar < ActiveFile::Base
41
+ set_filename "bar-izzle"
42
+ end
43
+ end
44
+
45
+ it "sets the filename on a per-subclass basis" do
46
+ Country.filename.should == "foo-izzle"
47
+ Bar.filename.should == "bar-izzle"
48
+ end
49
+ end
50
+
51
+ describe ".root_path=" do
52
+ before do
53
+ Country.root_path = "foo-izzle"
54
+
55
+ class Bar < ActiveFile::Base
56
+ self.root_path = "bar-izzle"
57
+ end
58
+ end
59
+
60
+ it "sets the root_path on a per-subclass basis" do
61
+ Country.root_path.should == "foo-izzle"
62
+ Bar.root_path.should == "bar-izzle"
63
+ end
64
+ end
65
+
66
+ describe ".set_root_path" do
67
+ before do
68
+ Country.set_root_path "foo-izzle"
69
+
70
+ class Bar < ActiveFile::Base
71
+ set_root_path "bar-izzle"
72
+ end
73
+ end
74
+
75
+ it "sets the root_path on a per-subclass basis" do
76
+ Country.root_path.should == "foo-izzle"
77
+ Bar.root_path.should == "bar-izzle"
78
+ end
79
+ end
80
+
81
+ describe ".full_path" do
82
+ it "defaults to the directory of the calling file" do
83
+ class Country
84
+ def self.extension() "foo" end
85
+ end
86
+
87
+ Country.full_path.should == "#{Dir.pwd}/countries.foo"
88
+ end
89
+ end
90
+
91
+ describe ".all" do
92
+ before do
93
+ class MyClass
94
+ end
95
+ end
96
+
97
+ it "loads the data from the load_file method" do
98
+ class Country
99
+ class << self
100
+ def extension
101
+ "myfile"
102
+ end
103
+
104
+ def load_file
105
+ MyClass.load_file(full_path)
106
+ end
107
+ end
108
+ end
109
+
110
+ Country.reload_active_file = true
111
+ File.stub!(:mtime).and_return(1234)
112
+ MyClass.should_receive(:load_file).and_return([{:id => 1}, {:id => 2}, {:id => 3}])
113
+
114
+ records = Country.all
115
+ records.length.should == 3
116
+ records.should =~ [Country.new(:id => 1), Country.new(:id => 2), Country.new(:id => 3)]
117
+ end
118
+
119
+ context "with reload=true" do
120
+ it "does not re-fetch the data if the file's mtime has not changed" do
121
+ class Country < ActiveFile::Base
122
+ class << self
123
+ def extension
124
+ "myfile"
125
+ end
126
+
127
+ def load_file
128
+ MyClass.load_file(full_path)
129
+ end
130
+ end
131
+ end
132
+
133
+ Country.reload_active_file = true
134
+ File.stub!(:mtime).and_return(1234)
135
+ MyClass.should_receive(:load_file).once.and_return([{:foo => :bar}])
136
+ Country.all
137
+ Country.all
138
+ end
139
+
140
+ it "does re-fetch the data if the yaml file's mtime has changed" do
141
+ class Country < ActiveFile::Base
142
+ class << self
143
+ def extension
144
+ "myfile"
145
+ end
146
+
147
+ def load_file
148
+ MyClass.load_file(full_path)
149
+ end
150
+ end
151
+ end
152
+
153
+ Country.reload_active_file = true
154
+ MyClass.should_receive(:load_file).twice.and_return([{:foo => :bar}])
155
+
156
+ File.stub!(:mtime).and_return(1234)
157
+ Country.all
158
+
159
+ File.stub!(:mtime).and_return(3456)
160
+ Country.all
161
+ end
162
+ end
163
+
164
+ context "with reload=false" do
165
+ it "does not re-fetch the data after the first call to .all" do
166
+ class Country < ActiveFile::Base
167
+ class << self
168
+ def extension
169
+ "myfile"
170
+ end
171
+
172
+ def load_file
173
+ MyClass.load_file(full_path)
174
+ end
175
+ end
176
+ end
177
+
178
+ File.stub!(:mtime).once.and_return(1234)
179
+ MyClass.should_receive(:load_file).once.and_return([{:foo => :bar}])
180
+ Country.all
181
+
182
+ Country.all
183
+ end
184
+
185
+ it "does not re-fetch the data if the yaml file's mtime has changed" do
186
+ class Country < ActiveFile::Base
187
+ class << self
188
+ def extension
189
+ "myfile"
190
+ end
191
+
192
+ def load_file
193
+ MyClass.load_file(full_path)
194
+ end
195
+ end
196
+ end
197
+
198
+ MyClass.should_receive(:load_file).once.and_return([{:foo => :bar}])
199
+
200
+ File.stub!(:mtime).and_return(1234)
201
+ Country.all
202
+
203
+ File.stub!(:mtime).and_return(3456)
204
+ Country.all
205
+ end
206
+
207
+ it "does not fetch data if data has been set to non-nil" do
208
+ class Country < ActiveFile::Base
209
+ class << self
210
+ def extension
211
+ "myfile"
212
+ end
213
+
214
+ def load_file
215
+ MyClass.load_file(full_path)
216
+ end
217
+ end
218
+ end
219
+
220
+ MyClass.should_not_receive(:load_file)
221
+ Country.data = [{:foo => :bar}]
222
+ Country.all
223
+ end
224
+
225
+ it "does not fetch data if data has been set to nil" do
226
+ class Country < ActiveFile::Base
227
+ class << self
228
+ def extension
229
+ "myfile"
230
+ end
231
+
232
+ def load_file
233
+ MyClass.load_file(full_path)
234
+ end
235
+ end
236
+ end
237
+
238
+ MyClass.should_not_receive(:load_file)
239
+ Country.data = nil
240
+ Country.all
241
+ end
242
+
243
+ it "does re-fetch data if force is true" do
244
+ class Country < ActiveFile::Base
245
+ class << self
246
+ def extension
247
+ "myfile"
248
+ end
249
+
250
+ def load_file
251
+ MyClass.load_file(full_path)
252
+ end
253
+ end
254
+ end
255
+
256
+ MyClass.should_receive(:load_file).once.and_return([{:foo => :bar}])
257
+ Country.data = nil
258
+ Country.reload(true)
259
+ Country.all.first.foo.should == :bar
260
+ end
261
+
262
+ end
263
+ end
264
+
265
+ end
@@ -0,0 +1,541 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe ActiveHash, "Base" do
4
+
5
+ before do
6
+ class Country < ActiveHash::Base
7
+ end
8
+ end
9
+
10
+ after do
11
+ Object.send :remove_const, :Country
12
+ end
13
+
14
+ describe ".fields" do
15
+ before do
16
+ Country.fields :name, :iso_name
17
+ end
18
+
19
+ it "defines a reader for each field" do
20
+ Country.new.should respond_to(:name)
21
+ Country.new.should respond_to(:iso_name)
22
+ end
23
+
24
+ it "defines interrogator methods for each field" do
25
+ Country.new.should respond_to(:name?)
26
+ Country.new.should respond_to(:iso_name?)
27
+ end
28
+
29
+ it "defines single finder methods for each field" do
30
+ Country.should respond_to(:find_by_name)
31
+ Country.should respond_to(:find_by_iso_name)
32
+ end
33
+
34
+ it "defines array finder methods for each field" do
35
+ Country.should respond_to(:find_all_by_name)
36
+ Country.should respond_to(:find_all_by_iso_name)
37
+ end
38
+
39
+ it "defines single finder methods for all combinations of fields" do
40
+ Country.should respond_to(:find_by_name_and_iso_name)
41
+ Country.should respond_to(:find_by_iso_name_and_name)
42
+ end
43
+
44
+ it "defines array finder methods for all combinations of fields" do
45
+ Country.should respond_to(:find_all_by_name_and_iso_name)
46
+ Country.should respond_to(:find_all_by_iso_name_and_name)
47
+ end
48
+ end
49
+
50
+ describe ".data=" do
51
+ before do
52
+ class Region < ActiveHash::Base
53
+ field :description
54
+ end
55
+ end
56
+
57
+ it "populates the object with data" do
58
+ Country.data = [{:name => "US"}, {:name => "Canada"}]
59
+ Country.data.should == [{:name => "US"}, {:name => "Canada"}]
60
+ end
61
+
62
+ it "allows each of it's subclasses to have it's own data" do
63
+ Country.data = [{:name => "US"}, {:name => "Canada"}]
64
+ Region.data = [{:description => "A big region"}, {:description => "A remote region"}]
65
+
66
+ Country.data.should == [{:name => "US"}, {:name => "Canada"}]
67
+ Region.data.should == [{:description => "A big region"}, {:description => "A remote region"}]
68
+ end
69
+ end
70
+
71
+ describe ".all" do
72
+ before do
73
+ Country.field :name
74
+ Country.data = [
75
+ {:id => 1, :name => "US"},
76
+ {:id => 2, :name => "Canada"}
77
+ ]
78
+ end
79
+
80
+ it "returns an empty array if data is nil" do
81
+ Country.data = nil
82
+ Country.all.should be_empty
83
+ end
84
+
85
+ it "returns all data as inflated objects" do
86
+ Country.all.all?{|country| country.should be_kind_of(Country)}
87
+ end
88
+
89
+ it "populates the data correctly" do
90
+ records = Country.all
91
+ records.first.id.should == 1
92
+ records.first.name.should == "US"
93
+ records.last.id.should == 2
94
+ records.last.name.should == "Canada"
95
+ end
96
+
97
+ it "re-populates the records after data= is called" do
98
+ Country.data = [
99
+ {:id => 45, :name => "Canada"}
100
+ ]
101
+ records = Country.all
102
+ records.first.id.should == 45
103
+ records.first.name.should == "Canada"
104
+ records.length.should == 1
105
+ end
106
+ end
107
+
108
+ describe ".count" do
109
+ before do
110
+ Country.data = [
111
+ {:id => 1, :name => "US"},
112
+ {:id => 2, :name => "Canada"}
113
+ ]
114
+ end
115
+
116
+ it "returns the number of elements in the array" do
117
+ Country.count.should == 2
118
+ end
119
+ end
120
+
121
+ describe ".first" do
122
+ before do
123
+ Country.data = [
124
+ {:id => 1, :name => "US"},
125
+ {:id => 2, :name => "Canada"}
126
+ ]
127
+ end
128
+
129
+ it "returns the first object" do
130
+ Country.first.should == Country.new(:id => 1)
131
+ end
132
+ end
133
+
134
+ describe ".last" do
135
+ before do
136
+ Country.data = [
137
+ {:id => 1, :name => "US"},
138
+ {:id => 2, :name => "Canada"}
139
+ ]
140
+ end
141
+
142
+ it "returns the last object" do
143
+ Country.last.should == Country.new(:id => 2)
144
+ end
145
+ end
146
+
147
+ describe ".find" do
148
+ before do
149
+ Country.data = [
150
+ {:id => 1, :name => "US"},
151
+ {:id => 2, :name => "Canada"}
152
+ ]
153
+ end
154
+
155
+ context "with an id" do
156
+ it "finds the record with the specified id" do
157
+ Country.find(2).id.should == 2
158
+ end
159
+
160
+ it "finds the record with the specified id as a string" do
161
+ Country.find("2").id.should == 2
162
+ end
163
+ end
164
+
165
+ context "with :all" do
166
+ it "returns all records" do
167
+ Country.find(:all).should =~ [Country.new(:id => 1), Country.new(:id => 2)]
168
+ end
169
+ end
170
+
171
+ context "with 2 arguments" do
172
+ it "returns the record with the given id and ignores the conditions" do
173
+ Country.find(1, :conditions => "foo=bar").should == Country.new(:id => 1)
174
+ Country.find(:all, :conditions => "foo=bar").length.should == 2
175
+ end
176
+ end
177
+
178
+ context "with an array of ids" do
179
+ before do
180
+ Country.data = [
181
+ {:id => 1},
182
+ {:id => 2},
183
+ {:id => 3}
184
+ ]
185
+ end
186
+
187
+ it "returns all matching ids" do
188
+ Country.find([1,3]).should =~ [Country.new(:id => 1), Country.new(:id => 3)]
189
+ end
190
+ end
191
+ end
192
+
193
+ describe ".find_by_id" do
194
+ before do
195
+ Country.data = [
196
+ {:id => 1, :name => "US"},
197
+ {:id => 2, :name => "Canada"}
198
+ ]
199
+ end
200
+
201
+ context "with an id" do
202
+ it "finds the record with the specified id" do
203
+ Country.find_by_id(2).id.should == 2
204
+ end
205
+ end
206
+
207
+ context "with nil" do
208
+ it "returns nil" do
209
+ Country.find_by_id(nil).should be_nil
210
+ end
211
+ end
212
+
213
+ context "with an id not present" do
214
+ it "returns nil" do
215
+ Country.find_by_id(4567).should be_nil
216
+ end
217
+ end
218
+ end
219
+
220
+ describe "custom finders" do
221
+ before do
222
+ Country.fields :name, :monarch, :language
223
+
224
+ # Start ids above 4 lest we get nil and think it's an AH::Base model with id=4.
225
+ Country.data = [
226
+ {:id => 11, :name => nil, :monarch => nil, :language => "Latin"},
227
+ {:id => 12, :name => "US", :monarch => nil, :language => "English"},
228
+ {:id => 13, :name => "Canada", :monarch => "The Crown of England", :language => "English"},
229
+ {:id => 14, :name => "UK", :monarch => "The Crown of England", :language => "English"}
230
+ ]
231
+ end
232
+
233
+ describe "find_by_<field_name>" do
234
+ describe "with a match" do
235
+ context "for a non-nil argument" do
236
+ it "returns the first matching record" do
237
+ Country.find_by_name("US").id.should == 12
238
+ end
239
+ end
240
+
241
+ context "for a nil argument" do
242
+ it "returns the first matching record" do
243
+ Country.find_by_name(nil).id.should == 11
244
+ end
245
+ end
246
+ end
247
+
248
+ describe "without a match" do
249
+ before do
250
+ Country.data = []
251
+ end
252
+
253
+ context "for a non-nil argument" do
254
+ it "returns nil" do
255
+ Country.find_by_name("Mexico").should be_nil
256
+ end
257
+ end
258
+
259
+ context "for a nil argument" do
260
+ it "returns nil" do
261
+ Country.find_by_name(nil).should be_nil
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ describe "find_all_by_<field_name>" do
268
+ describe "with matches" do
269
+ it "returns all matching records" do
270
+ countries = Country.find_all_by_monarch("The Crown of England")
271
+ countries.length.should == 2
272
+ countries.first.name.should == "Canada"
273
+ countries.last.name.should == "UK"
274
+ end
275
+ end
276
+
277
+ describe "without matches" do
278
+ it "returns an empty array" do
279
+ Country.find_all_by_name("Mexico").should be_empty
280
+ end
281
+ end
282
+ end
283
+
284
+ describe "find_by_<field_one>_and_<field_two>" do
285
+ describe "with a match" do
286
+ it "returns the first matching record" do
287
+ Country.find_by_name_and_monarch("Canada", "The Crown of England").id.should == 13
288
+ Country.find_by_monarch_and_name("The Crown of England", "Canada").id.should == 13
289
+ end
290
+ end
291
+
292
+ describe "without a match" do
293
+ it "returns nil" do
294
+ Country.find_by_name_and_monarch("US", "The Crown of England").should be_nil
295
+ end
296
+ end
297
+
298
+ describe "for fields the class doesn't have" do
299
+ it "raises a NoMethodError" do
300
+ lambda {
301
+ Country.find_by_name_and_shoe_size("US", 10)
302
+ }.should raise_error(NoMethodError, "undefined method `find_by_name_and_shoe_size' for Country:Class")
303
+ end
304
+ end
305
+ end
306
+
307
+ describe "find_all_by_<field_one>_and_<field_two>" do
308
+ describe "with matches" do
309
+ it "returns all matching records" do
310
+ countries = Country.find_all_by_monarch_and_language("The Crown of England", "English")
311
+ countries.length.should == 2
312
+ countries.first.name.should == "Canada"
313
+ countries.last.name.should == "UK"
314
+ end
315
+ end
316
+
317
+ describe "without matches" do
318
+ it "returns an empty array" do
319
+ Country.find_all_by_monarch_and_language("Shaka Zulu", "Zulu").should be_empty
320
+ end
321
+ end
322
+ end
323
+ end
324
+
325
+
326
+ describe "#attributes" do
327
+ it "returns the hash passed in the initializer" do
328
+ country = Country.new(:foo => :bar)
329
+ country.attributes.should == {:foo => :bar}
330
+ end
331
+
332
+ it "symbolizes keys" do
333
+ country = Country.new("foo" => :bar)
334
+ country.attributes.should == {:foo => :bar}
335
+ end
336
+ end
337
+
338
+ describe "reader methods" do
339
+ context "for regular fields" do
340
+ before do
341
+ Country.fields :name, :iso_name
342
+ end
343
+
344
+ it "returns the given attribute when present" do
345
+ country = Country.new(:name => "Spain")
346
+ country.name.should == "Spain"
347
+ end
348
+
349
+ it "returns nil when not present" do
350
+ country = Country.new
351
+ country.name.should be_nil
352
+ end
353
+ end
354
+
355
+ context "for fields with default values" do
356
+ before do
357
+ Country.field :name, :default => "foobar"
358
+ end
359
+
360
+ it "returns the given attribute when present" do
361
+ country = Country.new(:name => "Spain")
362
+ country.name.should == "Spain"
363
+ end
364
+
365
+ it "returns the default value when not present" do
366
+ country = Country.new
367
+ country.name.should == "foobar"
368
+ end
369
+ end
370
+ end
371
+
372
+ describe "interrogator methods" do
373
+ before do
374
+ Country.fields :name, :iso_name
375
+ end
376
+
377
+ it "returns true if the given attribute is non-blank" do
378
+ country = Country.new(:name => "Spain")
379
+ country.should be_name
380
+ end
381
+
382
+ it "returns false if the given attribute is blank" do
383
+ country = Country.new(:name => " ")
384
+ country.should_not be_name
385
+ end
386
+
387
+ it "returns false if the given attribute was not passed" do
388
+ country = Country.new
389
+ country.should_not be_name
390
+ end
391
+ end
392
+
393
+ describe "#id" do
394
+ context "when passed an id" do
395
+ it "returns the id as an integer" do
396
+ country = Country.new :id => "1"
397
+ country.id.should == 1
398
+ end
399
+ end
400
+ context "when not passed an id" do
401
+ it "returns nil" do
402
+ country = Country.new
403
+ country.id.should be_nil
404
+ end
405
+ end
406
+ end
407
+
408
+ describe "#new_record?" do
409
+ it "should be false" do
410
+ Country.new.should_not be_new_record
411
+ end
412
+ end
413
+
414
+ describe "#quoted_id" do
415
+ it "should return id" do
416
+ Country.new(:id => 2).quoted_id.should == 2
417
+ end
418
+ end
419
+
420
+ describe "#to_param" do
421
+ it "should return id as a string" do
422
+ Country.new(:id => 2).to_param.should == "2"
423
+ end
424
+ end
425
+
426
+ describe "#eql?" do
427
+ before do
428
+ class Region < ActiveHash::Base
429
+ end
430
+ end
431
+
432
+ it "should return true with the same class and id" do
433
+ Country.new(:id => 23).eql?(Country.new(:id => 23)).should be_true
434
+ end
435
+
436
+ it "should return false with the same class and different ids" do
437
+ Country.new(:id => 24).eql?(Country.new(:id => 23)).should be_false
438
+ end
439
+
440
+ it "should return false with the different classes and the same id" do
441
+ Country.new(:id => 23).eql?(Region.new(:id => 23)).should be_false
442
+ end
443
+
444
+ it "returns false when id is nil" do
445
+ Country.new.eql?(Country.new).should be_false
446
+ end
447
+ end
448
+
449
+ describe "#==" do
450
+ before do
451
+ class Region < ActiveHash::Base
452
+ end
453
+ end
454
+
455
+ it "should return true with the same class and id" do
456
+ Country.new(:id => 23).should == Country.new(:id => 23)
457
+ end
458
+
459
+ it "should return false with the same class and different ids" do
460
+ Country.new(:id => 24).should_not == Country.new(:id => 23)
461
+ end
462
+
463
+ it "should return false with the different classes and the same id" do
464
+ Country.new(:id => 23).should_not == Region.new(:id => 23)
465
+ end
466
+
467
+ it "returns false when id is nil" do
468
+ Country.new.should_not == Country.new
469
+ end
470
+ end
471
+
472
+ describe "#hash" do
473
+ it "returns id for hash" do
474
+ Country.new(:id => 45).hash.should == 45.hash
475
+ Country.new.hash.should == nil.hash
476
+ end
477
+
478
+ it "is hashable" do
479
+ { Country.new(:id => 4) => "bar"}.should == {Country.new(:id => 4) => "bar" }
480
+ { Country.new(:id => 3) => "bar"}.should_not == {Country.new(:id => 4) => "bar" }
481
+ end
482
+ end
483
+
484
+ describe "#readonly?" do
485
+ it "returns true" do
486
+ Country.new.should be_readonly
487
+ end
488
+ end
489
+
490
+ describe "auto-discovery of fields" do
491
+ it "dynamically creates fields for all keys in the hash" do
492
+ Country.data = [
493
+ {:field1 => "foo"},
494
+ {:field2 => "bar"},
495
+ {:field3 => "biz"}
496
+ ]
497
+
498
+ Country.all
499
+
500
+ [:field1, :field2, :field3].each do |field|
501
+ Country.should respond_to("find_by_#{field}")
502
+ Country.should respond_to("find_all_by_#{field}")
503
+ Country.new.should respond_to(field)
504
+ Country.new.should respond_to("#{field}?")
505
+ end
506
+ end
507
+
508
+ it "doesn't override methods already defined" do
509
+ Country.class_eval do
510
+ class << self
511
+ def find_by_name(name)
512
+ "find_by_name defined manually"
513
+ end
514
+
515
+ def find_all_by_name(name)
516
+ "find_all_by_name defined manually"
517
+ end
518
+ end
519
+
520
+ def name
521
+ "name defined manually"
522
+ end
523
+
524
+ def name?
525
+ "name? defined manually"
526
+ end
527
+ end
528
+
529
+ Country.data = [
530
+ {:name => "foo"}
531
+ ]
532
+
533
+ Country.all
534
+ Country.find_by_name("foo").should == "find_by_name defined manually"
535
+ Country.find_all_by_name("foo").should == "find_all_by_name defined manually"
536
+ Country.new.name.should == "name defined manually"
537
+ Country.new.name?.should == "name? defined manually"
538
+ end
539
+ end
540
+
541
+ end