services 2.0.0 → 2.0.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: b511fc23143b4bd0ddf9eef2574f76f3c0bf12cf
4
- data.tar.gz: f34f312482dc3e0025b98d3acf0dae0c92d45275
3
+ metadata.gz: 497d42516b9a0d6a95bdf84cc5387fddae35c1a0
4
+ data.tar.gz: 25e03e9ceea6e04dadac955e5027c654d0a95dda
5
5
  SHA512:
6
- metadata.gz: c7f9e9850c3435b6e525dc7708521bef50682544088b93afa8fb81554730b4b7faf9d8d92a71eb06ad56b4a2c55ef8e9a404055dd3b5161e40daa5f0e571fa54
7
- data.tar.gz: ee3ec81535144e849340bc75b037c829731da0edc1bebe0ac666704dbd8c384be2fb5daf8f51c65251851e30c7222f8cd68c109cc5a8f90ef79c9be89c03c8e3
6
+ metadata.gz: 8ea3bb811cc4b5f55e3e8eefda6b6d64731250b46b143ea2ed1a456819d605727e58fca08417cb15a3e3e9eb37741dee8684c225af5ba4ef83d6f3162cb821d9
7
+ data.tar.gz: 757435307628879d21fe2a7106630e39901c1f806a7e4b024215d4fbdb98f336a85d6752fb88055b74c60863d29d508228d51275c27ebf69c75b123dbe47f1ea
data/.travis.yml CHANGED
@@ -2,5 +2,6 @@ language: ruby
2
2
  rvm:
3
3
  - 2.0
4
4
  - 2.1
5
+ - 2.2
5
6
  services:
6
7
  - redis-server
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 2.0.0
2
+
3
+ * Improve call logging
4
+ * Implement `disable_call_logging` and `enable_call_logging` to control call logging for specific services
5
+ * Disable call logging for `BaseFinder` by default
6
+ * Rename `check_uniqueness!` to `check_uniqueness`
7
+
1
8
  ## 1.3.0
2
9
 
3
10
  * Allow only certain classes in Redis logger meta (NilClass, TrueClass, FalseClass, Symbol, String, Numeric)
data/Guardfile CHANGED
@@ -1,25 +1,54 @@
1
- class Guard::StartRedisAndSidekiq
2
- def call(guard_class, event, *args)
3
- puts guard_class
4
- puts event
5
- puts args
6
- end
7
- end
1
+ # require 'guard/rspec'
8
2
 
9
- class Guard::StopRedisAndSidekiq
10
- def call(guard_class, event, *args)
11
- sleep 5
12
- puts guard_class
13
- puts event
14
- puts args
15
- end
16
- end
3
+ # module ::Guard
4
+ # class RSpec < Plugin
5
+ # # Add `stop` method if not defined
6
+ # # so that `stop_*` callbacks work.
7
+ # unless instance_methods.include?(:stop)
8
+ # def stop; end
9
+ # end
10
+ # end
11
+ # end
12
+
13
+ # class Guard::StartRedisAndSidekiq
14
+ # def call(guard_class, event, *args)
15
+ # # Start Redis
16
+ # redis_options = {
17
+ # daemonize: 'yes',
18
+ # port: redis_port,
19
+ # dir: support_dir,
20
+ # dbfilename: 'redis.rdb',
21
+ # logfile: log_dir.join('redis.log'),
22
+ # pidfile: redis_pidfile
23
+ # }
24
+ # redis = support_dir.join('redis-server')
25
+ # system "#{redis} #{options_hash_to_string(redis_options)}"
26
+
27
+ # # Copy call proxy
28
+ # FileUtils.cp CALL_PROXY_SOURCE, CALL_PROXY_DESTINATION
29
+ # end
30
+ # end
31
+
32
+ # class Guard::StopRedisAndSidekiq
33
+ # def call(guard_class, event, *args)
34
+ # # Delete call proxy
35
+ # FileUtils.rm CALL_PROXY_DESTINATION
36
+
37
+ # # Stop Redis
38
+ # redis_cli = support_dir.join('redis-cli')
39
+ # system "#{redis_cli} -p #{redis_port} shutdown"
40
+
41
+ # # Truncate log files
42
+ # max_len = 1024 * 1024 # 1 MB
43
+ # Dir[File.join(log_dir, '*.log')].each do |file|
44
+ # File.truncate file, max_len if File.size(file) > max_len
45
+ # end
46
+ # end
47
+ # end
17
48
 
18
49
  guard 'rspec', cmd: 'bundle exec rspec' do
19
50
  # callback StartRedisAndSidekiq.new, :start_begin
20
- # callback StopRedisAndSidekiq.new, :stop
21
51
  # callback StopRedisAndSidekiq.new, :stop_begin
22
- # callback StopRedisAndSidekiq.new, :stop_end
23
52
 
24
53
  # Specs
25
54
  watch(%r(^spec/.+_spec\.rb$))
data/README.md CHANGED
@@ -9,35 +9,37 @@ Services is a collection of modules and base classes that let you implement a ni
9
9
 
10
10
  ## Motivation
11
11
 
12
- A lot has been written about service layers in Rails apps. There are advantages and disadvantages of course, 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 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 makes it 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 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.**
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, 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.
19
19
 
20
20
  ### Basic principles
21
21
 
22
- Services is based on a couple of basic principles of what a service should be and do in your Rails app:
22
+ Services is based on a couple of basic principles of what a service should be and do in your app:
23
23
 
24
- * a service does one thing well (Unix philosophy)
25
- * a service can be run synchronously (in the foreground) or asynchronously (in the background)
26
- * a service can be unique, meaning only one instance of it should be run at a time
27
- * a service logs all the things (start time, end time, caller, exceptions etc.)
28
- * a service has its own exception class and all exceptions that it may raise must be of that class or a subclass
29
- * a service can be called with one or multiple objects or one or multiple object IDs
24
+ A service...
25
+
26
+ * ...does one thing well (Unix philosophy)
27
+ * ...can be run synchronously (in the foreground) or asynchronously (in the background)
28
+ * ...can be unique, meaning only one instance of it should be run at a time
29
+ * ...logs all the things (start time, end time, duration, caller, exceptions etc.)
30
+ * ...has its own exception class(es) that all exceptions that it may raise inherit from
31
+ * ...can be called with one or multiple objects or one or multiple object IDs
30
32
 
31
33
  Apart from these basic principles, you can implement the actual logic in a service any way you want.
32
34
 
33
35
  ### Conventions
34
36
 
35
- Follow these conventions that Services expects/recommends:
37
+ Follow these conventions when using Services in your Rails app:
36
38
 
37
39
  * services inherit from `Services::Base` (or `Services::BaseFinder`)
38
40
  * services are located in `app/services/`
39
- * 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::DoLotsOfStuff`) or namespace them by logical groups unrelated to models (`Services::Maintenance::CleanOldUsers`, `Services::Maintenance::SendDailySummary`, etc.)
40
- * Sometimes services must 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.
41
+ * 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.)
42
+ * 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.
41
43
 
42
44
  ### Rails autoload fix
43
45
 
@@ -50,9 +52,17 @@ config.autoload_paths += [config.root.join('app')]
50
52
 
51
53
  ### Dependence
52
54
 
53
- 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, a service will raise an exception when you try to enqueue it for background processing. If you're using Sidekiq, make sure to load the Services gem after the Sidekiq gem.
55
+ #### Redis
56
+
57
+ to be described...
58
+
59
+ #### Sidekiq
60
+
61
+ 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.
62
+
63
+ #### Postgres
54
64
 
55
- The SQL `Services::BaseFinder` (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::BaseFinder` or, even better, submit a PR that fixes it to work with your database!
65
+ The SQL that `Services::BaseFinder` (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::BaseFinder` or, even better, submit a [pull request](https://github.com/krautcomputing/services/issues) that fixes it to work with your database!
56
66
 
57
67
  ### Examples
58
68
 
@@ -75,6 +85,22 @@ module Services
75
85
  end
76
86
  ```
77
87
 
88
+ This service can be called in several ways:
89
+
90
+ ```ruby
91
+ # Execute synchronously/immediately
92
+ Services::Users::Delete.call User.find(1) # with a user object
93
+ Services::Users::Delete.call User.where(id: [1, 2, 3]) # with multiple user objects
94
+ Services::Users::Delete.call 1 # with a user ID
95
+ Services::Users::Delete.call [1, 2, 3] # with multiple user IDs
96
+
97
+ # Execute asynchronously/in the background
98
+ Services::Users::Delete.perform_async 1 # with a user ID
99
+ Services::Users::Delete.perform_async [1, 2, 3] # with multiple user IDs
100
+ ```
101
+
102
+ As you can see, you cannot use objects when calling a service asynchronously since the arguments are serialized to Redis.
103
+
78
104
  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.
79
105
 
80
106
  It's good practice to always return the objects a service has been operating on at the end of the service.
@@ -3,9 +3,17 @@ module Services
3
3
  module CallLogger
4
4
  def self.prepended(mod)
5
5
  mod.extend ClassMethods
6
+ mod.instance_eval do
7
+ def inherited(subclass)
8
+ subclass.extend ClassMethods
9
+ subclass.disable_call_logging if self.call_logging_disabled
10
+ end
11
+ end
6
12
  end
7
13
 
8
14
  module ClassMethods
15
+ @call_logging_disabled = false
16
+
9
17
  attr_accessor :call_logging_disabled
10
18
 
11
19
  def disable_call_logging
@@ -1,3 +1,3 @@
1
1
  module Services
2
- VERSION = '2.0.0'
2
+ VERSION = '2.0.1'
3
3
  end
@@ -1,7 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Services::BaseFinder do
4
- it 'does not log start and end' do
5
- expect { Services::Models::BaseFind.call }.to_not change { @logs }
4
+ include_context 'capture logs'
5
+
6
+ it 'has call logging disabled by default' do
7
+ expect(Services::Models::BaseFind.call_logging_disabled).to eq(true)
8
+ expect { Services::Models::BaseFind.call }.to_not change { logs }
6
9
  end
7
10
  end
@@ -1,30 +1,14 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Services::Base::CallLogger do
4
- let(:logger) { spy('logger') }
5
-
6
- before do
7
- Services.configuration.logger = logger
8
- @logs = []
9
- allow(logger).to receive(:log) do |message, meta, severity|
10
- @logs << {
11
- message: message,
12
- meta: meta,
13
- severity: severity
14
- }
15
- end
16
- end
17
-
18
- after do
19
- Services.configuration.logger = nil
20
- end
4
+ include_context 'capture logs'
21
5
 
22
6
  context 'when call logging is enabled' do
23
7
  it 'logs start and end' do
24
8
  service, args = EmptyService.new, %w(foo bar)
25
9
  service.call *args
26
10
  caller_regex = /\A#{Regexp.escape __FILE__}:\d+\z/
27
- expect(@logs.first).to match(
11
+ expect(logs.first).to match(
28
12
  message: "START with args: #{args}",
29
13
  meta: {
30
14
  caller: a_string_matching(caller_regex),
@@ -33,7 +17,7 @@ describe Services::Base::CallLogger do
33
17
  },
34
18
  severity: 'info'
35
19
  )
36
- expect(@logs.last).to match(
20
+ expect(logs.last).to match(
37
21
  message: 'END',
38
22
  meta: {
39
23
  duration: 0.0,
@@ -53,7 +37,7 @@ describe Services::Base::CallLogger do
53
37
  Services::CallProxy.call(called_service, :call)
54
38
  caller_regex = /\A#{Regexp.escape __FILE__}:\d+/
55
39
  expect(
56
- @logs.detect do |log|
40
+ logs.detect do |log|
57
41
  log[:meta][:caller] =~ caller_regex
58
42
  end
59
43
  ).to be_present
@@ -64,7 +48,7 @@ describe Services::Base::CallLogger do
64
48
  service_calling_service.call called_service
65
49
  caller_regex = /\A#{Regexp.escape PROJECT_ROOT.join(TEST_SERVICES_PATH).to_s}:\d+/
66
50
  expect(
67
- @logs.detect do |log|
51
+ logs.detect do |log|
68
52
  log[:meta][:caller] =~ caller_regex
69
53
  end
70
54
  ).to be_present
@@ -88,7 +72,7 @@ describe Services::Base::CallLogger do
88
72
  service_calling_service.call called_service
89
73
  caller_regex = /\A#{Regexp.escape TEST_SERVICES_PATH.to_s}:\d+/
90
74
  expect(
91
- @logs.detect do |log|
75
+ logs.detect do |log|
92
76
  log[:meta][:caller] =~ caller_regex
93
77
  end
94
78
  ).to be_present
@@ -99,13 +83,14 @@ describe Services::Base::CallLogger do
99
83
 
100
84
  context 'when call logging is disabled' do
101
85
  it 'does not log start and end' do
102
- expect { EmptyServiceWithoutCallLogging.call }.to_not change { @logs }
86
+ expect(EmptyServiceWithoutCallLogging.call_logging_disabled).to eq(true)
87
+ expect { EmptyServiceWithoutCallLogging.call }.to_not change { logs }
103
88
  end
104
89
  end
105
90
 
106
91
  it 'logs exceptions' do
107
92
  [ErrorService, ErrorServiceWithoutCallLogging].each do |klass|
108
- expect { klass.call rescue nil }.to change { @logs }
93
+ expect { klass.call rescue nil }.to change { logs }
109
94
  end
110
95
  end
111
96
 
@@ -116,7 +101,7 @@ describe Services::Base::CallLogger do
116
101
  %w(NestedError1 NestedError2).each do |error|
117
102
  message_regex = /caused by: #{service.class}::#{error}/
118
103
  expect(
119
- @logs.detect do |log|
104
+ logs.detect do |log|
120
105
  log[:message] =~ message_regex
121
106
  end
122
107
  ).to be_present
@@ -0,0 +1,19 @@
1
+ shared_context 'capture logs' do
2
+ let(:logger) { spy('logger') }
3
+ let(:logs) { [] }
4
+
5
+ before do
6
+ Services.configuration.logger = logger
7
+ allow(logger).to receive(:log) do |message, meta, severity|
8
+ logs << {
9
+ message: message,
10
+ meta: meta,
11
+ severity: severity
12
+ }
13
+ end
14
+ end
15
+
16
+ after do
17
+ Services.configuration.logger = nil
18
+ end
19
+ end
@@ -43,7 +43,9 @@ end
43
43
  module Services
44
44
  module Models
45
45
  class BaseFind < Services::BaseFinder
46
- private def process(scope, conditions)
46
+ private
47
+
48
+ def process(scope, conditions)
47
49
  scope
48
50
  end
49
51
  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: 2.0.0
4
+ version: 2.0.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: 2014-11-16 00:00:00.000000000 Z
11
+ date: 2014-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -174,6 +174,7 @@ files:
174
174
  - spec/support/log/.gitkeep
175
175
  - spec/support/redis-cli
176
176
  - spec/support/redis-server
177
+ - spec/support/shared.rb
177
178
  - spec/support/test_services.rb
178
179
  homepage: http://krautcomputing.github.io/services
179
180
  licenses:
@@ -212,5 +213,6 @@ test_files:
212
213
  - spec/support/log/.gitkeep
213
214
  - spec/support/redis-cli
214
215
  - spec/support/redis-server
216
+ - spec/support/shared.rb
215
217
  - spec/support/test_services.rb
216
218
  has_rdoc: