apiable_model_errors 1.0.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 +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +43 -0
- data/MIT-LICENCE +20 -0
- data/README.md +70 -0
- data/apiable_model_errors.gemspec +12 -0
- data/lib/apiable_model_errors.rb +72 -0
- data/spec/apiable_model_errors_spec.rb +134 -0
- data/spec/spec_helper.rb +10 -0
- metadata +74 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ac03665a8b0e776827ef06713dc1bbaf02395d24
|
4
|
+
data.tar.gz: bd876bf793d113b27749f3fd6ed266dc5613a1f4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e7117ef3e97f0cf9acf70b5fcc057bbfe45f4a8d564cbb34fdd878e0e2a96f271550ea7de1d3697fbc9a4e5e873cbf7b99d4efe9298aa0409c05c3a89019f30e
|
7
|
+
data.tar.gz: 7274e7ce09b19d90cf66d56fb0b706da54e25fb9258686fa69e64fe65f634f3eafbe64b65f3e64fb793a8869428cd3565e615e075b8db7e591e602899a506630
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (4.2.5)
|
5
|
+
activesupport (= 4.2.5)
|
6
|
+
builder (~> 3.1)
|
7
|
+
activesupport (4.2.5)
|
8
|
+
i18n (~> 0.7)
|
9
|
+
json (~> 1.7, >= 1.7.7)
|
10
|
+
minitest (~> 5.1)
|
11
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
12
|
+
tzinfo (~> 1.1)
|
13
|
+
builder (3.2.2)
|
14
|
+
diff-lcs (1.2.5)
|
15
|
+
i18n (0.7.0)
|
16
|
+
json (1.8.3)
|
17
|
+
minitest (5.8.3)
|
18
|
+
rspec (3.4.0)
|
19
|
+
rspec-core (~> 3.4.0)
|
20
|
+
rspec-expectations (~> 3.4.0)
|
21
|
+
rspec-mocks (~> 3.4.0)
|
22
|
+
rspec-core (3.4.2)
|
23
|
+
rspec-support (~> 3.4.0)
|
24
|
+
rspec-expectations (3.4.0)
|
25
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
26
|
+
rspec-support (~> 3.4.0)
|
27
|
+
rspec-mocks (3.4.1)
|
28
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
29
|
+
rspec-support (~> 3.4.0)
|
30
|
+
rspec-support (3.4.1)
|
31
|
+
thread_safe (0.3.5)
|
32
|
+
tzinfo (1.2.2)
|
33
|
+
thread_safe (~> 0.1)
|
34
|
+
|
35
|
+
PLATFORMS
|
36
|
+
ruby
|
37
|
+
|
38
|
+
DEPENDENCIES
|
39
|
+
activemodel
|
40
|
+
rspec
|
41
|
+
|
42
|
+
BUNDLED WITH
|
43
|
+
1.10.6
|
data/MIT-LICENCE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 Adam Cooke.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# APIable Model Errors
|
2
|
+
|
3
|
+
ActiveModel provides a great framework for handling validations
|
4
|
+
for models. It easily allows you to add any validation message to
|
5
|
+
any attribute however these arbitary strings aren't very helpful
|
6
|
+
if you want to deliver validation errors to APIs. API developers
|
7
|
+
don't want to see `Number is too long (maximum is 5 characters)`,
|
8
|
+
they would much rather see something they can handle programatically
|
9
|
+
like `too_long` or `blank`.
|
10
|
+
|
11
|
+
This gem extends the `ActiveModel::Errors` class to allow for a
|
12
|
+
more API-friendly hash of errors to be returned.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem "apiable_model_errors"
|
18
|
+
```
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
Once you've installed the gems, you can call the `to_api_hash`
|
23
|
+
method on any `ActiveModel::Errors` object.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
user = User.new(params)
|
27
|
+
unless user.valid?
|
28
|
+
user.errors.to_api_hash
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
## Example Output
|
33
|
+
|
34
|
+
When using this you'll receive a ruby hash however you can easily
|
35
|
+
convert this into JSON by calling `to_json` on the hash.
|
36
|
+
|
37
|
+
```javascript
|
38
|
+
{
|
39
|
+
"name":[
|
40
|
+
{
|
41
|
+
"code":"too_long",
|
42
|
+
"message":"is too long (maximum is 4 characters)",
|
43
|
+
"options":{
|
44
|
+
"count":4
|
45
|
+
}
|
46
|
+
}
|
47
|
+
]
|
48
|
+
}
|
49
|
+
```
|
50
|
+
|
51
|
+
As you can see you'll receive the code, the actual translated
|
52
|
+
message from ActiveModel and any options.
|
53
|
+
|
54
|
+
## `has_message?`
|
55
|
+
|
56
|
+
In addition to provide the `to_api_hash` method, we also provide
|
57
|
+
a method called `has_message?` which allows you to determine if a
|
58
|
+
message exists for a given attribute. This is similar to the
|
59
|
+
`added?` however does not require that you provide all options to
|
60
|
+
see if a message has been added.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# See if there are any :too_long messages on the name attribute
|
64
|
+
user.errors.has_message?(:name, :too_long)
|
65
|
+
# See if there are any :too_long messages on the name attribute
|
66
|
+
# with the options provided.
|
67
|
+
user.errors.has_message?(:name, :too_long, :count => 4)
|
68
|
+
# See if there are any :blank messages on the name attribute
|
69
|
+
user.errors.has_message?(:name, :blank)
|
70
|
+
```
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "apiable_model_errors"
|
3
|
+
s.version = "1.0.0"
|
4
|
+
s.authors = ["Adam Cooke"]
|
5
|
+
s.email = ["adam@atechmedia.com"]
|
6
|
+
s.homepage = "http://adamcooke.io"
|
7
|
+
s.licenses = ['MIT']
|
8
|
+
s.summary = "Provide ActiveModel errors in an format suitable for API consumers"
|
9
|
+
s.description = "Avoid sending plain text messages to API consumers by sending them more detailed information about validation errors."
|
10
|
+
s.files = Dir["**/*"]
|
11
|
+
s.add_dependency "activemodel", ">= 4.0", "< 5.0"
|
12
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module ApiableModelErrors
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.alias_method_chain :add, :api_errors
|
7
|
+
base.alias_method_chain :clear, :api_errors
|
8
|
+
base.alias_method_chain :delete, :api_errors
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_api_hash
|
12
|
+
errors_for_api.each_with_object({}) do |(attribute, errors), hash|
|
13
|
+
hash[attribute] = []
|
14
|
+
errors.each_with_index do |(message, options), index|
|
15
|
+
if message.is_a?(String)
|
16
|
+
error = {:message => message}
|
17
|
+
else
|
18
|
+
error = {
|
19
|
+
:code => message,
|
20
|
+
:message => messages[attribute][index],
|
21
|
+
:options => options
|
22
|
+
}
|
23
|
+
end
|
24
|
+
hash[attribute] << error
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def errors_for_api
|
30
|
+
@errors_for_api ||= {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_with_api_errors
|
34
|
+
clear_without_api_errors.tap do
|
35
|
+
@errors_for_api = {}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete_with_api_errors(key)
|
40
|
+
delete_without_api_errors(key).tap do
|
41
|
+
errors_for_api.delete(key)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_with_api_errors(attribute, message = :invalid, options = {})
|
46
|
+
add_without_api_errors(attribute, message, options).tap do
|
47
|
+
errors_for_api[attribute] ||= []
|
48
|
+
errors_for_api[attribute] << [message, options || {}]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def has_message?(attribute, message, options = nil)
|
53
|
+
if errors = errors_for_api[attribute]
|
54
|
+
if error = errors.select { |e| e[0] == message }.first
|
55
|
+
if options.nil?
|
56
|
+
true # No options specified, if it exists, it's here
|
57
|
+
elsif options == error[1]
|
58
|
+
true # The options match those on the subejct
|
59
|
+
else
|
60
|
+
false # The options selected don't match those on the error
|
61
|
+
end
|
62
|
+
else
|
63
|
+
false # No errors of this type
|
64
|
+
end
|
65
|
+
else
|
66
|
+
false # No errors for this attribute
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
ActiveModel::Errors.send :include, ApiableModelErrors
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ApiableModelErrors do
|
4
|
+
|
5
|
+
context "#add" do
|
6
|
+
it "should should add errors to the API hash too" do
|
7
|
+
errors = ActiveModel::Errors.new(ExampleClass.new)
|
8
|
+
errors.add :example, :too_long, :count => 4
|
9
|
+
expect(errors.errors_for_api[:example]).to be_a Array
|
10
|
+
expect(errors.errors_for_api[:example].size).to be 1
|
11
|
+
expect(errors.errors_for_api[:example][0][0]).to eq(:too_long)
|
12
|
+
expect(errors.errors_for_api[:example][0][1]).to eq({:count => 4})
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should still return the full message to keep same behaviour" do
|
16
|
+
errors = ActiveModel::Errors.new(ExampleClass.new)
|
17
|
+
expect(errors.add(:example, :blank)).to eq(["can't be blank"])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "#clear" do
|
22
|
+
it "should clear API errors" do
|
23
|
+
errors = ActiveModel::Errors.new(ExampleClass.new)
|
24
|
+
errors.add :example, :blank
|
25
|
+
expect(errors.messages).to_not be_empty
|
26
|
+
expect(errors.errors_for_api).to_not be_empty
|
27
|
+
errors.clear
|
28
|
+
expect(errors.messages).to be_empty
|
29
|
+
expect(errors.errors_for_api).to be_empty
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "#delete" do
|
34
|
+
it "should delete a given API error" do
|
35
|
+
errors = ActiveModel::Errors.new(ExampleClass.new)
|
36
|
+
errors.add :example, :blank
|
37
|
+
expect {errors.delete(:example)}.to change {errors.errors_for_api.has_key?(:example)}.from(true).to(false)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#to_api_hash" do
|
42
|
+
subject(:errors) do
|
43
|
+
errors = ActiveModel::Errors.new(ExampleClass.new)
|
44
|
+
errors.add :example, :too_long, :count => 4
|
45
|
+
errors.add :example, :invalid
|
46
|
+
errors
|
47
|
+
end
|
48
|
+
|
49
|
+
it "shuold return a hash for returning to API consumers" do
|
50
|
+
expect(hash = errors.to_api_hash).to be_a(Hash)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should have an array of errors for each attribute" do
|
54
|
+
expect(errors.to_api_hash[:example]).to be_a(Array)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should have the code, message and options" do
|
58
|
+
error = errors.to_api_hash[:example][0]
|
59
|
+
expect(error[:code]).to eq(:too_long)
|
60
|
+
expect(error[:message]).to match /is too long/
|
61
|
+
expect(error[:options]).to be_a Hash
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "#to_api_hash (with string messages)" do
|
66
|
+
subject(:errors) do
|
67
|
+
errors = ActiveModel::Errors.new(ExampleClass.new)
|
68
|
+
errors.add :example, "is not unique"
|
69
|
+
errors
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should not have a code or options" do
|
73
|
+
error = errors.to_api_hash[:example][0]
|
74
|
+
expect(error[:message]).to eq('is not unique')
|
75
|
+
expect(error.has_key?(:code)).to be false
|
76
|
+
expect(error.has_key?(:options)).to be false
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "#has_message?" do
|
81
|
+
it "should return true if the given error exists on the attribute" do
|
82
|
+
errors = ActiveModel::Errors.new(ExampleClass.new)
|
83
|
+
errors.add :example, :too_long, :count => 4
|
84
|
+
expect(errors.has_message?(:example, :too_long)).to be true
|
85
|
+
expect(errors.has_message?(:example, :too_long, :count => 4)).to be true
|
86
|
+
expect(errors.has_message?(:example, :too_long, :count => 3)).to be false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "with presence validator" do
|
91
|
+
class PresenceExampleClass < ExampleClass
|
92
|
+
validates :example, :presence => true
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should have a blank error" do
|
96
|
+
p = PresenceExampleClass.new(nil)
|
97
|
+
expect(p.valid?).to be false
|
98
|
+
expect(p.errors.has_message?(:example, :blank)).to be true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "with numericality validator" do
|
103
|
+
class NumericalityExampleClass < ExampleClass
|
104
|
+
validates :example, :numericality => {:only_integer => true}
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should have a not_a_number error" do
|
108
|
+
p = NumericalityExampleClass.new("potato")
|
109
|
+
expect(p.valid?).to be false
|
110
|
+
expect(p.errors.has_message?(:example, :not_a_number)).to be true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "with length validator" do
|
115
|
+
class LengthExampleClass < ExampleClass
|
116
|
+
validates :example, :length => {:minimum => 2, :maximum => 5}
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should have a too long error when too long" do
|
120
|
+
p = LengthExampleClass.new("morethanfive")
|
121
|
+
expect(p.valid?).to be false
|
122
|
+
expect(p.errors.has_message?(:example, :too_long)).to be true
|
123
|
+
expect(p.errors.has_message?(:example, :too_long, :count => 5)).to be true
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should have a too short error when too long" do
|
127
|
+
p = LengthExampleClass.new("a")
|
128
|
+
expect(p.valid?).to be false
|
129
|
+
expect(p.errors.has_message?(:example, :too_short)).to be true
|
130
|
+
expect(p.errors.has_message?(:example, :too_short, :count => 2)).to be true
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: apiable_model_errors
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Cooke
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-02-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activemodel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '5.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5.0'
|
33
|
+
description: Avoid sending plain text messages to API consumers by sending them more
|
34
|
+
detailed information about validation errors.
|
35
|
+
email:
|
36
|
+
- adam@atechmedia.com
|
37
|
+
executables: []
|
38
|
+
extensions: []
|
39
|
+
extra_rdoc_files: []
|
40
|
+
files:
|
41
|
+
- Gemfile
|
42
|
+
- Gemfile.lock
|
43
|
+
- MIT-LICENCE
|
44
|
+
- README.md
|
45
|
+
- apiable_model_errors.gemspec
|
46
|
+
- lib/apiable_model_errors.rb
|
47
|
+
- spec/apiable_model_errors_spec.rb
|
48
|
+
- spec/spec_helper.rb
|
49
|
+
homepage: http://adamcooke.io
|
50
|
+
licenses:
|
51
|
+
- MIT
|
52
|
+
metadata: {}
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 2.4.5
|
70
|
+
signing_key:
|
71
|
+
specification_version: 4
|
72
|
+
summary: Provide ActiveModel errors in an format suitable for API consumers
|
73
|
+
test_files: []
|
74
|
+
has_rdoc:
|