materialist 0.0.2 → 0.0.3

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: 3e1d3d42e45eb0a9130ca412078f6df11e29da53
4
- data.tar.gz: 0041f0349dfce34d055ff59a5a4f2d4fc694bb26
3
+ metadata.gz: 1816eac41c61b0649d368f678affea67e2e58b01
4
+ data.tar.gz: 326dee47ecc2f228212ffd474b153eb2bd5de7b9
5
5
  SHA512:
6
- metadata.gz: 0df7a1d5dab2b2d6f4fe9769e73b67028b2398dca4b309081d172e7b97c3b8729088957ffb8fcd24c1e045f6eba287cd1af96f81d9811fb2fa5470f36800e6e5
7
- data.tar.gz: bfaf53768945dc8b98cad7a6191aa9ef588b680898546353d5a756f4e4286a867ae3f00067c69961fe2efb9c5b820457ac620df2cf74f5ca1db5bb2983090ed9
6
+ metadata.gz: 9443b581e97f752ef7d70922d38625e1019f825bb3bd9abcd0522143de8bea8cd947d0c7fa01b5abdf534611ccf24a3937c353e8e3b8e916edfe3efa976de16d
7
+ data.tar.gz: acd16b5c0a34c3b2be365223f67d92c5506c927f0fd47efc7be3db48118810eb0b764395d30f66ef7288da75741b7e71be88b4caf3274eba873e56e0e3a5057a
data/.gitignore CHANGED
@@ -10,3 +10,4 @@ tags
10
10
  coverage/
11
11
  .byebug_history
12
12
  *.gem
13
+ Gemfile.lock
data/Gemfile CHANGED
@@ -2,25 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in routemaster_client.gemspec
4
4
  gemspec
5
-
6
- gem 'activesupport'
7
- gem 'sidekiq'
8
- gem 'routemaster-drain', '~> 3.0'
9
-
10
- # Just here to avoid a safety warning
11
- gem 'psych', require: false
12
-
13
- # Used in builds and tests
14
- gem 'bundler', require: false
15
- gem 'dotenv', require: false
16
- gem 'simplecov', require: false
17
- gem 'codecov', require: false
18
- gem 'webmock', require: false
19
-
20
- gem 'guard-rspec', require: false
21
- gem 'pry', require: false
22
- gem 'byebug', require: false
23
- gem 'rspec', require: false
24
- gem 'appraisal', require: false
25
- gem 'dogstatsd', require: false
26
- gem 'fork_break', require: false
data/README.md CHANGED
@@ -8,7 +8,51 @@ materializing the remote resource (described by the event) in database.
8
8
  This library is a set of utilities that provide both the wiring and the DSL to
9
9
  painlessly do so.
10
10
 
11
- ### Configuration
11
+ ### Install
12
+
13
+ In your `gemfile`
14
+
15
+ ```ruby
16
+ gem 'materialist'
17
+ ```
18
+
19
+ Then do
20
+
21
+ ```bash
22
+ bundle
23
+ ```
24
+
25
+ ### Entity
26
+
27
+ Your materialised entity need to have a **unique** `source_url` column, alongside any other field you wish to materialise.
28
+
29
+ ```ruby
30
+ class CreateZones < ActiveRecord::Migration[5.0]
31
+ def change
32
+ create_table :zones do |t|
33
+ t.integer :orderweb_id
34
+ t.string :code, null: false
35
+ t.string :name
36
+ t.string :timezone
37
+ t.string :country_name
38
+ t.string :country_iso_alpha2_code
39
+ t.string :source_url
40
+
41
+ t.timestamps
42
+
43
+ t.index :code, unique: true
44
+ t.index :source_url, unique: true
45
+ end
46
+ end
47
+ end
48
+ ```
49
+
50
+ ```ruby
51
+ class Zone < ApplicationRecord
52
+ end
53
+ ```
54
+
55
+ ### Routemaster Configuration
12
56
 
13
57
  First you need an "event handler":
14
58
 
@@ -102,7 +146,7 @@ describes materializing from a relation of the resource. This can be nested to a
102
146
 
103
147
  When inside the block of a `link` any other part of DSL can be used and will be evaluated in the context of the relation resource.
104
148
 
105
- #### `after_upsert <method>`
149
+ #### `after_upsert <method>` -- also `after_destroy`
106
150
  describes the name of the instance method to be invoked after a record was materialized.
107
151
 
108
152
  ```ruby
@@ -133,10 +177,17 @@ class Rider
133
177
  end
134
178
  ```
135
179
 
136
- Above will give you `.source`, `.city` and `.country` on any instances of `Rider`, allowing you to access remote keys.
180
+ #### DSL
181
+
182
+ - `source_link_reader <key>, via: <key> (default: none), allow_nil: true/false (default: false)`: Adds a method named `<key>` to the class giving access to the specified linked resource. If `allow_nil` is set to `false` (default) and error is raised if the resource is missing.
183
+
184
+ The above example will give you `.source`, `.city` and `.country` on any instances of `Rider`, allowing you to access remote resources.
137
185
 
138
186
  e.g.
139
187
 
140
188
  ```ruby
141
- Rider.last.country.created_at
189
+ rider = Rider.last
190
+ rider.source.name
191
+ rider.city.code
192
+ rider.country.created_at
142
193
  ```
@@ -0,0 +1,3 @@
1
+ module Materialist
2
+ class ResourceNotFound < StandardError; end
3
+ end
@@ -1,4 +1,5 @@
1
1
  require 'routemaster/api_client'
2
+ require_relative './errors'
2
3
 
3
4
  module Materialist
4
5
  module MaterializedRecord
@@ -8,12 +9,20 @@ module Materialist
8
9
  end
9
10
 
10
11
  module ClassMethods
11
- def source_link_reader(*keys, via: nil)
12
+ def source_link_reader(*keys, via: nil, allow_nil: false)
12
13
  keys.each do |key|
13
14
  define_method(key) do
14
- raw = source_raw
15
- raw = raw.send(via).show if via
16
- raw.send(key).show.body
15
+ (via ? [via, key] : [key])
16
+ .inject(source_raw) do |res, path|
17
+ begin
18
+ (res && res.body._links.include?(path)) ?
19
+ res.send(path).show :
20
+ (allow_nil ? nil : raise(ResourceNotFound))
21
+ rescue Routemaster::Errors::ResourceNotFound
22
+ (allow_nil ? nil : raise(ResourceNotFound))
23
+ end
24
+ end
25
+ &.body
17
26
  end
18
27
  end
19
28
  end
@@ -23,14 +32,12 @@ module Materialist
23
32
  source_raw.body
24
33
  end
25
34
 
26
- def source_raw
27
- api_client.get(source_url)
28
- end
35
+ private
29
36
 
30
- def api_client
31
- @_api_client ||= Routemaster::APIClient.new(
37
+ def source_raw
38
+ Routemaster::APIClient.new(
32
39
  response_class: Routemaster::Responses::HateoasResponse
33
- )
40
+ ).get(source_url)
34
41
  end
35
42
  end
36
43
  end
@@ -76,10 +76,19 @@ module Materialist
76
76
  @options = klass.materialist_options
77
77
  end
78
78
 
79
- def upsert
79
+ def upsert(retry_on_race_condition: true)
80
80
  upsert_record.tap do |entity|
81
81
  instance.send(after_upsert, entity) if after_upsert
82
82
  end
83
+ rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid
84
+ # when there is a race condition and uniqueness of :source_url
85
+ # is enforced by database index, this error is raised
86
+ # so we simply try upsert again
87
+ # if error is due to another type of uniqueness constraint
88
+ # second call will also fail and error would bubble up
89
+ retry_on_race_condition ?
90
+ upsert(retry_on_race_condition: false) :
91
+ raise
83
92
  end
84
93
 
85
94
  def destroy
data/lib/materialist.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Materialist
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
data/materialist.gemspec CHANGED
@@ -17,4 +17,14 @@ Gem::Specification.new do |spec|
17
17
  spec.require_paths = %w(lib)
18
18
 
19
19
  spec.add_runtime_dependency 'sidekiq'
20
+ spec.add_runtime_dependency 'activesupport'
21
+ spec.add_runtime_dependency 'routemaster-drain', '>= 3.0'
22
+
23
+ spec.add_development_dependency 'dotenv'
24
+ spec.add_development_dependency 'simplecov'
25
+ spec.add_development_dependency 'codecov'
26
+ spec.add_development_dependency 'webmock'
27
+ spec.add_development_dependency 'pry'
28
+ spec.add_development_dependency 'rspec'
29
+ spec.add_development_dependency 'appraisal'
20
30
  end
@@ -10,8 +10,11 @@ RSpec.describe Materialist::MaterializedRecord do
10
10
  include Materialist::MaterializedRecord
11
11
 
12
12
  attr_accessor :source_url
13
+
13
14
  source_link_reader :city
15
+ source_link_reader :device, allow_nil: true
14
16
  source_link_reader :country, via: :city
17
+ source_link_reader :region, via: :city, allow_nil: true
15
18
  end
16
19
  end
17
20
 
@@ -48,17 +51,125 @@ RSpec.describe Materialist::MaterializedRecord do
48
51
  expect(record.source.name).to eq 'jack'
49
52
  expect(record.source.age).to eq 30
50
53
  end
54
+
55
+ context "when remote source returns 404" do
56
+ before do
57
+ stub_request(:get, source_url).to_return(status: 404)
58
+ end
59
+
60
+ it "bubbles up routemaster error" do
61
+ expect{ record.source }.to raise_error(Routemaster::Errors::ResourceNotFound)
62
+ end
63
+ end
51
64
  end
52
65
 
53
66
  describe "simple link reader" do
54
67
  it "returns the representation of the link source" do
55
68
  expect(record.city.timezone).to eq 'Europe/Paris'
56
69
  end
70
+
71
+ context "when linked resource returns 404" do
72
+ before do
73
+ stub_request(:get, city_url).to_return(status: 404)
74
+ end
75
+
76
+ it { expect{ record.city }.to raise_error Materialist::ResourceNotFound }
77
+ end
78
+
79
+ context "remote source is not linked to city" do
80
+ let(:source_body) {{ _links: { }, name: 'jack', age: 30 }}
81
+
82
+ it { expect{ record.city }.to raise_error Materialist::ResourceNotFound }
83
+ end
84
+
85
+ context "when nil is allowed" do
86
+ let(:device_url) { 'https://service.dev/devices/1' }
87
+ let(:source_body) {{ _links: { device: { href: device_url }}, name: 'jack', age: 30 }}
88
+
89
+ context "when linked resource returns 404" do
90
+ before do
91
+ stub_request(:get, device_url).to_return(status: 404)
92
+ end
93
+
94
+ it { expect(record.device).to be_nil }
95
+ end
96
+
97
+ context "remote source is not linked to device" do
98
+ let(:source_body) {{ _links: { }, name: 'jack', age: 30 }}
99
+
100
+ it { expect(record.device).to be_nil }
101
+ end
102
+ end
103
+
57
104
  end
58
105
 
59
106
  describe "simple link reader via another link" do
60
107
  it "returns the representation of the link source" do
61
108
  expect(record.country.tld).to eq 'fr'
62
109
  end
110
+
111
+ context "when remote city returns 404" do
112
+ before do
113
+ stub_request(:get, city_url).to_return(status: 404)
114
+ end
115
+
116
+ it { expect{ record.country }.to raise_error Materialist::ResourceNotFound }
117
+ end
118
+
119
+ context "when remote country returns 404" do
120
+ before do
121
+ stub_request(:get, country_url).to_return(status: 404)
122
+ end
123
+
124
+ it { expect{ record.country }.to raise_error Materialist::ResourceNotFound }
125
+ end
126
+
127
+ context "remote source is not linked to city" do
128
+ let(:source_body) {{ _links: { }, name: 'jack', age: 30 }}
129
+
130
+ it { expect{ record.country }.to raise_error Materialist::ResourceNotFound }
131
+ end
132
+
133
+ context "remote city is not linked to country" do
134
+ let(:city_body) {{ _links: { }, timezone: 'Europe/Paris' }}
135
+
136
+ it { expect{ record.country }.to raise_error Materialist::ResourceNotFound }
137
+ end
138
+
139
+ context "when nil is allowed" do
140
+
141
+ context "when remote city returns 404" do
142
+ before do
143
+ stub_request(:get, city_url).to_return(status: 404)
144
+ end
145
+
146
+ it { expect(record.region).to be_nil }
147
+ end
148
+
149
+ context "remote city is not linked to region" do
150
+ let(:city_body) {{ _links: { }, timezone: 'Europe/Paris' }}
151
+
152
+ it { expect(record.region).to be_nil }
153
+ end
154
+
155
+ context "when region is defined" do
156
+ let(:region_url) { 'https://service.dev/regions/1' }
157
+ let(:city_body) {{ _links: { region: { href: region_url }}, timezone: 'Europe/Paris' }}
158
+
159
+ context "when remote region returns 404" do
160
+ before do
161
+ stub_request(:get, region_url).to_return(status: 404)
162
+ end
163
+
164
+ it { expect(record.region).to be_nil }
165
+ end
166
+
167
+ context "remote source is not linked to city" do
168
+ let(:city_body) {{ _links: {}, timezone: 'Europe/Paris' }}
169
+
170
+ it { expect(record.region).to be_nil }
171
+ end
172
+ end
173
+ end
63
174
  end
64
175
  end
@@ -49,7 +49,14 @@ RSpec.describe Materialist::Materializer do
49
49
  end
50
50
 
51
51
  class << self
52
+ attr_accessor :error_to_throw_once_in_find_or_initialize_by
53
+
52
54
  def find_or_initialize_by(source_url:)
55
+ if(err = error_to_throw_once_in_find_or_initialize_by)
56
+ self.error_to_throw_once_in_find_or_initialize_by = nil
57
+ raise err
58
+ end
59
+
53
60
  (all[source_url] || Foobar.new).tap do |record|
54
61
  record.source_url = source_url
55
62
  end
@@ -80,6 +87,11 @@ RSpec.describe Materialist::Materializer do
80
87
  end
81
88
  end
82
89
 
90
+ module ActiveRecord
91
+ class RecordNotUnique < StandardError; end
92
+ class RecordInvalid < StandardError; end
93
+ end
94
+
83
95
  let(:country_url) { 'https://service.dev/countries/1' }
84
96
  let(:country_body) {{ tld: 'fr' }}
85
97
  let(:city_url) { 'https://service.dev/cities/1' }
@@ -134,6 +146,34 @@ RSpec.describe Materialist::Materializer do
134
146
  end
135
147
  end
136
148
 
149
+ context "when there is a race condition between a create and update" do
150
+ let(:error) { }
151
+ let!(:record) { Foobar.create!(source_url: source_url, name: 'mo') }
152
+
153
+ before { Foobar.error_to_throw_once_in_find_or_initialize_by = error }
154
+
155
+ [ ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid ].each do |error_type|
156
+ context "when error of type #{error_type.name} is thrown" do
157
+ let(:error) { error_type }
158
+
159
+ it "still updates the record" do
160
+ expect{ perform }.to change { record.reload.name }
161
+ .from('mo').to('jack')
162
+ end
163
+
164
+ context "if error was thrown second time" do
165
+ before { allow(Foobar).to receive(:find_or_initialize_by).and_raise error }
166
+
167
+ it "bubbles up the error" do
168
+ expect{ perform }.to raise_error error
169
+ end
170
+ end
171
+
172
+ end
173
+ end
174
+
175
+ end
176
+
137
177
  %i(create update noop).each do |action_name|
138
178
  context "when action is :#{action_name}" do
139
179
  let(:action) { action_name }
data/spec/spec_helper.rb CHANGED
@@ -7,7 +7,6 @@ SimpleCov.formatter = SimpleCov::Formatter::Codecov if ENV['CI']
7
7
  require 'webmock/rspec'
8
8
  require 'dotenv'
9
9
  require 'pry'
10
- require 'byebug'
11
10
  Dotenv.overload('.env.test')
12
11
 
13
12
  # This file was generated by the `rspec --init` command. Conventionally, all
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: materialist
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mo Valipour
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-01 00:00:00.000000000 Z
11
+ date: 2017-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sidekiq
@@ -24,6 +24,132 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: routemaster-drain
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: dotenv
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: codecov
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: appraisal
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
27
153
  description:
28
154
  email:
29
155
  - valipour@gmail.com
@@ -38,13 +164,13 @@ files:
38
164
  - ".travis.yml"
39
165
  - Appraisals
40
166
  - Gemfile
41
- - Gemfile.lock
42
167
  - Guardfile
43
168
  - LICENSE.txt
44
169
  - README.md
45
170
  - Rakefile
46
171
  - appraise
47
172
  - lib/materialist.rb
173
+ - lib/materialist/errors.rb
48
174
  - lib/materialist/event_handler.rb
49
175
  - lib/materialist/event_worker.rb
50
176
  - lib/materialist/materialized_record.rb
data/Gemfile.lock DELETED
@@ -1,165 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- materialist (0.0.1)
5
- sidekiq
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- activesupport (5.1.3)
11
- concurrent-ruby (~> 1.0, >= 1.0.2)
12
- i18n (~> 0.7)
13
- minitest (~> 5.1)
14
- tzinfo (~> 1.1)
15
- addressable (2.5.0)
16
- public_suffix (~> 2.0, >= 2.0.2)
17
- appraisal (2.2.0)
18
- bundler
19
- rake
20
- thor (>= 0.14.0)
21
- byebug (9.1.0)
22
- codecov (0.1.10)
23
- json
24
- simplecov
25
- url
26
- coderay (1.1.1)
27
- concurrent-ruby (1.0.5)
28
- connection_pool (2.2.1)
29
- crack (0.4.3)
30
- safe_yaml (~> 1.0.0)
31
- diff-lcs (1.3)
32
- docile (1.1.5)
33
- dogstatsd (2.0.0)
34
- dotenv (2.2.1)
35
- ethon (0.10.1)
36
- ffi (>= 1.3.0)
37
- faraday (0.13.1)
38
- multipart-post (>= 1.2, < 3)
39
- faraday_middleware (0.12.2)
40
- faraday (>= 0.7.4, < 1.0)
41
- ffi (1.9.18)
42
- fork (1.0.1)
43
- fork_break (0.1.4)
44
- fork (= 1.0.1)
45
- formatador (0.2.5)
46
- guard (2.14.1)
47
- formatador (>= 0.2.4)
48
- listen (>= 2.7, < 4.0)
49
- lumberjack (~> 1.0)
50
- nenv (~> 0.1)
51
- notiffany (~> 0.0)
52
- pry (>= 0.9.12)
53
- shellany (~> 0.0)
54
- thor (>= 0.18.1)
55
- guard-compat (1.2.1)
56
- guard-rspec (4.7.3)
57
- guard (~> 2.1)
58
- guard-compat (~> 1.1)
59
- rspec (>= 2.99.0, < 4.0)
60
- hashdiff (0.3.2)
61
- hashie (3.5.6)
62
- i18n (0.8.6)
63
- json (2.1.0)
64
- listen (3.1.5)
65
- rb-fsevent (~> 0.9, >= 0.9.4)
66
- rb-inotify (~> 0.9, >= 0.9.7)
67
- ruby_dep (~> 1.2)
68
- lumberjack (1.0.12)
69
- method_source (0.8.2)
70
- minitest (5.10.3)
71
- multipart-post (2.0.0)
72
- nenv (0.3.0)
73
- notiffany (0.1.1)
74
- nenv (~> 0.1)
75
- shellany (~> 0.0)
76
- pry (0.10.4)
77
- coderay (~> 1.1.0)
78
- method_source (~> 0.8.1)
79
- slop (~> 3.4)
80
- psych (2.2.4)
81
- public_suffix (2.0.5)
82
- rack (2.0.3)
83
- rack-protection (2.0.0)
84
- rack
85
- rake (12.0.0)
86
- rb-fsevent (0.10.2)
87
- rb-inotify (0.9.10)
88
- ffi (>= 0.5.0, < 2)
89
- redis (3.3.3)
90
- redis-namespace (1.5.3)
91
- redis (~> 3.0, >= 3.0.4)
92
- routemaster-drain (3.0.1)
93
- addressable
94
- concurrent-ruby
95
- faraday (>= 0.9.0)
96
- faraday_middleware
97
- hashie
98
- rack (>= 1.4.5)
99
- redis-namespace
100
- typhoeus (~> 1.1)
101
- wisper (~> 1.6.1)
102
- rspec (3.6.0)
103
- rspec-core (~> 3.6.0)
104
- rspec-expectations (~> 3.6.0)
105
- rspec-mocks (~> 3.6.0)
106
- rspec-core (3.6.0)
107
- rspec-support (~> 3.6.0)
108
- rspec-expectations (3.6.0)
109
- diff-lcs (>= 1.2.0, < 2.0)
110
- rspec-support (~> 3.6.0)
111
- rspec-mocks (3.6.0)
112
- diff-lcs (>= 1.2.0, < 2.0)
113
- rspec-support (~> 3.6.0)
114
- rspec-support (3.6.0)
115
- ruby_dep (1.5.0)
116
- safe_yaml (1.0.4)
117
- shellany (0.0.1)
118
- sidekiq (5.0.4)
119
- concurrent-ruby (~> 1.0)
120
- connection_pool (~> 2.2, >= 2.2.0)
121
- rack-protection (>= 1.5.0)
122
- redis (~> 3.3, >= 3.3.3)
123
- simplecov (0.15.0)
124
- docile (~> 1.1.0)
125
- json (>= 1.8, < 3)
126
- simplecov-html (~> 0.10.0)
127
- simplecov-html (0.10.2)
128
- slop (3.6.0)
129
- thor (0.20.0)
130
- thread_safe (0.3.6)
131
- typhoeus (1.3.0)
132
- ethon (>= 0.9.0)
133
- tzinfo (1.2.3)
134
- thread_safe (~> 0.1)
135
- url (0.3.2)
136
- webmock (2.3.2)
137
- addressable (>= 2.3.6)
138
- crack (>= 0.3.2)
139
- hashdiff
140
- wisper (1.6.1)
141
-
142
- PLATFORMS
143
- ruby
144
-
145
- DEPENDENCIES
146
- activesupport
147
- appraisal
148
- bundler
149
- byebug
150
- codecov
151
- dogstatsd
152
- dotenv
153
- fork_break
154
- guard-rspec
155
- materialist!
156
- pry
157
- psych
158
- routemaster-drain (~> 3.0)
159
- rspec
160
- sidekiq
161
- simplecov
162
- webmock
163
-
164
- BUNDLED WITH
165
- 1.15.4