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 ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify dependencies in datamapper-shim.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,45 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ activemodel (3.2.16)
5
+ activesupport (= 3.2.16)
6
+ builder (~> 3.0.0)
7
+ activerecord (3.2.16)
8
+ activemodel (= 3.2.16)
9
+ activesupport (= 3.2.16)
10
+ arel (~> 3.0.2)
11
+ tzinfo (~> 0.3.29)
12
+ activesupport (3.2.16)
13
+ i18n (~> 0.6, >= 0.6.4)
14
+ multi_json (~> 1.0)
15
+ arel (3.0.3)
16
+ attribute-defaults (0.6.0)
17
+ activerecord (>= 3.0.0)
18
+ builder (3.0.4)
19
+ coderay (1.1.0)
20
+ composite_primary_keys (5.0.13)
21
+ activerecord (~> 3.2.0, >= 3.2.9)
22
+ i18n (0.6.9)
23
+ json (1.8.1)
24
+ method_source (0.8.2)
25
+ minitest (5.2.3)
26
+ multi_json (1.8.4)
27
+ pry (0.9.12.4)
28
+ coderay (~> 1.0)
29
+ method_source (~> 0.8)
30
+ slop (~> 3.4)
31
+ slop (3.4.7)
32
+ sqlite3 (1.3.8)
33
+ tzinfo (0.3.38)
34
+
35
+ PLATFORMS
36
+ ruby
37
+
38
+ DEPENDENCIES
39
+ activerecord (~> 3.2.0)
40
+ attribute-defaults (~> 0.6.0)
41
+ composite_primary_keys (~> 5.0.0)
42
+ json (~> 1.4)
43
+ minitest (~> 5.2.3)
44
+ pry
45
+ sqlite3 (~> 1.3.8)
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "datamapper-shim"
7
+ spec.version = "0.0.1"
8
+ spec.authors = ["ecin"]
9
+ spec.email = ["ecin@copypastel.com"]
10
+ spec.description = "Dress ActiveRecord models in DataMapper robes."
11
+ spec.summary = "Migrate DataMapper models to ActiveRecord."
12
+ spec.homepage = "https://github.com/typekit/datamapper-shim"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files`.split($/)
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "json", "~> 1.4"
21
+ spec.add_dependency "activerecord", "~> 3.2.0"
22
+ spec.add_dependency "attribute-defaults", "~> 0.6.0"
23
+ spec.add_dependency "composite_primary_keys", "~> 5.0.0"
24
+ spec.add_dependency "json", "~> 1.4"
25
+ spec.add_dependency "sqlite3", "~> 1.3.8"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.3"
28
+ spec.add_development_dependency "minitest", "~> 5.2.3"
29
+ spec.add_development_dependency "pry", "~> 0.9.12.4"
30
+ end
@@ -0,0 +1,724 @@
1
+ require "active_record"
2
+ require "active_support/concern"
3
+ require "attribute_defaults"
4
+ require "composite_primary_keys"
5
+ require "json"
6
+
7
+ module DataMapper
8
+ module Shim
9
+ RAISE_ON_UNHANDLED_OPTIONS = true
10
+
11
+ extend ActiveSupport::Concern
12
+
13
+ # Placeholders for DataMapper types
14
+ BigInt = Class.new
15
+ Boolean = Class.new
16
+ Discriminator = Class.new
17
+ ParanoidDateTime = Class.new
18
+ Resource = Class.new
19
+ Serial = Class.new
20
+ Text = Class.new
21
+
22
+ # Serializable classes
23
+ Yaml = Class.new
24
+ Json = Class.new
25
+
26
+ class Enum
27
+ attr_accessor :values
28
+
29
+ def self.[](*values)
30
+ self.new(values)
31
+ end
32
+
33
+ def initialize(values)
34
+ # [:active, :inactive] => { :active => 1, :inactive => 2, 1 => :active, 2 => :inactive }
35
+ self.values = values.each_with_index.inject({}) do |hash, (val, i)|
36
+ hash[val] = i + 1
37
+ hash[i + 1] = val
38
+ hash
39
+ end
40
+ end
41
+
42
+ def [](index)
43
+ self.values[index]
44
+ end
45
+ end
46
+
47
+ class List
48
+ def load(value)
49
+ value ? value.to_s.split(",") : nil
50
+ end
51
+
52
+ def dump(value)
53
+ value ? value.collect { |x| x.to_s.strip }.join(',') : nil
54
+ end
55
+ end
56
+
57
+ # Mischellaneous helpers
58
+
59
+ def attribute_get(name)
60
+ read_attribute(name)
61
+ end
62
+
63
+ def attribute_set(name, value)
64
+ write_attribute(name, value)
65
+ end
66
+
67
+ def attribute_dirty?(attribute)
68
+ changed_attributes.has_key?(attribute.to_s)
69
+ end
70
+
71
+ def dirty?
72
+ changed?
73
+ end
74
+
75
+ class OriginalValues
76
+ def initialize(model)
77
+ @model = model
78
+ end
79
+
80
+ def [](attribute)
81
+ @model.__send__ :"#{attribute}_was"
82
+ end
83
+ end
84
+
85
+ class ActiveRecord::Relation
86
+ def destroy!
87
+ destroy_all
88
+ end
89
+ end
90
+
91
+ def original_values
92
+ OriginalValues.new(self)
93
+ end
94
+
95
+ module ClassMethods
96
+
97
+ # RFC2822 (No attribution reference available)
98
+ EmailAddress = begin
99
+ alpha = "a-zA-Z"
100
+ digit = "0-9"
101
+ atext = "[#{alpha}#{digit}\!\#\$\%\&\'\*+\/\=\?\^\_\`\{\|\}\~\-]"
102
+ dot_atom_text = "#{atext}+([.]#{atext}*)*"
103
+ dot_atom = "#{dot_atom_text}"
104
+ qtext = '[^\\x0d\\x22\\x5c\\x8 0-\\xff]'
105
+ text = "[\\x01-\\x09\\x11\\x12\\x14-\\x7f]"
106
+ quoted_pair = "(\\x5c#{text})"
107
+ qcontent = "(?:#{qtext}|#{quoted_pair})"
108
+ quoted_string = "[\"]#{qcontent}+[\"]"
109
+ atom = "#{atext}+"
110
+ word = "(?:#{atom}|#{quoted_string})"
111
+ obs_local_part = "#{word}([.]#{word})*"
112
+ local_part = "(?:#{dot_atom}|#{quoted_string}|#{obs_local_part})"
113
+ no_ws_ctl = "\\x01-\\x08\\x11\\x12\\x14-\\x1f\\x7f"
114
+ dtext = "[#{no_ws_ctl}\\x21-\\x5a\\x5e-\\x7e]"
115
+ dcontent = "(?:#{dtext}|#{quoted_pair})"
116
+ domain_literal = "\\[#{dcontent}+\\]"
117
+ obs_domain = "#{atom}([.]#{atom})*"
118
+ domain = "(?:#{dot_atom}|#{domain_literal}|#{obs_domain})"
119
+ addr_spec = "#{local_part}\@#{domain}"
120
+ pattern = /^#{addr_spec}$/
121
+ end
122
+
123
+ # Helpers
124
+
125
+ # As in 'has n, :messages'
126
+ def n
127
+ 1/0.0
128
+ end
129
+
130
+ class Property
131
+ attr_reader :name, :type
132
+
133
+ def initialize(name, type)
134
+ @name = name
135
+ @type = type
136
+ end
137
+ end
138
+
139
+ def properties
140
+ @__datamapper_shim_properties ||= Set.new
141
+ end
142
+
143
+ def storage_names
144
+ storage = Object.new
145
+ storage.instance_variable_set :@model, self
146
+
147
+ def storage.[]=(repo, name)
148
+ @model.table_name = name
149
+ end
150
+ storage
151
+ end
152
+
153
+ def timestamps(suffix)
154
+ # ActiveRecord handles timestamps (created_at, updated_at)
155
+ # automatically if the columns exist.
156
+ end
157
+
158
+ # Finders
159
+
160
+ def all(*args)
161
+ if args.empty?
162
+ scoped
163
+ else
164
+ where(*args)
165
+ end
166
+ end
167
+
168
+ def first(*args)
169
+ if args.empty?
170
+ scoped.first
171
+ else
172
+ where(*args).first
173
+ end
174
+ end
175
+
176
+ def get(*args)
177
+ find_by_id(*args)
178
+ end
179
+ # Model definition
180
+
181
+ def property(name, type, options = {})
182
+ properties << Property.new(name, type)
183
+
184
+ case
185
+ when type == Boolean
186
+ # property :trial, Boolean
187
+ #
188
+ # If the :trial column in the DB is created with TINYINT(4),
189
+ # Rails doesn't map it to a boolean type,
190
+ # so we need to coerce it here if necessary.
191
+ define_method name do
192
+ value = read_attribute(name)
193
+ if value.is_a? Integer
194
+ value.zero? ? false : true
195
+ else
196
+ value
197
+ end
198
+ end
199
+ when type.is_a?(Enum)
200
+ warn "Rails 4 handles enums out of the box."
201
+
202
+ # Attribute accessors for the property
203
+ self.instance_eval do
204
+ define_method(name) do
205
+ type[read_attribute(name)]
206
+ end
207
+
208
+ define_method("#{name}=") do |value|
209
+ value = value.is_a?(Integer) ? value : type[value]
210
+ write_attribute(name, value)
211
+ end
212
+ end
213
+
214
+ @__datamapper_shim_enums ||= {}
215
+ @__datamapper_shim_enums[name] = type
216
+
217
+ # Substitute enum values in where clauses
218
+ # Note, we don't define this on ActiveRecord::Relation
219
+ def self.where(opts, *rest)
220
+ @__datamapper_shim_enums.each do |name, enum|
221
+ if opts.has_key?(name) && !opts[name].is_a?(Integer)
222
+ opts[name] = enum[opts[name]]
223
+ end
224
+ end
225
+
226
+ super
227
+ end
228
+ when type == Json
229
+ serialize name, JSON
230
+ when type == List
231
+ serialize name, List.new
232
+ when type == ParanoidDateTime
233
+ self.instance_eval do
234
+ define_method :destroy do
235
+ update_attribute(name, Time.zone.try(:now) || Time.now)
236
+ end
237
+ end
238
+
239
+ default_scope where(:deleted_at => nil)
240
+
241
+ def self.with_deleted
242
+ self.unscoped.where("`#{self.table_name}`.deleted_at IS NOT NULL").scoping do
243
+ yield if block_given?
244
+ end
245
+ end
246
+ when type == Yaml
247
+ serialize name
248
+ end
249
+
250
+ if type == DateTime || type == ParanoidDateTime
251
+ # ActiveRecord maps datetime columns to TimeWithZone,
252
+ # which can have some slight inconsistencies with DateTime
253
+ self.instance_eval do
254
+ define_method "#{name}" do
255
+ read_attribute(name).try(:to_datetime)
256
+ end
257
+ end
258
+ end
259
+
260
+ if options.has_key?(:default)
261
+ default = options.delete(:default)
262
+ # :if => :class guarantees that the default value is always returned
263
+ attr_default(name, :if => :class, :persisted => false) do |record|
264
+ if record.new_record? && !record.__send__(:"#{name}_changed?")
265
+ default
266
+ else
267
+ record.instance_eval do
268
+ read_attribute(name)
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ if reader = options.delete(:reader)
275
+ self.instance_eval do
276
+ define_method name do
277
+ read_attribute(name)
278
+ end
279
+ end
280
+
281
+ case reader
282
+ when :private
283
+ private name
284
+ when :protected
285
+ protected name
286
+ else
287
+ raise "option :#{reader} for :reader not handled in shim"
288
+ end
289
+ end
290
+
291
+ if writer = options.delete(:writer)
292
+ self.instance_eval do
293
+ define_method(:"#{name}=") do |value|
294
+ write_attribute(name, value)
295
+ end
296
+ end
297
+
298
+ case writer
299
+ when :protected
300
+ protected :"#{name}="
301
+ attr_protected name
302
+ when :private
303
+ private :"#{name}="
304
+ attr_protected name
305
+ else
306
+ raise "option :#{writer} for :writer not handled in shim"
307
+ end
308
+ else
309
+ attr_accessible name
310
+ end
311
+
312
+ # http://rubydoc.info/github/datamapper/dm-core/master/DataMapper/Property
313
+ # No validation necessary here, as these just create a database index.
314
+ options.delete(:index)
315
+ options.delete(:unique_index)
316
+
317
+ # DataMapper does some default validation on certain datatypes, e.g.
318
+ # Strings can't have a length longer than 50 chars. ActiveRecord doesn't
319
+ # do any such validations, so we can ignore this option.
320
+ options.delete(:auto_validation)
321
+
322
+ # http://stackoverflow.com/questions/95061/stop-activerecord-from-loading-blob-column
323
+ # No obvious way to lazily load an attribute
324
+ options.delete(:lazy)
325
+
326
+ # Primary keys
327
+
328
+ if options.delete(:key)
329
+ @__datamapper_shim_primary_keys ||= []
330
+ @__datamapper_shim_primary_keys << name
331
+
332
+ self.primary_keys = *@__datamapper_shim_primary_keys
333
+ validates_presence_of name
334
+ end
335
+
336
+ # Validations
337
+
338
+ validation_options = {}
339
+
340
+ if length = options.delete(:length) || options.delete(:size)
341
+ validation_options.merge! :length => { :maximum => length }
342
+ end
343
+
344
+ if unique = options.delete(:unique)
345
+ validation_options.merge! :uniqueness => true
346
+ end
347
+
348
+ if format = options.delete(:format)
349
+ if format == :email_address
350
+ validation_options.merge! :format => { :with => EmailAddress }
351
+ else
352
+ validation_options.merge! :format => { :with => format }
353
+ end
354
+ end
355
+
356
+ # This needs to be checked last due to how
357
+ # :allow_nil works.
358
+ if options.has_key?(:nullable)
359
+ if options.delete(:nullable) == false
360
+ if type == Boolean
361
+ # http://stackoverflow.com/questions/7781174/rspec-validation-failed-attribute-cant-be-blank-but-it-isnt-blank
362
+ validation_options.merge! :inclusion => { :in => [true, false, 0, 1] }
363
+ else
364
+ validation_options.merge! :presence => true
365
+ end
366
+ else
367
+ # Can't add an :allow_nil option unless there's an actual
368
+ # validation happening.
369
+ unless validation_options.empty?
370
+ validation_options.merge! :allow_nil => true
371
+ end
372
+ end
373
+ end
374
+
375
+ validates(name, validation_options) unless validation_options.empty?
376
+ handle_leftover_options(options)
377
+ end
378
+
379
+ # Relationships
380
+
381
+ def has(cardinality, name, options = {})
382
+ relationship_options = {}
383
+
384
+ if class_name = options.delete(:class_name)
385
+ relationship_options.merge! :class_name => class_name
386
+ end
387
+
388
+ if foreign_key = options.delete(:child_key)
389
+ relationship_options.merge! :foreign_key => foreign_key
390
+ end
391
+
392
+ if key = options.delete(:association_foreign_key)
393
+ relationship_options.merge! :association_foreign_key => key
394
+ end
395
+
396
+ if order = options.delete(:order)
397
+ relationship_options.merge! :order => order
398
+ end
399
+
400
+ if inverse_of = options.delete(:inverse_of)
401
+ relationship_options.merge! :inverse_of => inverse_of
402
+ end
403
+
404
+ if mutable = options.delete(:mutable)
405
+ # no-op, ActiveRecord relationships are mutable by default
406
+ end
407
+
408
+ if constraint = options.delete(:constraint)
409
+ # https://github.com/datamapper/dm-constraints for more information on
410
+ # all possible values for :constraint
411
+ case constraint
412
+ when :protect
413
+ relationship_options[:dependent] = :restrict
414
+
415
+ # DataMapper returns false if a model can't be destroyed.
416
+ # Here, we capture the exception AR raises if a model
417
+ # can't be destroyed due to an association.
418
+ self.instance_eval do
419
+ define_method(:destroy) do
420
+ begin
421
+ super
422
+ rescue ActiveRecord::DeleteRestrictionError => e
423
+ false
424
+ end
425
+ end
426
+ end
427
+ when :destroy
428
+ relationship_options[:dependent] = :destroy
429
+ when :destroy!
430
+ relationship_options[:dependent] = :delete_all
431
+ when :set_nil
432
+ relationship_options[:dependent] = :nullify
433
+ when :skip
434
+ # no-op
435
+ end
436
+ end
437
+
438
+ if through = options.delete(:through)
439
+ if through == Resource
440
+ cardinality = -1
441
+ else
442
+ relationship_options.merge! :through => through
443
+ end
444
+ end
445
+
446
+ case cardinality
447
+ when -1
448
+ if relationship_options.key?(:dependent)
449
+ case dependent = relationship_options.delete(:dependent)
450
+ when :destroy
451
+ before_destroy do
452
+ self.__send__(name).each do |record|
453
+ record.destroy
454
+ end
455
+ end
456
+ when :restrict
457
+ warn "ActiveRecord's HABTM doesn't support :dependent => :restrict; implement relationship as a has_many :through instead"
458
+ else
459
+ raise "Missing implementation for :#{dependent} constraint on #{name} for #{self}"
460
+ end
461
+ end
462
+
463
+ has_and_belongs_to_many name, relationship_options
464
+ when 1
465
+ has_one name, relationship_options
466
+ when n
467
+ has_many name, relationship_options
468
+ end
469
+
470
+ attr_accessible name
471
+ handle_leftover_options(options)
472
+ end
473
+
474
+ def belongs_to(name, options = {})
475
+ relationship_options = {}
476
+
477
+ if options.has_key?(:nullable) && options.delete(:nullable) == false
478
+ validates_presence_of name
479
+ end
480
+
481
+ if foreign_key = options.delete(:child_key)
482
+ if foreign_key.is_a?(Enumerable) && foreign_key.length == 1
483
+ foreign_key = foreign_key.first
484
+ end
485
+
486
+ relationship_options.merge! :foreign_key => foreign_key
487
+ end
488
+
489
+ if class_name = options.delete(:class_name)
490
+ relationship_options.merge! :class_name => class_name
491
+ end
492
+
493
+ if foreign_key = options.delete(:parent_key)
494
+ warn "Not handling parent_key options in belongs_to"
495
+ end
496
+
497
+ if inverse_of = options.delete(:inverse_of)
498
+ relationship_options.merge! :inverse_of => inverse_of
499
+ end
500
+
501
+ if autosave = options.delete(:autosave)
502
+ relationship_options.merge! :autosave => autosave
503
+ end
504
+
505
+ handle_leftover_options(options)
506
+ attr_accessible name
507
+ super name, relationship_options
508
+ end
509
+
510
+ # Callbacks
511
+
512
+ def after(event, callback = nil, &block)
513
+ case event
514
+ when :create
515
+ callback ? after_create(callback, &block) : after_create(&block)
516
+ when :save
517
+ callback ? after_save(callback, &block) : after_save(&block)
518
+ when :destroy
519
+ callback ? after_destroy(callback, &block) : after_destroy(&block)
520
+ else
521
+ raise "Implement `after #{event.inspect}` in DataMapper::Shim"
522
+ end
523
+ end
524
+
525
+ def before(event, callback = nil, &block)
526
+ case event
527
+ when :create
528
+ callback ? before_create(callback, &block) : before_create(&block)
529
+ when :save
530
+ callback ? before_save(callback, &block) : before_save(&block)
531
+ when :destroy
532
+ callback ? before_destroy(callback, &block) : before_destroy(&block)
533
+ when :valid?
534
+ # Per http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html,
535
+ # before_validation callbacks in AR cause a #save call to fail if they
536
+ # return false. Since this wasn't the case in DM, we need to wrap
537
+ # before_validation callbacks in a proc that always returns true.
538
+ if callback
539
+ raise "Need to handle before_validation with callback AND block" unless block.nil?
540
+ callback_with_true_return = proc do |*args|
541
+ self.__send__ callback
542
+ true
543
+ end
544
+ before_validation &callback_with_true_return
545
+ else
546
+ block_with_true_return = block && proc { |*args| self.instance_eval &block; true }
547
+ before_validation &block_with_true_return
548
+ end
549
+ when :update
550
+ callback ? before_update(callback, &block) : before_update(&block)
551
+ else
552
+ raise "Add before case statement for #{event} event"
553
+ end
554
+ end
555
+
556
+ # Validations
557
+
558
+ def validates_present(name, options = {})
559
+ validation_options = {}
560
+
561
+ if message = options.delete(:message)
562
+ validation_options.merge! :message => message
563
+ end
564
+
565
+ if condition = options.delete(:if)
566
+ validation_options.merge! :if => condition
567
+ end
568
+
569
+ if on = options.delete(:when)
570
+ if on.length > 1
571
+ raise "Can't handle :when option with more than one value (had #{on})"
572
+ else
573
+ validation_options.merge! :on => on.first
574
+ end
575
+ end
576
+
577
+ validates name, :presence => validation_options
578
+ handle_leftover_options(options)
579
+ end
580
+
581
+ def validates_is_unique(name, options = {})
582
+ validation_options = { :uniqueness => true }
583
+
584
+ if options.delete(:allow_nil)
585
+ validation_options.merge! :allow_nil => true
586
+ end
587
+
588
+ if scope = options.delete(:scope)
589
+ validation_options.merge! :uniqueness => { :scope => scope }
590
+ end
591
+
592
+ if condition = options.delete(:if)
593
+ validation_options.merge! :if => condition
594
+ end
595
+
596
+ validates name, validation_options
597
+ handle_leftover_options(options)
598
+ end
599
+
600
+ def validates_length(name, options)
601
+ validation_options = {}
602
+
603
+ if max = options.delete(:max)
604
+ validation_options.merge! :maximum => max
605
+ end
606
+
607
+ if max = options.delete(:maximum)
608
+ validation_options.merge! :maximum => max
609
+ end
610
+
611
+ if min = options.delete(:min)
612
+ validation_options.merge! :minimum => min
613
+ end
614
+
615
+ if condition = options.delete(:if)
616
+ validation_options.merge! :if => condition
617
+ end
618
+
619
+ if message = options.delete(:message)
620
+ validation_options.merge! :message => message
621
+ end
622
+
623
+ validates name, :length => validation_options
624
+ handle_leftover_options(options)
625
+ end
626
+
627
+ def validates_is_confirmed(name, options = {})
628
+ confirmation_options = { }
629
+
630
+ if message = options.delete(:message)
631
+ confirmation_options.merge! :message => message
632
+ end
633
+
634
+ if condition = options.delete(:if)
635
+ confirmation_options.merge! :if => condition
636
+ end
637
+
638
+ validates name, :confirmation => confirmation_options
639
+ validates :"#{name}_confirmation", :presence => true, :if => confirmation_options[:if]
640
+
641
+ handle_leftover_options(options)
642
+ end
643
+
644
+ def validates_with_block(name, &block)
645
+ validates_each name do |record, property_name|
646
+ valid, error_message = record.instance_exec(record.__send__(property_name), &block)
647
+ unless valid
648
+ record.errors.add(name, error_message)
649
+ end
650
+ end
651
+ end
652
+
653
+ def validates_format(name, options = {})
654
+ validation_options = {}
655
+
656
+ if with = options.delete(:with)
657
+ validation_options.merge! :format => { :with => with }
658
+ end
659
+
660
+ validates name, validation_options
661
+ handle_leftover_options(options)
662
+ end
663
+
664
+ def validates_within(name, options = {})
665
+ validation_options = {}
666
+
667
+ if set = options.delete(:set)
668
+ validation_options.merge! :inclusion => { :in => set }
669
+ end
670
+
671
+ validates name, validation_options
672
+ handle_leftover_options(options)
673
+ end
674
+
675
+ def validates_with_method(name, options)
676
+ validation_options = {}
677
+
678
+ if on = options.delete(:when)
679
+ case on
680
+ when Array
681
+ if on.length > 1
682
+ raise "Can't handle :when option with more than one value (had #{on})"
683
+ else
684
+ validation_options.merge! :on => on.first
685
+ end
686
+ else
687
+ validation_options.merge! :on => on
688
+ end
689
+ end
690
+
691
+ method = options.delete(:method)
692
+
693
+ # Validation methods made for DM return either true if the model validation passed,
694
+ # or [false, "error message"] if validation failed. Here we add the error message to
695
+ # the model's ActiveModel::Errors object.
696
+ block = proc do
697
+ valid, error_message = self.__send__ method
698
+ unless valid
699
+ self.errors.add(name, error_message)
700
+ end
701
+ end
702
+
703
+ validate validation_options, &block
704
+ handle_leftover_options(options)
705
+ end
706
+
707
+ protected
708
+
709
+ def handle_leftover_options(options)
710
+ return if options.empty?
711
+
712
+ method = caller[0][/`.*'/][1..-2]
713
+ message = "DataMapper::Shim##{method} doesn't yet handle #{options.inspect} (#{name})"
714
+
715
+ if RAISE_ON_UNHANDLED_OPTIONS
716
+ raise NotImplementedError, message
717
+ else
718
+ warn message
719
+ end
720
+ end
721
+ end
722
+ end
723
+ end
724
+