apiable_model_errors 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,6 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+ group :development do
4
+ gem "activemodel"
5
+ gem "rspec"
6
+ end
@@ -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
@@ -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.
@@ -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
@@ -0,0 +1,10 @@
1
+ $:.unshift(File.expand_path(File.join('..', '..', 'lib'), __FILE__))
2
+ require 'apiable_model_errors'
3
+
4
+ class ExampleClass
5
+ include ActiveModel::Validations
6
+ attr_reader :example
7
+ def initialize(example = nil)
8
+ @example = example
9
+ end
10
+ end
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: