services 0.0.1

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 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: