nobrainer 0.20.0 → 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|