action_mailer_kafka 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +158 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.rubocop.yml +64 -0
- data/Appraisals +21 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +4 -0
- data/LICENSE.md +23 -0
- data/README.md +138 -0
- data/Rakefile +6 -0
- data/action_mailer_kafka.gemspec +52 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/example/.gitignore +21 -0
- data/example/Gemfile +39 -0
- data/example/README.rdoc +10 -0
- data/example/Rakefile +6 -0
- data/example/app/assets/images/.keep +0 -0
- data/example/app/assets/javascripts/application.js +16 -0
- data/example/app/assets/javascripts/users.js.coffee +3 -0
- data/example/app/assets/stylesheets/application.css +15 -0
- data/example/app/assets/stylesheets/scaffolds.css.scss +69 -0
- data/example/app/assets/stylesheets/users.css.scss +3 -0
- data/example/app/controllers/application_controller.rb +5 -0
- data/example/app/controllers/concerns/.keep +0 -0
- data/example/app/controllers/users_controller.rb +82 -0
- data/example/app/helpers/application_helper.rb +2 -0
- data/example/app/helpers/users_helper.rb +2 -0
- data/example/app/jobs/send_email_job.rb +8 -0
- data/example/app/mailers/.keep +0 -0
- data/example/app/mailers/example_mailer.rb +6 -0
- data/example/app/models/.keep +0 -0
- data/example/app/models/concerns/.keep +0 -0
- data/example/app/models/user.rb +2 -0
- data/example/app/views/example_mailer/sample_email.html.erb +398 -0
- data/example/app/views/example_mailer/sample_email.text.erb +2 -0
- data/example/app/views/layouts/application.html.erb +14 -0
- data/example/app/views/users/_form.html.erb +25 -0
- data/example/app/views/users/edit.html.erb +6 -0
- data/example/app/views/users/index.html.erb +27 -0
- data/example/app/views/users/index.json.jbuilder +4 -0
- data/example/app/views/users/new.html.erb +5 -0
- data/example/app/views/users/show.html.erb +14 -0
- data/example/app/views/users/show.json.jbuilder +1 -0
- data/example/config/application.rb +23 -0
- data/example/config/boot.rb +4 -0
- data/example/config/database.yml +11 -0
- data/example/config/environment.rb +5 -0
- data/example/config/environments/development.rb +43 -0
- data/example/config/environments/production.rb +105 -0
- data/example/config/environments/test.rb +39 -0
- data/example/config/initializers/backtrace_silencers.rb +7 -0
- data/example/config/initializers/cookies_serializer.rb +3 -0
- data/example/config/initializers/filter_parameter_logging.rb +4 -0
- data/example/config/initializers/inflections.rb +16 -0
- data/example/config/initializers/mime_types.rb +4 -0
- data/example/config/initializers/session_store.rb +3 -0
- data/example/config/initializers/wrap_parameters.rb +14 -0
- data/example/config/locales/en.yml +23 -0
- data/example/config/routes.rb +4 -0
- data/example/config/secrets.yml +22 -0
- data/example/config.ru +4 -0
- data/example/db/migrate/20141111080045_create_users.rb +10 -0
- data/example/db/migrate/20141115060216_create_delayed_jobs.rb +22 -0
- data/example/db/schema.rb +40 -0
- data/example/db/seeds.rb +7 -0
- data/example/public/404.html +67 -0
- data/example/public/422.html +67 -0
- data/example/public/500.html +66 -0
- data/example/public/favicon.ico +0 -0
- data/example/public/robots.txt +5 -0
- data/example/vendor/assets/javascripts/.keep +0 -0
- data/example/vendor/assets/stylesheets/.keep +0 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/mail_2_5.gemfile +7 -0
- data/gemfiles/mail_2_6.gemfile +7 -0
- data/gemfiles/mail_2_7.gemfile +7 -0
- data/gemfiles/rails_4.gemfile +7 -0
- data/gemfiles/rails_5.gemfile +7 -0
- data/lib/action_mailer_kafka/delivery_method.rb +139 -0
- data/lib/action_mailer_kafka/error.rb +22 -0
- data/lib/action_mailer_kafka/railtie.rb +7 -0
- data/lib/action_mailer_kafka/version.rb +3 -0
- data/lib/action_mailer_kafka.rb +10 -0
- data/logo.png +0 -0
- metadata +328 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b7345d1145d840492978828091a1a9ebe4b3c08b
|
4
|
+
data.tar.gz: ffdb7a81954a33a0e03bad1f3e5757403af7743a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a4db5bd4229ff0091911ac61c188f33029c4c0f67bda9b147fd6a91267b1fc435087e63b5c2e75aa875771cbeb2e65a0ee1461625583b6c7d5f5de6a3c5b8f40
|
7
|
+
data.tar.gz: dfd02e92da664c07175beb643cff16cdfa572df8c5be70eb90bfbf1cb9ce620e9ce07728df08b5ed271092f40b62c0ade9b1fc78c6cf23f178142d1b026f2076
|
@@ -0,0 +1,158 @@
|
|
1
|
+
version: 2
|
2
|
+
jobs:
|
3
|
+
rubycritic:
|
4
|
+
docker:
|
5
|
+
- image: circleci/ruby:2.3
|
6
|
+
environment:
|
7
|
+
LOG_LEVEL: DEBUG
|
8
|
+
steps:
|
9
|
+
- checkout
|
10
|
+
- restore_cache:
|
11
|
+
keys:
|
12
|
+
- v1-dependencies-{{ checksum "eh-mailer.gemspec" }}
|
13
|
+
# fallback to using the latest cache if no exact match is found
|
14
|
+
- v1-dependencies-
|
15
|
+
- run:
|
16
|
+
name: install dependencies
|
17
|
+
command: |
|
18
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
19
|
+
|
20
|
+
- save_cache:
|
21
|
+
paths:
|
22
|
+
- ./vendor/bundle
|
23
|
+
key: v1-dependencies-{{ checksum "eh-mailer.gemspec" }}
|
24
|
+
- run: bundle exec rubycritic -s 90 --no-browser -f console --suppress-ratings lib/
|
25
|
+
|
26
|
+
rubocop:
|
27
|
+
docker:
|
28
|
+
- image: circleci/ruby:2.3
|
29
|
+
environment:
|
30
|
+
LOG_LEVEL: DEBUG
|
31
|
+
steps:
|
32
|
+
- checkout
|
33
|
+
- restore_cache:
|
34
|
+
keys:
|
35
|
+
- v1-dependencies-{{ checksum "eh-mailer.gemspec" }}
|
36
|
+
# fallback to using the latest cache if no exact match is found
|
37
|
+
- v1-dependencies-
|
38
|
+
- run:
|
39
|
+
name: install dependencies
|
40
|
+
command: |
|
41
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
42
|
+
|
43
|
+
- save_cache:
|
44
|
+
paths:
|
45
|
+
- ./vendor/bundle
|
46
|
+
key: v1-dependencies-{{ checksum "eh-mailer.gemspec" }}
|
47
|
+
- run: bundle exec rubocop
|
48
|
+
|
49
|
+
unit:
|
50
|
+
docker:
|
51
|
+
- image: circleci/ruby:2.3
|
52
|
+
environment:
|
53
|
+
LOG_LEVEL: DEBUG
|
54
|
+
working_directory: ~/app
|
55
|
+
steps:
|
56
|
+
- checkout
|
57
|
+
|
58
|
+
# download and cache dependencies
|
59
|
+
- restore_cache:
|
60
|
+
keys:
|
61
|
+
- v1-dependencies-{{ checksum "eh-mailer.gemspec" }}
|
62
|
+
# fallback to using the latest cache if no exact match is found
|
63
|
+
- v1-dependencies-
|
64
|
+
|
65
|
+
- run:
|
66
|
+
name: install dependencies
|
67
|
+
command: |
|
68
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
69
|
+
bundle exec appraisal install
|
70
|
+
|
71
|
+
- save_cache:
|
72
|
+
paths:
|
73
|
+
- ./vendor/bundle
|
74
|
+
key: v1-dependencies-{{ checksum "eh-mailer.gemspec" }}
|
75
|
+
|
76
|
+
# run tests!
|
77
|
+
- run:
|
78
|
+
name: run tests
|
79
|
+
command: |
|
80
|
+
mkdir /tmp/test-results
|
81
|
+
TEST_FILES="$(circleci tests glob "spec/units/**/*_spec.rb" | \
|
82
|
+
circleci tests split --split-by=timings)"
|
83
|
+
|
84
|
+
COVERAGE=true bundle exec appraisal rspec \
|
85
|
+
--format progress \
|
86
|
+
--color \
|
87
|
+
$TEST_FILES
|
88
|
+
|
89
|
+
# collect reports
|
90
|
+
- store_test_results:
|
91
|
+
path: /tmp/test-results
|
92
|
+
- store_artifacts:
|
93
|
+
path: /tmp/test-results
|
94
|
+
destination: test-results
|
95
|
+
|
96
|
+
integration_rails_4:
|
97
|
+
docker:
|
98
|
+
- image: circleci/ruby:2.3
|
99
|
+
environment:
|
100
|
+
LOG_LEVEL: DEBUG
|
101
|
+
INTEGRATION_TEST: true
|
102
|
+
- image: wurstmeister/zookeeper
|
103
|
+
- image: wurstmeister/kafka:0.11.0.1
|
104
|
+
environment:
|
105
|
+
KAFKA_ADVERTISED_HOST_NAME: localhost
|
106
|
+
KAFKA_ADVERTISED_PORT: 9092
|
107
|
+
KAFKA_PORT: 9092
|
108
|
+
KAFKA_ZOOKEEPER_CONNECT: localhost:2181
|
109
|
+
KAFKA_DELETE_TOPIC_ENABLE: true
|
110
|
+
|
111
|
+
working_directory: ~/app
|
112
|
+
steps:
|
113
|
+
- checkout
|
114
|
+
- run:
|
115
|
+
name: install dependencies
|
116
|
+
command: |
|
117
|
+
BUNDLE_GEMFILE=./gemfiles/rails_4.gemfile bundle install --path ~/app/vendor/bundle
|
118
|
+
- run:
|
119
|
+
name: run integration tests
|
120
|
+
command: |
|
121
|
+
bundle exec rspec spec/integrations/rails_integration_spec.rb
|
122
|
+
|
123
|
+
integration_rails_5:
|
124
|
+
docker:
|
125
|
+
- image: circleci/ruby:2.3
|
126
|
+
environment:
|
127
|
+
LOG_LEVEL: DEBUG
|
128
|
+
INTEGRATION_TEST: true
|
129
|
+
- image: wurstmeister/zookeeper
|
130
|
+
- image: wurstmeister/kafka:0.11.0.1
|
131
|
+
environment:
|
132
|
+
KAFKA_ADVERTISED_HOST_NAME: localhost
|
133
|
+
KAFKA_ADVERTISED_PORT: 9092
|
134
|
+
KAFKA_PORT: 9092
|
135
|
+
KAFKA_ZOOKEEPER_CONNECT: localhost:2181
|
136
|
+
KAFKA_DELETE_TOPIC_ENABLE: true
|
137
|
+
|
138
|
+
working_directory: ~/app
|
139
|
+
steps:
|
140
|
+
- checkout
|
141
|
+
- run:
|
142
|
+
name: install dependencies
|
143
|
+
command: |
|
144
|
+
BUNDLE_GEMFILE=./gemfiles/rails_5.gemfile bundle install --path ~/app/vendor/bundle
|
145
|
+
- run:
|
146
|
+
name: run integration tests
|
147
|
+
command: |
|
148
|
+
bundle exec rspec spec/integrations/rails_integration_spec.rb
|
149
|
+
|
150
|
+
workflows:
|
151
|
+
version: 2
|
152
|
+
test:
|
153
|
+
jobs:
|
154
|
+
- unit
|
155
|
+
- integration_rails_4
|
156
|
+
- integration_rails_5
|
157
|
+
- rubycritic
|
158
|
+
- rubocop
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require: rubocop-rspec
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
TargetRubyVersion: 2.3
|
5
|
+
Exclude:
|
6
|
+
- 'vendor/**/*'
|
7
|
+
- 'Rakefile'
|
8
|
+
- 'scripts/**/*'
|
9
|
+
- 'bin/*'
|
10
|
+
- 'example/**/*'
|
11
|
+
- 'gemfiles/*'
|
12
|
+
- 'Appraisals'
|
13
|
+
- 'tmp/**/*'
|
14
|
+
|
15
|
+
Style/GuardClause:
|
16
|
+
Enabled: no
|
17
|
+
|
18
|
+
Style/Documentation:
|
19
|
+
Enabled: no
|
20
|
+
|
21
|
+
Style/HashSyntax:
|
22
|
+
Enabled: no
|
23
|
+
|
24
|
+
Style/IfUnlessModifier:
|
25
|
+
Enabled: no
|
26
|
+
|
27
|
+
RSpec/NestedGroups:
|
28
|
+
Max: 4
|
29
|
+
|
30
|
+
RSpec/ExampleLength:
|
31
|
+
Enabled: no
|
32
|
+
|
33
|
+
RSpec/MultipleExpectations:
|
34
|
+
Enabled: no
|
35
|
+
|
36
|
+
RSpec/EmptyExampleGroup:
|
37
|
+
Enabled: no
|
38
|
+
|
39
|
+
RSpec/DescribeClass:
|
40
|
+
Enabled: no
|
41
|
+
|
42
|
+
Metrics/BlockLength:
|
43
|
+
Enabled: no
|
44
|
+
|
45
|
+
Metrics/MethodLength:
|
46
|
+
Enabled: no
|
47
|
+
|
48
|
+
Metrics/AbcSize:
|
49
|
+
Enabled: no
|
50
|
+
|
51
|
+
Metrics/ClassLength:
|
52
|
+
Enabled: no
|
53
|
+
|
54
|
+
Metrics/ParameterLists:
|
55
|
+
Enabled: no
|
56
|
+
|
57
|
+
Lint/UnusedMethodArgument:
|
58
|
+
Enabled: no
|
59
|
+
|
60
|
+
Style/FrozenStringLiteralComment:
|
61
|
+
Enabled: no
|
62
|
+
|
63
|
+
Metrics/LineLength:
|
64
|
+
Max: 120
|
data/Appraisals
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
if ENV['INTEGRATION_TEST']
|
2
|
+
appraise "rails-4" do
|
3
|
+
gem "rails", "~> 4.0"
|
4
|
+
end
|
5
|
+
|
6
|
+
appraise "rails-5" do
|
7
|
+
gem "rails", "~> 5.0"
|
8
|
+
end
|
9
|
+
else
|
10
|
+
appraise "mail-2-5" do
|
11
|
+
gem "mail", "~> 2.5.2"
|
12
|
+
end
|
13
|
+
|
14
|
+
appraise "mail-2-6" do
|
15
|
+
gem "mail", "~> 2.6.0"
|
16
|
+
end
|
17
|
+
|
18
|
+
appraise "mail-2-7" do
|
19
|
+
gem "mail", "~> 2.7.0"
|
20
|
+
end
|
21
|
+
end
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2019 Luong Vo
|
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.
|
23
|
+
|
data/README.md
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# ActionMailerKafka
|
2
|
+
[![CircleCI](https://circleci.com/gh/luong-komorebi/action-mailer-kafka/tree/master.svg?style=svg)](https://circleci.com/gh/luong-komorebi/action-mailer-kafka/tree/master)
|
3
|
+
|
4
|
+
|
5
|
+
<p align="center">
|
6
|
+
<img src="./logo.png">
|
7
|
+
</p>
|
8
|
+
|
9
|
+
This gem is a unified interface to send emails to Kafka message queue. It takes care of the transport layer by defining a delivery method for `action-mailer`.
|
10
|
+
|
11
|
+
To see the gem in action, go to example folder and start the example rails app.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'action_mailer_kafka'
|
19
|
+
```
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install action_mailer_kafka
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
### Rails
|
32
|
+
|
33
|
+
This Gem is tested with Rails version 4. and 5.
|
34
|
+
|
35
|
+
To use the gem, change action mailer's delivery method to `action_mailer_kafka`
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
# for example, in config/environments/production.rb
|
39
|
+
config.action_mailer.delivery_method = :action_mailer_kafka
|
40
|
+
```
|
41
|
+
|
42
|
+
#### Kafa settings
|
43
|
+
The gem accepts 2 kinds of kafka setting params:
|
44
|
+
|
45
|
+
1. Your Kafka publish proc
|
46
|
+
|
47
|
+
|
48
|
+
With this option, you should config as below:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
config.action_mailer.action_mailer_kafka_settings = {
|
52
|
+
kafka_mail_topic: 'YourKafkaTopic',
|
53
|
+
kafka_publish_proc: proc do |message_data, default_message_topic|
|
54
|
+
YourKafkaClientInstance.publish(message_data, default_message_topic)
|
55
|
+
end
|
56
|
+
}
|
57
|
+
```
|
58
|
+
|
59
|
+
and the data would go through your publish process.
|
60
|
+
|
61
|
+
|
62
|
+
2. Your kafka client info
|
63
|
+
|
64
|
+
With this option, the library will generate a kafka instance for you:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
config.action_mailer.action_mailer_kafka_settings = {
|
68
|
+
kafka_mail_topic: 'YourKafkaTopic',
|
69
|
+
kafka_client_info: {
|
70
|
+
seed_brokers: ['localhost:9090'],
|
71
|
+
logger: logger,
|
72
|
+
ssl_ca_cert: File.read('/path/to/cert')
|
73
|
+
# For more option on what to pass here, see https://github.com/zendesk/ruby-kafka/blob/master/lib/kafka/client.rb#L20
|
74
|
+
}
|
75
|
+
}
|
76
|
+
```
|
77
|
+
|
78
|
+
Other settings params:
|
79
|
+
- `raise_on_delivery_error` : a boolean value that decides if this library should raise error.
|
80
|
+
- `logger` : pass your own logger instance.
|
81
|
+
- `fallback`: fallback method in case Kafka fails due to message being too big or other errors.
|
82
|
+
+ `fallback_delivery_method`
|
83
|
+
+ `fallback_delivery_method_settings`
|
84
|
+
|
85
|
+
Example of smtp as a fallback method:
|
86
|
+
```ruby
|
87
|
+
config.action_mailer.action_mailer_kafka_settings = {
|
88
|
+
kafka_mail_topic: 'Mail.Mails.Send.Staging',
|
89
|
+
kafka_client_info: {
|
90
|
+
# .....
|
91
|
+
},
|
92
|
+
fallback: {
|
93
|
+
fallback_delivery_method: :smtp,
|
94
|
+
fallback_delivery_method_settings: {
|
95
|
+
address: 'smtp.sendgrid.net',
|
96
|
+
port: (ENV['SENDGRID_SMTP_PORT'] || 587).to_i,
|
97
|
+
authentication: :plain,
|
98
|
+
user_name: ENV['SENDGRID_USERNAME'],
|
99
|
+
password: ENV['SENDGRID_PASSWORD']
|
100
|
+
}
|
101
|
+
}
|
102
|
+
}
|
103
|
+
```
|
104
|
+
|
105
|
+
#### Service name
|
106
|
+
|
107
|
+
Because the email is sent to Kafka may come from a microservice in your system, an additional field to specify that would be useful. By default, if there is no service name added, the gem will ignore the service name. If provided, the serice name would become a value to the `author` field to the kafka message
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
config.action_mailer.action_mailer_kafka_settings = {
|
111
|
+
sevice_name: 'local app'
|
112
|
+
}
|
113
|
+
```
|
114
|
+
|
115
|
+
### Other frameworks
|
116
|
+
|
117
|
+
Not tested. But this gem is not tight coupled with Rails. It just needs action-mailer.
|
118
|
+
|
119
|
+
### Custom headers
|
120
|
+
|
121
|
+
The gem would only accepts custom headers for emails which follow [RFC822](tools.ietf.org/html/rfc822) (which means that a custom header should begin with 'X-'). Other custom headers would not be parsed and sent to Kafka to be consumed.
|
122
|
+
|
123
|
+
|
124
|
+
## Gem Development
|
125
|
+
|
126
|
+
### Testing
|
127
|
+
- To run the test, do ` bundle exec rspec `.
|
128
|
+
- To run just the unit tests: ` bundle exec rspec spec/units `
|
129
|
+
- To run just the integration tests: ` bundle exec rspec spec/integrations `
|
130
|
+
- To see the coverage of your codes, run ` COVERAGE=true bundle exec rspec `. If coverage drops below 80%, CI will fail
|
131
|
+
- To see quality reporter, run ` bundle exec rubycritic -s 90 --suppress-ratings app/ `. If quality score drops below 90, CI will fail
|
132
|
+
- On CI, the test is run with multiple versions of `mail` and `rails` gems to ensure compatability. If you want to run such tests on your local machine:
|
133
|
+
- To run unit tests:
|
134
|
+
- First of all, install all dependencies with: ` bundle exec appraisal install `
|
135
|
+
- To run the unit tests, run ` bundle exec appraisal rspec spec/units `
|
136
|
+
- To run integration tests:
|
137
|
+
- First of all, install all dependencies with: ` INTEGRATION_TEST='true' bundle exec appraisal install `
|
138
|
+
- To run the unit tests, run ` bundle exec appraisal rspec spec/integrations `
|