├── .document ├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── lib ├── retriable_proxy.rb └── retriable_proxy │ └── version.rb ├── retriable_proxy.gemspec └── spec ├── retriable_proxy_spec.rb └── spec_helper.rb /.document: -------------------------------------------------------------------------------- 1 | lib/**/*.rb 2 | bin/* 3 | - 4 | features/**/*.feature 5 | LICENSE.txt 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ruby-version 2 | 3 | # rcov generated 4 | coverage 5 | coverage.data 6 | 7 | # rdoc generated 8 | rdoc 9 | 10 | # yard generated 11 | doc 12 | .yardoc 13 | 14 | # bundler 15 | .bundle 16 | <<<<<<< Local Changes 17 | <<<<<<< Local Changes 18 | Gemfile.lock 19 | P 20 | ======= 21 | ======= 22 | >>>>>>> External Changes 23 | 24 | >>>>>>> External Changes 25 | # jeweler generated 26 | pkg 27 | 28 | # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: 29 | # 30 | # * Create a file at ~/.gitignore 31 | # * Include files you want ignored 32 | # * Run: git config --global core.excludesfile ~/.gitignore 33 | # 34 | # After doing this, these files will be ignored in all your git projects, 35 | # saving you from having to 'pollute' every project you touch with them 36 | # 37 | # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) 38 | # 39 | # For MacOS: 40 | # 41 | #.DS_Store 42 | 43 | # For TextMate 44 | #*.tmproj 45 | #tmtags 46 | 47 | # For emacs: 48 | #*~ 49 | #\#* 50 | #.\#* 51 | 52 | # For vim: 53 | #*.swp 54 | 55 | # For redcar: 56 | #.redcar 57 | 58 | # For rubinius: 59 | #*.rbc 60 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | cache: bundler 3 | language: ruby 4 | rvm: 5 | - 1.9.3 6 | - 2.0 7 | - 2.1 8 | - 2.2 9 | - 2.3 10 | - 2.4 11 | - 2.5 12 | - jruby-head 13 | 14 | matrix: 15 | allow_failures: 16 | - rvm: 1.9.3 17 | - rvm: ruby-head 18 | - rvm: jruby-head 19 | 20 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Julik Tarkhanov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # retriable_proxy 2 | 3 | [![Build Status](https://travis-ci.org/julik/retriable_proxy.svg?branch=master)](https://travis-ci.org/julik/retriable_proxy) 4 | 5 | Creates an object wrapper on top of [Retriable.](https://github.com/kamui/retriable) 6 | The accepted keyword arguments are the same as for Retriable itself. 7 | 8 | network_interface = API.new(unreliable_server: 'https://api-server.com') 9 | stubborn_interface = RetriableProxy.for_object(network_interface, on: Net::TimeoutError) 10 | stubborn_interface.create(id: 123, name: 'Some object that gets created') # Will retry 11 | 12 | If you only want to wrap certain methods, add the `:methods` option and pass in an `Array` of symbols. 13 | 14 | network_interface = API.new(unreliable_server: 'https://api-server.com') 15 | stubborn_interface = RetriableProxy.for_object(network_interface, on: Net::TimeoutError, methods: [:fetch_comments]) 16 | stubborn_interface.create(id: 123, name: 'Some object that gets created') # Will not retry 17 | stubborn_interface.fetch_comments # Will do retry 18 | 19 | ## Contributing to retriable_proxy 20 | 21 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet. 22 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it. 23 | * Fork the project. 24 | * Start a feature/bugfix branch. 25 | * Commit and push until you are happy with your contribution. 26 | * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally. 27 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. 28 | 29 | ## Copyright 30 | 31 | Copyright (c) 2015 Julik Tarkhanov. See LICENSE.txt for 32 | further details. 33 | 34 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | task default: :spec 6 | -------------------------------------------------------------------------------- /lib/retriable_proxy.rb: -------------------------------------------------------------------------------- 1 | require 'retriable' 2 | require_relative 'retriable_proxy/version' 3 | 4 | module RetriableProxy 5 | 6 | class Wrapper 7 | # Creates a new Wrapper that will wrap the messages to the wrapped object with 8 | # a +retriable+ block. 9 | # 10 | # If the :methods option is passed, only the methods in the given array will be 11 | # subjected to retries. 12 | def initialize(with_object, options_for_retriable = {}) 13 | @o = with_object 14 | @methods = options_for_retriable.delete(:methods) 15 | @retriable_options = options_for_retriable 16 | end 17 | 18 | # Returns the wrapped object 19 | def __getobj__ 20 | @o 21 | end 22 | 23 | # Assists in supporting method_missing 24 | def respond_to_missing?(*a) 25 | @o.respond_to?(*a) 26 | end 27 | 28 | # Forwards all methods not defined on the Wrapper to the wrapped object. 29 | def method_missing(*a) 30 | method_name = a[0] 31 | if block_given? 32 | __retrying(method_name) { @o.public_send(*a){|*ba| yield(*ba)} } 33 | else 34 | __retrying(method_name) { @o.public_send(*a) } 35 | end 36 | end 37 | 38 | private 39 | 40 | # Executes a block within Retriable setup with @retriable_options 41 | def __retrying(method_name_on_delegate) 42 | if @methods.nil? || @methods.include?(method_name_on_delegate) 43 | Retriable.retriable(@retriable_options) { yield } 44 | else 45 | yield 46 | end 47 | end 48 | end 49 | 50 | 51 | def self.for_object(object, retriable_opts={}) 52 | Wrapper.new(object, retriable_opts) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/retriable_proxy/version.rb: -------------------------------------------------------------------------------- 1 | module RetriableProxy 2 | VERSION = '1.0.2' 3 | end 4 | -------------------------------------------------------------------------------- /retriable_proxy.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | lib = File.expand_path('../lib', __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'retriable_proxy/version' 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "retriable_proxy" 9 | s.version = RetriableProxy::VERSION 10 | 11 | if s.respond_to?(:metadata) 12 | s.metadata['allowed_push_host'] = 'https://rubygems.org' 13 | else 14 | raise 'RubyGems 2.0 or newer is required to protect against ' \ 15 | 'public gem pushes.' 16 | end 17 | 18 | s.require_paths = ["lib"] 19 | s.authors = ["Julik Tarkhanov"] 20 | s.description = "Adds an object wrapper on top of Retriable" 21 | s.email = "me@julik.nl" 22 | s.extra_rdoc_files = [ 23 | "LICENSE.txt", 24 | "README.md" 25 | ] 26 | s.files = `git ls-files -z`.split("\x0") 27 | s.homepage = "http://github.com/julik/retriable_proxy" 28 | s.licenses = ["MIT"] 29 | s.rubygems_version = "2.2.2" 30 | s.summary = "Retry arbitrary method calls on a given object, no matter its class" 31 | s.specification_version = 4 32 | 33 | s.add_runtime_dependency(%q, [">= 2.0", "< 4.0"]) 34 | s.add_development_dependency(%q, ["~> 3.2.0"]) 35 | s.add_development_dependency(%q, ["~> 3.12"]) 36 | s.add_development_dependency(%q, ["~> 10"]) 37 | s.add_development_dependency(%q, ["~> 1.0"]) 38 | end 39 | -------------------------------------------------------------------------------- /spec/retriable_proxy_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/spec_helper') 2 | 3 | require_relative "spec_helper" 4 | 5 | describe RetriableProxy do 6 | 7 | describe '.for_object' do 8 | it 'wraps the object with a Retriable' do 9 | w = described_class.for_object(:symbol_to_wrap) 10 | expect(w).to be_kind_of(RetriableProxy::Wrapper) 11 | end 12 | end 13 | 14 | describe 'RetriableProxy::Wrapper' do 15 | let(:subject) { RetriableProxy::Wrapper } 16 | 17 | it 'returns the wrapped object via __setobj__' do 18 | item = :foo 19 | wrapped = subject.new(item) 20 | expect(wrapped).to be_kind_of(subject) 21 | unwrapped = wrapped.__getobj__ 22 | expect(unwrapped).to be_kind_of(Symbol) 23 | end 24 | 25 | it 'supports setting retrying methods explicitly' do 26 | raising_class = Class.new do 27 | def self.perform_failing_operation 28 | @tries_failing ||= 0 29 | @tries_failing += 1 30 | raise StandardError.new if @tries_failing < 3 31 | "success" 32 | end 33 | 34 | def self.perform_non_retrying_operation 35 | @tries_non_retrying ||= 0 36 | @tries_non_retrying += 1 37 | raise StandardError.new if @tries_non_retrying < 2 38 | end 39 | end 40 | 41 | wrapped_raising_class = subject.new(raising_class, methods: [:perform_failing_operation]) 42 | expect(wrapped_raising_class.perform_failing_operation).to eq("success") 43 | 44 | expect { 45 | wrapped_raising_class.perform_non_retrying_operation 46 | }.to raise_error(StandardError) 47 | end 48 | 49 | it "makes 3 tries when retrying block of code raising StandardError with no arguments" do 50 | $tries = 0 51 | 52 | raising_class = Class.new do 53 | def self.perform_failing_operation 54 | $tries += 1 55 | raise StandardError.new 56 | end 57 | end 58 | 59 | expect { 60 | wrapped_raising_class = subject.new(raising_class, {}) 61 | wrapped_raising_class.perform_failing_operation 62 | }.to raise_error(StandardError) 63 | 64 | expect($tries).to eq(3) 65 | end 66 | end 67 | end 68 | 69 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | 4 | require 'rspec' 5 | require 'retriable_proxy' 6 | 7 | # Requires supporting files with custom matchers and macros, etc, 8 | # in ./support/ and its subdirectories. 9 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 10 | 11 | RSpec.configure do |config| 12 | 13 | end 14 | --------------------------------------------------------------------------------