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.
- data/Gemfile +4 -0
- data/Gemfile.lock +45 -0
- data/datamapper-shim.gemspec +30 -0
- data/lib/data_mapper/shim.rb +724 -0
- data/test/data_mapper/shim_test.rb +1258 -0
- metadata +211 -0
@@ -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
|