lhs 11.0.3 → 11.1.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 +4 -4
- data/README.md +33 -1
- data/lib/lhs/collection.rb +9 -3
- data/lib/lhs/concerns/item/save.rb +1 -1
- data/lib/lhs/concerns/item/update.rb +1 -1
- data/lib/lhs/concerns/item/validation.rb +1 -1
- data/lib/lhs/concerns/proxy/accessors.rb +22 -11
- data/lib/lhs/concerns/proxy/errors.rb +21 -0
- data/lib/lhs/data.rb +6 -2
- data/lib/lhs/errors/base.rb +126 -0
- data/lib/lhs/errors/nested.rb +60 -0
- data/lib/lhs/item.rb +0 -9
- data/lib/lhs/proxy.rb +1 -1
- data/lib/lhs/record.rb +2 -2
- data/lib/lhs/version.rb +1 -1
- data/spec/collection/to_a_spec.rb +32 -0
- data/spec/item/errors_spec.rb +56 -0
- data/spec/record/create_spec.rb +2 -2
- data/spec/record/new_spec.rb +1 -1
- metadata +8 -4
- data/lib/lhs/errors.rb +0 -124
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abf5de2dde8813263d07b049e423c569bf0e187c
|
4
|
+
data.tar.gz: 2bf40b2701cfa58783ce975c260da734a8952b1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e87e3813fd4afa5c1aa2a9053745e473936ce7262e3bcba83d75032a3261c9ecc045fa70d4a8350428a754baf1dcc3d5c0c5439ab1121ae37da30c447ead8cbd
|
7
|
+
data.tar.gz: 78c83def6fbe617c6d1c5c7bcb91d8ebe3ca3fdd1b092eb8e51166b8fec51699cbbbe6ffdf3297cb7ff7c364fe11191d6e7a02a98ad05f56b6e7819daa7bb217
|
data/README.md
CHANGED
@@ -707,7 +707,7 @@ unless user.valid?
|
|
707
707
|
fail(user.errors[:email])
|
708
708
|
end
|
709
709
|
|
710
|
-
user.errors #<LHS::Errors>
|
710
|
+
user.errors #<LHS::Errors::Base>
|
711
711
|
user.errors.include?(:email) # true
|
712
712
|
user.errors[:email] # ['REQUIRED_PROPERTY_VALUE']
|
713
713
|
user.errors.messages # {:email=>["REQUIRED_PROPERTY_VALUE"]}
|
@@ -739,6 +739,38 @@ In case you want to add custom validation errors to an instance of LHS::Record:
|
|
739
739
|
user.errors.add(:name, 'The name you provided is not valid.')
|
740
740
|
```
|
741
741
|
|
742
|
+
### Validation errors for nested data
|
743
|
+
|
744
|
+
If you work with complex data structures, you sometimes need to have validation errors delegated/scoped to nested data.
|
745
|
+
|
746
|
+
This also makes LHS::Records compatible with how Rails or Simpleform renders/builds forms and especially error messages.
|
747
|
+
|
748
|
+
```ruby
|
749
|
+
# controller.rb
|
750
|
+
unless @customer.save
|
751
|
+
@errors = @customer.errors
|
752
|
+
end
|
753
|
+
|
754
|
+
# view.html
|
755
|
+
= form_for @customer, as: :customer do |customer_form|
|
756
|
+
|
757
|
+
= fields_for 'customer[:address]', @customer.address, do |address_form|
|
758
|
+
|
759
|
+
= fields_for 'customer[:address][:street]', @customer.address.street, do |street_form|
|
760
|
+
|
761
|
+
= street_form.input :name
|
762
|
+
= street_form.input :house_number
|
763
|
+
```
|
764
|
+
|
765
|
+
Would render nested forms and would also render nested form errors for nested data structures.
|
766
|
+
|
767
|
+
You can also access those nested errors like:
|
768
|
+
|
769
|
+
```ruby
|
770
|
+
@customer.address.errors
|
771
|
+
@customer.address.street.errors
|
772
|
+
```
|
773
|
+
|
742
774
|
### Know issue with `ActiveModel::Validations`
|
743
775
|
If you are using `ActiveModel::Validations` and add errors to the LHS::Record instance - as described above - then those errors will be overwritten by the errors from `ActiveModel::Validations` when using `save` or `valid?`. [Open issue](https://github.com/local-ch/lhs/issues/159)
|
744
776
|
|
data/lib/lhs/collection.rb
CHANGED
@@ -7,6 +7,8 @@ class LHS::Collection < LHS::Proxy
|
|
7
7
|
include InternalCollection
|
8
8
|
include Create
|
9
9
|
|
10
|
+
METHOD_NAMES_EXLCUDED_FROM_WRAPPING = %w(to_a map).freeze
|
11
|
+
|
10
12
|
delegate :select, :length, :size, to: :_collection
|
11
13
|
delegate :_record, :_raw, to: :_data
|
12
14
|
delegate :limit, :count, :total, :offset, :current_page, :start,
|
@@ -48,8 +50,10 @@ class LHS::Collection < LHS::Proxy
|
|
48
50
|
def method_missing(name, *args, &block)
|
49
51
|
if _collection.respond_to?(name)
|
50
52
|
value = _collection.send(name, *args, &block)
|
51
|
-
|
52
|
-
value
|
53
|
+
record = LHS::Record.for_url(value[:href]) if value.is_a?(Hash) && value[:href]
|
54
|
+
value = enclose_item_in_data(value) if value.is_a?(Hash)
|
55
|
+
return value if METHOD_NAMES_EXLCUDED_FROM_WRAPPING.include?(name.to_s)
|
56
|
+
wrap_return(value, record, name)
|
53
57
|
elsif _data._raw.is_a?(Hash)
|
54
58
|
get(name, *args)
|
55
59
|
end
|
@@ -67,7 +71,9 @@ class LHS::Collection < LHS::Proxy
|
|
67
71
|
:items
|
68
72
|
end
|
69
73
|
|
70
|
-
|
74
|
+
# Encloses accessed collection item
|
75
|
+
# by wrapping it in an LHS::Item
|
76
|
+
def enclose_item_in_data(value)
|
71
77
|
data = LHS::Data.new(value, _data)
|
72
78
|
item = LHS::Item.new(data)
|
73
79
|
LHS::Data.new(item, _data)
|
@@ -18,15 +18,14 @@ class LHS::Proxy
|
|
18
18
|
def get(name, *args)
|
19
19
|
name = args.first if name == :[]
|
20
20
|
value = _data._raw[name.to_s]
|
21
|
-
if value.nil? && _data._raw.present?
|
21
|
+
if value.nil? && _data._raw.present? && _data._raw.is_a?(Hash)
|
22
22
|
value = _data._raw[name.to_sym]
|
23
23
|
value = _data._raw[name.to_s.classify.to_sym] if value.nil?
|
24
24
|
end
|
25
25
|
|
26
26
|
record = LHS::Record.for_url(value[:href]) if value.is_a?(Hash) && value[:href]
|
27
|
-
|
28
|
-
|
29
|
-
access_collection(value, record) ||
|
27
|
+
access_item(value, record, name) ||
|
28
|
+
access_collection(value, record, name) ||
|
30
29
|
convert(value)
|
31
30
|
end
|
32
31
|
|
@@ -54,24 +53,36 @@ class LHS::Proxy
|
|
54
53
|
end
|
55
54
|
end
|
56
55
|
|
57
|
-
def access_item(value, record)
|
56
|
+
def access_item(value, record, name)
|
58
57
|
return unless accessing_item?(value, record)
|
59
|
-
wrap_return(value, record)
|
58
|
+
wrap_return(value, record, name)
|
60
59
|
end
|
61
60
|
|
62
|
-
def access_collection(value, record)
|
61
|
+
def access_collection(value, record, name)
|
63
62
|
return unless accessing_collection?(value, record)
|
64
63
|
collection_data = LHS::Data.new(value, _data)
|
65
64
|
collection = LHS::Collection.new(collection_data)
|
66
|
-
wrap_return(collection, record)
|
65
|
+
wrap_return(collection, record, name)
|
67
66
|
end
|
68
67
|
|
69
|
-
|
70
|
-
|
71
|
-
|
68
|
+
# Wraps with record and adds nested errors to data,
|
69
|
+
# if errors are existing
|
70
|
+
def wrap_return(value, record, name)
|
71
|
+
return value unless worth_wrapping?(value)
|
72
|
+
data = value.is_a?(LHS::Data) || value.is_a?(LHS::Record) ? value : LHS::Data.new(value, _data)
|
73
|
+
data.errors = LHS::Errors::Nested.new(errors, name) if errors
|
74
|
+
return record.new(data) if record && !value.is_a?(LHS::Record)
|
72
75
|
data
|
73
76
|
end
|
74
77
|
|
78
|
+
def worth_wrapping?(value)
|
79
|
+
value.is_a?(LHS::Proxy) ||
|
80
|
+
value.is_a?(LHS::Data) ||
|
81
|
+
value.is_a?(LHS::Record) ||
|
82
|
+
value.is_a?(Hash) ||
|
83
|
+
value.is_a?(Array)
|
84
|
+
end
|
85
|
+
|
75
86
|
def date?(value)
|
76
87
|
value[date_time_regex, :date].presence
|
77
88
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
class LHS::Proxy
|
4
|
+
|
5
|
+
module Errors
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
attr_writer :errors
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(data)
|
13
|
+
super(data)
|
14
|
+
self.errors = LHS::Errors::Base.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def errors
|
18
|
+
@errors ||= LHS::Errors::Base.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/lhs/data.rb
CHANGED
@@ -37,7 +37,7 @@ class LHS::Data
|
|
37
37
|
|
38
38
|
def parent
|
39
39
|
if _parent && _parent._record
|
40
|
-
_parent._record.new(_parent)
|
40
|
+
_parent._record.new(_parent, false)
|
41
41
|
else
|
42
42
|
_parent
|
43
43
|
end
|
@@ -112,7 +112,7 @@ class LHS::Data
|
|
112
112
|
end
|
113
113
|
|
114
114
|
def raw_from_input(input)
|
115
|
-
if
|
115
|
+
if json?(input)
|
116
116
|
raw_from_json_string(input)
|
117
117
|
elsif defined?(input._raw)
|
118
118
|
input._raw
|
@@ -123,6 +123,10 @@ class LHS::Data
|
|
123
123
|
end
|
124
124
|
end
|
125
125
|
|
126
|
+
def json?(input)
|
127
|
+
input.is_a?(String) && !input.empty? && !!input.match(/^("|\[|'|\{)/)
|
128
|
+
end
|
129
|
+
|
126
130
|
def raw_from_json_string(input)
|
127
131
|
json = JSON.parse(input)
|
128
132
|
if json.is_a?(Hash)
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# Like ActiveModel::Errors
|
2
|
+
module LHS::Errors
|
3
|
+
class Base
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :messages, :message, :raw
|
7
|
+
|
8
|
+
def initialize(response = nil)
|
9
|
+
@raw = response.body if response
|
10
|
+
@messages = messages_from_response(response)
|
11
|
+
@message = message_from_response(response)
|
12
|
+
rescue JSON::ParserError
|
13
|
+
@messages = messages || {}
|
14
|
+
@message = 'parse error'
|
15
|
+
add_error(@messages, 'body', 'parse error')
|
16
|
+
end
|
17
|
+
|
18
|
+
def include?(attribute)
|
19
|
+
messages[attribute].present?
|
20
|
+
end
|
21
|
+
alias has_key? include?
|
22
|
+
alias key? include?
|
23
|
+
|
24
|
+
def add(attribute, message = :invalid, _options = {})
|
25
|
+
self[attribute]
|
26
|
+
messages[attribute] << message
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(key)
|
30
|
+
messages[key]
|
31
|
+
end
|
32
|
+
|
33
|
+
def set(key, value)
|
34
|
+
messages[key] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
delegate :delete, to: :messages
|
38
|
+
|
39
|
+
def [](attribute)
|
40
|
+
get(attribute.to_sym) || set(attribute.to_sym, [])
|
41
|
+
end
|
42
|
+
|
43
|
+
def []=(attribute, error)
|
44
|
+
self[attribute] << error
|
45
|
+
end
|
46
|
+
|
47
|
+
def each
|
48
|
+
messages.each_key do |attribute|
|
49
|
+
self[attribute].each { |error| yield attribute, error }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def size
|
54
|
+
values.flatten.size
|
55
|
+
end
|
56
|
+
|
57
|
+
def clear
|
58
|
+
@raw = nil
|
59
|
+
@messages.clear
|
60
|
+
end
|
61
|
+
|
62
|
+
delegate :values, to: :messages
|
63
|
+
|
64
|
+
delegate :keys, to: :messages
|
65
|
+
|
66
|
+
def count
|
67
|
+
to_a.size
|
68
|
+
end
|
69
|
+
|
70
|
+
def empty?
|
71
|
+
all? { |_k, v| v && v.empty? && !v.is_a?(String) }
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def add_error(messages, key, value)
|
77
|
+
key = key.to_sym
|
78
|
+
messages[key] ||= []
|
79
|
+
messages[key].push(value)
|
80
|
+
end
|
81
|
+
|
82
|
+
def parse_messages(json)
|
83
|
+
messages = {}
|
84
|
+
fields_to_errors(json, messages) if json['fields']
|
85
|
+
field_errors_to_errors(json, messages) if json['field_errors']
|
86
|
+
fallback_errors(json, messages) if messages.empty?
|
87
|
+
messages
|
88
|
+
end
|
89
|
+
|
90
|
+
def fallback_errors(json, messages)
|
91
|
+
if json.present?
|
92
|
+
json.each do |key, value|
|
93
|
+
add_error(messages, key, value)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
add_error(messages, 'unknown', 'error')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def field_errors_to_errors(json, messages)
|
101
|
+
json['field_errors'].each do |field_error|
|
102
|
+
add_error(messages, field_error['path'].join('.').to_sym, field_error['code'])
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def fields_to_errors(json, messages)
|
107
|
+
json['fields'].each do |field|
|
108
|
+
field['details'].each do |detail|
|
109
|
+
add_error(messages, field['name'].to_sym, detail['code'])
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def messages_from_response(response = nil)
|
115
|
+
return {} if !response || !response.body.is_a?(String) || response.body.length.zero?
|
116
|
+
json = JSON.parse(response.body)
|
117
|
+
parse_messages(json)
|
118
|
+
end
|
119
|
+
|
120
|
+
def message_from_response(response = nil)
|
121
|
+
return unless response
|
122
|
+
json = JSON.parse(response.body)
|
123
|
+
json['message']
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module LHS::Errors
|
2
|
+
class Nested < Base
|
3
|
+
|
4
|
+
def initialize(errors, scope)
|
5
|
+
@raw = errors
|
6
|
+
@messages = nest(errors.messages, scope)
|
7
|
+
@message = errors.message
|
8
|
+
@scope = scope
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# Filters base errors by scope
|
14
|
+
# and reduces key by given scope name;
|
15
|
+
# returns plain array if end of tree is reached
|
16
|
+
def nest(messages, scope = nil)
|
17
|
+
scope = translate_rails_to_api_scope(scope)
|
18
|
+
return messages unless scope
|
19
|
+
messages = messages.select do |key, _|
|
20
|
+
key.match(/^#{scope}/)
|
21
|
+
end
|
22
|
+
# if only one key and this key has no dots, exit with plain
|
23
|
+
if reached_leaf?(messages)
|
24
|
+
messages.first[1]
|
25
|
+
else
|
26
|
+
remove_scope(messages, scope)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Identifies if the end of nested errors tree is reached
|
31
|
+
def reached_leaf?(messages)
|
32
|
+
messages.keys.length == 1 &&
|
33
|
+
!messages.first[0].match(/\./)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Removes scope from given messages' key
|
37
|
+
def remove_scope(messages, scope)
|
38
|
+
messages.each_with_object({}) do |element, hash|
|
39
|
+
key = element[0].to_s.gsub(/^#{scope}\./, '')
|
40
|
+
hash[key.to_sym] = element[1]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Translates rails like accessors for collections
|
45
|
+
# like first, second, last to api error paths
|
46
|
+
def translate_rails_to_api_scope(scope)
|
47
|
+
case scope
|
48
|
+
when :first
|
49
|
+
0
|
50
|
+
when :second
|
51
|
+
1
|
52
|
+
when :last
|
53
|
+
return values.length - 1 if messages.present?
|
54
|
+
0
|
55
|
+
else
|
56
|
+
scope
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/lhs/item.rb
CHANGED
@@ -11,17 +11,8 @@ class LHS::Item < LHS::Proxy
|
|
11
11
|
include Validation
|
12
12
|
|
13
13
|
delegate :present?, :blank?, :empty?, to: :_raw
|
14
|
-
|
15
|
-
# prevent clashing with attributes of underlying data
|
16
|
-
attr_accessor :errors
|
17
|
-
|
18
14
|
delegate :_raw, to: :_data
|
19
15
|
|
20
|
-
def initialize(data)
|
21
|
-
self.errors = LHS::Errors.new
|
22
|
-
super(data)
|
23
|
-
end
|
24
|
-
|
25
16
|
def collection?
|
26
17
|
false
|
27
18
|
end
|
data/lib/lhs/proxy.rb
CHANGED
@@ -3,9 +3,9 @@ Dir[File.dirname(__FILE__) + '/concerns/proxy/*.rb'].each { |file| require file
|
|
3
3
|
# Proxy makes different kind of data accessible
|
4
4
|
# If href is present it also alows loading/reloading
|
5
5
|
class LHS::Proxy
|
6
|
-
|
7
6
|
include Accessors
|
8
7
|
include Create
|
8
|
+
include Errors
|
9
9
|
include Link
|
10
10
|
|
11
11
|
# prevent clashing with attributes of underlying data
|
data/lib/lhs/record.rb
CHANGED
@@ -21,11 +21,11 @@ class LHS::Record
|
|
21
21
|
|
22
22
|
delegate :_proxy, :_endpoint, :merge_raw!, :select, to: :_data
|
23
23
|
|
24
|
-
def initialize(data = nil)
|
24
|
+
def initialize(data = nil, apply_customer_setters = true)
|
25
25
|
data = LHS::Data.new({}, nil, self.class) unless data
|
26
26
|
data = LHS::Data.new(data, nil, self.class) unless data.is_a?(LHS::Data)
|
27
27
|
define_singleton_method(:_data) { data }
|
28
|
-
apply_custom_setters!
|
28
|
+
apply_custom_setters! if apply_customer_setters
|
29
29
|
end
|
30
30
|
|
31
31
|
def as_json(options = nil)
|
data/lib/lhs/version.rb
CHANGED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
|
3
|
+
describe LHS::Collection do
|
4
|
+
let(:items) { [{ name: 'Steve' }] }
|
5
|
+
let(:extra) { 'extra' }
|
6
|
+
let(:collection) { Record.where }
|
7
|
+
|
8
|
+
context 'to_a' do
|
9
|
+
let(:response_data) do
|
10
|
+
{
|
11
|
+
items: items,
|
12
|
+
extra: extra,
|
13
|
+
total: 1
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:subject) { collection.to_a }
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
class Record < LHS::Record
|
21
|
+
endpoint 'http://datastore/records`'
|
22
|
+
end
|
23
|
+
stub_request(:get, %r{http://datastore/records})
|
24
|
+
.to_return(body: response_data.to_json)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'returns an array and not LHS::Data' do
|
28
|
+
expect(subject).to be_kind_of Array
|
29
|
+
expect(subject).not_to be_kind_of LHS::Data
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/spec/item/errors_spec.rb
CHANGED
@@ -173,4 +173,60 @@ describe LHS::Item do
|
|
173
173
|
expect(record.errors.any?).to eq false
|
174
174
|
end
|
175
175
|
end
|
176
|
+
|
177
|
+
context 'nested data' do
|
178
|
+
let(:body_with_errors) do
|
179
|
+
{
|
180
|
+
"status" => 400,
|
181
|
+
"message" => "Some data in the request body failed validation. Inspect the field errors for details.",
|
182
|
+
"field_errors" => [{
|
183
|
+
"code" => "UNSUPPORTED_PROPERTY_VALUE",
|
184
|
+
"path" => ["reviews", 0, "name"],
|
185
|
+
"message" => "The property value is unsupported. Supported values are: FEMALE, MALE"
|
186
|
+
}, {
|
187
|
+
"code" => "INCOMPLETE_PROPERTY_VALUE",
|
188
|
+
"path" => ["address", "street", "name"],
|
189
|
+
"message" => "The property value is incomplete. It misses some data"
|
190
|
+
}, {
|
191
|
+
"code" => "REQUIRED_PROPERTY_VALUE",
|
192
|
+
"path" => ["address", "street", "additional_line1"],
|
193
|
+
"message" => "The property value is required"
|
194
|
+
}]
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
let(:record) do
|
199
|
+
Record.build(
|
200
|
+
reviews: [{ name: 123 }],
|
201
|
+
address: {
|
202
|
+
additional_line1: '',
|
203
|
+
street: {
|
204
|
+
name: 'Förlib'
|
205
|
+
}
|
206
|
+
}
|
207
|
+
)
|
208
|
+
end
|
209
|
+
|
210
|
+
let(:errrors) { record.errors }
|
211
|
+
|
212
|
+
before(:each) do
|
213
|
+
stub_request(:post, "#{datastore}/feedbacks")
|
214
|
+
.to_return(status: 400, body: body_with_errors.to_json)
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'forwards errors to nested data' do
|
218
|
+
record.save
|
219
|
+
expect(record.errors['address.street.name']).to include 'INCOMPLETE_PROPERTY_VALUE'
|
220
|
+
expect(record.errors['reviews.0.name']).to include 'UNSUPPORTED_PROPERTY_VALUE'
|
221
|
+
expect(record.address.errors).to be
|
222
|
+
expect(record.address.errors['street.name']).to be
|
223
|
+
expect(record.address.street.errors).to be
|
224
|
+
expect(record.address.street.errors[:name]).to include 'INCOMPLETE_PROPERTY_VALUE'
|
225
|
+
expect(record.reviews.errors).to be
|
226
|
+
expect(record.reviews.first.errors).to be
|
227
|
+
expect(record.reviews.first.errors[:name]).to include 'UNSUPPORTED_PROPERTY_VALUE'
|
228
|
+
expect(record.reviews.last.errors).to be
|
229
|
+
expect(record.reviews.last.errors[:name]).to include 'UNSUPPORTED_PROPERTY_VALUE'
|
230
|
+
end
|
231
|
+
end
|
176
232
|
end
|
data/spec/record/create_spec.rb
CHANGED
@@ -66,7 +66,7 @@ describe LHS::Record do
|
|
66
66
|
stub_request(:post, "#{datastore}/content-ads/12345/feedbacks")
|
67
67
|
.to_return(status: 400, body: creation_error.to_json)
|
68
68
|
feedback = Feedback.create(object.merge(campaign_id: '12345'))
|
69
|
-
expect(feedback.errors).to be_kind_of LHS::Errors
|
69
|
+
expect(feedback.errors).to be_kind_of LHS::Errors::Base
|
70
70
|
end
|
71
71
|
|
72
72
|
it 'raises an exception when creation failed using create!' do
|
@@ -107,7 +107,7 @@ describe LHS::Record do
|
|
107
107
|
|
108
108
|
it 'are used by create' do
|
109
109
|
feedback = Feedback.create(ratings: ratings)
|
110
|
-
expect(feedback.ratings.
|
110
|
+
expect(feedback.ratings._raw).to eq(converted_ratings)
|
111
111
|
end
|
112
112
|
|
113
113
|
it 'can be used directly to change raw data' do
|
data/spec/record/new_spec.rb
CHANGED
@@ -56,7 +56,7 @@ describe LHS::Record do
|
|
56
56
|
|
57
57
|
it 'are used by initializer' do
|
58
58
|
feedback = Rating.new(ratings: { a: 1, b: 2 })
|
59
|
-
expect(feedback.ratings.
|
59
|
+
expect(feedback.ratings._raw).to eq([{ name: :a, value: 1 }, { name: :b, value: 2 }])
|
60
60
|
end
|
61
61
|
|
62
62
|
it 'can be used directly to change raw data' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lhs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 11.0
|
4
|
+
version: 11.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- https://github.com/local-ch/lhs/graphs/contributors
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: lhc
|
@@ -195,6 +195,7 @@ files:
|
|
195
195
|
- lib/lhs/concerns/lhs/configuration.rb
|
196
196
|
- lib/lhs/concerns/proxy/accessors.rb
|
197
197
|
- lib/lhs/concerns/proxy/create.rb
|
198
|
+
- lib/lhs/concerns/proxy/errors.rb
|
198
199
|
- lib/lhs/concerns/proxy/link.rb
|
199
200
|
- lib/lhs/concerns/record/batch.rb
|
200
201
|
- lib/lhs/concerns/record/chainable.rb
|
@@ -216,7 +217,8 @@ files:
|
|
216
217
|
- lib/lhs/config.rb
|
217
218
|
- lib/lhs/data.rb
|
218
219
|
- lib/lhs/endpoint.rb
|
219
|
-
- lib/lhs/errors.rb
|
220
|
+
- lib/lhs/errors/base.rb
|
221
|
+
- lib/lhs/errors/nested.rb
|
220
222
|
- lib/lhs/item.rb
|
221
223
|
- lib/lhs/pagination/base.rb
|
222
224
|
- lib/lhs/pagination/offset.rb
|
@@ -237,6 +239,7 @@ files:
|
|
237
239
|
- spec/collection/href_spec.rb
|
238
240
|
- spec/collection/meta_data_spec.rb
|
239
241
|
- spec/collection/respond_to_spec.rb
|
242
|
+
- spec/collection/to_a_spec.rb
|
240
243
|
- spec/collection/without_object_items_spec.rb
|
241
244
|
- spec/complex/reduce_spec.rb
|
242
245
|
- spec/concerns/record/request_spec.rb
|
@@ -386,7 +389,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
386
389
|
requirements:
|
387
390
|
- Ruby >= 2.0.0
|
388
391
|
rubyforge_project:
|
389
|
-
rubygems_version: 2.
|
392
|
+
rubygems_version: 2.6.8
|
390
393
|
signing_key:
|
391
394
|
specification_version: 4
|
392
395
|
summary: Rails gem providing an easy, active-record-like interface for http json services
|
@@ -400,6 +403,7 @@ test_files:
|
|
400
403
|
- spec/collection/href_spec.rb
|
401
404
|
- spec/collection/meta_data_spec.rb
|
402
405
|
- spec/collection/respond_to_spec.rb
|
406
|
+
- spec/collection/to_a_spec.rb
|
403
407
|
- spec/collection/without_object_items_spec.rb
|
404
408
|
- spec/complex/reduce_spec.rb
|
405
409
|
- spec/concerns/record/request_spec.rb
|
data/lib/lhs/errors.rb
DELETED
@@ -1,124 +0,0 @@
|
|
1
|
-
# Like ActiveModel::Errors
|
2
|
-
class LHS::Errors
|
3
|
-
include Enumerable
|
4
|
-
|
5
|
-
attr_reader :messages, :message, :raw
|
6
|
-
|
7
|
-
def initialize(response = nil)
|
8
|
-
@raw = response.body if response
|
9
|
-
@messages = messages_from_response(response)
|
10
|
-
@message = message_from_response(response)
|
11
|
-
rescue JSON::ParserError
|
12
|
-
@messages = messages || {}
|
13
|
-
@message = 'parse error'
|
14
|
-
add_error(@messages, 'body', 'parse error')
|
15
|
-
end
|
16
|
-
|
17
|
-
def include?(attribute)
|
18
|
-
messages[attribute].present?
|
19
|
-
end
|
20
|
-
alias has_key? include?
|
21
|
-
alias key? include?
|
22
|
-
|
23
|
-
def add(attribute, message = :invalid, _options = {})
|
24
|
-
self[attribute]
|
25
|
-
messages[attribute] << message
|
26
|
-
end
|
27
|
-
|
28
|
-
def get(key)
|
29
|
-
messages[key]
|
30
|
-
end
|
31
|
-
|
32
|
-
def set(key, value)
|
33
|
-
messages[key] = value
|
34
|
-
end
|
35
|
-
|
36
|
-
delegate :delete, to: :messages
|
37
|
-
|
38
|
-
def [](attribute)
|
39
|
-
get(attribute.to_sym) || set(attribute.to_sym, [])
|
40
|
-
end
|
41
|
-
|
42
|
-
def []=(attribute, error)
|
43
|
-
self[attribute] << error
|
44
|
-
end
|
45
|
-
|
46
|
-
def each
|
47
|
-
messages.each_key do |attribute|
|
48
|
-
self[attribute].each { |error| yield attribute, error }
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def size
|
53
|
-
values.flatten.size
|
54
|
-
end
|
55
|
-
|
56
|
-
def clear
|
57
|
-
@raw = nil
|
58
|
-
@messages.clear
|
59
|
-
end
|
60
|
-
|
61
|
-
delegate :values, to: :messages
|
62
|
-
|
63
|
-
delegate :keys, to: :messages
|
64
|
-
|
65
|
-
def count
|
66
|
-
to_a.size
|
67
|
-
end
|
68
|
-
|
69
|
-
def empty?
|
70
|
-
all? { |_k, v| v && v.empty? && !v.is_a?(String) }
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
def add_error(messages, key, value)
|
76
|
-
key = key.to_sym
|
77
|
-
messages[key] ||= []
|
78
|
-
messages[key].push(value)
|
79
|
-
end
|
80
|
-
|
81
|
-
def parse_messages(json)
|
82
|
-
messages = {}
|
83
|
-
fields_to_errors(json, messages) if json['fields']
|
84
|
-
field_errors_to_errors(json, messages) if json['field_errors']
|
85
|
-
fallback_errors(json, messages) if messages.empty?
|
86
|
-
messages
|
87
|
-
end
|
88
|
-
|
89
|
-
def fallback_errors(json, messages)
|
90
|
-
if json.present?
|
91
|
-
json.each do |key, value|
|
92
|
-
add_error(messages, key, value)
|
93
|
-
end
|
94
|
-
else
|
95
|
-
add_error(messages, 'unknown', 'error')
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
def field_errors_to_errors(json, messages)
|
100
|
-
json['field_errors'].each do |field_error|
|
101
|
-
add_error(messages, field_error['path'].join('.').to_sym, field_error['code'])
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def fields_to_errors(json, messages)
|
106
|
-
json['fields'].each do |field|
|
107
|
-
field['details'].each do |detail|
|
108
|
-
add_error(messages, field['name'].to_sym, detail['code'])
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
def messages_from_response(response = nil)
|
114
|
-
return {} if !response || !response.body.is_a?(String) || response.body.length.zero?
|
115
|
-
json = JSON.parse(response.body)
|
116
|
-
parse_messages(json)
|
117
|
-
end
|
118
|
-
|
119
|
-
def message_from_response(response = nil)
|
120
|
-
return unless response
|
121
|
-
json = JSON.parse(response.body)
|
122
|
-
json['message']
|
123
|
-
end
|
124
|
-
end
|