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.
Files changed (45) hide show
  1. data/.document +5 -0
  2. data/.gitmodules +3 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +17 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.md +183 -0
  8. data/Rakefile +57 -0
  9. data/VERSION +1 -0
  10. data/active_model-better_errors.gemspec +113 -0
  11. data/lib/active_model/better_errors.rb +15 -0
  12. data/lib/active_model/error_collecting.rb +49 -0
  13. data/lib/active_model/error_collecting/array_reporter.rb +9 -0
  14. data/lib/active_model/error_collecting/core_ext.rb +6 -0
  15. data/lib/active_model/error_collecting/emulation.rb +65 -0
  16. data/lib/active_model/error_collecting/error_collection.rb +86 -0
  17. data/lib/active_model/error_collecting/error_message.rb +88 -0
  18. data/lib/active_model/error_collecting/error_message_set.rb +35 -0
  19. data/lib/active_model/error_collecting/errors.rb +52 -0
  20. data/lib/active_model/error_collecting/hash_reporter.rb +9 -0
  21. data/lib/active_model/error_collecting/human_array_reporter.rb +9 -0
  22. data/lib/active_model/error_collecting/human_hash_reporter.rb +15 -0
  23. data/lib/active_model/error_collecting/human_message_formatter.rb +60 -0
  24. data/lib/active_model/error_collecting/human_message_reporter.rb +32 -0
  25. data/lib/active_model/error_collecting/machine_array_reporter.rb +19 -0
  26. data/lib/active_model/error_collecting/machine_hash_reporter.rb +22 -0
  27. data/lib/active_model/error_collecting/message_reporter.rb +17 -0
  28. data/lib/active_model/error_collecting/reporter.rb +14 -0
  29. data/spec/lib/active_model/better_errors_spec.rb +7 -0
  30. data/spec/lib/active_model/error_collecting/emulation_spec.rb +45 -0
  31. data/spec/lib/active_model/error_collecting/error_collection_spec.rb +205 -0
  32. data/spec/lib/active_model/error_collecting/error_message_set_spec.rb +96 -0
  33. data/spec/lib/active_model/error_collecting/error_message_spec.rb +293 -0
  34. data/spec/lib/active_model/error_collecting/errors_spec.rb +95 -0
  35. data/spec/lib/active_model/error_collecting/human_array_reporter_spec.rb +33 -0
  36. data/spec/lib/active_model/error_collecting/human_hash_reporter_spec.rb +32 -0
  37. data/spec/lib/active_model/error_collecting/human_message_formatter_spec.rb +22 -0
  38. data/spec/lib/active_model/error_collecting/human_message_reporter_spec.rb +61 -0
  39. data/spec/lib/active_model/error_collecting/machine_array_reporter_spec.rb +40 -0
  40. data/spec/lib/active_model/error_collecting/machine_hash_reporter_spec.rb +40 -0
  41. data/spec/lib/active_model/error_collecting_spec.rb +22 -0
  42. data/spec/spec_helper.rb +15 -0
  43. data/spec/support/models.rb +28 -0
  44. data/test/integration.rb +10 -0
  45. metadata +274 -0
@@ -0,0 +1,9 @@
1
+ module ActiveModel
2
+ module ErrorCollecting
3
+ class ArrayReporter < Reporter
4
+ def to_a
5
+ raise "abstract method"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ class String
2
+ def ==(other)
3
+ return super other.to_s if other.is_a? ActiveModel::ErrorCollecting::ErrorMessage
4
+ super
5
+ end
6
+ end
@@ -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,9 @@
1
+ module ActiveModel
2
+ module ErrorCollecting
3
+ class HashReporter < Reporter
4
+ def to_hash
5
+ raise "abstract method"
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module ActiveModel
2
+ module ErrorCollecting
3
+ class HumanArrayReporter < ArrayReporter
4
+ def to_a
5
+ HumanMessageReporter.new(collection).full_messages
6
+ end
7
+ end
8
+ end
9
+ 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