attune 0.0.1 → 0.0.2
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 +5 -13
- data/README.md +29 -6
- data/attune.gemspec +2 -1
- data/lib/attune.rb +13 -0
- data/lib/attune/call_dropping.rb +52 -0
- data/lib/attune/client.rb +41 -14
- data/lib/attune/configurable.rb +14 -0
- data/lib/attune/default.rb +5 -0
- data/lib/attune/version.rb +1 -1
- data/spec/attune/call_dropping_spec.rb +84 -0
- data/spec/attune/client_spec.rb +57 -0
- data/spec/attune_spec.rb +14 -0
- data/spec/spec_helper.rb +6 -0
- metadata +33 -16
    
        checksums.yaml
    CHANGED
    
    | @@ -1,15 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
             | 
| 5 | 
            -
              data.tar.gz: !binary |-
         | 
| 6 | 
            -
                YmRkYWIzMTRhOGNlOTVlYzBhZDFjY2FkMGFkNDY2MGVlNWIzMGY2ZA==
         | 
| 2 | 
            +
            SHA1:
         | 
| 3 | 
            +
              metadata.gz: a768188791c5cf1b4dbb0d9bb7639c79be622f49
         | 
| 4 | 
            +
              data.tar.gz: 0b55667d588255b97df0fc7fbc7d7571f2a3a420
         | 
| 7 5 | 
             
            SHA512:
         | 
| 8 | 
            -
              metadata.gz:  | 
| 9 | 
            -
             | 
| 10 | 
            -
                ZTQ3N2Q3ZGU3MzE0OWUzZWNiNmM2ZjUyMTQxMjU4NGJhMTA2MGUzYTI4ODk2
         | 
| 11 | 
            -
                ODhhYmQ1MWQ0ZjM0MDA3ZDU4OWEyNDJjMDExYjY4ODk2MWE1MDk=
         | 
| 12 | 
            -
              data.tar.gz: !binary |-
         | 
| 13 | 
            -
                NWI4YmYxMDMxNWZkMWQxYTZjZTIyODQ1MjdiYjkxMWIwZjYzM2E1MDM3MDk1
         | 
| 14 | 
            -
                M2Y5ZTU5YWQ4ODcwNTk4ZDQ0NWE0ZjA1NzMzOTI1MTQyMGQ0NzQ5MDE3MzFi
         | 
| 15 | 
            -
                YjJmYzVlNTQxNmQ5ZjUyMWQyNzZkZjU5NDMxNjQxZTIzMjA5YTc=
         | 
| 6 | 
            +
              metadata.gz: a90ccfd4d4aa1893c315e161545a3bd04cdddcdaabdc98f7e4daf809a7a8acbefd3e15d03344fd933ae83e290b7ff3f4bdd2c15b0180565d4b00597c4c7d9e74
         | 
| 7 | 
            +
              data.tar.gz: 1686b9cfa601e45f89590446c8c69a65f9c6f80b775f4031056fc6f9438c790514f836d7f00622ce31b0562d92e62c643aa19da5a53491c92d08cb2289be1ec8
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,6 +1,10 @@ | |
| 1 1 | 
             
            # Attune
         | 
| 2 2 |  | 
| 3 | 
            -
            A client for the [Attune ranking API](http://attune.co/).  | 
| 3 | 
            +
            A client for the [Attune ranking API](http://attune.co/). Built using the excellent [faraday](https://github.com/lostisland/faraday) library.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            * [Github](https://github.com/DigitalStripe/attune-ruby)
         | 
| 6 | 
            +
            * [RubyGems](https://rubygems.org/gems/attune)
         | 
| 7 | 
            +
            * [Documentation](http://rdoc.info/github/DigitalStripe/attune-ruby)
         | 
| 4 8 |  | 
| 5 9 | 
             
            ## Installation
         | 
| 6 10 |  | 
| @@ -55,22 +59,24 @@ The client can then perform rankings | |
| 55 59 | 
             
            ``` ruby
         | 
| 56 60 | 
             
            class ProductsController
         | 
| 57 61 | 
             
              def index
         | 
| 58 | 
            -
                @products = Product.all
         | 
| 62 | 
            +
                @products = sorted(Product.all)
         | 
| 63 | 
            +
              end
         | 
| 59 64 |  | 
| 65 | 
            +
              private
         | 
| 66 | 
            +
              def sorted products
         | 
| 60 67 | 
             
                ranking = attune_client.get_ranking(
         | 
| 61 68 | 
             
                  id: session[:attune_id],
         | 
| 62 69 | 
             
                  view: request.fullpath,
         | 
| 63 70 | 
             
                  collection: 'products',
         | 
| 64 | 
            -
                  entities:  | 
| 71 | 
            +
                  entities: products.map(&:id)
         | 
| 65 72 | 
             
                )
         | 
| 66 | 
            -
                 | 
| 73 | 
            +
                products.sort_by do |product|
         | 
| 67 74 | 
             
                  ranking.index(product.id.to_s)
         | 
| 68 75 | 
             
                end
         | 
| 69 76 | 
             
              end
         | 
| 70 77 | 
             
            end
         | 
| 71 78 | 
             
            ```
         | 
| 72 79 |  | 
| 73 | 
            -
             | 
| 74 80 | 
             
            ### Configuration
         | 
| 75 81 |  | 
| 76 82 | 
             
            Attune can be configured globally
         | 
| @@ -88,9 +94,26 @@ Settings can also be overridden on a client object | |
| 88 94 | 
             
            client = Attune::Client.new(timeout: 2)
         | 
| 89 95 | 
             
            ```
         | 
| 90 96 |  | 
| 97 | 
            +
            See the documentation for
         | 
| 98 | 
            +
            [Attune::Configurable](http://rdoc.info/github/DigitalStripe/attune-ruby/master/Attune/Configurable)
         | 
| 99 | 
            +
            and the
         | 
| 100 | 
            +
            [default configuration](http://rdoc.info/github/DigitalStripe/attune-ruby/master/Attune/Default)
         | 
| 101 | 
            +
            for more details.
         | 
| 102 | 
            +
             | 
| 103 | 
            +
            ### Testing
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            For testing and development, the ranking API can be simulated using.
         | 
| 106 | 
            +
             | 
| 107 | 
            +
            ``` ruby
         | 
| 108 | 
            +
            Attune.test_mode!
         | 
| 109 | 
            +
            ```
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            In this mode no API calls will be made, and rankings will be returned in their original order.
         | 
| 112 | 
            +
            Calling `.test_mode!` is equivalent to setting `disabled` to true and `exception_handler` to `:mock`.
         | 
| 113 | 
            +
             | 
| 91 114 | 
             
            ## Contributing
         | 
| 92 115 |  | 
| 93 | 
            -
            1. Fork it ( http://github.com/ | 
| 116 | 
            +
            1. Fork it ( http://github.com/DigitalStripe/attune/fork )
         | 
| 94 117 | 
             
            2. Create your feature branch (`git checkout -b my-new-feature`)
         | 
| 95 118 | 
             
            3. Commit your changes (`git commit -am 'Add some feature'`)
         | 
| 96 119 | 
             
            4. Push to the branch (`git push origin my-new-feature`)
         | 
    
        data/attune.gemspec
    CHANGED
    
    | @@ -10,7 +10,7 @@ Gem::Specification.new do |spec| | |
| 10 10 | 
             
              spec.email         = ["john@freerunningtechnologies.com"]
         | 
| 11 11 | 
             
              spec.summary       = %q{Client for the Attune product ranking API.}
         | 
| 12 12 | 
             
              spec.description   = %q{Client for the Attune product ranking API.}
         | 
| 13 | 
            -
              spec.homepage      = "https://github.com/ | 
| 13 | 
            +
              spec.homepage      = "https://github.com/DigitalStripe/attune-ruby"
         | 
| 14 14 | 
             
              spec.license       = "MIT"
         | 
| 15 15 |  | 
| 16 16 | 
             
              spec.files         = `git ls-files`.split($/)
         | 
| @@ -25,4 +25,5 @@ Gem::Specification.new do |spec| | |
| 25 25 | 
             
              spec.add_development_dependency "rspec"
         | 
| 26 26 | 
             
              spec.add_development_dependency "yard"
         | 
| 27 27 | 
             
              spec.add_development_dependency "redcarpet"
         | 
| 28 | 
            +
              spec.add_development_dependency "simplecov"
         | 
| 28 29 | 
             
            end
         | 
    
        data/lib/attune.rb
    CHANGED
    
    | @@ -9,4 +9,17 @@ module Attune | |
| 9 9 | 
             
              def self.client
         | 
| 10 10 | 
             
                Client.new
         | 
| 11 11 | 
             
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              # Simulate all API calls
         | 
| 14 | 
            +
              # This is equivalent to setting disabled to true and exception_handler to
         | 
| 15 | 
            +
              # :mock
         | 
| 16 | 
            +
              def self.test_mode!
         | 
| 17 | 
            +
                configure do |c|
         | 
| 18 | 
            +
                  c.exception_handler = :mock
         | 
| 19 | 
            +
                  c.disabled = true
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
              def self.configure(&block)
         | 
| 23 | 
            +
                Default.configure(&block)
         | 
| 24 | 
            +
              end
         | 
| 12 25 | 
             
            end
         | 
| @@ -0,0 +1,52 @@ | |
| 1 | 
            +
            module Attune
         | 
| 2 | 
            +
              class CallDropping < Faraday::Middleware
         | 
| 3 | 
            +
                def initialize app, storage=default_storage
         | 
| 4 | 
            +
                  @app = app
         | 
| 5 | 
            +
                  @storage = storage
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def call env
         | 
| 9 | 
            +
                  raise_timeout if should_skip?
         | 
| 10 | 
            +
                  begin
         | 
| 11 | 
            +
                    result = @app.call(env)
         | 
| 12 | 
            +
                    increment_skips(-SKIP_STEP)
         | 
| 13 | 
            +
                    result
         | 
| 14 | 
            +
                  rescue Faraday::Error::TimeoutError => e
         | 
| 15 | 
            +
                    increment_skips(SKIP_STEP)
         | 
| 16 | 
            +
                    raise e
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                private
         | 
| 21 | 
            +
                # requests to skip before retry
         | 
| 22 | 
            +
                SKIP_STEP = 0.25
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                # The most requests to skip before retrying
         | 
| 25 | 
            +
                SKIP_MAX = 10.0
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def raise_timeout
         | 
| 28 | 
            +
                  raise Faraday::Error::TimeoutError, "dropped request"
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def should_skip?
         | 
| 32 | 
            +
                  rand > (1.0 / (1.0 + skip_rate))
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def increment_skips amount
         | 
| 36 | 
            +
                  self.skip_rate = skip_rate + amount
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  if skip_rate <= 0.0
         | 
| 39 | 
            +
                    self.skip_rate = 0.0
         | 
| 40 | 
            +
                  elsif skip_rate >= SKIP_MAX
         | 
| 41 | 
            +
                    self.skip_rate = SKIP_MAX
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                def skip_rate; @storage[:skip_rate] || 0.0; end
         | 
| 46 | 
            +
                def skip_rate= v; @storage[:skip_rate]= v; end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                def default_storage
         | 
| 49 | 
            +
                  Thread.current
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
            end
         | 
    
        data/lib/attune/client.rb
    CHANGED
    
    | @@ -1,8 +1,12 @@ | |
| 1 1 | 
             
            require 'json'
         | 
| 2 2 |  | 
| 3 3 | 
             
            module Attune
         | 
| 4 | 
            +
              class DisabledException < Faraday::Error::ClientError
         | 
| 5 | 
            +
                def initialize(message="Attune library disabled though config")
         | 
| 6 | 
            +
                  super(message)
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
              end
         | 
| 4 9 |  | 
| 5 | 
            -
              # Client for the attune
         | 
| 6 10 | 
             
              class Client
         | 
| 7 11 | 
             
                include Attune::Configurable
         | 
| 8 12 |  | 
| @@ -38,14 +42,19 @@ module Attune | |
| 38 42 | 
             
                # @option options [String] :user_agent The user agent for the application used by the anonymous users
         | 
| 39 43 | 
             
                # @return id [String]
         | 
| 40 44 | 
             
                # @raise [ArgumentError] if user_agent is not provided
         | 
| 45 | 
            +
                # @raise [Faraday::Error] if the request fails or exceeds the timeout
         | 
| 41 46 | 
             
                def create_anonymous(options)
         | 
| 42 47 | 
             
                  raise ArgumentError, "user_agent required" unless options[:user_agent]
         | 
| 43 48 | 
             
                  if id = options[:id]
         | 
| 44 | 
            -
                     | 
| 49 | 
            +
                    put("anonymous/#{id}", {user_agent: options[:user_agent]})
         | 
| 45 50 | 
             
                    id
         | 
| 46 51 | 
             
                  else
         | 
| 47 | 
            -
                    response = post("anonymous", {user_agent: options[:user_agent]})
         | 
| 48 | 
            -
             | 
| 52 | 
            +
                    if response = post("anonymous", {user_agent: options[:user_agent]})
         | 
| 53 | 
            +
                      response[:location][/\Aurn:id:([a-z0-9\-]+)\Z/, 1]
         | 
| 54 | 
            +
                    else
         | 
| 55 | 
            +
                      # Return a new UUID if there was an exception and we're in mock mode
         | 
| 56 | 
            +
                      SecureRandom.uuid
         | 
| 57 | 
            +
                    end
         | 
| 49 58 | 
             
                  end
         | 
| 50 59 | 
             
                end
         | 
| 51 60 |  | 
| @@ -67,10 +76,15 @@ module Attune | |
| 67 76 | 
             
                # @option options [String] :customer id of customer (optional)
         | 
| 68 77 | 
             
                # @return ranking [Array<String>] The entities in their ranked order
         | 
| 69 78 | 
             
                # @raise [ArgumentError] if required parameters are missing
         | 
| 79 | 
            +
                # @raise [Faraday::Error] if the request fails or exceeds the timeout
         | 
| 70 80 | 
             
                def get_rankings(options)
         | 
| 71 81 | 
             
                  qs = encoded_ranking_params(options)
         | 
| 72 | 
            -
                  response = get("rankings/#{qs}", customer: options.fetch(:customer, 'none'))
         | 
| 73 | 
            -
             | 
| 82 | 
            +
                  if response = get("rankings/#{qs}", customer: options.fetch(:customer, 'none'))
         | 
| 83 | 
            +
                    JSON.parse(response.body)['ranking']
         | 
| 84 | 
            +
                  else
         | 
| 85 | 
            +
                    # In mock mode: return the entities in the order passed in
         | 
| 86 | 
            +
                    options[:entities]
         | 
| 87 | 
            +
                  end
         | 
| 74 88 | 
             
                end
         | 
| 75 89 |  | 
| 76 90 | 
             
                # Get multiple rankings in one call
         | 
| @@ -92,14 +106,21 @@ module Attune | |
| 92 106 | 
             
                #   ])
         | 
| 93 107 | 
             
                # @param [Array<Hash>] multi_options An array of options (see #get_rankings)
         | 
| 94 108 | 
             
                # @return [Array<Array<String>>] rankings
         | 
| 109 | 
            +
                # @raise [Faraday::Error] if the request fails or exceeds the timeout
         | 
| 95 110 | 
             
                def multi_get_rankings(multi_options)
         | 
| 96 111 | 
             
                  requests = multi_options.map do |options|
         | 
| 97 112 | 
             
                    encoded_ranking_params(options)
         | 
| 98 113 | 
             
                  end
         | 
| 99 | 
            -
                  response = get("rankings", ids: requests)
         | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 114 | 
            +
                  if response = get("rankings", ids: requests)
         | 
| 115 | 
            +
                    results = JSON.parse(response.body)['results']
         | 
| 116 | 
            +
                    results.values.map do |result|
         | 
| 117 | 
            +
                      result['ranking']
         | 
| 118 | 
            +
                    end
         | 
| 119 | 
            +
                  else
         | 
| 120 | 
            +
                    # In mock mode: return the entities in the order passed in
         | 
| 121 | 
            +
                    multi_options.map do |options|
         | 
| 122 | 
            +
                      options[:entities]
         | 
| 123 | 
            +
                    end
         | 
| 103 124 | 
             
                  end
         | 
| 104 125 | 
             
                end
         | 
| 105 126 |  | 
| @@ -112,6 +133,7 @@ module Attune | |
| 112 133 | 
             
                #     '25892e17-80f6-415f-9c65-7395632f022',
         | 
| 113 134 | 
             
                #     'cd171f7c-560d-4a62-8d65-16b87419a58'
         | 
| 114 135 | 
             
                #   )
         | 
| 136 | 
            +
                # @raise [Faraday::Error] if the request fails or exceeds the timeout
         | 
| 115 137 | 
             
                def bind(id, customer_id)
         | 
| 116 138 | 
             
                  put("bindings/anonymous=#{id}&customer=#{customer_id}")
         | 
| 117 139 | 
             
                  true
         | 
| @@ -131,27 +153,32 @@ module Attune | |
| 131 153 |  | 
| 132 154 | 
             
                def get(path, params={})
         | 
| 133 155 | 
             
                  adapter.get(path, params)
         | 
| 134 | 
            -
                rescue Faraday::ClientError => e
         | 
| 156 | 
            +
                rescue Faraday::Error::ClientError => e
         | 
| 135 157 | 
             
                  handle_exception(e)
         | 
| 136 158 | 
             
                end
         | 
| 137 159 |  | 
| 138 160 | 
             
                def put(path, params={})
         | 
| 139 161 | 
             
                  adapter.put(path, ::JSON.dump(params))
         | 
| 140 | 
            -
                rescue Faraday::ClientError => e
         | 
| 162 | 
            +
                rescue Faraday::Error::ClientError => e
         | 
| 141 163 | 
             
                  handle_exception(e)
         | 
| 142 164 | 
             
                end
         | 
| 143 165 |  | 
| 144 166 | 
             
                def post(path, params={})
         | 
| 145 167 | 
             
                  adapter.post(path, ::JSON.dump(params))
         | 
| 146 | 
            -
                rescue Faraday::ClientError => e
         | 
| 168 | 
            +
                rescue Faraday::Error::ClientError => e
         | 
| 147 169 | 
             
                  handle_exception(e)
         | 
| 148 170 | 
             
                end
         | 
| 149 171 |  | 
| 150 172 | 
             
                def handle_exception e
         | 
| 151 | 
            -
                   | 
| 173 | 
            +
                  if exception_handler == :mock
         | 
| 174 | 
            +
                    nil
         | 
| 175 | 
            +
                  else
         | 
| 176 | 
            +
                    raise e
         | 
| 177 | 
            +
                  end
         | 
| 152 178 | 
             
                end
         | 
| 153 179 |  | 
| 154 180 | 
             
                def adapter
         | 
| 181 | 
            +
                  raise DisabledException if disabled?
         | 
| 155 182 | 
             
                  Faraday.new(url: endpoint, builder: middleware, request: {timeout: timeout})
         | 
| 156 183 | 
             
                end
         | 
| 157 184 | 
             
              end
         | 
    
        data/lib/attune/configurable.rb
    CHANGED
    
    | @@ -4,6 +4,7 @@ module Attune | |
| 4 4 | 
             
                  :endpoint,
         | 
| 5 5 | 
             
                  :middleware,
         | 
| 6 6 | 
             
                  :disabled,
         | 
| 7 | 
            +
                  :exception_handler,
         | 
| 7 8 | 
             
                  :timeout
         | 
| 8 9 | 
             
                ]
         | 
| 9 10 |  | 
| @@ -15,10 +16,23 @@ module Attune | |
| 15 16 |  | 
| 16 17 | 
             
                # FIXME
         | 
| 17 18 | 
             
                attr_accessor :disabled
         | 
| 19 | 
            +
                alias_method :disabled?, :disabled
         | 
| 18 20 |  | 
| 19 21 | 
             
                # Time (in seconds) to wait for requests to finish
         | 
| 20 22 | 
             
                attr_accessor :timeout
         | 
| 21 23 |  | 
| 24 | 
            +
                # How to deal with HTTP exceptions
         | 
| 25 | 
            +
                # @param [:mock, :raise] handler Method used for handling exceptions.
         | 
| 26 | 
            +
                # @raise [ArgumentError] if handler is not :mock or :raise
         | 
| 27 | 
            +
                attr_reader :exception_handler
         | 
| 28 | 
            +
                def exception_handler= handler
         | 
| 29 | 
            +
                  if [:mock, :raise].include?(handler)
         | 
| 30 | 
            +
                    @exception_handler = handler
         | 
| 31 | 
            +
                  else
         | 
| 32 | 
            +
                    raise ArgumentError, "exception_handler must be :mock or :raise"
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 22 36 | 
             
                # @example configure
         | 
| 23 37 | 
             
                #   Attune.configure do |c|
         | 
| 24 38 | 
             
                #     c.endpoint = "http://example.com:8080/"
         | 
    
        data/lib/attune/default.rb
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            require 'attune/param_flattener'
         | 
| 2 | 
            +
            require "attune/call_dropping"
         | 
| 2 3 | 
             
            require "attune/json_logger"
         | 
| 3 4 |  | 
| 4 5 | 
             
            module Attune
         | 
| @@ -12,6 +13,9 @@ module Attune | |
| 12 13 | 
             
                  # Needed for encoding of BATCH GET requests
         | 
| 13 14 | 
             
                  builder.use Attune::ParamFlattener
         | 
| 14 15 |  | 
| 16 | 
            +
                  # Log all requests
         | 
| 17 | 
            +
                  builder.use Attune::CallDropping
         | 
| 18 | 
            +
             | 
| 15 19 | 
             
                  # Allow one retry per request
         | 
| 16 20 | 
             
                  builder.request :retry, 1
         | 
| 17 21 |  | 
| @@ -27,6 +31,7 @@ module Attune | |
| 27 31 | 
             
                  c.endpoint = ENDPOINT
         | 
| 28 32 | 
             
                  c.middleware = MIDDLEWARE
         | 
| 29 33 | 
             
                  c.disabled = false
         | 
| 34 | 
            +
                  c.exception_handler = :raise
         | 
| 30 35 | 
             
                  c.timeout = 1
         | 
| 31 36 | 
             
                end
         | 
| 32 37 | 
             
              end
         | 
    
        data/lib/attune/version.rb
    CHANGED
    
    
| @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Attune::CallDropping do
         | 
| 4 | 
            +
              let(:stubs){ Faraday::Adapter::Test::Stubs.new }
         | 
| 5 | 
            +
              let(:connection) do
         | 
| 6 | 
            +
                Faraday.new(url: 'http://example.com/') do |builder|
         | 
| 7 | 
            +
                  builder.use described_class, storage
         | 
| 8 | 
            +
                  builder.adapter :test, stubs
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
              let(:response){ connection.get("/") }
         | 
| 12 | 
            +
              let(:timeout){ Faraday::Error::TimeoutError.new("simulated timeout") }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              context "using default storage" do
         | 
| 15 | 
            +
                let(:connection) do
         | 
| 16 | 
            +
                  Faraday.new(url: 'http://example.com/') do |builder|
         | 
| 17 | 
            +
                    builder.use described_class
         | 
| 18 | 
            +
                    builder.adapter :test, stubs
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
                it "can be initialized" do
         | 
| 22 | 
            +
                  stubs.get("/"){ [200, {}, "foobar"] }
         | 
| 23 | 
            +
                  expect(response.body).to eq "foobar"
         | 
| 24 | 
            +
                  stubs.verify_stubbed_calls
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              context "without previous timeouts" do
         | 
| 29 | 
            +
                let(:storage){ {} }
         | 
| 30 | 
            +
                context "no timeout" do
         | 
| 31 | 
            +
                  it "runs request" do
         | 
| 32 | 
            +
                    stubs.get("/"){ [200, {}, "foobar"] }
         | 
| 33 | 
            +
                    expect(response.body).to eq "foobar"
         | 
| 34 | 
            +
                    expect(storage).to eq(skip_rate: 0.0)
         | 
| 35 | 
            +
                    stubs.verify_stubbed_calls
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                context "with a timeout" do
         | 
| 40 | 
            +
                  it "raises the timeout" do
         | 
| 41 | 
            +
                    stubs.get("/"){ raise timeout }
         | 
| 42 | 
            +
                    expect {
         | 
| 43 | 
            +
                      response
         | 
| 44 | 
            +
                    }.to raise_exception(Faraday::Error::TimeoutError)
         | 
| 45 | 
            +
                    expect(storage).to eq(skip_rate: 0.25)
         | 
| 46 | 
            +
                    stubs.verify_stubbed_calls
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
              end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
              context "with an existing skip rate" do
         | 
| 52 | 
            +
                let(:storage){ {skip_rate: 1.0} }
         | 
| 53 | 
            +
                before{ described_class.any_instance.stub(rand: random) }
         | 
| 54 | 
            +
                context "request skipped" do
         | 
| 55 | 
            +
                  let(:random){ 1 }
         | 
| 56 | 
            +
                  it "raises timeout" do
         | 
| 57 | 
            +
                    expect {
         | 
| 58 | 
            +
                      response
         | 
| 59 | 
            +
                    }.to raise_exception(Faraday::Error::TimeoutError)
         | 
| 60 | 
            +
                    expect(storage).to eq(skip_rate: 1.00)
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                context "request attempted" do
         | 
| 65 | 
            +
                  context "at max drop rate" do
         | 
| 66 | 
            +
                    let(:storage){ {skip_rate: 10.0} }
         | 
| 67 | 
            +
                    it "leaves drop rate unchanged" do
         | 
| 68 | 
            +
                      stubs.get("/"){ raise timeout }
         | 
| 69 | 
            +
                      expect { response }.to raise_exception(Faraday::Error::TimeoutError)
         | 
| 70 | 
            +
                      expect(storage).to eq(skip_rate: 10.0)
         | 
| 71 | 
            +
                      stubs.verify_stubbed_calls
         | 
| 72 | 
            +
                    end
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  let(:random){ 0 }
         | 
| 76 | 
            +
                  it "succeeds an reduces skip rate" do
         | 
| 77 | 
            +
                    stubs.get("/"){ [200, {}, "foobar"] }
         | 
| 78 | 
            +
                    expect(response.body).to eq "foobar"
         | 
| 79 | 
            +
                    expect(storage).to eq(skip_rate: 0.75)
         | 
| 80 | 
            +
                    stubs.verify_stubbed_calls
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
            end
         | 
    
        data/spec/attune/client_spec.rb
    CHANGED
    
    | @@ -24,6 +24,63 @@ describe Attune::Client do | |
| 24 24 | 
             
                end
         | 
| 25 25 | 
             
              end
         | 
| 26 26 |  | 
| 27 | 
            +
              describe "API errors" do
         | 
| 28 | 
            +
                it "will raise timeout" do
         | 
| 29 | 
            +
                  stubs.post("anonymous", %[{"user_agent":"Mozilla/5.0"}]){ raise Faraday::Error::TimeoutError.new("test") }
         | 
| 30 | 
            +
                  expect {
         | 
| 31 | 
            +
                    client.create_anonymous(user_agent: 'Mozilla/5.0')
         | 
| 32 | 
            +
                  }.to raise_exception(Faraday::Error::TimeoutError)
         | 
| 33 | 
            +
                  stubs.verify_stubbed_calls
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
                it "will raise ConnectionFailed" do
         | 
| 36 | 
            +
                  stubs.post("anonymous", %[{"user_agent":"Mozilla/5.0"}]){ raise Faraday::Error::ConnectionFailed.new("test") }
         | 
| 37 | 
            +
                  expect {
         | 
| 38 | 
            +
                    client.create_anonymous(user_agent: 'Mozilla/5.0')
         | 
| 39 | 
            +
                  }.to raise_exception(Faraday::Error::ConnectionFailed)
         | 
| 40 | 
            +
                  stubs.verify_stubbed_calls
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              describe "disabled" do
         | 
| 45 | 
            +
                context "with raise" do
         | 
| 46 | 
            +
                  let(:options){ {disabled: true, exception_handler: :raise} }
         | 
| 47 | 
            +
                  it "will raise DisalbedException" do
         | 
| 48 | 
            +
                    expect {
         | 
| 49 | 
            +
                      client.create_anonymous(user_agent: 'Mozilla/5.0')
         | 
| 50 | 
            +
                    }.to raise_exception(Attune::DisabledException)
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
                context "with mock" do
         | 
| 54 | 
            +
                  let(:options){ {disabled: true, exception_handler: :mock} }
         | 
| 55 | 
            +
                  it "mocks create_anonymous with an id" do
         | 
| 56 | 
            +
                    result = client.create_anonymous(id: '12345', user_agent: 'Mozilla/5.0')
         | 
| 57 | 
            +
                    expect(result).to eq('12345')
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                  it "mocks create_anonymous with no id" do
         | 
| 60 | 
            +
                    result = client.create_anonymous(user_agent: 'Mozilla/5.0')
         | 
| 61 | 
            +
                    expect(result).to match(/^[a-z0-9\-]+$/)
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
                  it "mocks get_rankings" do
         | 
| 64 | 
            +
                    result = client.get_rankings(
         | 
| 65 | 
            +
                      id: 'abcd123',
         | 
| 66 | 
            +
                      view: 'b/mens-pants',
         | 
| 67 | 
            +
                      collection: 'products',
         | 
| 68 | 
            +
                      entities: %w[1001, 1002, 1003, 1004]
         | 
| 69 | 
            +
                    )
         | 
| 70 | 
            +
                    expect(result).to eq %w[1001, 1002, 1003, 1004]
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
                  it "mocks multi_get_rankings" do
         | 
| 73 | 
            +
                    result = client.multi_get_rankings([
         | 
| 74 | 
            +
                      id: 'abcd123',
         | 
| 75 | 
            +
                      view: 'b/mens-pants',
         | 
| 76 | 
            +
                      collection: 'products',
         | 
| 77 | 
            +
                      entities: %w[1001, 1002, 1003, 1004]
         | 
| 78 | 
            +
                    ])
         | 
| 79 | 
            +
                    expect(result).to eq [%w[1001, 1002, 1003, 1004]]
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
              end
         | 
| 83 | 
            +
             | 
| 27 84 | 
             
              it "can create_anonymous generating an id" do
         | 
| 28 85 | 
             
                stubs.post("anonymous", %[{"user_agent":"Mozilla/5.0"}]){ [200, {location: 'urn:id:abcd123'}, nil] }
         | 
| 29 86 | 
             
                id = client.create_anonymous(user_agent: 'Mozilla/5.0')
         | 
    
        data/spec/attune_spec.rb
    CHANGED
    
    | @@ -9,4 +9,18 @@ describe Attune do | |
| 9 9 | 
             
                specify { expect(subject.endpoint).to eq 'http://localhost/' }
         | 
| 10 10 | 
             
                specify { expect(subject.disabled).to eq false }
         | 
| 11 11 | 
             
              end
         | 
| 12 | 
            +
              describe 'configure' do
         | 
| 13 | 
            +
                it "yields with Attune::Default" do
         | 
| 14 | 
            +
                  Attune.configure do |c|
         | 
| 15 | 
            +
                    expect(c).to be Attune::Default
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
              describe 'test_mode' do
         | 
| 20 | 
            +
                it "sets correct test options" do
         | 
| 21 | 
            +
                  expect(Attune::Default).to receive(:exception_handler=).with(:mock)
         | 
| 22 | 
            +
                  expect(Attune::Default).to receive(:disabled=).with(true)
         | 
| 23 | 
            +
                  Attune.test_mode!
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 12 26 | 
             
            end
         | 
    
        data/spec/spec_helper.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,27 +1,27 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: attune
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0. | 
| 4 | 
            +
              version: 0.0.2
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - John Hawthorn
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2013-12- | 
| 11 | 
            +
            date: 2013-12-31 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: faraday
         | 
| 15 15 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 16 | 
             
                requirements:
         | 
| 17 | 
            -
                - -  | 
| 17 | 
            +
                - - '>='
         | 
| 18 18 | 
             
                  - !ruby/object:Gem::Version
         | 
| 19 19 | 
             
                    version: '0'
         | 
| 20 20 | 
             
              type: :runtime
         | 
| 21 21 | 
             
              prerelease: false
         | 
| 22 22 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 23 | 
             
                requirements:
         | 
| 24 | 
            -
                - -  | 
| 24 | 
            +
                - - '>='
         | 
| 25 25 | 
             
                  - !ruby/object:Gem::Version
         | 
| 26 26 | 
             
                    version: '0'
         | 
| 27 27 | 
             
            - !ruby/object:Gem::Dependency
         | 
| @@ -42,56 +42,70 @@ dependencies: | |
| 42 42 | 
             
              name: rake
         | 
| 43 43 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 44 44 | 
             
                requirements:
         | 
| 45 | 
            -
                - -  | 
| 45 | 
            +
                - - '>='
         | 
| 46 46 | 
             
                  - !ruby/object:Gem::Version
         | 
| 47 47 | 
             
                    version: '0'
         | 
| 48 48 | 
             
              type: :development
         | 
| 49 49 | 
             
              prerelease: false
         | 
| 50 50 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 51 51 | 
             
                requirements:
         | 
| 52 | 
            -
                - -  | 
| 52 | 
            +
                - - '>='
         | 
| 53 53 | 
             
                  - !ruby/object:Gem::Version
         | 
| 54 54 | 
             
                    version: '0'
         | 
| 55 55 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 56 56 | 
             
              name: rspec
         | 
| 57 57 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 58 58 | 
             
                requirements:
         | 
| 59 | 
            -
                - -  | 
| 59 | 
            +
                - - '>='
         | 
| 60 60 | 
             
                  - !ruby/object:Gem::Version
         | 
| 61 61 | 
             
                    version: '0'
         | 
| 62 62 | 
             
              type: :development
         | 
| 63 63 | 
             
              prerelease: false
         | 
| 64 64 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 65 65 | 
             
                requirements:
         | 
| 66 | 
            -
                - -  | 
| 66 | 
            +
                - - '>='
         | 
| 67 67 | 
             
                  - !ruby/object:Gem::Version
         | 
| 68 68 | 
             
                    version: '0'
         | 
| 69 69 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 70 70 | 
             
              name: yard
         | 
| 71 71 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 72 72 | 
             
                requirements:
         | 
| 73 | 
            -
                - -  | 
| 73 | 
            +
                - - '>='
         | 
| 74 74 | 
             
                  - !ruby/object:Gem::Version
         | 
| 75 75 | 
             
                    version: '0'
         | 
| 76 76 | 
             
              type: :development
         | 
| 77 77 | 
             
              prerelease: false
         | 
| 78 78 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 79 79 | 
             
                requirements:
         | 
| 80 | 
            -
                - -  | 
| 80 | 
            +
                - - '>='
         | 
| 81 81 | 
             
                  - !ruby/object:Gem::Version
         | 
| 82 82 | 
             
                    version: '0'
         | 
| 83 83 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 84 84 | 
             
              name: redcarpet
         | 
| 85 85 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 86 86 | 
             
                requirements:
         | 
| 87 | 
            -
                - -  | 
| 87 | 
            +
                - - '>='
         | 
| 88 88 | 
             
                  - !ruby/object:Gem::Version
         | 
| 89 89 | 
             
                    version: '0'
         | 
| 90 90 | 
             
              type: :development
         | 
| 91 91 | 
             
              prerelease: false
         | 
| 92 92 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 93 93 | 
             
                requirements:
         | 
| 94 | 
            -
                - -  | 
| 94 | 
            +
                - - '>='
         | 
| 95 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 96 | 
            +
                    version: '0'
         | 
| 97 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 98 | 
            +
              name: simplecov
         | 
| 99 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 100 | 
            +
                requirements:
         | 
| 101 | 
            +
                - - '>='
         | 
| 102 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 103 | 
            +
                    version: '0'
         | 
| 104 | 
            +
              type: :development
         | 
| 105 | 
            +
              prerelease: false
         | 
| 106 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 107 | 
            +
                requirements:
         | 
| 108 | 
            +
                - - '>='
         | 
| 95 109 | 
             
                  - !ruby/object:Gem::Version
         | 
| 96 110 | 
             
                    version: '0'
         | 
| 97 111 | 
             
            description: Client for the Attune product ranking API.
         | 
| @@ -111,18 +125,20 @@ files: | |
| 111 125 | 
             
            - Rakefile
         | 
| 112 126 | 
             
            - attune.gemspec
         | 
| 113 127 | 
             
            - lib/attune.rb
         | 
| 128 | 
            +
            - lib/attune/call_dropping.rb
         | 
| 114 129 | 
             
            - lib/attune/client.rb
         | 
| 115 130 | 
             
            - lib/attune/configurable.rb
         | 
| 116 131 | 
             
            - lib/attune/default.rb
         | 
| 117 132 | 
             
            - lib/attune/json_logger.rb
         | 
| 118 133 | 
             
            - lib/attune/param_flattener.rb
         | 
| 119 134 | 
             
            - lib/attune/version.rb
         | 
| 135 | 
            +
            - spec/attune/call_dropping_spec.rb
         | 
| 120 136 | 
             
            - spec/attune/client_spec.rb
         | 
| 121 137 | 
             
            - spec/attune/json_logger_spec.rb
         | 
| 122 138 | 
             
            - spec/attune_spec.rb
         | 
| 123 139 | 
             
            - spec/remote_spec.rb
         | 
| 124 140 | 
             
            - spec/spec_helper.rb
         | 
| 125 | 
            -
            homepage: https://github.com/ | 
| 141 | 
            +
            homepage: https://github.com/DigitalStripe/attune-ruby
         | 
| 126 142 | 
             
            licenses:
         | 
| 127 143 | 
             
            - MIT
         | 
| 128 144 | 
             
            metadata: {}
         | 
| @@ -132,21 +148,22 @@ require_paths: | |
| 132 148 | 
             
            - lib
         | 
| 133 149 | 
             
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 134 150 | 
             
              requirements:
         | 
| 135 | 
            -
              - -  | 
| 151 | 
            +
              - - '>='
         | 
| 136 152 | 
             
                - !ruby/object:Gem::Version
         | 
| 137 153 | 
             
                  version: '0'
         | 
| 138 154 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 139 155 | 
             
              requirements:
         | 
| 140 | 
            -
              - -  | 
| 156 | 
            +
              - - '>='
         | 
| 141 157 | 
             
                - !ruby/object:Gem::Version
         | 
| 142 158 | 
             
                  version: '0'
         | 
| 143 159 | 
             
            requirements: []
         | 
| 144 160 | 
             
            rubyforge_project: 
         | 
| 145 | 
            -
            rubygems_version: 2.1. | 
| 161 | 
            +
            rubygems_version: 2.1.11
         | 
| 146 162 | 
             
            signing_key: 
         | 
| 147 163 | 
             
            specification_version: 4
         | 
| 148 164 | 
             
            summary: Client for the Attune product ranking API.
         | 
| 149 165 | 
             
            test_files:
         | 
| 166 | 
            +
            - spec/attune/call_dropping_spec.rb
         | 
| 150 167 | 
             
            - spec/attune/client_spec.rb
         | 
| 151 168 | 
             
            - spec/attune/json_logger_spec.rb
         | 
| 152 169 | 
             
            - spec/attune_spec.rb
         |