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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: dc9d93456e8c4ad57a3883f582dd868169272980
4
- data.tar.gz: d741c6a54fcdbe5c7b72a8f226fd290485d5e218
3
+ metadata.gz: abf5de2dde8813263d07b049e423c569bf0e187c
4
+ data.tar.gz: 2bf40b2701cfa58783ce975c260da734a8952b1c
5
5
  SHA512:
6
- metadata.gz: a1dbba4cf7b6d320d0a16ff0582159a50565d6f5e5ba037e0c0bbebc3c708d3e50403df9846a35b26879dc48f116349ec961cd4c418c3a97ac6302c916280c91
7
- data.tar.gz: d6d14efc2d3558dd996aba3bcc9da903e8a19ca118dd8d2b760a3bab415b13807512740ae947524f0894984f1e373c7e9d96cfa49e0cb29de6d95c3fd30b2156
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
 
@@ -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
- return enclose_in_data(value) if value.is_a? Hash
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
- def enclose_in_data(value)
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)
@@ -20,7 +20,7 @@ class LHS::Item < LHS::Proxy
20
20
  apply_default_creation_options(options, url, data)
21
21
  )
22
22
  rescue LHC::Error => e
23
- self.errors = LHS::Errors.new(e.response)
23
+ self.errors = LHS::Errors::Base.new(e.response)
24
24
  raise e
25
25
  end
26
26
 
@@ -9,7 +9,7 @@ class LHS::Item < LHS::Proxy
9
9
  def update(params, options = nil)
10
10
  update!(params, options)
11
11
  rescue LHC::Error => e
12
- self.errors = LHS::Errors.new(e.response)
12
+ self.errors = LHS::Errors::Base.new(e.response)
13
13
  false
14
14
  end
15
15
 
@@ -16,7 +16,7 @@ class LHS::Item < LHS::Proxy
16
16
  run_validation!(record, options, url, params)
17
17
  true
18
18
  rescue LHC::Error => e
19
- self.errors = LHS::Errors.new(e.response)
19
+ self.errors = LHS::Errors::Base.new(e.response)
20
20
  false
21
21
  end
22
22
  alias validate valid?
@@ -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
- access_item(value, record) ||
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
- def wrap_return(value, record)
70
- data = LHS::Data.new(value, _data)
71
- return record.new(data) if record
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
@@ -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 input.is_a?(String) && !input.empty?
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
@@ -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
@@ -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
@@ -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)
@@ -1,3 +1,3 @@
1
1
  module LHS
2
- VERSION = "11.0.3"
2
+ VERSION = "11.1.0"
3
3
  end
@@ -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
@@ -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
@@ -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.raw).to eq(converted_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
@@ -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.raw).to eq([{ name: :a, value: 1 }, { name: :b, value: 2 }])
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.3
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-05-31 00:00:00.000000000 Z
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.5.1
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
@@ -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