instructure-active_model-better_errors 1.6.3.rails2

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 +18 -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 +110 -0
  11. data/lib/active_model/better_errors.rb +17 -0
  12. data/lib/active_model/error_collecting/array_reporter.rb +9 -0
  13. data/lib/active_model/error_collecting/core_ext.rb +6 -0
  14. data/lib/active_model/error_collecting/emulation.rb +65 -0
  15. data/lib/active_model/error_collecting/error_collection.rb +86 -0
  16. data/lib/active_model/error_collecting/error_message.rb +88 -0
  17. data/lib/active_model/error_collecting/error_message_set.rb +33 -0
  18. data/lib/active_model/error_collecting/errors.rb +52 -0
  19. data/lib/active_model/error_collecting/hash_reporter.rb +9 -0
  20. data/lib/active_model/error_collecting/human_array_reporter.rb +9 -0
  21. data/lib/active_model/error_collecting/human_hash_reporter.rb +15 -0
  22. data/lib/active_model/error_collecting/human_message_formatter.rb +72 -0
  23. data/lib/active_model/error_collecting/human_message_reporter.rb +32 -0
  24. data/lib/active_model/error_collecting/machine_array_reporter.rb +19 -0
  25. data/lib/active_model/error_collecting/machine_hash_reporter.rb +22 -0
  26. data/lib/active_model/error_collecting/message_reporter.rb +17 -0
  27. data/lib/active_model/error_collecting/reporter.rb +14 -0
  28. data/lib/active_model/error_collecting.rb +49 -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 +30 -0
  43. data/spec/support/models.rb +7 -0
  44. data/test/integration.rb +10 -0
  45. metadata +308 -0
