nobrainer 0.20.0 → 0.21.0
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.
- checksums.yaml +4 -4
- data/lib/no_brainer/autoload.rb +0 -5
- data/lib/no_brainer/config.rb +14 -9
- data/lib/no_brainer/connection.rb +1 -3
- data/lib/no_brainer/criteria.rb +1 -1
- data/lib/no_brainer/criteria/aggregate.rb +2 -2
- data/lib/no_brainer/criteria/cache.rb +8 -3
- data/lib/no_brainer/criteria/core.rb +1 -5
- data/lib/no_brainer/criteria/delete.rb +1 -1
- data/lib/no_brainer/criteria/eager_load.rb +51 -0
- data/lib/no_brainer/criteria/order_by.rb +1 -1
- data/lib/no_brainer/criteria/scope.rb +3 -10
- data/lib/no_brainer/criteria/update.rb +8 -6
- data/lib/no_brainer/criteria/where.rb +50 -13
- data/lib/no_brainer/document.rb +2 -2
- data/lib/no_brainer/document/aliases.rb +0 -8
- data/lib/no_brainer/document/association/belongs_to.rb +6 -2
- data/lib/no_brainer/document/association/core.rb +5 -4
- data/lib/no_brainer/document/association/eager_loader.rb +7 -8
- data/lib/no_brainer/document/association/has_many.rb +22 -8
- data/lib/no_brainer/document/association/has_many_through.rb +12 -3
- data/lib/no_brainer/document/atomic_ops.rb +63 -61
- data/lib/no_brainer/document/attributes.rb +11 -3
- data/lib/no_brainer/document/core.rb +5 -2
- data/lib/no_brainer/document/criteria.rb +14 -5
- data/lib/no_brainer/document/dirty.rb +11 -16
- data/lib/no_brainer/document/index.rb +0 -6
- data/lib/no_brainer/document/index/meta_store.rb +1 -1
- data/lib/no_brainer/document/persistance.rb +12 -2
- data/lib/no_brainer/document/types.rb +13 -12
- data/lib/no_brainer/document/types/binary.rb +0 -4
- data/lib/no_brainer/document/types/boolean.rb +0 -1
- data/lib/no_brainer/document/types/geo.rb +1 -0
- data/lib/no_brainer/document/types/string.rb +3 -0
- data/lib/no_brainer/document/types/text.rb +18 -0
- data/lib/no_brainer/document/validation.rb +31 -6
- data/lib/no_brainer/document/validation/not_null.rb +15 -0
- data/lib/no_brainer/document/{uniqueness.rb → validation/uniqueness.rb} +11 -10
- data/lib/no_brainer/error.rb +20 -23
- data/lib/no_brainer/locale/en.yml +1 -0
- data/lib/no_brainer/lock.rb +114 -0
- data/lib/no_brainer/query_runner/database_on_demand.rb +0 -1
- data/lib/no_brainer/query_runner/missing_index.rb +1 -1
- data/lib/no_brainer/query_runner/run_options.rb +0 -3
- data/lib/no_brainer/query_runner/table_on_demand.rb +2 -3
- data/lib/no_brainer/rql.rb +0 -4
- data/lib/nobrainer.rb +1 -1
- metadata +8 -4
- data/lib/no_brainer/criteria/preload.rb +0 -44
@@ -2,7 +2,7 @@ module NoBrainer::Document::Core
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
singleton_class.class_eval do
|
5
|
-
attr_accessor :_all
|
5
|
+
attr_accessor :_all, :_all_nobrainer
|
6
6
|
|
7
7
|
def all
|
8
8
|
Rails.application.eager_load! if defined?(Rails.application.eager_load!)
|
@@ -10,10 +10,12 @@ module NoBrainer::Document::Core
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
self._all = []
|
13
|
+
self._all_nobrainer = []
|
13
14
|
|
14
15
|
include ActiveModel::Conversion
|
15
16
|
|
16
17
|
def to_key
|
18
|
+
# ActiveModel::Conversion stuff
|
17
19
|
[pk_value]
|
18
20
|
end
|
19
21
|
|
@@ -22,6 +24,7 @@ module NoBrainer::Document::Core
|
|
22
24
|
extend ActiveModel::Naming
|
23
25
|
extend ActiveModel::Translation
|
24
26
|
|
25
|
-
|
27
|
+
list_name = self.name =~ /^NoBrainer::/ ? :_all_nobrainer : :_all
|
28
|
+
NoBrainer::Document::Core.__send__(list_name) << self
|
26
29
|
end
|
27
30
|
end
|
@@ -6,8 +6,9 @@ module NoBrainer::Document::Criteria
|
|
6
6
|
end
|
7
7
|
|
8
8
|
included do
|
9
|
-
cattr_accessor :default_scope_proc, :instance_accessor => false
|
10
9
|
cattr_accessor :perf_warnings_disabled, :instance_accessor => false
|
10
|
+
singleton_class.send(:attr_accessor, :default_scopes)
|
11
|
+
self.default_scopes = []
|
11
12
|
end
|
12
13
|
|
13
14
|
module ClassMethods
|
@@ -21,7 +22,7 @@ module NoBrainer::Document::Criteria
|
|
21
22
|
:with_cache, :without_cache, # Cache
|
22
23
|
:count, :empty?, :any?, # Count
|
23
24
|
:delete_all, :destroy_all, # Delete
|
24
|
-
:
|
25
|
+
:preload, :eager_load, # EagerLoad
|
25
26
|
:each, :to_a, # Enumerable
|
26
27
|
:first, :last, :first!, :last!, :sample, # First
|
27
28
|
:min, :max, :sum, :avg, # Aggregate
|
@@ -42,9 +43,17 @@ module NoBrainer::Document::Criteria
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def default_scope(criteria=nil, &block)
|
45
|
-
criteria
|
46
|
-
raise "
|
47
|
-
|
46
|
+
criteria_proc = block || (criteria.is_a?(Proc) ? criteria : proc { criteria })
|
47
|
+
raise "default_scope only accepts a criteria or a proc that returns criteria" unless criteria_proc.is_a?(Proc)
|
48
|
+
|
49
|
+
([self] + self.descendants).each do |model|
|
50
|
+
model.default_scopes << criteria_proc
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def inherited(subclass)
|
55
|
+
subclass.default_scopes = self.default_scopes.dup
|
56
|
+
super
|
48
57
|
end
|
49
58
|
|
50
59
|
def selector_for(pk)
|
@@ -31,14 +31,10 @@ module NoBrainer::Document::Dirty
|
|
31
31
|
changes.keys
|
32
32
|
end
|
33
33
|
|
34
|
-
def read_attribute_for_change(attr)
|
35
|
-
read_attribute(attr)
|
36
|
-
end
|
37
|
-
|
38
34
|
def changes
|
39
35
|
result = {}.with_indifferent_access
|
40
36
|
@_old_attributes.each do |attr, old_value|
|
41
|
-
current_value =
|
37
|
+
current_value = read_attribute(attr)
|
42
38
|
if current_value != old_value || !@_old_attributes_keys.include?(attr)
|
43
39
|
result[attr] = [old_value, current_value]
|
44
40
|
end
|
@@ -46,16 +42,15 @@ module NoBrainer::Document::Dirty
|
|
46
42
|
result
|
47
43
|
end
|
48
44
|
|
49
|
-
|
50
|
-
|
51
|
-
current_value
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
45
|
+
class None; end
|
46
|
+
def attribute_may_change(attr, current_value = None)
|
47
|
+
if current_value == None
|
48
|
+
current_value = begin
|
49
|
+
assert_access_field(attr)
|
50
|
+
read_attribute(attr)
|
51
|
+
rescue NoBrainer::Error::MissingAttribute => e
|
52
|
+
e
|
56
53
|
end
|
57
|
-
rescue NoBrainer::Error::MissingAttribute => e
|
58
|
-
e
|
59
54
|
end
|
60
55
|
|
61
56
|
unless @_old_attributes.has_key?(attr)
|
@@ -84,7 +79,7 @@ module NoBrainer::Document::Dirty
|
|
84
79
|
inject_in_layer :dirty_tracking do
|
85
80
|
define_method("#{attr}_change") do
|
86
81
|
if @_old_attributes.has_key?(attr)
|
87
|
-
result = [@_old_attributes[attr],
|
82
|
+
result = [@_old_attributes[attr], read_attribute(attr)]
|
88
83
|
result if result.first != result.last || !@_old_attributes_keys.include?(attr)
|
89
84
|
end
|
90
85
|
end
|
@@ -94,7 +89,7 @@ module NoBrainer::Document::Dirty
|
|
94
89
|
end
|
95
90
|
|
96
91
|
define_method("#{attr}_was") do
|
97
|
-
@_old_attributes.has_key?(attr) ? @_old_attributes[attr] :
|
92
|
+
@_old_attributes.has_key?(attr) ? @_old_attributes[attr] : read_attribute(attr)
|
98
93
|
end
|
99
94
|
end
|
100
95
|
end
|
@@ -14,12 +14,6 @@ module NoBrainer::Document::Index
|
|
14
14
|
def index(name, *args)
|
15
15
|
name = name.to_sym
|
16
16
|
options = args.extract_options!
|
17
|
-
|
18
|
-
if options[:as]
|
19
|
-
STDERR.puts "[NoBrainer] `:as' is deprecated and will be removed. Please use `:store_as' instead (from the #{self} model)"
|
20
|
-
options[:store_as] = options.delete(:as)
|
21
|
-
end
|
22
|
-
|
23
17
|
options.assert_valid_keys(*VALID_INDEX_OPTIONS)
|
24
18
|
|
25
19
|
raise "Too many arguments: #{args}" if args.size > 1
|
@@ -24,7 +24,7 @@ class NoBrainer::Document::Index::MetaStore
|
|
24
24
|
def self.on(db_name, &block)
|
25
25
|
old_db_name = Thread.current[:nobrainer_meta_store_db]
|
26
26
|
Thread.current[:nobrainer_meta_store_db] = db_name
|
27
|
-
|
27
|
+
block.call
|
28
28
|
ensure
|
29
29
|
Thread.current[:nobrainer_meta_store_db] = old_db_name
|
30
30
|
end
|
@@ -113,13 +113,11 @@ module NoBrainer::Document::Persistance
|
|
113
113
|
assign_attributes(attrs, options)
|
114
114
|
save?(options)
|
115
115
|
end
|
116
|
-
alias_method :update_attributes?, :update?
|
117
116
|
|
118
117
|
def update(*args)
|
119
118
|
update?(*args) or raise NoBrainer::Error::DocumentInvalid, self
|
120
119
|
nil
|
121
120
|
end
|
122
|
-
alias_method :update_attributes, :update
|
123
121
|
|
124
122
|
def update!(*args)
|
125
123
|
update(*args)
|
@@ -127,6 +125,18 @@ module NoBrainer::Document::Persistance
|
|
127
125
|
end
|
128
126
|
alias_method :update_attributes!, :update!
|
129
127
|
|
128
|
+
def update_attributes?(*args)
|
129
|
+
update?(*args).tap { STDERR.puts "[NoBrainer] update_attribute?() is deprecated. Please use update?() instead" }
|
130
|
+
end
|
131
|
+
|
132
|
+
def update_attributes(*args)
|
133
|
+
update(*args).tap { STDERR.puts "[NoBrainer] update_attribute() is deprecated. Please use update() instead" }
|
134
|
+
end
|
135
|
+
|
136
|
+
def update_attributes!(*args)
|
137
|
+
update!(*args).tap { STDERR.puts "[NoBrainer] update_attribute!() is deprecated. Please use update() instead" }
|
138
|
+
end
|
139
|
+
|
130
140
|
def delete
|
131
141
|
unless @destroyed
|
132
142
|
NoBrainer.run { selector.delete }
|
@@ -6,7 +6,7 @@ module NoBrainer::Document::Types
|
|
6
6
|
def add_type_errors
|
7
7
|
return unless @pending_type_errors
|
8
8
|
@pending_type_errors.each do |name, error|
|
9
|
-
errors.add(name, :invalid_type,
|
9
|
+
errors.add(name, :invalid_type, error.error)
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
@@ -22,7 +22,10 @@ module NoBrainer::Document::Types
|
|
22
22
|
module ClassMethods
|
23
23
|
def cast_user_to_model_for(attr, value)
|
24
24
|
type = fields[attr.to_sym].try(:[], :type)
|
25
|
-
return value if type.nil? || value.nil? ||
|
25
|
+
return value if type.nil? || value.nil? ||
|
26
|
+
value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) ||
|
27
|
+
value.is_a?(RethinkDB::RQL)
|
28
|
+
|
26
29
|
if type.respond_to?(:nobrainer_cast_user_to_model)
|
27
30
|
type.nobrainer_cast_user_to_model(value)
|
28
31
|
else
|
@@ -30,9 +33,7 @@ module NoBrainer::Document::Types
|
|
30
33
|
value
|
31
34
|
end
|
32
35
|
rescue NoBrainer::Error::InvalidType => error
|
33
|
-
error.type
|
34
|
-
error.value = value
|
35
|
-
error.attr_name = attr
|
36
|
+
error.update(:model => self, :value => value, :attr_name => attr, :type => type)
|
36
37
|
raise error
|
37
38
|
end
|
38
39
|
|
@@ -62,14 +63,15 @@ module NoBrainer::Document::Types
|
|
62
63
|
|
63
64
|
return unless options[:type]
|
64
65
|
|
65
|
-
|
66
|
-
|
66
|
+
raise "Please use a class for the type option" unless options[:type].is_a?(Class)
|
67
67
|
case options[:type].to_s
|
68
68
|
when "NoBrainer::Geo::Circle" then raise "Cannot store circles :("
|
69
69
|
when "NoBrainer::Geo::Polygon", "NoBrainer::Geo::LineString"
|
70
70
|
raise "Make a request on github if you'd like to store polygons/linestrings"
|
71
71
|
end
|
72
72
|
|
73
|
+
NoBrainer::Document::Types.load_type_extensions(options[:type]) if options[:type]
|
74
|
+
|
73
75
|
inject_in_layer :types do
|
74
76
|
define_method("#{attr}=") do |value|
|
75
77
|
begin
|
@@ -96,11 +98,10 @@ module NoBrainer::Document::Types
|
|
96
98
|
end
|
97
99
|
end
|
98
100
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
Geo = NoBrainer::Geo
|
101
|
+
%w(binary boolean text geo).each do |type|
|
102
|
+
require File.join(File.dirname(__FILE__), 'types', type)
|
103
|
+
const_set(type.camelize, NoBrainer.const_get(type.camelize))
|
104
|
+
end
|
104
105
|
|
105
106
|
class << self
|
106
107
|
mattr_accessor :loaded_extensions
|
@@ -0,0 +1 @@
|
|
1
|
+
# Look in lib/no_brainer/geo.rb instead
|
@@ -7,6 +7,9 @@ class String
|
|
7
7
|
when String then value
|
8
8
|
when Symbol then value.to_s
|
9
9
|
else raise InvalidType
|
10
|
+
end.tap do |str|
|
11
|
+
max_length = NoBrainer::Config.max_string_length
|
12
|
+
raise InvalidType.new(:error => { :message => :too_long, :count => max_length }) if str.size > max_length
|
10
13
|
end
|
11
14
|
end
|
12
15
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class NoBrainer::Text
|
2
|
+
def initialize; raise; end
|
3
|
+
def self.inspect; 'Text'; end
|
4
|
+
def self.to_s; inspect; end
|
5
|
+
def self.name; inspect; end
|
6
|
+
|
7
|
+
module NoBrainerExtentions
|
8
|
+
InvalidType = NoBrainer::Error::InvalidType
|
9
|
+
|
10
|
+
def nobrainer_cast_user_to_model(value)
|
11
|
+
case value
|
12
|
+
when String then value
|
13
|
+
else raise InvalidType
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
extend NoBrainerExtentions
|
18
|
+
end
|
@@ -1,8 +1,11 @@
|
|
1
1
|
module NoBrainer::Document::Validation
|
2
|
+
extend NoBrainer::Autoload
|
2
3
|
extend ActiveSupport::Concern
|
3
4
|
include ActiveModel::Validations
|
4
5
|
include ActiveModel::Validations::Callbacks
|
5
6
|
|
7
|
+
autoload_and_include :Uniqueness, :NotNull
|
8
|
+
|
6
9
|
included do
|
7
10
|
# We don't want before_validation returning false to halt the chain.
|
8
11
|
define_callbacks :validation, :skip_after_callbacks_if_terminated => true,
|
@@ -12,7 +15,7 @@ module NoBrainer::Document::Validation
|
|
12
15
|
def valid?(context=nil, options={})
|
13
16
|
context ||= new_record? ? :create : :update
|
14
17
|
|
15
|
-
|
18
|
+
# XXX Monkey Patching, because we need to have control on errors.clear
|
16
19
|
current_context, self.validation_context = validation_context, context
|
17
20
|
errors.clear unless options[:clear_errors] == false
|
18
21
|
run_validations!
|
@@ -20,15 +23,37 @@ module NoBrainer::Document::Validation
|
|
20
23
|
self.validation_context = current_context
|
21
24
|
end
|
22
25
|
|
26
|
+
SHORTHANDS = { :format => :format, :length => :length, :required => :presence,
|
27
|
+
:uniq => :uniqueness, :unique => :uniqueness, :in => :inclusion }
|
28
|
+
|
23
29
|
module ClassMethods
|
24
30
|
def _field(attr, options={})
|
25
31
|
super
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
validates(attr,
|
30
|
-
|
32
|
+
|
33
|
+
shorthands = SHORTHANDS
|
34
|
+
shorthands = shorthands.merge(:required => :not_null) if options[:type] == NoBrainer::Boolean
|
35
|
+
shorthands.each { |k,v| validates(attr, v => options[k]) if options.has_key?(k) }
|
36
|
+
|
31
37
|
validates(attr, options[:validates]) if options[:validates]
|
38
|
+
validates(attr, :length => { :minimum => options[:min_length] }) if options[:min_length]
|
39
|
+
validates(attr, :length => { :maximum => options[:max_length] }) if options[:max_length]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class ActiveModel::EachValidator
|
45
|
+
def should_validate_field?(record, attribute)
|
46
|
+
record.new_record? || record.__send__("#{attribute}_changed?")
|
47
|
+
end
|
48
|
+
|
49
|
+
# XXX Monkey Patching :(
|
50
|
+
def validate(record)
|
51
|
+
attributes.each do |attribute|
|
52
|
+
next unless should_validate_field?(record, attribute) # <--- Added
|
53
|
+
value = record.read_attribute_for_validation(attribute)
|
54
|
+
next if value.is_a?(NoBrainer::Document::AtomicOps::PendingAtomic) # <--- Added
|
55
|
+
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
56
|
+
validate_each(record, attribute, value)
|
32
57
|
end
|
33
58
|
end
|
34
59
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module NoBrainer::Document::Validation::NotNull
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def validates_not_null(*attr_names)
|
6
|
+
validates_with(NotNullValidator, _merge_attributes(attr_names))
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class NotNullValidator < ActiveModel::EachValidator
|
11
|
+
def validate_each(doc, attr, value)
|
12
|
+
doc.errors.add(attr, :undefined, options) if value.nil?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module NoBrainer::Document::Uniqueness
|
1
|
+
module NoBrainer::Document::Validation::Uniqueness
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
def _create(options={})
|
@@ -27,7 +27,7 @@ module NoBrainer::Document::Uniqueness
|
|
27
27
|
self.class.unique_validators
|
28
28
|
.map { |validator| validator.attributes.map { |attr| [attr, validator] } }
|
29
29
|
.flatten(1)
|
30
|
-
.select { |f, validator| validator.
|
30
|
+
.select { |f, validator| validator.should_validate_field?(self, f) }
|
31
31
|
.map { |f, options| _lock_key_from_field(f) }
|
32
32
|
.sort
|
33
33
|
.uniq
|
@@ -51,7 +51,7 @@ module NoBrainer::Document::Uniqueness
|
|
51
51
|
|
52
52
|
module ClassMethods
|
53
53
|
def validates_uniqueness_of(*attr_names)
|
54
|
-
validates_with
|
54
|
+
validates_with(UniquenessValidator, _merge_attributes(attr_names))
|
55
55
|
end
|
56
56
|
|
57
57
|
def inherited(subclass)
|
@@ -72,19 +72,20 @@ module NoBrainer::Document::Uniqueness
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
def
|
76
|
-
(scope + [field]).any? { |f| doc.__send__("#{f}_changed?") }
|
75
|
+
def should_validate_field?(doc, field)
|
76
|
+
doc.new_record? || (scope + [field]).any? { |f| doc.__send__("#{f}_changed?") }
|
77
77
|
end
|
78
78
|
|
79
79
|
def validate_each(doc, attr, value)
|
80
|
-
return true unless should_validate_uniquess_of?(doc, attr)
|
81
|
-
|
82
80
|
criteria = doc.root_class.unscoped.where(attr => value)
|
83
81
|
criteria = apply_scopes(criteria, doc)
|
84
82
|
criteria = exclude_doc(criteria, doc) if doc.persisted?
|
85
|
-
|
86
|
-
|
87
|
-
|
83
|
+
doc.errors.add(attr, :taken, options.except(:scope).merge(:value => value)) unless criteria.empty?
|
84
|
+
rescue NoBrainer::Error::InvalidType
|
85
|
+
# We can't run the uniqueness validator: where() won't accept bad types
|
86
|
+
# and we have some values that don't have the right type.
|
87
|
+
# Note that it's fine to not add errors because the type validations will
|
88
|
+
# prevent the document from being saved.
|
88
89
|
end
|
89
90
|
|
90
91
|
def apply_scopes(criteria, doc)
|
data/lib/no_brainer/error.rb
CHANGED
@@ -5,25 +5,13 @@ module NoBrainer::Error
|
|
5
5
|
class ChildrenExist < RuntimeError; end
|
6
6
|
class CannotUseIndex < RuntimeError; end
|
7
7
|
class MissingIndex < RuntimeError; end
|
8
|
-
class InvalidType < RuntimeError; end
|
9
8
|
class AssociationNotPersisted < RuntimeError; end
|
10
9
|
class ReadonlyField < RuntimeError; end
|
11
10
|
class MissingAttribute < RuntimeError; end
|
12
11
|
class UnknownAttribute < RuntimeError; end
|
13
12
|
class AtomicBlock < RuntimeError; end
|
14
|
-
|
15
|
-
class
|
16
|
-
attr_accessor :instance, :field, :value
|
17
|
-
def initialize(instance, field, value)
|
18
|
-
@instance = instance
|
19
|
-
@field = field
|
20
|
-
@value = value
|
21
|
-
end
|
22
|
-
|
23
|
-
def message
|
24
|
-
"Cannot read #{field}, atomic operations are pending"
|
25
|
-
end
|
26
|
-
end
|
13
|
+
class LostLock < RuntimeError; end
|
14
|
+
class LockUnavailable < RuntimeError; end
|
27
15
|
|
28
16
|
class DocumentInvalid < RuntimeError
|
29
17
|
attr_accessor :instance
|
@@ -37,23 +25,32 @@ module NoBrainer::Error
|
|
37
25
|
end
|
38
26
|
|
39
27
|
class InvalidType < RuntimeError
|
40
|
-
attr_accessor :attr_name, :value, :type
|
28
|
+
attr_accessor :model, :attr_name, :value, :type, :error
|
41
29
|
def initialize(options={})
|
42
|
-
|
43
|
-
|
44
|
-
|
30
|
+
update(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def update(options={})
|
34
|
+
options.assert_valid_keys(:model, :attr_name, :type, :value, :error)
|
35
|
+
options.each { |k,v| instance_variable_set("@#{k}", v) }
|
45
36
|
end
|
46
37
|
|
47
38
|
def human_type_name
|
48
39
|
type.to_s.underscore.humanize.downcase
|
49
40
|
end
|
50
41
|
|
42
|
+
def error
|
43
|
+
# dup because errors.add eventually .delete() on our option.
|
44
|
+
@error.nil? ? (type && { :type => human_type_name }) : @error.dup
|
45
|
+
end
|
46
|
+
|
51
47
|
def message
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
48
|
+
return super unless model && attr_name && error
|
49
|
+
value = self.value
|
50
|
+
mock = model.allocate
|
51
|
+
mock.singleton_class.send(:define_method, :read_attribute_for_validation) { |_| value }
|
52
|
+
mock.errors.add(attr_name, :invalid_type, error)
|
53
|
+
mock.errors.full_messages.first
|
57
54
|
end
|
58
55
|
end
|
59
56
|
|