services 3.1.1 → 4.0.0

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: aa36865886b193fa90e404a25e97a77f02b8db14
4
- data.tar.gz: 98215ed1fe3f3c11426c8946d6e5fed0cf9f5c61
3
+ metadata.gz: 74080ca61d88a944b2f35bfe0f81cd00815c96cc
4
+ data.tar.gz: 4be4bae8d75734489654038732c34d922e28b1bc
5
5
  SHA512:
6
- metadata.gz: 23145a46e65887c619eb18c4be711bbecbf0016de66c0c89c791f4aacf4d6eb7bd2f71cc975ae180700912d93a5a1deaa2edc3427e323541d743bc95292e39ef
7
- data.tar.gz: 9e3cbf5c22d2ea747bc91b08b443e8f8c7856da671104cb2bc80d998a8bc8ce6741ac3edf6c46a96912dcc08599343772810026d344c429b902d76bbdd3c88b1
6
+ metadata.gz: a0332a0d2e3be17d07698ee992d85fce822d77744a06051cf8fe1015f6325805aeeb175e21d1d7845d77bfe054c8fa590a54eeed64afba7a20a98d168450bd90
7
+ data.tar.gz: 4c4d2fce4a8df600e1457d8ef70c45f1f62d267e0aa43670bd9e9e962ec2ff8c402fe0bd651176e14babe295f9fa99ad209ebf122bcb11f423df3384a4ddaa24
data/README.md CHANGED
@@ -5,48 +5,52 @@
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 collection of modules and base classes that let you implement a nifty service layer in your Rails app.
8
+ Services is a small collection of modules and base classes that let you implement a nifty service layer in your Rails app.
9
9
 
10
10
  ## Motivation
11
11
 
12
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.
13
13
 
14
- **The biggest benefit you get with 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 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.**
15
15
 
16
16
  ## Usage
17
17
 
18
- For disambiguation, we let's write Services with a uppercase "S" when we mean this gem, and services with a lowercase "s" when we mean, well, the plural of service.
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.
19
19
 
20
20
  ### Requirements
21
21
 
22
- #### Rails
22
+ ### Ruby
23
23
 
24
- to be described...
24
+ Ruby >= 2.0
25
25
 
26
- #### Redis
26
+ #### Rails
27
27
 
28
- to be described...
28
+ Rails >= 3.2
29
29
 
30
- #### Sidekiq
30
+ #### Redis
31
31
 
32
- To process services in the background, Services uses [Sidekiq](https://github.com/mperham/sidekiq). Sidekiq is not 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.
32
+ 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.
33
33
 
34
34
  #### Postgres
35
35
 
36
36
  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!
37
37
 
38
+ #### Sidekiq (optional)
39
+
40
+ 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.
41
+
38
42
  ### Basic principles
39
43
 
40
- Services is based on a couple of basic principles of what a service should be and do in your app:
44
+ Services is based on a couple of basic principles around what a service should be and do in your app:
41
45
 
42
46
  A service...
43
47
 
44
- * ...does one thing well (Unix philosophy)
45
- * ...can be run synchronously (in the foreground) or asynchronously (in the background)
46
- * ...can be unique, meaning only one instance of it should be run at a time
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 one or multiple objects or one or multiple object IDs
48
+ * does one thing and does it well (Unix philosophy)
49
+ * can be run synchronously (in the foreground) or asynchronously (in the background)
50
+ * can be configured as "unique", meaning only one instance of it should be run at any time
51
+ * logs all the things (start time, end time, duration, caller, exceptions etc.)
52
+ * has its own exception class(es) that all exceptions that it may raise inherit from
53
+ * can be called with objects or IDs as parameters
50
54
 
51
55
  Apart from these basic principles, you can implement the actual logic in a service any way you want.
52
56
 
@@ -54,20 +58,20 @@ Apart from these basic principles, you can implement the actual logic in a servi
54
58
 
55
59
  Follow these conventions when using Services in your Rails app:
56
60
 
57
- * services inherit from `Services::Base` (or `Services::Query`)
58
- * services are located in `app/services/`
59
- * services are namespaced with the model they operate on and their names are verbs, 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::CleanOldUsers`, `Services::Maintenance::SendDailySummary`, etc.)
60
- * 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
+ * Let your services inherit from `Services::Base` (or `Services::Query`)
62
+ * Put your services in `app/services/`
63
+ * 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.)
64
+ * 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
65
 
62
- ### Configure
66
+ ### Configuration
63
67
 
64
- You can set some configuration options in an initializer:
68
+ You can/should configure Services in an initializer:
65
69
 
66
70
  ```ruby
67
71
  # config/initializers/services.rb
68
72
  Services.configure do |config|
69
- config.logger = Services::Logger::Redis.new(REDIS) # or Services::Logger::File.new(Rails.root.join('log'))
70
- config.redis = REDIS
73
+ config.logger = Services::Logger::Redis.new(Redis.new) # or Services::Logger::File.new(Rails.root.join('log'))
74
+ config.redis = Redis.new
71
75
  end
72
76
  ```
73
77
 
@@ -104,7 +108,7 @@ end
104
108
  This service can be called in several ways:
105
109
 
106
110
  ```ruby
107
- # Execute synchronously/immediately
111
+ # Execute synchronously/in the foreground
108
112
  Services::Users::Delete.call User.find(1) # with a user object
109
113
  Services::Users::Delete.call User.where(id: [1, 2, 3]) # with multiple user objects
110
114
  Services::Users::Delete.call 1 # with a user ID
@@ -117,7 +121,7 @@ Services::Users::Delete.perform_async [1, 2, 3] # with multiple user ID
117
121
 
118
122
  As you can see, you cannot use objects when calling a service asynchronously since the arguments are serialized to Redis.
119
123
 
120
- As you can see, 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.
124
+ 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.
121
125
 
122
126
  It's good practice to always return the objects a service has been operating on at the end of the service.
123
127
 
@@ -149,9 +153,9 @@ end
149
153
 
150
154
  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.
151
155
 
152
- `Services::Query` inherits from `Services::Base` and 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.
156
+ `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.
153
157
 
154
- Check out [the source of `Services::Query`](lib/services/base_finder.rb) to understand what it does in more detail.
158
+ Check out [the source of `Services::Query`](lib/services/query.rb) to understand what it does in more detail.
155
159
 
156
160
  ### Helpers
157
161
 
@@ -160,7 +164,6 @@ Your services inherit from `Services::Base` which makes several helper methods a
160
164
  * `Rails.application.routes.url_helpers` is included so you use all Rails URL helpers.
161
165
  * `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
166
  * `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
- * `controller` creates a `ActionController::Base` instance with an empty request. You can use it to call `render_to_string` to render a view from your service for example.
164
167
 
165
168
  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.
166
169
 
@@ -168,10 +171,13 @@ Your services also automatically get a custom `Error` class, so you can `raise E
168
171
 
169
172
  You can choose between logging to Redis or to a file.
170
173
 
171
-
172
174
  #### Redis
173
175
 
176
+ to be described...
177
+
178
+ #### File
174
179
 
180
+ to be described...
175
181
 
176
182
  ### Exception wrapping
177
183
 
@@ -185,10 +191,6 @@ to be described...
185
191
 
186
192
  to be described...
187
193
 
188
- ## Requirements
189
-
190
- Ruby >= 2.0
191
-
192
194
  ## Installation
193
195
 
194
196
  Add this line to your application's Gemfile:
@@ -213,4 +215,4 @@ Or install it yourself as:
213
215
 
214
216
  ## Testing
215
217
 
216
- You need Redis to run tests, check out the Guardfile!
218
+ You need Redis to run tests, check out the [Guardfile](Guardfile) which loads it automatically when you start Guard!
data/lib/services.rb CHANGED
@@ -6,7 +6,6 @@ module Services
6
6
  BackgroundProcessorNotFound = Class.new(StandardError)
7
7
 
8
8
  with_configuration do
9
- has :host, classes: String
10
9
  has :logger
11
10
  has :redis
12
11
  end
data/lib/services/base.rb CHANGED
@@ -66,16 +66,5 @@ module Services
66
66
  end.first
67
67
  end
68
68
  end
69
-
70
- def controller
71
- @controller ||= begin
72
- raise 'Please configure host.' if Services.configuration.host.nil?
73
- request = ActionDispatch::TestRequest.new
74
- request.host = Services.configuration.host
75
- ActionController::Base.new.tap do |controller|
76
- controller.instance_variable_set('@_request', request)
77
- end
78
- end
79
- end
80
69
  end
81
70
  end
@@ -1,3 +1,3 @@
1
1
  module Services
2
- VERSION = '3.1.1'
2
+ VERSION = '4.0.0'
3
3
  end
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: 3.1.1
4
+ version: 4.0.0
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-01-22 00:00:00.000000000 Z
11
+ date: 2015-02-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake