services 4.1.0 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8f7714cb0e9390aa539649cc30b178705ef47aa6
4
- data.tar.gz: 8821244fee82df7be557f4bbc4591e7d084c2c56
3
+ metadata.gz: cd7dc93d539be1dfb493ae76fa3517df9f91c50e
4
+ data.tar.gz: 6c5c5c79fb229e7c1ab98d68ede41939b800e8d2
5
5
  SHA512:
6
- metadata.gz: ecbfa1b8fe169b98f68fcab6ea905b75a70e51c54872a39bbcc3a6e0b6c838caf18f093df743c0a1a84d61b9de209516205ab0a61114674ccc707ffafe5edd43
7
- data.tar.gz: 6b81875b67297940f7fbae262b9a6532dca7aa2497e346940f3e8033bf67d325f9952725e273521fae06ce613a5768d40dca1b1194b0290177315b9f42118cb3
6
+ metadata.gz: 470ab654cddf1cc75439e0301090865ec6269f31acd76e42d0e614ecbf003373330954d3f1b03376eb3eda59815bf6513cfab1cc7dc79deab9f002e4b4c32ea9
7
+ data.tar.gz: 4ff35e833918ff1c6269388242bcbdaa4d57f9eaa38f25a1c34450766ceaaf82f12180e964fd39636bd490d917d5461d5ed07eb694a7ceaff76f3ca432ffc31f
@@ -3,5 +3,9 @@ rvm:
3
3
  - 2.0
4
4
  - 2.1
5
5
  - 2.2
6
+ - 2.3.0
6
7
  services:
7
8
  - redis-server
9
+ before_install:
10
+ - gem update --system
11
+ - gem update bundler
data/Guardfile CHANGED
@@ -15,7 +15,7 @@ module ::Guard
15
15
  REDIS_CLI = SPEC_SUPPORT_DIR.join('redis-cli')
16
16
  REDIS_PIDFILE = SPEC_SUPPORT_DIR.join('redis.pid')
17
17
  REDIS_LOGFILE = SPEC_SUPPORT_DIR.join('log', 'redis.log')
18
- REDIS_PORT = 6479
18
+ REDIS_PORT = 6379
19
19
 
20
20
  def self.options_to_string(options)
21
21
  options.map { |k, v| "-#{'-' if k.length > 1}#{k} #{v}" }.join(' ')
