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
         
     |