services 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 29c86c30a1166901d26fdf5d0484a368c1486011
4
+ data.tar.gz: 8617c1b8bd2543fdcf22e1eac5e30ab274b13a5c
5
+ SHA512:
6
+ metadata.gz: 544977f3bdb51a170867318270d11acf79b2039981efbc53d6e19c61935ddf31db3b94634470f418dede84970d0334dcc7c71eb137a61f621c31a6bded717bcf
7
+ data.tar.gz: a9d4bad15dabe9c9f9c92fe096f4b44318f9b72f4e37fdc531c216e2a103ae02a46ff2844f81d4260e15f4d04228a490973fb28ea868f5344831a95c0809cf21
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ spec/support/logs/**/*.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ guard 'rspec', cmd: 'bundle exec rspec', failed_mode: :none, all_after_pass: true, all_on_start: true do
2
+ # Specs
3
+ watch(%r(^spec/.+_spec\.rb$))
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
+ watch(%r(^spec/support/(.+)\.rb$)) { 'spec' }
6
+
7
+ # Files
8
+ watch(%r(^lib/(.+)\.rb$)) { |m| "spec/#{m[1]}_spec.rb" }
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 kraut computing UG (haftungsbeschränkt)
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # Services
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/services.png)](http://badge.fury.io/rb/services)
4
+ [![Build Status](https://secure.travis-ci.org/krautcomputing/services.png)](http://travis-ci.org/krautcomputing/services)
5
+ [![Dependency Status](https://gemnasium.com/krautcomputing/services.png)](https://gemnasium.com/krautcomputing/services)
6
+ [![Code Climate](https://codeclimate.com/github/krautcomputing/services.png)](https://codeclimate.com/github/krautcomputing/services)
7
+
8
+ Services is a opinionated set of modules that can be used to implement the use cases (or services, contexts, whatchamacallit) of a Ruby application.
9
+
10
+ ## Requirements
11
+
12
+ Ruby >= 2.0
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'services'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install services
27
+
28
+ ## Usage
29
+
30
+ Coming soon...
31
+
32
+ ## Contributing
33
+
34
+ 1. Fork it
35
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
36
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
37
+ 4. Push to the branch (`git push origin my-new-feature`)
38
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
4
+
5
+ task default: :spec
data/lib/services.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'gem_config'
2
+
3
+ module Services
4
+ include GemConfig::Base
5
+
6
+ BackgroundProcessorNotFound = Class.new(StandardError)
7
+
8
+ with_configuration do
9
+ has :host, classes: String
10
+ has :log_dir, classes: [String, Pathname]
11
+ has :redis
12
+ end
13
+ end
14
+
15
+ require_relative 'services/version'
16
+ require_relative 'services/logger'
17
+ begin
18
+ require_relative 'services/asyncable'
19
+ rescue Services::BackgroundProcessorNotFound
20
+ end
21
+ require_relative 'services/base/call_logger'
22
+ require_relative 'services/base/exception_Wrapper'
23
+ require_relative 'services/base/uniqueness_checker'
24
+ require_relative 'services/base'
25
+ require_relative 'services/railtie' if defined?(Rails)
@@ -0,0 +1,75 @@
1
+ require 'active_support/concern'
2
+
3
+ begin
4
+ require 'sidekiq'
5
+ rescue LoadError
6
+ raise Services::BackgroundProcessorNotFound
7
+ end
8
+
9
+ begin
10
+ require 'sidetiq'
11
+ rescue LoadError
12
+ end
13
+
14
+ module Services
15
+ module Asyncable
16
+ extend ActiveSupport::Concern
17
+
18
+ # The name of the parameter that is added to the parameter list when calling a method to be processed in the background.
19
+ TARGET_PARAM_NAME = :async_target_id
20
+
21
+ included do
22
+ include Sidekiq::Worker
23
+ include Sidetiq::Schedulable if defined?(Sidetiq)
24
+ end
25
+
26
+ module ClassMethods
27
+ # Bulk enqueue items
28
+ # args can either be a one-dimensional or two-dimensional array,
29
+ # each item in args should be the arguments for one job.
30
+ def bulk_perform_async(args)
31
+ # Convert args to two-dimensional array if it isn't already.
32
+ args = args.map { |arg| [arg] } if args.none? { |arg| arg.is_a?(Array) }
33
+ Sidekiq::Client.push_bulk 'class' => self, 'args' => args
34
+ end
35
+ end
36
+
37
+ %w(perform_async perform_in).each do |method_name|
38
+ define_method method_name do |*args|
39
+ self.class.send method_name, *args, TARGET_PARAM_NAME => self.id
40
+ end
41
+ end
42
+ alias_method :perform_at, :perform_in
43
+
44
+ def perform(*args)
45
+ return self.call(*args) if self.is_a?(Services::Base)
46
+
47
+ target = if args.last.is_a?(Hash) && args.last.keys.first.to_sym == TARGET_PARAM_NAME
48
+ self.class.find args.pop.values.first
49
+ else
50
+ self.class
51
+ end
52
+
53
+ target.send *args
54
+ end
55
+
56
+ def own_worker
57
+ return @own_worker if defined?(@own_worker)
58
+ @own_worker = if self.jid.nil?
59
+ nil
60
+ else
61
+ own_worker = Sidekiq::Workers.new.detect do |_, work, _|
62
+ work['payload']['jid'] == self.jid
63
+ end
64
+ raise self.class::Error, "Could not find own worker with jid #{self.jid}: #{Sidekiq::Workers.new.map{ |*args| args }}" if own_worker.nil?
65
+ own_worker
66
+ end
67
+ end
68
+
69
+ def sibling_workers
70
+ @sibling_workers ||= Sidekiq::Workers.new.select do |_, work, _|
71
+ work['payload']['class'] == self.class.to_s && (own_worker.nil? || work['payload']['jid'] != own_worker[1]['payload']['jid'])
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,68 @@
1
+ require 'nesty'
2
+ require 'securerandom'
3
+ require 'action_dispatch'
4
+ require 'digest'
5
+
6
+ module Services
7
+ class Base
8
+ class << self
9
+ def inherited(subclass)
10
+ subclass.const_set :Error, Class.new(Nesty::NestedStandardError)
11
+ subclass.send :include, Rails.application.routes.url_helpers if defined?(Rails)
12
+ subclass.send :include, Asyncable if defined?(Asyncable)
13
+ subclass.send :prepend, CallLogger, ExceptionWrapper, UniquenessChecker
14
+ end
15
+
16
+ delegate :call, to: :new
17
+ end
18
+
19
+ def initialize
20
+ @id = SecureRandom.hex(6)
21
+ @logger = Logger.new
22
+ end
23
+
24
+ def call(*args)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ private
29
+
30
+ def find_object(ids_or_objects, klass = nil)
31
+ if klass.nil?
32
+ klass = self.class.to_s[/Services::([^:]+)/, 1].singularize.constantize rescue nil
33
+ raise "Could not determine class from #{self.class}" if klass.nil?
34
+ end
35
+ case ids_or_objects
36
+ when klass
37
+ return ids_or_objects
38
+ when Array
39
+ raise 'Array can only contain IDs.' if ids_or_objects.any? { |ids_or_object| !ids_or_object.is_a?(Fixnum) }
40
+ objects = "Services::#{klass.to_s.pluralize}::Find".constantize.call(ids_or_objects)
41
+ missing_ids = ids_or_objects - objects.pluck(:id)
42
+ raise self.class::Error, "#{klass.to_s.pluralize(missing_ids)} #{missing_ids.join(', ')} not found." if missing_ids.present?
43
+ return objects
44
+ when Fixnum
45
+ object = "Services::#{klass.to_s.pluralize}::Find".constantize.call(ids_or_objects).first
46
+ raise self.class::Error, "#{klass} #{ids_or_objects} not found." if object.nil?
47
+ return object
48
+ else
49
+ raise "Unexpected ids_or_objects class: #{ids_or_objects.class}"
50
+ end
51
+ end
52
+
53
+ def log(message, severity = :info)
54
+ @logger.log [self.class, @id], message, severity
55
+ end
56
+
57
+ def controller
58
+ @controller ||= begin
59
+ raise 'Please configure host.' unless Services.configuration.host?
60
+ request = ActionDispatch::TestRequest.new
61
+ request.host = Services.configuration.host
62
+ ActionController::Base.new.tap do |controller|
63
+ controller.instance_variable_set('@_request', request)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,22 @@
1
+ module Services
2
+ class Base
3
+ module CallLogger
4
+ def call(*args)
5
+ log "START with args: #{args}"
6
+ start = Time.now
7
+ begin
8
+ result = super
9
+ rescue StandardError => e
10
+ log "#{e.class}: #{e.message}"
11
+ e.backtrace.each do |line|
12
+ log line
13
+ end
14
+ raise e
15
+ ensure
16
+ log "END after #{(Time.now - start).round(2)} seconds"
17
+ result
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ module Services
2
+ class Base
3
+ module ExceptionWrapper
4
+ def call(*args)
5
+ super
6
+ rescue StandardError => e
7
+ if e.class <= self.class::Error
8
+ raise e
9
+ else
10
+ raise self.class::Error, e
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,36 @@
1
+ module Services
2
+ class Base
3
+ module UniquenessChecker
4
+ def self.prepended(mod)
5
+ mod.const_set :NotUniqueError, Class.new(mod::Error)
6
+ end
7
+
8
+ def call(*args)
9
+ key = unique_key(args)
10
+ if Services.configuration.redis.exists(key)
11
+ raise self.class::NotUniqueError
12
+ else
13
+ Services.configuration.redis.setex key, 60 * 60, Time.now
14
+ begin
15
+ super
16
+ ensure
17
+ Services.configuration.redis.del key
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def unique_key(args)
25
+ # TODO: symbolize keys in hashes in args and sort hashes by key
26
+ args = args.dup
27
+ key = [
28
+ 'services',
29
+ 'uniqueness',
30
+ self.class.to_s,
31
+ Digest::MD5.hexdigest(args.to_s)
32
+ ].join(':')
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_support/tagged_logging'
2
+
3
+ module Services
4
+ class Logger
5
+ def initialize
6
+ unless Services.configuration.log_dir.nil?
7
+ log_file = File.join(Services.configuration.log_dir, 'services.log')
8
+ @logger = ActiveSupport::TaggedLogging.new(::Logger.new(log_file))
9
+ @logger.clear_tags!
10
+ end
11
+ end
12
+
13
+ def log(tags, message, severity = :info)
14
+ unless @logger.nil?
15
+ @logger.tagged Time.now, severity.upcase, *tags do
16
+ @logger.send severity, message
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module Services
2
+ class Railtie < Rails::Railtie
3
+ config.after_initialize do
4
+ Services.configuration.log_dir = Rails.root.join('log')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Services
2
+ VERSION = '0.0.1'
3
+ end
data/services.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'services/version'
7
+
8
+ Gem::Specification.new do |gem|
9
+ gem.name = 'services'
10
+ gem.version = Services::VERSION
11
+ gem.platform = Gem::Platform::RUBY
12
+ gem.author = 'Manuel Meurer'
13
+ gem.email = 'manuel@krautcomputing.com'
14
+ gem.summary = ''
15
+ gem.description = ''
16
+ gem.homepage = ''
17
+ gem.license = 'MIT'
18
+
19
+ gem.files = `git ls-files`.split($/)
20
+ gem.executables = gem.files.grep(%r(^bin/)).map { |f| File.basename(f) }
21
+ gem.test_files = gem.files.grep(%r(^(test|spec|features)/))
22
+ gem.require_paths = ['lib']
23
+
24
+ gem.add_development_dependency 'rake'
25
+ gem.add_development_dependency 'guard-rspec', '~> 4.2'
26
+ gem.add_development_dependency 'rspec', '>= 3.0.0.beta2', '< 4'
27
+ gem.add_development_dependency 'sidekiq', '~> 3.0'
28
+ gem.add_development_dependency 'redis', '~> 3.0'
29
+ gem.add_runtime_dependency 'rails', '>= 3.0.0'
30
+ gem.add_runtime_dependency 'gem_config', '~> 0.3.1'
31
+ gem.add_runtime_dependency 'nesty', '~> 1.0.2'
32
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe Services::Base do
4
+ context 'wrapping exceptions' do
5
+ it 'does not wrap service errors or subclasses' do
6
+ class ServiceWithError < Services::Base
7
+ def call
8
+ raise Error.new('I am a service error.', nil)
9
+ end
10
+ end
11
+ expect do
12
+ ServiceWithError.call
13
+ end.to raise_error do |error|
14
+ expect(error).to be_a(ServiceWithError::Error)
15
+ expect(error.message).to eq('I am a service error.')
16
+ expect(error.nested).to be_nil
17
+ end
18
+
19
+ class ServiceWithCustomError < Services::Base
20
+ CustomError = Class.new(self::Error)
21
+ def call
22
+ raise CustomError.new('I am a custom error.', nil)
23
+ end
24
+ end
25
+ expect do
26
+ ServiceWithCustomError.call
27
+ end.to raise_error do |error|
28
+ expect(error).to be_a(ServiceWithCustomError::CustomError)
29
+ expect(error.message).to eq('I am a custom error.')
30
+ expect(error.nested).to be_nil
31
+ end
32
+ end
33
+
34
+ it 'wraps all other exceptions' do
35
+ class ServiceWithStandardError < Services::Base
36
+ def call
37
+ raise 'I am a StandardError.'
38
+ end
39
+ end
40
+ expect do
41
+ ServiceWithStandardError.call
42
+ end.to raise_error do |error|
43
+ expect(error).to be_a(ServiceWithStandardError::Error)
44
+ expect(error.message).to eq('I am a StandardError.')
45
+ expect(error.nested).to be_a(StandardError)
46
+ expect(error.nested.message).to eq('I am a StandardError.')
47
+ end
48
+
49
+ class ServiceWithCustomStandardError < Services::Base
50
+ CustomStandardError = Class.new(StandardError)
51
+ def call
52
+ raise CustomStandardError, 'I am a custom StandardError.'
53
+ end
54
+ end
55
+ expect do
56
+ ServiceWithCustomStandardError.call
57
+ end.to raise_error do |error|
58
+ expect(error).to be_a(ServiceWithCustomStandardError::Error)
59
+ expect(error.message).to eq('I am a custom StandardError.')
60
+ expect(error.nested).to be_a(ServiceWithCustomStandardError::CustomStandardError)
61
+ expect(error.nested.message).to eq('I am a custom StandardError.')
62
+ end
63
+ end
64
+ end
65
+
66
+ context 'checking for uniqueness' do
67
+ it 'raises an error when the same job is executed twice' do
68
+ LongRunningService.perform_async
69
+ sleep 0.5 # Wait for Sidekiq to start processing the job
70
+ expect do
71
+ LongRunningService.call
72
+ end.to raise_error(LongRunningService::NotUniqueError)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,87 @@
1
+ require 'rspec'
2
+ require 'redis'
3
+ require 'sidekiq'
4
+ require_relative '../lib/services'
5
+ require_relative 'support/test_services'
6
+
7
+ support_dir = Pathname.new(File.expand_path('../support', __FILE__))
8
+ log_dir = support_dir.join('logs')
9
+
10
+ redis_port = 6379
11
+ redis_pidfile = support_dir.join('redis.pid')
12
+ redis_url = "redis://localhost:#{redis_port}/0"
13
+
14
+ sidekiq_pidfile = support_dir.join('sidekiq.pid')
15
+ sidekiq_timeout = 60
16
+
17
+ Services.configure do |config|
18
+ config.redis = Redis.new
19
+ config.log_dir = log_dir
20
+ end
21
+
22
+ Sidekiq.configure_client do |config|
23
+ config.redis = { redis: redis_url, namespace: 'sidekiq', size: 1 }
24
+ end
25
+
26
+ Sidekiq.configure_server do |config|
27
+ config.redis = { redis: redis_url, namespace: 'sidekiq' }
28
+ end
29
+
30
+ RSpec.configure do |config|
31
+ config.run_all_when_everything_filtered = true
32
+ config.filter_run :focus
33
+ config.order = 'random'
34
+
35
+ config.before :all do
36
+ # Start Redis
37
+ redis_options = {
38
+ daemonize: 'yes',
39
+ port: redis_port,
40
+ dir: support_dir,
41
+ dbfilename: 'redis.rdb',
42
+ logfile: log_dir.join('redis.log'),
43
+ pidfile: redis_pidfile
44
+ }
45
+ redis = support_dir.join('redis-server')
46
+ system "#{redis} #{options_hash_to_string(redis_options)}"
47
+
48
+ # Start Sidekiq
49
+ sidekiq_options = {
50
+ concurrency: 1,
51
+ daemon: true,
52
+ timeout: sidekiq_timeout,
53
+ verbose: true,
54
+ require: __FILE__,
55
+ logfile: log_dir.join('sidekiq.log'),
56
+ pidfile: sidekiq_pidfile
57
+ }
58
+ system "bundle exec sidekiq #{options_hash_to_string(sidekiq_options)}"
59
+ end
60
+
61
+ config.after :all do
62
+ # Stop Sidekiq
63
+ system "bundle exec sidekiqctl stop #{sidekiq_pidfile} #{sidekiq_timeout}"
64
+ while File.exist?(sidekiq_pidfile)
65
+ puts 'Waiting for Sidekiq to shut down...'
66
+ sleep 1
67
+ end
68
+
69
+ # Stop Redis
70
+ redis_cli = support_dir.join('redis-cli')
71
+ system "#{redis_cli} -p #{redis_port} shutdown"
72
+ while File.exist?(redis_pidfile)
73
+ puts 'Waiting for Redis to shut down...'
74
+ sleep 1
75
+ end
76
+
77
+ # Truncate log files
78
+ max_len = 1024 * 1024 # 1 MB
79
+ Dir[File.join(log_dir, '*.log')].each do |file|
80
+ File.truncate file, max_len if File.size(file) > max_len
81
+ end
82
+ end
83
+ end
84
+
85
+ def options_hash_to_string(options)
86
+ options.map { |k, v| "--#{k} #{v}" }.join(' ')
87
+ end
File without changes
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ class LongRunningService < Services::Base
2
+ def call
3
+ sleep 2
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,190 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: services
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Manuel Meurer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: guard-rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '4.2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '4.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: 3.0.0.beta2
48
+ - - <
49
+ - !ruby/object:Gem::Version
50
+ version: '4'
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - '>='
56
+ - !ruby/object:Gem::Version
57
+ version: 3.0.0.beta2
58
+ - - <
59
+ - !ruby/object:Gem::Version
60
+ version: '4'
61
+ - !ruby/object:Gem::Dependency
62
+ name: sidekiq
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ~>
66
+ - !ruby/object:Gem::Version
67
+ version: '3.0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ~>
73
+ - !ruby/object:Gem::Version
74
+ version: '3.0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: redis
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ~>
80
+ - !ruby/object:Gem::Version
81
+ version: '3.0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ~>
87
+ - !ruby/object:Gem::Version
88
+ version: '3.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rails
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - '>='
94
+ - !ruby/object:Gem::Version
95
+ version: 3.0.0
96
+ type: :runtime
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - '>='
101
+ - !ruby/object:Gem::Version
102
+ version: 3.0.0
103
+ - !ruby/object:Gem::Dependency
104
+ name: gem_config
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: 0.3.1
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ~>
115
+ - !ruby/object:Gem::Version
116
+ version: 0.3.1
117
+ - !ruby/object:Gem::Dependency
118
+ name: nesty
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ~>
122
+ - !ruby/object:Gem::Version
123
+ version: 1.0.2
124
+ type: :runtime
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ~>
129
+ - !ruby/object:Gem::Version
130
+ version: 1.0.2
131
+ description: ''
132
+ email: manuel@krautcomputing.com
133
+ executables: []
134
+ extensions: []
135
+ extra_rdoc_files: []
136
+ files:
137
+ - .gitignore
138
+ - Gemfile
139
+ - Guardfile
140
+ - LICENSE.txt
141
+ - README.md
142
+ - Rakefile
143
+ - lib/services.rb
144
+ - lib/services/asyncable.rb
145
+ - lib/services/base.rb
146
+ - lib/services/base/call_logger.rb
147
+ - lib/services/base/exception_wrapper.rb
148
+ - lib/services/base/uniqueness_checker.rb
149
+ - lib/services/logger.rb
150
+ - lib/services/railtie.rb
151
+ - lib/services/version.rb
152
+ - services.gemspec
153
+ - spec/services/base_spec.rb
154
+ - spec/spec_helper.rb
155
+ - spec/support/logs/.gitkeep
156
+ - spec/support/redis-cli
157
+ - spec/support/redis-server
158
+ - spec/support/test_services.rb
159
+ homepage: ''
160
+ licenses:
161
+ - MIT
162
+ metadata: {}
163
+ post_install_message:
164
+ rdoc_options: []
165
+ require_paths:
166
+ - lib
167
+ required_ruby_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - '>='
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ required_rubygems_version: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - '>='
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ requirements: []
178
+ rubyforge_project:
179
+ rubygems_version: 2.2.2
180
+ signing_key:
181
+ specification_version: 4
182
+ summary: ''
183
+ test_files:
184
+ - spec/services/base_spec.rb
185
+ - spec/spec_helper.rb
186
+ - spec/support/logs/.gitkeep
187
+ - spec/support/redis-cli
188
+ - spec/support/redis-server
189
+ - spec/support/test_services.rb
190
+ has_rdoc: