lhs 11.0.3 → 11.1.0

Sign up to get free protection for your applications and to get access to all the features.
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