active_model-better_errors 1.6.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.
- data/.document +5 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +20 -0
- data/README.md +183 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/active_model-better_errors.gemspec +113 -0
- data/lib/active_model/better_errors.rb +15 -0
- data/lib/active_model/error_collecting.rb +49 -0
- data/lib/active_model/error_collecting/array_reporter.rb +9 -0
- data/lib/active_model/error_collecting/core_ext.rb +6 -0
- data/lib/active_model/error_collecting/emulation.rb +65 -0
- data/lib/active_model/error_collecting/error_collection.rb +86 -0
- data/lib/active_model/error_collecting/error_message.rb +88 -0
- data/lib/active_model/error_collecting/error_message_set.rb +35 -0
- data/lib/active_model/error_collecting/errors.rb +52 -0
- data/lib/active_model/error_collecting/hash_reporter.rb +9 -0
- data/lib/active_model/error_collecting/human_array_reporter.rb +9 -0
- data/lib/active_model/error_collecting/human_hash_reporter.rb +15 -0
- data/lib/active_model/error_collecting/human_message_formatter.rb +60 -0
- data/lib/active_model/error_collecting/human_message_reporter.rb +32 -0
- data/lib/active_model/error_collecting/machine_array_reporter.rb +19 -0
- data/lib/active_model/error_collecting/machine_hash_reporter.rb +22 -0
- data/lib/active_model/error_collecting/message_reporter.rb +17 -0
- data/lib/active_model/error_collecting/reporter.rb +14 -0
- data/spec/lib/active_model/better_errors_spec.rb +7 -0
- data/spec/lib/active_model/error_collecting/emulation_spec.rb +45 -0
- data/spec/lib/active_model/error_collecting/error_collection_spec.rb +205 -0
- data/spec/lib/active_model/error_collecting/error_message_set_spec.rb +96 -0
- data/spec/lib/active_model/error_collecting/error_message_spec.rb +293 -0
- data/spec/lib/active_model/error_collecting/errors_spec.rb +95 -0
- data/spec/lib/active_model/error_collecting/human_array_reporter_spec.rb +33 -0
- data/spec/lib/active_model/error_collecting/human_hash_reporter_spec.rb +32 -0
- data/spec/lib/active_model/error_collecting/human_message_formatter_spec.rb +22 -0
- data/spec/lib/active_model/error_collecting/human_message_reporter_spec.rb +61 -0
- data/spec/lib/active_model/error_collecting/machine_array_reporter_spec.rb +40 -0
- data/spec/lib/active_model/error_collecting/machine_hash_reporter_spec.rb +40 -0
- data/spec/lib/active_model/error_collecting_spec.rb +22 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/models.rb +28 -0
- data/test/integration.rb +10 -0
- metadata +274 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
#
|
2
|
+
# Allows included class to emulate ActiveModel::Errors class
|
3
|
+
# by defining a set of methods to delegate to facilities
|
4
|
+
# in this gem.
|
5
|
+
#
|
6
|
+
module ActiveModel
|
7
|
+
module ErrorCollecting
|
8
|
+
module Emulation
|
9
|
+
MODEL_METHODS = [
|
10
|
+
:clear, :include?, :get, :set, :delete, :[], :[]=,
|
11
|
+
:each, :size, :values, :keys, :count, :empty?, :any?,
|
12
|
+
:added?, :entries
|
13
|
+
]
|
14
|
+
|
15
|
+
MESSAGE_REPORTER_METHODS = [
|
16
|
+
:full_messages, :full_message, :generate_message
|
17
|
+
]
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.class_eval do
|
21
|
+
extend Forwardable
|
22
|
+
def_delegators :error_collection, *MODEL_METHODS
|
23
|
+
def_delegators :message_reporter, *MESSAGE_REPORTER_METHODS
|
24
|
+
def_delegators :hash_reporter, :to_hash
|
25
|
+
def_delegators :array_reporter, :to_a
|
26
|
+
|
27
|
+
alias_method :blank?, :empty?
|
28
|
+
alias_method :has_key?, :include?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_on_empty(attributes, options = {})
|
33
|
+
[attributes].flatten.each do |attribute|
|
34
|
+
value = @base.send(:read_attribute_for_validation, attribute)
|
35
|
+
is_empty = value.respond_to?(:empty?) ? value.empty? : false
|
36
|
+
add(attribute, :empty, options) if value.nil? || is_empty
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_on_blank(attributes, options = {})
|
41
|
+
[attributes].flatten.each do |attribute|
|
42
|
+
value = @base.send(:read_attribute_for_validation, attribute)
|
43
|
+
add(attribute, :blank, options) if value.blank?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add(attribute, message, options = {})
|
48
|
+
if options[:strict]
|
49
|
+
error = ErrorMessage.build(attribute, message, options)
|
50
|
+
message = HumanMessageFormatter.new(@base, error).format_message
|
51
|
+
raise ActiveModel::StrictValidationFailed, full_message(attribute, message)
|
52
|
+
end
|
53
|
+
error_collection.add attribute, message, options
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_xml(options={})
|
57
|
+
to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
|
58
|
+
end
|
59
|
+
|
60
|
+
def as_json(options=nil)
|
61
|
+
to_hash
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module ErrorCollecting
|
3
|
+
class ErrorCollection
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :base
|
7
|
+
def initialize(base)
|
8
|
+
@base = base
|
9
|
+
@collection = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def clear
|
13
|
+
@collection.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
def include?(attribute)
|
17
|
+
( v = @collection[attribute] ) && v.any?
|
18
|
+
end
|
19
|
+
|
20
|
+
def get(attribute)
|
21
|
+
@collection[attribute]
|
22
|
+
end
|
23
|
+
|
24
|
+
def set(attribute, errors)
|
25
|
+
return delete attribute if errors.nil?
|
26
|
+
@collection[attribute] = ErrorMessageSet.new(base, attribute, errors)
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete(attribute)
|
30
|
+
@collection.delete attribute
|
31
|
+
end
|
32
|
+
|
33
|
+
def [](attribute)
|
34
|
+
get(attribute.to_sym) || set(attribute.to_sym, [])
|
35
|
+
end
|
36
|
+
|
37
|
+
def []=(attribute, error)
|
38
|
+
self[attribute] << error
|
39
|
+
end
|
40
|
+
|
41
|
+
def each
|
42
|
+
@collection.each_key do |attribute|
|
43
|
+
self[attribute].each { |error_message| yield attribute, error_message }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def size
|
48
|
+
values.inject(0){ |sum, set| sum += set.size }
|
49
|
+
end
|
50
|
+
alias_method :count, :size
|
51
|
+
|
52
|
+
def values
|
53
|
+
@collection.values
|
54
|
+
end
|
55
|
+
|
56
|
+
def keys
|
57
|
+
@collection.keys
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_a
|
61
|
+
array = []
|
62
|
+
@collection.each_key do |attribute|
|
63
|
+
self[attribute].each { |error_message| array << error_message }
|
64
|
+
end
|
65
|
+
|
66
|
+
array
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_hash
|
70
|
+
@collection.dup
|
71
|
+
end
|
72
|
+
|
73
|
+
def empty?
|
74
|
+
size == 0
|
75
|
+
end
|
76
|
+
|
77
|
+
def add(attribute, message, options = {})
|
78
|
+
self[attribute] << [ message, options ]
|
79
|
+
end
|
80
|
+
|
81
|
+
def added?(attribute, message = nil, options = {})
|
82
|
+
self[attribute].include? ErrorMessage.build(base, attribute, message, options)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module ErrorCollecting
|
3
|
+
class ErrorMessage
|
4
|
+
include Comparable
|
5
|
+
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
|
6
|
+
|
7
|
+
# return the message either as nil, symbol, or string
|
8
|
+
def self.normalize(message)
|
9
|
+
case message
|
10
|
+
when nil
|
11
|
+
nil
|
12
|
+
when Symbol
|
13
|
+
message
|
14
|
+
when Proc
|
15
|
+
message.call
|
16
|
+
else
|
17
|
+
message.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.identify(message, override)
|
22
|
+
symbol = string = nil
|
23
|
+
|
24
|
+
message = normalize message
|
25
|
+
override = normalize override
|
26
|
+
|
27
|
+
symbol = message if message.is_a?(Symbol)
|
28
|
+
symbol = override if override.is_a?(Symbol)
|
29
|
+
|
30
|
+
string = message if message.is_a?(String)
|
31
|
+
string = override if override.is_a?(String)
|
32
|
+
|
33
|
+
string = nil if string.blank?
|
34
|
+
|
35
|
+
[symbol, string]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.build(base, attribute, message, options=nil)
|
39
|
+
options = options ? options : {}
|
40
|
+
options = options.except(*CALLBACKS_OPTIONS)
|
41
|
+
|
42
|
+
symbol, string = identify message, options.delete(:message)
|
43
|
+
|
44
|
+
new(base, attribute, symbol, string, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :base, :attribute, :type, :message, :options
|
48
|
+
|
49
|
+
def initialize(base, attribute, type, message, options = {})
|
50
|
+
@base = base
|
51
|
+
@attribute = attribute
|
52
|
+
@type = type
|
53
|
+
@message = message
|
54
|
+
@options = options
|
55
|
+
end
|
56
|
+
|
57
|
+
def <=> (other)
|
58
|
+
to_hash <=> other.to_hash
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_hash
|
62
|
+
{
|
63
|
+
attribute: attribute,
|
64
|
+
type: type,
|
65
|
+
message: message,
|
66
|
+
options: options
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def hash
|
71
|
+
to_hash.hash
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s
|
75
|
+
HumanMessageFormatter.new(base, self).format_message
|
76
|
+
end
|
77
|
+
|
78
|
+
def inspect
|
79
|
+
to_s.inspect
|
80
|
+
end
|
81
|
+
|
82
|
+
def ==(other)
|
83
|
+
return type == other if other.is_a?(Symbol)
|
84
|
+
to_s == other.to_s
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module ErrorCollecting
|
5
|
+
class ErrorMessageSet < Array
|
6
|
+
def initialize(base, attribute, errors=[])
|
7
|
+
@base = base
|
8
|
+
@attribute = attribute
|
9
|
+
errors.each do |error|
|
10
|
+
push(*error)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(error)
|
15
|
+
super ErrorMessage.build(@base, @attribute, *error)
|
16
|
+
end
|
17
|
+
|
18
|
+
def push(message, options = {})
|
19
|
+
super ErrorMessage.build(@base, @attribute, message, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(index, error)
|
23
|
+
super index, ErrorMessage.build(@base, @attribute, *error)
|
24
|
+
end
|
25
|
+
|
26
|
+
def insert(index, error)
|
27
|
+
super index, ErrorMessage.build(@base, @attribute, *error)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_a
|
31
|
+
dup
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module ErrorCollecting
|
3
|
+
class Errors
|
4
|
+
include Emulation
|
5
|
+
|
6
|
+
attr_reader :base
|
7
|
+
def initialize(base)
|
8
|
+
@base = base
|
9
|
+
@reporters = {}
|
10
|
+
@reporter_classes = reporter_classes
|
11
|
+
end
|
12
|
+
|
13
|
+
def error_collection
|
14
|
+
@error_collection ||= ErrorCollection.new(@base)
|
15
|
+
end
|
16
|
+
|
17
|
+
def message_reporter
|
18
|
+
get_reporter(:message)
|
19
|
+
end
|
20
|
+
|
21
|
+
def hash_reporter
|
22
|
+
get_reporter(:hash)
|
23
|
+
end
|
24
|
+
|
25
|
+
def array_reporter
|
26
|
+
get_reporter(:array)
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_reporter(type, reporter)
|
30
|
+
type = type.to_s
|
31
|
+
klass = ::ActiveModel::ErrorCollecting.get_reporter_class(type, reporter)
|
32
|
+
@reporter_classes[type] = klass
|
33
|
+
@reporters.delete type
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_reporter(type)
|
37
|
+
type = type.to_s
|
38
|
+
klass = get_reporter_class(type)
|
39
|
+
@reporters[type] = klass.new(error_collection)
|
40
|
+
end
|
41
|
+
|
42
|
+
def reporter_classes
|
43
|
+
::ActiveModel::ErrorCollecting.reporters
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_reporter_class(type)
|
47
|
+
type = type.to_s
|
48
|
+
@reporter_classes[type]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module ErrorCollecting
|
3
|
+
class HumanHashReporter < HashReporter
|
4
|
+
def to_hash
|
5
|
+
collection.to_hash.inject({}) do |hash, kv|
|
6
|
+
attribute, error_message_set = kv
|
7
|
+
hash[attribute] = error_message_set.map do |error_message|
|
8
|
+
HumanMessageFormatter.new(base, error_message).format_message
|
9
|
+
end
|
10
|
+
hash
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module ErrorCollecting
|
3
|
+
class HumanMessageFormatter
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def_delegators :@error_message, :attribute, :message, :options
|
7
|
+
|
8
|
+
attr_reader :base, :error_message
|
9
|
+
|
10
|
+
def initialize(base, error_message)
|
11
|
+
@base, @error_message = base, error_message
|
12
|
+
end
|
13
|
+
|
14
|
+
def type
|
15
|
+
@error_message.type
|
16
|
+
end
|
17
|
+
|
18
|
+
def format_message
|
19
|
+
return message if message && type.nil?
|
20
|
+
|
21
|
+
type = self.type || :invalid
|
22
|
+
|
23
|
+
keys = i18n_keys
|
24
|
+
key = keys.shift
|
25
|
+
|
26
|
+
value = (attribute != :base ? base.send(:read_attribute_for_validation, attribute) : nil)
|
27
|
+
|
28
|
+
options = {
|
29
|
+
:default => keys,
|
30
|
+
:model => base.class.model_name.human,
|
31
|
+
:attribute => base.class.human_attribute_name(attribute),
|
32
|
+
:value => value
|
33
|
+
}.merge(self.options)
|
34
|
+
|
35
|
+
I18n.translate key, options
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def i18n_keys
|
41
|
+
if base.class.respond_to?(:i18n_scope)
|
42
|
+
keys = base.class.lookup_ancestors.map do |klass|
|
43
|
+
[ :"#{base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
|
44
|
+
:"#{base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
|
45
|
+
end
|
46
|
+
else
|
47
|
+
keys = []
|
48
|
+
end
|
49
|
+
keys << message
|
50
|
+
keys << :"#{base.class.i18n_scope}.errors.messages.#{type}" if base.class.respond_to?(:i18n_scope)
|
51
|
+
keys << :"errors.attributes.#{attribute}.#{type}"
|
52
|
+
keys << :"errors.messages.#{type}"
|
53
|
+
|
54
|
+
keys.compact!
|
55
|
+
keys.flatten!
|
56
|
+
keys
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|