datamapper-shim 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1258 @@
1
+ require "logger"
2
+ require "minitest/autorun"
3
+ require "minitest/pride"
4
+ require "pry"
5
+
6
+ require "data_mapper/shim"
7
+
8
+ describe DataMapper::Shim do
9
+
10
+ # Hide deprecation notice, but figure out why first
11
+ I18n.enforce_available_locales = false
12
+
13
+ # Use in-memory database for easy setup
14
+ ActiveRecord::Base.establish_connection :adapter => "sqlite3", :database => ":memory:"
15
+
16
+ # Migrations
17
+ ActiveRecord::Migration.create_table :assets do |t|
18
+ t.references :owner
19
+ end
20
+
21
+ ActiveRecord::Migration.create_table :avatars do |t|
22
+ t.string :filename
23
+ t.references :user
24
+ end
25
+
26
+ ActiveRecord::Migration.create_table :callbackers do |t|
27
+ end
28
+
29
+ ActiveRecord::Migration.create_table :channels do |t|
30
+ t.string :name
31
+ t.integer :state
32
+ end
33
+
34
+ ActiveRecord::Migration.create_table :channels_users do |t|
35
+ t.references :user
36
+ t.references :channel
37
+ end
38
+
39
+ ActiveRecord::Migration.create_table :handles do |t|
40
+ t.string :type
41
+ t.string :value
42
+ t.references :user
43
+ end
44
+
45
+ ActiveRecord::Migration.create_table :messages do |t|
46
+ t.text :body
47
+ t.references :user
48
+ t.datetime :deleted_at
49
+ t.timestamps
50
+ end
51
+
52
+ ActiveRecord::Migration.create_table :users do |t|
53
+ t.string :alias
54
+ t.string :email
55
+ t.boolean :admin
56
+ t.timestamps
57
+ end
58
+
59
+ ActiveRecord::Migration.create_table :validators do |t|
60
+ t.string :value
61
+ t.string :value_confirmation
62
+ t.boolean :flag
63
+ end
64
+
65
+ ActiveRecord::Base.connection.execute("ALTER TABLE users ADD COLUMN active TINYINT(4)")
66
+
67
+ ActiveRecord::Migration.create_table :friendships do |t|
68
+ t.references :user
69
+ t.references :friend
70
+ end
71
+
72
+ ActiveRecord::Migration.create_table :typecasters do |t|
73
+ t.string :list
74
+ t.string :json
75
+ t.string :yaml
76
+ t.integer :enum
77
+ t.datetime :datetime
78
+ end
79
+
80
+ # DataMapper creates boolean fields with TINYINT(4) type
81
+ # instead of the more usual TINYINT(1)
82
+ ActiveRecord::Base.connection.execute("ALTER TABLE typecasters ADD COLUMN boolean TINYINT(4)")
83
+
84
+ # Enemies table
85
+ ActiveRecord::Migration.create_table :users_users do |t|
86
+ t.references :user
87
+ t.references :enemy
88
+ end
89
+
90
+ # Fixture classes
91
+ class Asset < ActiveRecord::Base
92
+ include DataMapper::Shim
93
+
94
+ belongs_to :user, :child_key => :owner_id
95
+
96
+ before_destroy do
97
+ raise ActiveRecord::DeleteRestrictionError.new(self.class.name)
98
+ end
99
+ end
100
+
101
+ class Avatar < ActiveRecord::Base
102
+ include DataMapper::Shim
103
+
104
+ belongs_to :user, :nullable => false, :autosave => true
105
+ end
106
+
107
+ class Channel < ActiveRecord::Base
108
+ include DataMapper::Shim
109
+
110
+ property :name, String, :format => /\A#.*\Z/, :key => true
111
+ property :state, Enum[:archived, :open], :default => :open, :nullable => false
112
+
113
+ has n, :users, :through => Resource
114
+ has n, :assets, :child_key => :owner_id, :constraint => :destroy!
115
+ end
116
+
117
+ class Friendship < ActiveRecord::Base
118
+ include DataMapper::Shim
119
+
120
+ property :user_id, Integer, :key => true
121
+ property :friend_id, Integer, :key => true
122
+
123
+ belongs_to :user
124
+ belongs_to :friend, :class_name => "User"
125
+
126
+ # Reciprocate friendship
127
+ after_create do
128
+ unless Friendship.where(:user_id => friend.id, :friend_id => user.id).first
129
+ Friendship.create(:user_id => friend.id, :friend_id => user.id)
130
+ end
131
+ end
132
+ end
133
+
134
+ class Message < ActiveRecord::Base
135
+ include DataMapper::Shim
136
+
137
+ property :deleted_at, ParanoidDateTime, :writer => :protected
138
+ property :body, Text
139
+
140
+ belongs_to :user
141
+ end
142
+
143
+ class Typecaster < ActiveRecord::Base
144
+ include DataMapper::Shim
145
+
146
+ property :list, List
147
+ property :json, Json
148
+ property :yaml, Yaml
149
+ property :boolean, Boolean
150
+ property :enum, Enum[:pending, :active]
151
+ property :datetime, DateTime
152
+ end
153
+
154
+ class Handle < ActiveRecord::Base
155
+ belongs_to :user, :inverse_of => :pseudonyms
156
+ end
157
+
158
+ class User < ActiveRecord::Base
159
+ include DataMapper::Shim
160
+
161
+ property :email, String, :length => 50, :unique => true, :format => :email_address
162
+ property :active, Boolean, :default => true, :nullable => false
163
+ property :alias, String, :reader => :private, :size => 10
164
+ property :admin, Boolean, :default => false, :writer => :private
165
+
166
+ # One-to-one relationships
167
+ has 1, :avatar, :constraint => :destroy
168
+
169
+ # One-to-many relationships
170
+ has n, :messages, :order => "body", :constraint => :set_nil
171
+ has n, :texts, :class_name => "Message"
172
+ has n, :assets, :child_key => :owner_id, :constraint => :protect
173
+ has n, :pseudonyms, :class_name => "Handle", :inverse_of => :user
174
+
175
+ # Many-to-many relationships
176
+ has n, :friendships, :constraint => :skip
177
+ has n, :friends, :through => :friendships, :mutable => true
178
+ has n, :enemies, :through => Resource, :class_name => "User", :association_foreign_key => :enemy_id, :constraint => :destroy
179
+ has n, :channels, :through => Resource
180
+ end
181
+
182
+ # Wrap tests in a transaction
183
+ def run
184
+ ActiveRecord::Base.transaction do
185
+ @result = super
186
+ raise ActiveRecord::Rollback
187
+ end
188
+ @result
189
+ end
190
+
191
+ # Fixtures
192
+ before do
193
+ @user = User.create :email => "steve@apple.com"
194
+ @avatar = Avatar.create :filename => "steve.png"
195
+
196
+ @message = Message.create :body => "Be hungry. Be foolish."
197
+ @asset = Asset.create
198
+ @friend = User.create :email => "woz@apple.com"
199
+ @enemy = User.create :email => "eric@google.com"
200
+ @channel = Channel.create :name => "#general"
201
+ end
202
+
203
+ describe "relationships" do
204
+
205
+ it "raises on leftover options" do
206
+ proc do
207
+ class User
208
+ has 1, :outfit, :turtleneck => true
209
+ end
210
+ end.must_raise NotImplementedError
211
+ end
212
+
213
+ it "can set up a one-to-one relationship" do
214
+ @user.avatar.must_be_nil
215
+
216
+ @user.avatar = @avatar
217
+ @user.save!
218
+
219
+ @avatar.user.must_equal @user
220
+ end
221
+
222
+ it "can set up a one-to-many relationship" do
223
+ @user.messages.must_be_empty
224
+
225
+ @user.messages = [@message]
226
+ @user.save!
227
+
228
+ @user.messages.must_equal [@message]
229
+ @message.user.must_equal @user
230
+ end
231
+
232
+ it "can set up a many-to-many relationship (with a join table model)" do
233
+ @user.friends.must_be_empty
234
+
235
+ @user.friends = [@friend]
236
+ @user.save!
237
+
238
+ @friend.friends.must_equal [@user]
239
+ end
240
+
241
+ it "can set up a many-to-many relationship (with no join table model)" do
242
+ @user.enemies.must_be_empty
243
+
244
+ @user.enemies = [@enemy]
245
+ @user.save!
246
+
247
+ result = User.connection.execute("SELECT enemy_id, user_id FROM users_users").first
248
+ result["user_id"].must_equal @user.id
249
+ result["enemy_id"].must_equal @enemy.id
250
+ end
251
+
252
+ it "can use a custom class name" do
253
+ @user.messages << @message
254
+
255
+ @user.texts.must_equal [@message]
256
+ end
257
+
258
+ it "can use a custom child key" do
259
+ @user.assets.must_be_empty
260
+
261
+ @user.assets = [@asset]
262
+ @user.save!
263
+
264
+ @user.assets.must_equal [@asset]
265
+ @asset.user.must_equal @user
266
+ end
267
+
268
+ it "can order relationships" do
269
+ %w(3 2 1).map do |n|
270
+ @user.messages << Message.create(:body => n)
271
+ end
272
+
273
+ messages = @user.messages
274
+ messages.must_equal Message.where(:user_id => @user.id).order(:body).to_a
275
+ messages.wont_equal Message.where(:user_id => @user.id).to_a
276
+ end
277
+
278
+ it "can require a parent object" do
279
+ @avatar.wont_be :valid?
280
+ @avatar.errors[:user].must_match /can't be blank/
281
+ end
282
+
283
+ it "can autosave a parent" do
284
+ @avatar.user = @user
285
+ @user.email = "zuck@facebook.com"
286
+
287
+ @avatar.save
288
+ @user.reload.email.must_equal "zuck@facebook.com"
289
+ end
290
+
291
+ it "avoids extra queries with :inverse_of" do
292
+ @user.pseudonyms << Handle.create(:type => "twitter", :value => "@n")
293
+ @user.save
294
+
295
+ @user.pseudonyms.first.user.object_id.must_equal @user.object_id
296
+ end
297
+
298
+ end
299
+
300
+ describe "restrictive relationships" do
301
+
302
+ it "stops a parent's deletion with :constraint => :protect" do
303
+ @user.assets << @asset
304
+ @user.destroy.must_equal false
305
+
306
+ @user.wont_be :destroyed?
307
+ end
308
+
309
+ it "destroys children with :constraint => :destroy" do
310
+ @user.avatar = @avatar
311
+ @user.destroy
312
+
313
+ @avatar.must_be :destroyed?
314
+ end
315
+
316
+ it "destroys children without instantiating them with :constraint => :destroy!" do
317
+ @channel.assets << @asset
318
+ @channel.destroy
319
+
320
+ @channel.must_be :destroyed?
321
+ proc { @asset.reload }.must_raise ActiveRecord::RecordNotFound
322
+ end
323
+
324
+ it "nils out children with :constraint => :set_nil" do
325
+ @user.messages << @message
326
+ @message.user.wont_be_nil
327
+
328
+ @user.destroy
329
+
330
+ @message.reload.user.must_be_nil
331
+ end
332
+
333
+ it "leaves the children alone with :constraint => :skip" do
334
+ @user.friends << @friend
335
+ friendships = @user.friendships
336
+ @user.destroy
337
+
338
+ @user.must_be :destroyed?
339
+ friendships.each { |friendship| friendship.wont_be :destroyed? }
340
+ end
341
+
342
+ it "warns that many-to-many relationships without a join table model can't stop a parent's deletion" do
343
+ proc do
344
+ class User
345
+ has n, :channels, :through => Resource, :constraint => :protect
346
+ end
347
+ end.must_output nil, /ActiveRecord's HABTM doesn't support :dependent => :restrict/
348
+ end
349
+
350
+ it "destroys children with :constraint => :destroy for many-to-many relationships without a join table model" do
351
+ @user.enemies << @enemy
352
+ @user.destroy
353
+
354
+ @user.must_be :destroyed?
355
+ @enemy.must_be :destroyed?
356
+ end
357
+
358
+ end
359
+
360
+ describe "finders" do
361
+
362
+ it "returns all rows for a query" do
363
+ User.all.must_equal [@user, @friend, @enemy]
364
+ User.all(:id => @user.id).must_equal [@user]
365
+ end
366
+
367
+ it "returns the first row for a query" do
368
+ User.first.must_equal @user
369
+ User.first(:id => @friend.id).must_equal @friend
370
+ end
371
+
372
+ it "returns gets a row by id" do
373
+ User.get(@user.id).must_equal @user
374
+ User.get(0).must_equal nil
375
+ end
376
+
377
+ end
378
+
379
+ describe "miscellaneous helpers" do
380
+
381
+ it "can set an attribute" do
382
+ email = "cook@apple.com"
383
+
384
+ @user.attribute_set(:email, email)
385
+ @user.email.must_equal email
386
+ end
387
+
388
+ it "can get an attribute" do
389
+ @user.attribute_get(:email).must_equal @user.email
390
+ end
391
+
392
+ it "can tell if an attribute has been modified" do
393
+ @user.email = nil
394
+ @user.attribute_dirty?(:email).must_equal true
395
+ end
396
+
397
+ it "can tell if an instance has been modified" do
398
+ @user.email = nil
399
+ @user.must_be :dirty?
400
+ end
401
+
402
+ it "can return original values for changed attributes" do
403
+ original_email = @user.email
404
+ @user.email = nil
405
+ @user.original_values[:email].must_equal original_email
406
+ end
407
+
408
+ it "can delete all instances in a relation" do
409
+ @user.friends << @friend
410
+ @user.friends.destroy!
411
+
412
+ proc { @friend.reload }.must_raise ActiveRecord::RecordNotFound
413
+ end
414
+
415
+ it "returns a list of all registered properties" do
416
+ properties = User.properties.map(&:name).map(&:to_s).sort
417
+ properties.must_equal %w(active admin alias email)
418
+ end
419
+
420
+ it "handles custom table names" do
421
+ class Person < ActiveRecord::Base
422
+ include DataMapper::Shim
423
+
424
+ storage_names[:default] = "users"
425
+ end
426
+
427
+ Person.table_name.must_equal "users"
428
+ end
429
+
430
+ end
431
+
432
+ describe "custom property types" do
433
+
434
+ before do
435
+ class Proprietor < ActiveRecord::Base
436
+ include DataMapper::Shim
437
+ end
438
+ end
439
+
440
+ it "can define a BigInt property" do
441
+ class Proprietor
442
+ property :value, BigInt
443
+ end
444
+ end
445
+
446
+ it "can define a Boolean property" do
447
+ class Proprietor
448
+ property :value, Boolean
449
+ end
450
+ end
451
+
452
+ it "can define a Serial property" do
453
+ class Proprietor
454
+ property :value, Serial
455
+ end
456
+ end
457
+
458
+ it "can define a ParanoidDatetime property" do
459
+ class Proprietor
460
+ property :value, ParanoidDateTime
461
+ end
462
+ end
463
+
464
+ it "can define a Resource property" do
465
+ class Proprietor
466
+ property :value, Resource
467
+ end
468
+ end
469
+
470
+ it "can define a Text property" do
471
+ class Proprietor
472
+ property :value, Text
473
+ end
474
+ end
475
+
476
+ it "can define a Discriminator property" do
477
+ class Proprietor
478
+ property :value, Discriminator
479
+ end
480
+ end
481
+
482
+ it "can define a Yaml property" do
483
+ class Proprietor
484
+ property :value, Yaml
485
+ end
486
+ end
487
+
488
+ it "can define a Json property" do
489
+ class Proprietor
490
+ property :value, Json
491
+ end
492
+ end
493
+
494
+
495
+ end
496
+
497
+ describe "typecasted properties" do
498
+
499
+ before do
500
+ @typecaster = Typecaster.new
501
+ end
502
+
503
+ it "can typecast to a list (CSV)" do
504
+ list = %w(something other)
505
+ @typecaster.list = list
506
+ @typecaster.save
507
+
508
+ Typecaster.connection.select_value("SELECT list FROM typecasters").must_equal list.join(",")
509
+ Typecaster.first.list.must_equal list
510
+ end
511
+
512
+ it "can typecast to YAML" do
513
+ object = {}
514
+ @typecaster.yaml = object
515
+ @typecaster.save
516
+
517
+ Typecaster.connection.select_value("SELECT yaml FROM typecasters").must_equal object.to_yaml
518
+ Typecaster.first.yaml.must_equal object
519
+ end
520
+
521
+ it "can typecast to JSON" do
522
+ object = {"hello" => "world"}
523
+ @typecaster.json = object
524
+ @typecaster.save
525
+
526
+ Typecaster.connection.select_value("SELECT json FROM typecasters").must_equal object.to_json
527
+ Typecaster.first.json.must_equal object
528
+ end
529
+
530
+ it "can typecast enums" do
531
+ @typecaster.enum = :pending
532
+ @typecaster.save
533
+
534
+ Typecaster.connection.select_value("SELECT enum FROM typecasters").must_equal 1
535
+
536
+ @typecaster.enum = :active
537
+ @typecaster.save
538
+
539
+ Typecaster.connection.select_value("SELECT enum FROM typecasters").must_equal 2
540
+
541
+ Typecaster.where(:enum => :active).first.must_equal @typecaster
542
+ Typecaster.first.enum.must_equal :active
543
+ end
544
+
545
+ it "can typecast booleans" do
546
+ @typecaster.boolean = 0
547
+ @typecaster.boolean.must_equal false
548
+
549
+ @typecaster.boolean = 1
550
+ @typecaster.boolean.must_equal true
551
+
552
+ @typecaster.save
553
+
554
+ Typecaster.connection.select_value("SELECT boolean FROM typecasters").must_equal 1
555
+ Typecaster.first.boolean.must_equal true
556
+ end
557
+
558
+ it "can typecast datetime fields" do
559
+ @typecaster.datetime = Time.now
560
+ @typecaster.save
561
+
562
+ Typecaster.first.datetime.must_be_instance_of DateTime
563
+ end
564
+
565
+ it "defaults text fields to nil" do
566
+ Message.new.body.must_be_nil
567
+ end
568
+
569
+ end
570
+
571
+ describe "paranoia" do
572
+
573
+ it "doesn't delete rows with ParanoidDateTime fields" do
574
+ @message.deleted_at.must_be_nil
575
+ @message.destroy
576
+
577
+ @message.wont_be :destroyed?
578
+ @message.deleted_at.wont_be_nil
579
+ end
580
+
581
+ it "doesn't include pseudo-deleted rows in queries by default" do
582
+ @message.destroy
583
+
584
+ Channel.all.wont_include @message
585
+ end
586
+
587
+ it "can include pseudo-deleted rows in queries" do
588
+ @message.destroy
589
+
590
+ Message.with_deleted { Message.all.must_include @message }
591
+ end
592
+
593
+ end
594
+
595
+ describe "accessor visibility" do
596
+
597
+ it "can mark a property's reader as private" do
598
+ alter_ego = "Superman"
599
+ user = User.create :alias => alter_ego
600
+
601
+ proc { user.alias }.must_raise NoMethodError
602
+ user.__send__(:alias).must_equal alter_ego
603
+ end
604
+
605
+ it "can mark a property's writer as protected" do
606
+ message = Message.new :deleted_at => Time.now
607
+ message.deleted_at.must_be_nil
608
+
609
+ begin
610
+ message.deleted_at = Time.now
611
+ rescue NoMethodError => e
612
+ e.message.must_match /protected/
613
+ end
614
+ end
615
+
616
+ it "can mark a property's writer as private" do
617
+ user = User.new :admin => true
618
+ user.admin.must_equal false
619
+
620
+ begin
621
+ user.admin = true
622
+ rescue NoMethodError => e
623
+ e.message.must_match /private/
624
+ end
625
+ end
626
+
627
+ end
628
+
629
+ describe "property options" do
630
+
631
+ it "raises on leftover options" do
632
+ proc do
633
+ class User
634
+ property :age, Integer, :only_if_older_than => 18
635
+ end
636
+ end.must_raise NotImplementedError
637
+ end
638
+
639
+ it "handles default values" do
640
+ Channel.new.state.must_equal :open
641
+ end
642
+
643
+ it "handles default boolean values" do
644
+ User.new.must_be :active?
645
+ end
646
+
647
+ it "ignores index options" do
648
+ class User
649
+ property :email, String, :index => true, :unique_index => true
650
+ end
651
+ end
652
+
653
+ it "ignores auto-validation options" do
654
+ class User
655
+ property :email, :auto_validation => false
656
+ end
657
+ end
658
+
659
+ it "ignores lazy options" do
660
+ class User
661
+ property :email, :lazy => true
662
+ end
663
+ end
664
+
665
+ it "ignores timestamp declarations" do
666
+ class User
667
+ timestamps :at
668
+ end
669
+ end
670
+
671
+ end
672
+
673
+ describe "primary keys" do
674
+
675
+ it "sets a property as a primary key" do
676
+ Channel.primary_key.must_equal "name"
677
+ end
678
+
679
+ it "handles composite primary keys" do
680
+ Friendship.primary_keys.must_equal ["user_id", "friend_id"]
681
+ end
682
+
683
+ it "validates the presence of primary keys" do
684
+ channel = Channel.new
685
+ channel.wont_be :valid?
686
+
687
+ channel.errors[:name].wont_be_empty
688
+ channel.errors[:name].must_match /can't be blank/
689
+ end
690
+
691
+ end
692
+
693
+ describe "property validations" do
694
+
695
+ it "validates length" do
696
+ user = User.new :email => "#{"a"*50}@web.com"
697
+ user.wont_be :valid?
698
+
699
+ user.errors[:email].wont_be_empty
700
+ user.errors[:email].must_match /is too long/
701
+ end
702
+
703
+ it "validates uniqueness" do
704
+ email_in_use = User.first.email
705
+ user = User.new :email => email_in_use
706
+
707
+ user.wont_be :valid?
708
+ user.errors[:email].wont_be_empty
709
+ user.errors[:email].must_match /has already been taken/
710
+ end
711
+
712
+ it "validates size (max length)" do
713
+ user = User.new :alias => "#{"a"*11}"
714
+ user.wont_be :valid?
715
+
716
+ user.errors[:alias].wont_be_empty
717
+ user.errors[:alias].must_match /is too long/
718
+ end
719
+
720
+ it "validates email format" do
721
+ user = User.new :email => "qwerty"
722
+ user.wont_be :valid?
723
+
724
+ user.errors[:email].wont_be_empty
725
+ user.errors[:email].must_match /is invalid/
726
+
727
+ user.email = "hpotter@hogwarts.edu"
728
+ user.must_be :valid?
729
+ end
730
+
731
+ it "validates any format" do
732
+ channel = Channel.new :name => "lunch"
733
+ channel.wont_be :valid?
734
+
735
+ channel.errors[:name].wont_be_empty
736
+ channel.errors[:name].must_match /is invalid/
737
+
738
+ channel.name = "#lunch"
739
+ channel.must_be :valid?
740
+ end
741
+
742
+ it "validates presence" do
743
+ channel = Channel.new
744
+ channel.state = nil
745
+ channel.wont_be :valid?
746
+
747
+ channel.errors.must_include :state
748
+ channel.errors[:state].must_match /can't be blank/
749
+ end
750
+
751
+ it "validates presence for booleans" do
752
+ user = User.new
753
+ user.active = nil
754
+ user.wont_be :valid?
755
+
756
+ user.errors.must_include :active
757
+ user.errors[:active].must_match /is not included in the list/
758
+ end
759
+ end
760
+
761
+
762
+ describe "validates" do
763
+
764
+ before do
765
+ Validator = Class.new(ActiveRecord::Base) do
766
+ self.table_name = "validators"
767
+ include DataMapper::Shim
768
+ end
769
+
770
+ @validator = Validator.new
771
+ end
772
+
773
+ describe "presence" do
774
+
775
+ it "checks an attribute is present" do
776
+ Validator.class_eval do
777
+ validates_present :value
778
+ end
779
+
780
+ @validator.valid?
781
+ @validator.errors[:value].must_match /can't be blank/
782
+ end
783
+
784
+ it "can validate only when conditions are met" do
785
+ Validator.class_eval do
786
+ validates_present :value, :if => proc { flag }
787
+ end
788
+
789
+ @validator.valid?
790
+ @validator.errors[:value].must_be_empty
791
+
792
+ @validator.flag = true
793
+ @validator.valid?
794
+ @validator.errors[:value].must_match /can't be blank/
795
+ end
796
+
797
+ it "can validate in a context" do
798
+ Validator.class_eval do
799
+ validates_present :value, :when => [:context]
800
+ end
801
+
802
+ @validator.valid?
803
+ @validator.errors[:value].must_be_empty
804
+
805
+ @validator.valid?(:context)
806
+ @validator.errors[:value].must_match /can't be blank/
807
+ end
808
+
809
+ end
810
+
811
+ describe "uniqueness" do
812
+
813
+ it "checks an attribute is unique" do
814
+ Validator.class_eval do
815
+ validates_is_unique :value
816
+ end
817
+
818
+ @validator.value = nil
819
+ @validator.save.must_equal true
820
+
821
+ dup = Validator.new :value => nil
822
+
823
+ dup.wont_be :valid?
824
+ dup.errors[:value].must_match /has already been taken/
825
+ end
826
+
827
+ it "can ignore nil values" do
828
+ Validator.class_eval do
829
+ validates_is_unique :value, :allow_nil => true
830
+ end
831
+
832
+ @validator.value = nil
833
+ @validator.save.must_equal true
834
+
835
+ dup = Validator.new :value => nil
836
+ dup.must_be :valid?
837
+ end
838
+
839
+ it "can check an attribute is unique within a scope" do
840
+ Validator.class_eval do
841
+ validates_is_unique :value, :scope => [:flag]
842
+ end
843
+
844
+ @validator.value = "value"
845
+ @validator.save.must_equal true
846
+
847
+ dup = Validator.new :value => "value"
848
+ dup.wont_be :valid?
849
+
850
+ dup.flag = true
851
+ dup.must_be :valid?
852
+ end
853
+
854
+ it "can conditionally check an attribute is unique" do
855
+ Validator.class_eval do
856
+ validates_is_unique :value, :if => proc { flag }
857
+ end
858
+
859
+ @validator.value = "value"
860
+ @validator.save.must_equal true
861
+
862
+ dup = Validator.new :value => "value"
863
+ dup.must_be :valid?
864
+
865
+ dup.flag = true
866
+ dup.wont_be :valid?
867
+ end
868
+
869
+ end
870
+
871
+ describe "length" do
872
+
873
+ it "checks an attribute's length" do
874
+ Validator.class_eval do
875
+ validates_length :value, :max => 10
876
+ end
877
+
878
+ @validator.value = "v" * 11
879
+ @validator.wont_be :valid?
880
+ @validator.errors[:value].must_match /is too long \(maximum is 10 characters\)/
881
+ end
882
+
883
+ it "can use 'maximum' as an alias" do
884
+ Validator.class_eval do
885
+ validates_length :value, :maximum => 10
886
+ end
887
+
888
+ @validator.value = "v" * 11
889
+ @validator.wont_be :valid?
890
+ @validator.errors[:value].must_match /is too long \(maximum is 10 characters\)/
891
+ end
892
+
893
+ it "can check a minimum length" do
894
+ Validator.class_eval do
895
+ validates_length :value, :min => 10
896
+ end
897
+
898
+ @validator.value = "v" * 9
899
+ @validator.wont_be :valid?
900
+ @validator.errors[:value].must_match /is too short \(minimum is 10 characters\)/
901
+ end
902
+
903
+ it "can conditionally check an attribute's length" do
904
+ Validator.class_eval do
905
+ validates_length :value, :max => 10, :if => proc { flag }
906
+ end
907
+
908
+ @validator.value = "v" * 11
909
+ @validator.must_be :valid?
910
+
911
+ @validator.flag = true
912
+ @validator.wont_be :valid?
913
+ end
914
+
915
+ it "can display a custom message" do
916
+ Validator.class_eval do
917
+ validates_length :value, :max => 10, :message => "too much value"
918
+ end
919
+
920
+ @validator.value = "v" * 11
921
+ @validator.wont_be :valid?
922
+ @validator.errors[:value].must_match /too much value/
923
+ end
924
+
925
+ end
926
+
927
+ describe "confirmation" do
928
+
929
+ it "checks an attribute's confirmation value" do
930
+ Validator.class_eval do
931
+ validates_is_confirmed :value
932
+ end
933
+
934
+ @validator.value = "value"
935
+ @validator.wont_be :valid?
936
+ @validator.errors[:value_confirmation].must_match /can't be blank/
937
+
938
+ @validator.value_confirmation = "valu"
939
+ @validator.wont_be :valid?
940
+ @validator.errors[:value].must_match /doesn't match confirmation/
941
+
942
+ @validator.value_confirmation = "value"
943
+ @validator.must_be :valid?
944
+ end
945
+
946
+ it "can display a custom message" do
947
+ Validator.class_eval do
948
+ validates_is_confirmed :value, :message => "not alike enough"
949
+ end
950
+
951
+ @validator.value = "value"
952
+ @validator.value_confirmation = "valu"
953
+
954
+ @validator.wont_be :valid?
955
+ @validator.errors[:value].must_match /not alike enough/
956
+ end
957
+
958
+ it "can conditionally check a confirmation value" do
959
+ Validator.class_eval do
960
+ validates_is_confirmed :value, :if => proc { flag }
961
+ end
962
+
963
+ @validator.value = "value"
964
+ @validator.must_be :valid?
965
+
966
+ @validator.flag = true
967
+ @validator.wont_be :valid?
968
+
969
+ @validator.value_confirmation = "value"
970
+ @validator.must_be :valid?
971
+ end
972
+
973
+ end
974
+
975
+ describe "with a block" do
976
+
977
+ it "checks an attribute against a block" do
978
+ Validator.class_eval do
979
+ validates_with_block :value do |value|
980
+ if value == "secret"
981
+ [true, nil]
982
+ else
983
+ [false, "try again"]
984
+ end
985
+ end
986
+ end
987
+
988
+ @validator.value = "public"
989
+ @validator.wont_be :valid?
990
+ @validator.errors[:value].must_match /try again/
991
+
992
+ @validator.value = "secret"
993
+ @validator.must_be :valid?
994
+ end
995
+
996
+ end
997
+
998
+ describe "format" do
999
+
1000
+ it "checks an attribute's format" do
1001
+ Validator.class_eval do
1002
+ validates_format :value, :with => /^#.*$/
1003
+ end
1004
+
1005
+ @validator.value = "channel"
1006
+ @validator.wont_be :valid?
1007
+ @validator.errors[:value].must_match /is invalid/
1008
+
1009
+ @validator.value = "#channel"
1010
+ @validator.must_be :valid?
1011
+ end
1012
+
1013
+ end
1014
+
1015
+ describe "inclusion" do
1016
+
1017
+ it "checks an attribute is part of a set" do
1018
+ set = [0, 1]
1019
+ Validator.class_eval do
1020
+ validates_within :value, :set => set
1021
+ end
1022
+
1023
+ @validator.value = -1
1024
+ @validator.wont_be :valid?
1025
+ @validator.errors[:value].must_match /is not included in the list/
1026
+
1027
+ set.each do |n|
1028
+ @validator.value = n
1029
+ @validator.must_be :valid?
1030
+ end
1031
+
1032
+ @validator.value = 2
1033
+ @validator.wont_be :valid?
1034
+ end
1035
+
1036
+ end
1037
+
1038
+ describe "with a method" do
1039
+
1040
+ it "checks an attribute against a method" do
1041
+ Validator.class_eval do
1042
+ validates_with_method :value, :method => :validate
1043
+
1044
+ def validate
1045
+ if value == "secret"
1046
+ true
1047
+ else
1048
+ [false, "try again"]
1049
+ end
1050
+ end
1051
+ end
1052
+
1053
+ @validator.wont_be :valid?
1054
+ @validator.errors[:value].must_match /try again/
1055
+
1056
+ @validator.value = "secret"
1057
+ @validator.must_be :valid?
1058
+ end
1059
+
1060
+ it "can check against a method within a scope" do
1061
+ Validator.class_eval do
1062
+ validates_with_method :value, :method => :validate, :when => :context
1063
+ validates_with_method :value, :method => :validate, :when => [:context2]
1064
+
1065
+ def validate
1066
+ [false, "try again"]
1067
+ end
1068
+ end
1069
+
1070
+ @validator.must_be :valid?
1071
+
1072
+ @validator.flag = true
1073
+ @validator.valid?(:context).must_equal false
1074
+ @validator.errors[:value].must_match /try again/
1075
+
1076
+ @validator.valid?(:context2).must_equal false
1077
+ @validator.errors[:value].must_match /try again/
1078
+ end
1079
+
1080
+ end
1081
+
1082
+ end
1083
+
1084
+ describe "callbacks" do
1085
+
1086
+ before do
1087
+ Callbacker = Class.new(ActiveRecord::Base) do
1088
+ include DataMapper::Shim
1089
+
1090
+ # Callback methods
1091
+ [:create, :save, :destroy, :update, :valid?].each do |callback|
1092
+ define_method "#{callback}_with_method" do
1093
+ callbacks << "#{callback}_with_method"
1094
+ end
1095
+
1096
+ define_method "before_#{callback}_with_method" do
1097
+ callbacks << "before_#{callback}_with_method"
1098
+ end
1099
+ end
1100
+
1101
+ attr_accessor :callbacks
1102
+ def callbacks
1103
+ @callbacks ||= []
1104
+ end
1105
+ end
1106
+
1107
+ @callbacker = Callbacker.new
1108
+ end
1109
+
1110
+ describe ":after callbacks" do
1111
+
1112
+ before do
1113
+ Callbacker.class_eval do
1114
+ after(:create) { callbacks << "create" }
1115
+ after(:save) { callbacks << "save" }
1116
+ after(:destroy) { callbacks << "destroy" }
1117
+
1118
+ after :create, :create_with_method
1119
+ after :save, :save_with_method
1120
+ after :destroy, :destroy_with_method
1121
+ end
1122
+ end
1123
+
1124
+ it "handles after :save" do
1125
+ @callbacker.save
1126
+ @callbacker.callbacks.must_include "save"
1127
+ @callbacker.callbacks.must_include "save_with_method"
1128
+
1129
+ @callbacker.callbacks = []
1130
+ @callbacker.save
1131
+
1132
+ @callbacker.callbacks.must_include "save"
1133
+ @callbacker.callbacks.must_include "save_with_method"
1134
+ end
1135
+
1136
+ it "handles after :create" do
1137
+ @callbacker.save
1138
+ @callbacker.callbacks.must_include "create"
1139
+ @callbacker.callbacks.must_include "create_with_method"
1140
+
1141
+ @callbacker.callbacks = []
1142
+ @callbacker.save
1143
+
1144
+ @callbacker.callbacks.wont_include "create"
1145
+ @callbacker.callbacks.wont_include "create_with_method"
1146
+ end
1147
+
1148
+ it "handles after :destroy" do
1149
+ @callbacker.save
1150
+ @callbacker.callbacks.wont_include "destroy"
1151
+ @callbacker.callbacks.wont_include "destroy_with_method"
1152
+
1153
+ @callbacker.destroy
1154
+ @callbacker.callbacks.must_include "destroy"
1155
+ @callbacker.callbacks.must_include "destroy_with_method"
1156
+ end
1157
+
1158
+ end
1159
+
1160
+ describe ":before callbacks" do
1161
+
1162
+ before do
1163
+ Callbacker.class_eval do
1164
+ before(:create) { callbacks << "before_create" }
1165
+ before(:save) { callbacks << "before_save" }
1166
+ before(:destroy) { callbacks << "before_destroy" }
1167
+ before(:update) { callbacks << "before_update" }
1168
+ before(:valid?) { callbacks << "before_valid?" }
1169
+
1170
+ before :create, :before_create_with_method
1171
+ before :save, :before_save_with_method
1172
+ before :destroy, :before_destroy_with_method
1173
+ before :update, :before_update_with_method
1174
+ before :valid?, :"#{"before_valid?_with_method"}"
1175
+ end
1176
+ end
1177
+
1178
+ it "handles before :save" do
1179
+ @callbacker.save
1180
+ @callbacker.callbacks.must_include "before_save"
1181
+ @callbacker.callbacks.must_include "before_save_with_method"
1182
+
1183
+ @callbacker.callbacks = []
1184
+ @callbacker.save
1185
+
1186
+ @callbacker.callbacks.must_include "before_save"
1187
+ @callbacker.callbacks.must_include "before_save_with_method"
1188
+ end
1189
+
1190
+ it "handles before :create" do
1191
+ @callbacker.save
1192
+ @callbacker.callbacks.must_include "before_create"
1193
+ @callbacker.callbacks.must_include "before_create_with_method"
1194
+
1195
+ @callbacker.callbacks = []
1196
+ @callbacker.save
1197
+
1198
+ @callbacker.callbacks.wont_include "before_create"
1199
+ @callbacker.callbacks.wont_include "before_create_with_method"
1200
+ end
1201
+
1202
+ it "handles before :destroy" do
1203
+ @callbacker.save
1204
+ @callbacker.callbacks.wont_include "before_destroy"
1205
+ @callbacker.callbacks.wont_include "before_destroy_with_method"
1206
+
1207
+ @callbacker.destroy
1208
+ @callbacker.callbacks.must_include "before_destroy"
1209
+ @callbacker.callbacks.must_include "before_destroy_with_method"
1210
+ end
1211
+
1212
+ it "handles before :update" do
1213
+ @callbacker.save
1214
+ @callbacker.callbacks.wont_include "before_update"
1215
+ @callbacker.callbacks.wont_include "before_update_with_method"
1216
+
1217
+ @callbacker.save
1218
+ @callbacker.callbacks.must_include "before_update"
1219
+ @callbacker.callbacks.must_include "before_update_with_method"
1220
+ end
1221
+
1222
+ it "handles before :valid?" do
1223
+ @callbacker.save
1224
+ @callbacker.callbacks.must_include "before_valid?"
1225
+ @callbacker.callbacks.must_include "before_valid?_with_method"
1226
+
1227
+ @callbacker.callbacks = []
1228
+ @callbacker.valid?
1229
+ @callbacker.callbacks.must_include "before_valid?"
1230
+ @callbacker.callbacks.must_include "before_valid?_with_method"
1231
+ end
1232
+
1233
+ it "saves a record even if a before(:valid?) callback returns false" do
1234
+ Callbacker.class_eval do
1235
+ before(:valid?) { callbacks << "short_circuit"; false }
1236
+ end
1237
+
1238
+ @callbacker.save.must_equal true
1239
+ @callbacker.callbacks.must_include "short_circuit"
1240
+ end
1241
+
1242
+ end
1243
+
1244
+ end
1245
+
1246
+ protected
1247
+
1248
+ class Object
1249
+ def must_match(pattern)
1250
+ if self.is_a?(Array)
1251
+ self.any? { |element| element.match(pattern) }.must_equal true
1252
+ else
1253
+ super pattern
1254
+ end
1255
+ end
1256
+ end
1257
+
1258
+ end