@@ -0,0 +1,33 @@
1
+ module ActiveModel
2
+ module ErrorCollecting
3
+ class ErrorMessageSet < Array
4
+ def initialize(base, attribute, errors=[])
5
+ @base = base
6
+ @attribute = attribute
7
+ errors.each do |error|
8
+ push(*error)
9
+ end
10
+ end
11
+
12
+ def <<(error)
13
+ super ErrorMessage.build(@base, @attribute, *error)
14
+ end
15
+
16
+ def push(message, options = {})
17
+ super ErrorMessage.build(@base, @attribute, message, options)
18
+ end
19
+
20
+ def []=(index, error)
21
+ super index, ErrorMessage.build(@base, @attribute, *error)
22
+ end
23
+
24
+ def insert(index, error)
25
+ super index, ErrorMessage.build(@base, @attribute, *error)
26
+ end
27
+
28
+ def to_a
29
+ dup
30
+ end
31
+ end
32
+ end
33
+ 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,72 @@
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 || :invalid
16
+ end
17
+
18
+ def format_message
19
+ return message if message && error_message.type.nil?
20
+
21
+ keys = i18n_keys
22
+ key = keys.shift
23
+
24
+ options = {
25
+ :default => keys,
26
+ :model => base.class.human_name,
27
+ :attribute => base.class.human_attribute_name(attribute),
28
+ :value => value
29
+ }.merge(self.options)
30
+
31
+ I18n.translate key, options
32
+ end
33
+
34
+ private
35
+
36
+ def value
37
+ return if attribute == :base
38
+ base.send :read_attribute, attribute
39
+ end
40
+
41
+ def ancestor_keys
42
+ return [] unless base.class.respond_to?(:i18n_scope)
43
+ scope = base.class.i18n_scope
44
+ base.class.lookup_ancestors.map do |klass|
45
+ model_key = klass.model_name.i18n_key
46
+ [
47
+ :"#{scope}.errors.models.#{model_key}.attributes.#{attribute}.#{type}",
48
+ :"#{scope}.errors.models.#{model_key}.#{type}"
49
+ ]
50
+ end
51
+ end
52
+
53
+ def i18n_keys
54
+ keys = ancestor_keys
55
+ keys << message
56
+
57
+ if base.class.respond_to?(:i18n_scope)
58
+ keys << :"#{base.class.i18n_scope}.errors.messages.#{type}"
59
+ end
60
+
61
+ keys << :"errors.attributes.#{attribute}.#{type}"
62
+ keys << :"activerecord.errors.attributes.#{attribute}.#{type}"
63
+ keys << :"errors.messages.#{type}"
64
+ keys << :"activerecord.errors.messages.#{type}"
65
+
66
+ keys.compact!
67
+ keys.flatten!
68
+ keys
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveModel
2
+ module ErrorCollecting
3
+ class HumanMessageReporter < MessageReporter
4
+ def full_messages
5
+ @collection.map do |attribute, error_message|
6
+ formatter = HumanMessageFormatter.new(base, error_message)
7
+ message = formatter.format_message
8
+ full_message attribute, message
9
+ end
10
+ end
11
+
12
+ def full_message(attribute, message)
13
+ return message if attribute == :base
14
+ attr_name = attribute.to_s.gsub('.', '_').humanize
15
+ attr_name = base.class.human_attribute_name(attribute, :default => attr_name)
16
+ I18n.t(:"errors.format", {
17
+ :default => "%{attribute} %{message}",
18
+ :attribute => attr_name,
19
+ :message => message
20
+ })
21
+ end
22
+
23
+ # This method is not used internally.
24
+ # This is for API Compatibility with ActiveModel::Errors only
25
+ def generate_message(attribute, type = :invalid, options = {})
26
+ error_message = ErrorMessage.build(base, attribute, type, options)
27
+ formatter = HumanMessageFormatter.new(base, error_message)
28
+ formatter.format_message
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveModel
2
+ module ErrorCollecting
3
+ class MachineArrayReporter < ArrayReporter
4
+ def to_a
5
+ collection.to_a.map do |error_message|
6
+ format_error_message error_message
7
+ end
8
+ end
9
+
10
+ def format_error_message(error_message)
11
+ result = {}
12
+ result[:attribute] = error_message.attribute.to_s
13
+ result[:type] = error_message.type || :invalid
14
+ result[:options] = error_message.options unless error_message.options.blank?
15
+ result
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module ActiveModel
2
+ module ErrorCollecting
3
+ class MachineHashReporter < 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
+ format_error_message(error_message)
9
+ end
10
+ hash
11
+ end
12
+ end
13
+
14
+ def format_error_message(error_message)
15
+ result = {}
16
+ result[:type] = error_message.type || :invalid
17
+ result[:options] = error_message.options unless error_message.options.blank?
18
+ result
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveModel
2
+ module ErrorCollecting
3
+ class MessageReporter < Reporter
4
+ def full_messages
5
+ raise "abstract method"
6
+ end
7
+
8
+ def full_message(attribute, message)
9
+ raise "abstract method"
10
+ end
11
+
12
+ def generate_message(attribute, type = :invalid, options = {})
13
+ raise "abstract method"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module ActiveModel
2
+ module ErrorCollecting
3
+ class Reporter
4
+ attr_reader :collection
5
+ def initialize(collection)
6
+ @collection = collection
7
+ end
8
+
9
+ def base
10
+ @collection.base
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,49 @@
1
+ require 'active_model/error_collecting/error_message'
2
+ require 'active_model/error_collecting/error_message_set'
3
+ require 'active_model/error_collecting/error_collection'
4
+
5
+ require 'active_model/error_collecting/reporter'
6
+ require 'active_model/error_collecting/message_reporter'
7
+ require 'active_model/error_collecting/hash_reporter'
8
+ require 'active_model/error_collecting/array_reporter'
9
+
10
+ require 'active_model/error_collecting/human_message_formatter'
11
+ require 'active_model/error_collecting/human_message_reporter'
12
+ require 'active_model/error_collecting/human_hash_reporter'
13
+ require 'active_model/error_collecting/human_array_reporter'
14
+
15
+ require 'active_model/error_collecting/machine_hash_reporter'
16
+ require 'active_model/error_collecting/machine_array_reporter'
17
+
18
+ require 'active_model/error_collecting/emulation'
19
+ require 'active_model/error_collecting/errors'
20
+
21
+ require 'active_model/error_collecting/core_ext'
22
+
23
+ module ActiveModel
24
+ module ErrorCollecting
25
+ class << self
26
+ def set_reporter(name, reporter)
27
+ name = name.to_s
28
+ @reporter_maps ||= {}
29
+ return @reporter_maps.delete(name) unless reporter
30
+ @reporter_maps[name] = get_reporter_class(name, reporter)
31
+ end
32
+
33
+ def reporters
34
+ @reporter_maps ||= {}
35
+ @reporter_maps.clone
36
+ end
37
+
38
+ def get_reporter_class(name, reporter)
39
+ return reporter if reporter.is_a? Class
40
+ class_name = "active_model/error_collecting/#{reporter}_#{name}_reporter"
41
+ class_name.classify.constantize
42
+ end
43
+ end
44
+
45
+ set_reporter :message, :human
46
+ set_reporter :array, :human
47
+ set_reporter :hash, :human
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe "ActiveModel Better Errors" do
4
+ it "overrides ActiveModel Validations" do
5
+ User.new.errors.should be_a ActiveModel::ErrorCollecting::Errors
6
+ end
7
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples_for "a delegated method" do
4
+ let(:target_instance) { mock() }
5
+ before do
6
+ target_instance.should_receive method
7
+ instance.stub(target).and_return(target_instance)
8
+ end
9
+
10
+ specify { instance.send method }
11
+ end
12
+
13
+ describe ActiveModel::ErrorCollecting::Emulation do
14
+ subject(:instance) { klass.new }
15
+ let(:klass) { Class.new { include ActiveModel::ErrorCollecting::Emulation } }
16
+
17
+ delegation_map = {
18
+ error_collection: [
19
+ :clear, :include?, :get, :set, :delete, :[], :[]=, :each, :size,
20
+ :values, :keys, :count, :empty?, :added?, :any?, :entries
21
+ ],
22
+
23
+ message_reporter: [
24
+ :full_messages, :full_message, :generate_message
25
+ ],
26
+
27
+ hash_reporter: [
28
+ :to_hash
29
+ ],
30
+
31
+ hash_reporter: [
32
+ :to_hash
33
+ ]
34
+ }
35
+
36
+ delegation_map.each do |target, methods|
37
+ methods.each do |method|
38
+ describe "delegating ##{method} to ##{target}" do
39
+ let(:target) { target }
40
+ let(:method) { method }
41
+ it_should_behave_like "a delegated method"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,205 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveModel::ErrorCollecting::ErrorCollection do
4
+ subject(:collection) { klass.new(base) }
5
+ let(:klass) { ActiveModel::ErrorCollecting::ErrorCollection }
6
+ let(:base) { User.new }
7
+ let(:errors){{
8
+ :first_name => [ [ :too_long, { count: 3 } ] ],
9
+ 'last_name' => [ [ :invalid, { message: "Invalid" } ] ],
10
+ :email => [ :invalid ],
11
+ }}
12
+
13
+ before do
14
+ errors.each do |attribute, error_list|
15
+ error_list.each do |error|
16
+ collection[attribute] = error
17
+ end
18
+ end
19
+ end
20
+
21
+ describe "#clear" do
22
+ before { collection.clear }
23
+ it { should be_empty }
24
+ end
25
+
26
+ describe "#include?" do
27
+ it { should be_include :first_name }
28
+ it { should be_include :last_name }
29
+ it { should be_include :email }
30
+ it { should_not be_include 'first_name' }
31
+ it { should_not be_include 'last_name' }
32
+ it { should_not be_include 'email' }
33
+ end
34
+
35
+ describe "#get" do
36
+ subject { collection.get(:first_name) }
37
+ it { should be_a ActiveModel::ErrorCollecting::ErrorMessageSet }
38
+ its(:length) { should be 1 }
39
+
40
+ describe "when value is nil" do
41
+ before { collection.delete :first_name }
42
+ it { should be nil }
43
+ end
44
+ end
45
+
46
+ describe "#set" do
47
+ subject { collection.get :first_name }
48
+
49
+ describe "when value is array" do
50
+ before { collection.set(:first_name, []) }
51
+ it { should be_a ActiveModel::ErrorCollecting::ErrorMessageSet }
52
+ its(:length) { should be 0 }
53
+ end
54
+
55
+ describe "when value is nil" do
56
+ before { collection.set(:first_name, nil) }
57
+ it { should be_nil }
58
+ end
59
+ end
60
+
61
+ describe "#delete" do
62
+ subject { collection.get(:first_name) }
63
+ before { collection.delete(:first_name) }
64
+ it { should be_nil }
65
+ end
66
+
67
+ describe "#[]" do
68
+ subject { collection[:first_name] }
69
+
70
+ describe "when no error messages" do
71
+ before { collection.clear }
72
+ it { should be_blank }
73
+ it { should_not be_nil }
74
+ it { should be_a ActiveModel::ErrorCollecting::ErrorMessageSet }
75
+ end
76
+
77
+ describe "when there are error messages" do
78
+ it { should_not be_blank }
79
+ it { should_not be_nil }
80
+ it { should be_a ActiveModel::ErrorCollecting::ErrorMessageSet }
81
+ end
82
+ end
83
+
84
+ describe "#[]=" do
85
+ subject(:error_message_set) { collection.get field }
86
+
87
+ describe "when assigning existing attribute" do
88
+ let(:field) { :first_name }
89
+ it "should append to existing set" do
90
+ expect {
91
+ collection[field] = "I'm invalid."
92
+ }.to change { error_message_set.length }.by(1)
93
+ end
94
+ end
95
+
96
+ describe "when assigning to new attribute" do
97
+ let(:field) { :a_new_attribute }
98
+ before { collection[field] = "I'm invalid" }
99
+ it { should_not be_nil }
100
+ it { should be_a ActiveModel::ErrorCollecting::ErrorMessageSet }
101
+ its(:length) { should be 1 }
102
+ end
103
+ end
104
+
105
+ describe "#each" do
106
+ it "should loop through each error" do
107
+ count = 0
108
+ collection.each do |attribute, error|
109
+ attribute.should be_a Symbol
110
+ error.should be_a ActiveModel::ErrorCollecting::ErrorMessage
111
+ count += 1
112
+ end
113
+
114
+ expect(count).to be collection.size
115
+ end
116
+ end
117
+
118
+ describe "#size" do
119
+ subject { collection.size }
120
+ it { should be 3 }
121
+
122
+ describe "when adding one more error" do
123
+ before { collection[:name] = "Not Valid" }
124
+ it { should be 4 }
125
+ end
126
+ end
127
+
128
+ describe "#values" do
129
+ subject { collection.values }
130
+
131
+ it { should be_a Array }
132
+ its(:length) { should be 3 }
133
+
134
+ it "should contain ErrorMessageSet as elements" do
135
+ collection.values.each do |el|
136
+ el.should be_a ActiveModel::ErrorCollecting::ErrorMessageSet
137
+ end
138
+ end
139
+ end
140
+
141
+ describe "#keys" do
142
+ subject { collection.keys }
143
+ it { should == [:first_name, :last_name, :email] }
144
+ end
145
+
146
+ describe "#to_a" do
147
+ subject { collection.to_a }
148
+ its(:size) { should == 3 }
149
+
150
+ describe "when adding one more error" do
151
+ before { collection[:name] = "Not Valid" }
152
+ its(:size) { should == 4 }
153
+ end
154
+
155
+ it "should contain ErrorMessage as elements" do
156
+ collection.to_a.each do |error|
157
+ error.should be_a ActiveModel::ErrorCollecting::ErrorMessage
158
+ end
159
+ end
160
+ end
161
+
162
+ describe "#to_hash" do
163
+ subject { collection.to_hash }
164
+
165
+ it { should be_a Hash }
166
+ it { should == collection.instance_variable_get(:@collection) }
167
+ it { should_not be collection.instance_variable_get(:@collection) }
168
+ end
169
+
170
+ describe "#empty?" do
171
+ subject { collection.empty? }
172
+ it{ should be false }
173
+
174
+ describe "after clearing the collection" do
175
+ before { collection.clear }
176
+
177
+ it { should be true }
178
+ end
179
+ end
180
+
181
+ describe "#add" do
182
+ it "should add error to collection" do
183
+ expect {
184
+ collection.add :name, "Invalid"
185
+ }.to change { collection[:name].length }.by(1)
186
+ end
187
+ end
188
+
189
+ describe "#added?" do
190
+ describe "when an error message is not added" do
191
+ subject { collection.added? :name, :not_there }
192
+ it { should be false }
193
+ end
194
+
195
+ describe "when an error message with the same option is added" do
196
+ subject { collection.added? :first_name, :too_long, { :count => 3 } }
197
+ it { should be true }
198
+ end
199
+
200
+ describe "when an error message with the different option is added" do
201
+ subject { collection.added? :first_name, :too_long, { :count => 4 } }
202
+ it { should be false }
203
+ end
204
+ end
205
+ end