datamapper-shim 0.0.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,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