data/README.md CHANGED
@@ -5,17 +5,17 @@
5
5
  [![Dependency Status](https://gemnasium.com/krautcomputing/services.png)](https://gemnasium.com/krautcomputing/services)
6
6
  [![Code Climate](https://codeclimate.com/github/krautcomputing/services.png)](https://codeclimate.com/github/krautcomputing/services)
7
7
 
8
- Services is a small collection of modules and base classes that let you implement a nifty service layer in your Rails app.
8
+ Services is a collection of modules and base classes that let you simply add a service layer to your Rails app.
9
9
 
10
10
  ## Motivation
11
11
 
12
- A lot has been written about service layers in Rails apps. There are of course advantages and disadvantages, but after using Services since 2013 in several Rails apps, I must say that in my opinion the advantages far outweigh the disadvantages.
12
+ A lot has been written about service layers (service objects, SOA, etc.) for Rails. There are of course advantages and disadvantages, but after using Services since 2013 in several Rails apps, I must say that in my opinion the advantages far outweigh the disadvantages.
13
13
 
14
- **The biggest benefit you get when using a service layer is that it gets much easier to reason about your application, find a bug, or implement new features, when all your business logic is in services, not scattered in models, controllers, helpers etc.**
14
+ **The biggest benefit you get when using a service layer, in my opinion, is that it gets so much easier to reason about your application, find a bug, or implement new features, when all your business logic is in services, not scattered in models, controllers, helpers etc.**
15
15
 
16
16
  ## Usage
17
17
 
18
- For disambiguation: when I write "Services" with a uppercase "S", I mean this gem, whereas with "services" I mean, well, the plural of service.
18
+ For disambiguation: in this README, when you read "Services" with a uppercase "S", this gem is meant, whereas with "services", well, the plural of service is meant.
19
19
 
20
20
  ### Requirements
21
21
 
@@ -23,17 +23,17 @@ For disambiguation: when I write "Services" with a uppercase "S", I mean this ge
23
23
 
24
24
  #### Rails >= 3.2
25
25
 
26
- #### Redis
26
+ #### Redis >= 2.8
27
27
 
28
- Redis is used at several points, e.g. to store information about the currently running services, so you can make sure a certain service is not executed more than once simultaneously.
28
+ Redis is used at several points, e.g. to store information about the currently running services, so you can enforce uniqueness for specific services, i.e. make sure no more than one instance of such a service is executed simultaneously.
29
29
 
30
- #### Postgres
30
+ #### Postgres (optional)
31
31
 
32
- The SQL that `Services::Query` (discussed further down) generates is optimized for Postgres. It might work with other databases but it's not guaranteed. If you're not using Postgres, don't use `Services::Query` or, even better, submit a [pull request](https://github.com/krautcomputing/services/issues) that fixes it to work with your database!
32
+ The SQL that `Services::Query` (discussed further down) generates is optimized for Postgres. It might work with other databases but it's not guaranteed. If you're not using Postgres, you can still use all other parts of Services, just don't use `Services::Query` or, even better, submit a [pull request](https://github.com/krautcomputing/services/issues) that fixes it to work with your database!
33
33
 
34
34
  #### Sidekiq (optional)
35
35
 
36
- To process services in the background, Services uses [Sidekiq](https://github.com/mperham/sidekiq). Sidekiq is not absolutely required to use Services though. If it's not present when Services is loaded, a service will raise an exception when you try to enqueue it for background processing. If you use Sidekiq, make sure to load the Services gem after the Sidekiq gem.
36
+ To process services in the background, Services uses [Sidekiq](https://github.com/mperham/sidekiq). If you don't need background processing, you can still use Services without Sidekiq. When you then try to enqueue a service for background processing, an exception will be raised. If you use Sidekiq, make sure to load the Services gem after the Sidekiq gem.
37
37
 
38
38
  ### Basic principles
39
39
 
@@ -41,22 +41,23 @@ Services is based on a couple of basic principles around what a service should b
41
41
 
42
42
  A service...
43
43
 
44
- * does one thing and does it well (Unix philosophy)
45
- * can be run synchronously (in the foreground) or asynchronously (in the background)
46
- * can be configured as "unique", meaning only one instance of it should be run at any time
44
+ * does only one thing and does it well (Unix philosophy)
45
+ * can be run synchronously (i.e. blocking/in the foreground) or asynchronously (i.e. non-blocking/in the background)
46
+ * can be configured as "unique", meaning only one instance of it should be run at any time (including or ignoring parameters)
47
47
  * logs all the things (start time, end time, duration, caller, exceptions etc.)
48
- * has its own exception class(es) that all exceptions that it may raise inherit from
49
- * can be called with objects or IDs as parameters
48
+ * has its own exception class(es) which all exceptions that might be raised inherit from
49
+ * does not care whether certain parameters are objects or object IDs
50
50
 
51
- Apart from these basic principles, you can implement the actual logic in a service any way you want.
51
+ Apart from these basic principles, you are free to implement the actual logic in a service any way you want.
52
52
 
53
53
  ### Conventions
54
54
 
55
- Follow these conventions when using Services in your Rails app:
55
+ Follow these conventions when using Services in your Rails app, and you'll be fine:
56
56
 
57
- * Let your services inherit from `Services::Base` (or `Services::Query`)
57
+ * Let your services inherit from `Services::Base`
58
+ * Let your query objects inherit from `Services::Query`
58
59
  * Put your services in `app/services/`
59
- * Namespace your services with the model they operate on and give them verb names, e.g. `app/services/users/delete.rb` defines `Services::Users::Delete`. If a service operates on multiple models or no models at all, don't namespace them (`Services::DoStuff`) or namespace them by logical groups unrelated to models (`Services::Maintenance::CleanOldStuff`, `Services::Maintenance::SendDailySummary`, etc.)
60
+ * Namespace your services with the model they operate on and give them "verb names", e.g. `app/services/users/delete.rb` defines `Services::Users::Delete`. If a service operates on multiple models or no models at all, don't namespace them (`Services::DoStuff`) or namespace them by logical groups unrelated to models (`Services::Maintenance::CleanOldStuff`, `Services::Maintenance::SendDailySummary`, etc.)
60
61
  * Some services call other services. Try to not combine multiple calls to other services and business logic in one service. Instead, some services should contain only business logic and other services only a bunch of service calls but no (or little) business logic. This keeps your services nice and modular.
61
62
 
62
63
  ### Configuration
@@ -66,8 +67,9 @@ You can/should configure Services in an initializer:
66
67
  ```ruby
67
68
  # config/initializers/services.rb
68
69
  Services.configure do |config|
69
- config.logger = Services::Logger::Redis.new(Redis.new) # or Services::Logger::File.new(Rails.root.join('log'))
70
- config.redis = Redis.new
70
+ config.logger = Services::Logger::Redis.new(Redis.new) # see Logging
71
+ config.redis = Redis.new # optional, if Redis.current is defined. Otherwise it is recommended to use
72
+ # a [connection pool](https://github.com/mperham/connection_pool).
71
73
  end
72
74
  ```
73
75
 
@@ -80,9 +82,15 @@ By default, Rails expects `app/services/users/delete.rb` to define `Users::Delet
80
82
  config.autoload_paths += [config.root.join('app')]
81
83
  ```
82
84
 
83
- ### Examples
85
+ This looks as if it might break things, but I've never had any problems with it.
86
+
87
+ ### Services::Base
88
+
89
+ `Services::Base` is the base class you should use for all your services. It gives you a couply of helper methods and defines a custom exception class for you.
84
90
 
85
- The following service takes one or more users or user IDs as an argument.
91
+ Read [the source](lib/services/base.rb) to understand what it does in more detail.
92
+
93
+ The following example service takes one or more users or user IDs as an argument and deletes the users:
86
94
 
87
95
  ```ruby
88
96
  module Services
@@ -91,6 +99,9 @@ module Services
91
99
  def call(ids_or_objects)
92
100
  users = find_objects(ids_or_objects)
93
101
  users.each do |user|
102
+ if user.posts.any?
103
+ raise Error, "User #{user.id} has one or more posts, refusing to delete."
104
+ end
94
105
  user.destroy
95
106
  Mailer.user_deleted(user).deliver
96
107
  end
@@ -105,37 +116,44 @@ This service can be called in several ways:
105
116
 
106
117
  ```ruby
107
118
  # Execute synchronously/in the foreground
119
+
108
120
  Services::Users::Delete.call User.find(1) # with a user object
109
- Services::Users::Delete.call User.where(id: [1, 2, 3]) # with multiple user objects
121
+ Services::Users::Delete.call User.where(id: [1, 2, 3]) # with a ActiveRecord::Relation returning user objects
122
+ Services::Users::Delete.call [user1, user2, user3] # with an array of user objects
110
123
  Services::Users::Delete.call 1 # with a user ID
111
- Services::Users::Delete.call [1, 2, 3] # with multiple user IDs
124
+ Services::Users::Delete.call [1, 2, 3] # with an array of user IDs
112
125
 
113
126
  # Execute asynchronously/in the background
127
+
114
128
  Services::Users::Delete.perform_async 1 # with a user ID
115
129
  Services::Users::Delete.perform_async [1, 2, 3] # with multiple user IDs
116
130
  ```
117
131
 
118
- As you can see, you cannot use objects when calling a service asynchronously since the arguments are serialized to Redis.
132
+ As you can see, you cannot use objects or a ActiveRecord::Relation as parameters when calling a service asynchronously since the arguments are serialized to Redis. This might change once Services works with [ActiveJob](https://github.com/rails/rails/tree/master/activejob) and [GlobalID](https://github.com/rails/globalid/).
119
133
 
120
- The helper `find_objects` is used to make sure you are dealing with an array of users from that point on, no matter whether `ids_or_objects` is a single user ID or user, or an array of user IDs or users.
134
+ The helper `find_objects` is used to allow the `ids_or_objects` parameter to be a object, object ID, array or ActiveRecord::Relation, and make sure you we dealing with an array of objects from that point on.
121
135
 
122
136
  It's good practice to always return the objects a service has been operating on at the end of the service.
123
137
 
124
- Another example, this time using `Services::Query`:
138
+ ### Services::Query
139
+
140
+ `Services::Query` on the other hand should be the base class for all query objects.
141
+
142
+ Here is an example that is used to find users:
125
143
 
126
144
  ```ruby
127
145
  module Services
128
146
  module Users
129
147
  class Find < Services::Query
148
+ convert_condition_objects_to_ids :post
149
+
130
150
  private def process(scope, conditions)
131
151
  conditions.each do |k, v|
132
152
  case k
133
153
  when :email, :name
134
154
  scope = scope.where(k => v)
135
- when :product_id
136
- scope = scope.joins(:products).where("#{Product.table_name}.id" => v)
137
- when :product_category_id
138
- scope = scope.joins(:product_categories).where("#{ProductCategory.table_name}.id" => v)
155
+ when :post_id
156
+ scope = scope.joins(:posts).where("#{Post.table_name}.id" => v)
139
157
  else
140
158
  raise ArgumentError, "Unexpected condition: #{k}"
141
159
  end
@@ -147,25 +165,49 @@ module Services
147
165
  end
148
166
  ```
149
167
 
150
- Since you will create services to find objects for pretty much every model you have and they all look very similar, i.e. process the find conditions and return a `ActiveRecord::Relation`, you can let those services inherit from `Services::Query` to remove some of the boilerplate.
168
+ A query object that inherits from `Services::Query` always receives two parameters: an array of IDs and a hash of conditions. It always returns an array, even if none or only one object is found.
151
169
 
152
- `Services::Query` takes an array of IDs and a hash of conditions as parameters. It then extracts some special conditions (:order, :limit, :page, :per_page) that are handled separately and passes a `ActiveRecord::Relation` and the remaining conditions to the `process` method that the inheriting class must define. This method should handle all the conditions, extend the scope and return it.
170
+ When you write your query objects, the only method you have to write is `process` (preferably make it private). This method does the actual querying for all non-standard parameters (more about standard vs. non-standard parameters below).
171
+
172
+ This is how `Services::Users::Find` can be called:
173
+
174
+ ```ruby
175
+ Services::Users::Find.call [] # find all users, neither filtered by IDs nor by conditions
176
+ Services::Users::Find.call [1, 2, 3] # find users with ID 1, 2 or 3
177
+ Services::Users::Find.call 1 # find users with ID 1 (careful: returns an array containing this one user, if found, otherwise an empty array)
178
+ Services::Users::Find.call [], email: 'foo@bar.com' # find users with this email address
179
+ Services::Users::Find.call [1, 2], post: Post.find(1) # find users with ID 1 or 2 and having the post with ID 1
180
+ Services::Users::Find.call [1, 2], post: [Post.find(1)] # same as above
181
+ Services::Users::Find.call [1, 2], post: 1 # same as above
182
+ ```
153
183
 
154
184
  Check out [the source of `Services::Query`](lib/services/query.rb) to understand what it does in more detail.
155
185
 
186
+ #### Standard vs. non-standard parameters
187
+
188
+ to be described...
189
+
190
+ #### convert_condition_objects_to_ids
191
+
192
+ As with service objects, you want to be able to pass objects or IDs as conditions to query objects as well, and be sure that they behave the same way. This is what `convert_condition_objects_to_ids :post` does in the previous example: it tells the service object to convert the `post` condition, if present, to `post_id`.
193
+
194
+ For example, at some point in your app you have an array of posts and need to find the users that created these posts. `Services::Users::Find.call([], post: posts)` will find them for you. If you have a post ID on the other hand, simply use `Services::Users::Find.call([], post: post_id)`, or if you have a single post, use `Services::Users::Find.call([], post: post)`. Each of these calls will return an array of users, as you would expect.
195
+
196
+ `Services::Query` takes an array of IDs and a hash of conditions as parameters. It then extracts some special conditions (:order, :limit, :page, :per_page) that are handled separately and passes a `ActiveRecord::Relation` and the remaining conditions to the `process` method that the inheriting class must define. This method should handle all the conditions, extend the scope and return it.
197
+
156
198
  ### Helpers
157
199
 
158
- Your services inherit from `Services::Base` which makes several helper methods available:
200
+ Your services inherit from `Services::Base` which makes several helper methods available to them:
159
201
 
160
202
  * `Rails.application.routes.url_helpers` is included so you use all Rails URL helpers.
161
203
  * `find_objects` and `find_object` let you automatically find object or a single object from an array of objects or object IDs, or a single object or object ID. The only difference is that `find_object` returns a single object whereas `find_objects` always returns an array.
162
204
  * `object_class` tries to figure out the class the service operates on. If you follow the service naming conventions and you have a service `Services::Products::Find`, `object_class` will return `Product`. Don't call it if you have a service like `Services::DoStuff` or it will raise an exception.
163
205
 
164
- Your services also automatically get a custom `Error` class, so you can `raise Error, 'Uh-oh, something has gone wrong!'` and a `Services::MyService::Error` will be raised.
206
+ Your services also automatically get a custom `Error` class, so you can `raise Error, 'Uh-oh, something has gone wrong!'` in `Services::MyService` and a `Services::MyService::Error` will be raised.
165
207
 
166
208
  ### Logging
167
209
 
168
- You can choose between logging to Redis or to a file.
210
+ You can choose between logging to Redis or to a file, or turn logging off. By default logging is turned off.
169
211
 
170
212
  #### Redis
171
213
 
@@ -6,11 +6,16 @@ module Services
6
6
  include GemConfig::Base
7
7
 
8
8
  BackgroundProcessorNotFound = Class.new(StandardError)
9
+ RedisNotFound = Class.new(StandardError)
9
10
 
10
11
  with_configuration do
11
12
  has :logger, default: Services::Logger::Null.new
12
13
  has :redis
13
14
  end
15
+
16
+ def self.redis
17
+ @redis ||= self.configuration.redis || (defined?(Redis.current) && Redis.current) or fail RedisNotFound, 'Redis not configured.'
18
+ end
14
19
  end
15
20
 
16
21
  require_relative 'services/version'
@@ -27,7 +27,7 @@ module Services
27
27
  @_uniqueness_args = args.empty? ? @_service_args : args
28
28
  new_uniqueness_key = uniqueness_key(@_uniqueness_args)
29
29
  raise "A uniqueness key with args #{@_uniqueness_args.inspect} already exists." if @_uniqueness_keys && @_uniqueness_keys.include?(new_uniqueness_key)
30
- if @_similar_service_id = Services.configuration.redis.get(new_uniqueness_key)
30
+ if @_similar_service_id = Services.redis.get(new_uniqueness_key)
31
31
  if on_error.to_sym == :ignore
32
32
  return false
33
33
  else
@@ -37,7 +37,7 @@ module Services
37
37
  else
38
38
  @_uniqueness_keys ||= []
39
39
  @_uniqueness_keys << new_uniqueness_key
40
- Services.configuration.redis.setex new_uniqueness_key, ONE_DAY, @id
40
+ Services.redis.setex new_uniqueness_key, ONE_DAY, @id
41
41
  true
42
42
  end
43
43
  end
@@ -62,8 +62,8 @@ module Services
62
62
  raise "Unexpected on_error: #{@_on_error}"
63
63
  end
64
64
  ensure
65
- Services.configuration.redis.del @_uniqueness_keys unless Array(@_uniqueness_keys).empty?
66
- Services.configuration.redis.del error_count_key
65
+ Services.redis.del @_uniqueness_keys unless Array(@_uniqueness_keys).empty?
66
+ Services.redis.del error_count_key
67
67
  end
68
68
 
69
69
  private
@@ -99,11 +99,11 @@ module Services
99
99
  end
100
100
 
101
101
  def error_count
102
- (Services.configuration.redis.get(error_count_key) || 0).to_i
102
+ (Services.redis.get(error_count_key) || 0).to_i
103
103
  end
104
104
 
105
105
  def increase_error_count
106
- Services.configuration.redis.setex error_count_key, retry_delay + ONE_DAY, error_count + 1
106
+ Services.redis.setex error_count_key, retry_delay + ONE_DAY, error_count + 1
107
107
  end
108
108
 
109
109
  def uniqueness_key(args)
@@ -1,3 +1,3 @@
1
1
  module Services
2
- VERSION = '4.1.0'
2
+ VERSION = '4.1.1'
3
3
  end
@@ -45,7 +45,7 @@ describe Services::Base do
45
45
  context 'when passing in something else than a single object or ID' do
46
46
  it 'raises an error' do
47
47
  [%w(foo bar), nil, Object.new].each do |object|
48
- expect { Services::Models::FindObjectTest.call(object) }.to raise_error
48
+ expect { Services::Models::FindObjectTest.call(object) }.to raise_error(Services::Models::FindObjectTest::Error)
49
49
  end
50
50
  end
51
51
  end
@@ -91,7 +91,7 @@ describe Services::Base do
91
91
  context 'when passing in something else than a single object or ID' do
92
92
  it 'raises an error' do
93
93
  [%w(foo bar), nil, Object.new].each do |object|
94
- expect { Services::Models::FindIdTest.call(object) }.to raise_error
94
+ expect { Services::Models::FindIdTest.call(object) }.to raise_error(Services::Models::FindIdTest::Error)
95
95
  end
96
96
  end
97
97
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Services::Logger::Redis do
4
4
  let(:key) { 'custom_log_key' }
5
- let(:redis) { Redis.new }
5
+ let(:redis) { Redis.new(url: REDIS_URL) }
6
6
  let(:logger) { described_class.new(redis, key) }
7
7
  let(:logs) {
8
8
  [
@@ -77,7 +77,7 @@ shared_examples 'checking the uniqueness properly' do
77
77
 
78
78
  # Check that all Redis keys are deleted
79
79
  key_pattern = "#{described_class::KEY_PREFIX}*"
80
- expect(Services.configuration.redis.keys(key_pattern)).to be_empty
80
+ expect(Services.redis.keys(key_pattern)).to be_empty
81
81
  end
82
82
  end
83
83
 
@@ -17,22 +17,20 @@ SIDEKIQ_PIDFILE = SUPPORT_DIR.join('sidekiq.pid')
17
17
  WAIT = 0.5
18
18
  START_TIMEOUT = 5
19
19
  SIDEKIQ_TIMEOUT = 20
20
- REDIS_PORT = 6479
20
+ REDIS_URL = 'redis://localhost:6379/0'
21
21
 
22
22
  %w(shared helpers test_services).each do |file|
23
23
  require SUPPORT_DIR.join(file)
24
24
  end
25
25
 
26
- Services.configure do |config|
27
- config.redis = Redis.new
28
- end
26
+ Redis.current = Redis.new(url: REDIS_URL)
29
27
 
30
28
  Sidekiq.configure_client do |config|
31
- config.redis = { redis: "redis://localhost:#{REDIS_PORT}/0", namespace: 'sidekiq', size: 1 }
29
+ config.redis = { url: REDIS_URL, namespace: 'sidekiq', size: 1 }
32
30
  end
33
31
 
34
32
  Sidekiq.configure_server do |config|
35
- config.redis = { redis: "redis://localhost:#{REDIS_PORT}/0", namespace: 'sidekiq' }
33
+ config.redis = { url: REDIS_URL, namespace: 'sidekiq' }
36
34
  end
37
35
 
38
36
  RSpec.configure do |config|
@@ -27,7 +27,9 @@ module Services
27
27
  class FindRaiseConditions < Services::Query
28
28
  convert_condition_objects_to_ids :comment
29
29
 
30
- private def process(scope, conditions)
30
+ private
31
+
32
+ def process(scope, conditions)
31
33
  raise conditions.to_json
32
34
  end
33
35
  end
@@ -39,7 +41,9 @@ module Services
39
41
  class Find < Services::Query
40
42
  convert_condition_objects_to_ids :comment
41
43
 
42
- private def process(scope, conditions)
44
+ private
45
+
46
+ def process(scope, conditions)
43
47
  conditions.each do |k, v|
44
48
  case k
45
49
  when :title, :body
@@ -61,7 +65,9 @@ module Services
61
65
  class Find < Services::Query
62
66
  convert_condition_objects_to_ids :post
63
67
 
64
- private def process(scope, conditions)
68
+ private
69
+
70
+ def process(scope, conditions)
65
71
  conditions.each do |k, v|
66
72
  case k
67
73
  when :body, :post_id
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: services
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 4.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manuel Meurer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-05 00:00:00.000000000 Z
11
+ date: 2016-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -214,7 +214,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
214
214
  version: '0'
215
215
  requirements: []
216
216
  rubyforge_project:
217
- rubygems_version: 2.4.5
217
+ rubygems_version: 2.5.1
218
218
  signing_key:
219
219
  specification_version: 4
220
220
  summary: A nifty service layer for your Rails app