stitches 3.5.0 → 3.6.0.RC1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +102 -0
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/Gemfile.rails-4.2 +8 -0
- data/Gemfile.rails-5.0 +8 -0
- data/Gemfile.rails-5.1 +8 -0
- data/Gemfile.rails-5.2 +8 -0
- data/README.md +21 -1
- data/build-matrix.json +4 -0
- data/lib/stitches.rb +2 -1
- data/lib/stitches/add_deprecation_generator.rb +15 -0
- data/lib/stitches/api_generator.rb +3 -7
- data/lib/stitches/deprecation.rb +26 -0
- data/lib/stitches/generator_files/app/controllers/api/api_controller.rb +1 -0
- data/lib/stitches/spec.rb +2 -0
- data/lib/stitches/spec/be_gone.rb +19 -0
- data/lib/stitches/spec/show_deprecation.rb +46 -0
- data/lib/stitches/version.rb +1 -1
- data/lib/stitches_norailtie.rb +2 -0
- data/owners.json +7 -0
- data/spec/deprecation_spec.rb +49 -0
- data/spec/integration/add_to_rails_app_spec.rb +200 -0
- data/spec/spec/be_gone_spec.rb +18 -0
- data/spec/spec/show_deprecation_spec.rb +54 -0
- data/stitches.gemspec +4 -1
- metadata +52 -20
- data/Gemfile.lock +0 -154
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9af000ef971218e036d2b46e0bc508749ed8e834f216e857a57c6fb194928fe5
|
4
|
+
data.tar.gz: b39481868efbe1c8e373c265d9561d09989e1be3230b4716adf14176d5de63af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b6eb58de02836b34c9ddda0170c4d0b53f64d4b95e01ca4726213af7ae701978fe772e50d038c46cceb6c78b47de46029f04a9043b08fb4d77e3abe8eb9ba79a
|
7
|
+
data.tar.gz: 553079dcb2c338bff86d978db574634802db678344c30655d1db3c67b0625b576034b2f410dae975e04accd7514681df7aee6c0b5c58be2e1a48cb6a3b877a7c
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# DO NOT MODIFY - this is managed by Git Reduce in goro
|
2
|
+
#
|
3
|
+
---
|
4
|
+
version: 2
|
5
|
+
jobs:
|
6
|
+
ruby-2.5.1-rails-5.2:
|
7
|
+
docker:
|
8
|
+
- image: circleci/ruby:2.5.1
|
9
|
+
environment:
|
10
|
+
BUNDLE_GEMFILE: Gemfile.rails-5.2
|
11
|
+
working_directory: "~/stitches"
|
12
|
+
steps:
|
13
|
+
- checkout
|
14
|
+
- run: bundle install --full-index
|
15
|
+
- run: bundle exec rspec --format RspecJunitFormatter --out /tmp/test-results/rspec.xml
|
16
|
+
--format=doc
|
17
|
+
- run:
|
18
|
+
name: Notify Pager Duty
|
19
|
+
command: bundle exec y-notify eng-platform
|
20
|
+
when: on_fail
|
21
|
+
- store_test_results:
|
22
|
+
path: "/tmp/test-results"
|
23
|
+
ruby-2.4.4-rails-5.2:
|
24
|
+
docker:
|
25
|
+
- image: circleci/ruby:2.4.4
|
26
|
+
environment:
|
27
|
+
BUNDLE_GEMFILE: Gemfile.rails-5.2
|
28
|
+
working_directory: "~/stitches"
|
29
|
+
steps:
|
30
|
+
- checkout
|
31
|
+
- run: bundle install --full-index
|
32
|
+
- run: bundle exec rspec --format RspecJunitFormatter --out /tmp/test-results/rspec.xml
|
33
|
+
--format=doc
|
34
|
+
- run:
|
35
|
+
name: Notify Pager Duty
|
36
|
+
command: bundle exec y-notify eng-platform
|
37
|
+
when: on_fail
|
38
|
+
- store_test_results:
|
39
|
+
path: "/tmp/test-results"
|
40
|
+
ruby-2.5.1-rails-5.1:
|
41
|
+
docker:
|
42
|
+
- image: circleci/ruby:2.5.1
|
43
|
+
environment:
|
44
|
+
BUNDLE_GEMFILE: Gemfile.rails-5.1
|
45
|
+
working_directory: "~/stitches"
|
46
|
+
steps:
|
47
|
+
- checkout
|
48
|
+
- run: bundle install --full-index
|
49
|
+
- run: bundle exec rspec --format RspecJunitFormatter --out /tmp/test-results/rspec.xml
|
50
|
+
--format=doc
|
51
|
+
- run:
|
52
|
+
name: Notify Pager Duty
|
53
|
+
command: bundle exec y-notify eng-platform
|
54
|
+
when: on_fail
|
55
|
+
- store_test_results:
|
56
|
+
path: "/tmp/test-results"
|
57
|
+
ruby-2.4.4-rails-5.1:
|
58
|
+
docker:
|
59
|
+
- image: circleci/ruby:2.4.4
|
60
|
+
environment:
|
61
|
+
BUNDLE_GEMFILE: Gemfile.rails-5.1
|
62
|
+
working_directory: "~/stitches"
|
63
|
+
steps:
|
64
|
+
- checkout
|
65
|
+
- run: bundle install --full-index
|
66
|
+
- run: bundle exec rspec --format RspecJunitFormatter --out /tmp/test-results/rspec.xml
|
67
|
+
--format=doc
|
68
|
+
- run:
|
69
|
+
name: Notify Pager Duty
|
70
|
+
command: bundle exec y-notify eng-platform
|
71
|
+
when: on_fail
|
72
|
+
- store_test_results:
|
73
|
+
path: "/tmp/test-results"
|
74
|
+
workflows:
|
75
|
+
version: 2
|
76
|
+
on-commit:
|
77
|
+
jobs:
|
78
|
+
- ruby-2.5.1-rails-5.2:
|
79
|
+
context: org-global
|
80
|
+
- ruby-2.4.4-rails-5.2:
|
81
|
+
context: org-global
|
82
|
+
- ruby-2.5.1-rails-5.1:
|
83
|
+
context: org-global
|
84
|
+
- ruby-2.4.4-rails-5.1:
|
85
|
+
context: org-global
|
86
|
+
scheduled:
|
87
|
+
triggers:
|
88
|
+
- schedule:
|
89
|
+
cron: 53 20 * * 1,2,3,4,5
|
90
|
+
filters:
|
91
|
+
branches:
|
92
|
+
only:
|
93
|
+
- master
|
94
|
+
jobs:
|
95
|
+
- ruby-2.5.1-rails-5.2:
|
96
|
+
context: org-global
|
97
|
+
- ruby-2.4.4-rails-5.2:
|
98
|
+
context: org-global
|
99
|
+
- ruby-2.5.1-rails-5.1:
|
100
|
+
context: org-global
|
101
|
+
- ruby-2.4.4-rails-5.1:
|
102
|
+
context: org-global
|
data/.gitignore
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.5.1
|
data/Gemfile.rails-4.2
ADDED
data/Gemfile.rails-5.0
ADDED
data/Gemfile.rails-5.1
ADDED
data/Gemfile.rails-5.2
ADDED
data/README.md
CHANGED
@@ -35,6 +35,21 @@ Then, set it up:
|
|
35
35
|
> bundle exec rake db:migrate
|
36
36
|
```
|
37
37
|
|
38
|
+
### Upgrading from an older version
|
39
|
+
|
40
|
+
* If you have a version lower than 3.3.0, you need to run two generators, one of which creates a new database migration on your
|
41
|
+
`api_clients` table:
|
42
|
+
|
43
|
+
```
|
44
|
+
> bin/rails generate stitches:add_enabled_to_api_clients
|
45
|
+
> bin/rails generate stitches:add_deprecation
|
46
|
+
```
|
47
|
+
* If you have a version lower than 3.6.0, you need to run one generator:
|
48
|
+
|
49
|
+
```
|
50
|
+
> bin/rails generate stitches:add_deprecation
|
51
|
+
```
|
52
|
+
|
38
53
|
## Example Microservice Endpoint
|
39
54
|
|
40
55
|
Suppose we wish to allow our consumers to create Widgets
|
@@ -47,7 +62,7 @@ class Api::V1::WidgetsController < ApiController
|
|
47
62
|
head 201
|
48
63
|
else
|
49
64
|
render json: {
|
50
|
-
errors: Stitches::Errors.
|
65
|
+
errors: Stitches::Errors.from_active_record_object(widget)
|
51
66
|
}, status: 422
|
52
67
|
end
|
53
68
|
end
|
@@ -82,6 +97,7 @@ See [the wiki](https://github.com/stitchfix/stitches/wiki/Setup) for how to setu
|
|
82
97
|
- Versioned requests via HTTP content types
|
83
98
|
- Structured Errors
|
84
99
|
- ISO 8601-formatted dates
|
100
|
+
- Deprecation using the `Sunset` header
|
85
101
|
* The [Generator](https://github.com/stitchfix/stitches/wiki/Generator) sets up some code in your app, so you can start writing
|
86
102
|
APIs using vanilla Rails idioms:
|
87
103
|
- a "ping" controller that can vaidate your app is working
|
@@ -95,6 +111,10 @@ APIs using vanilla Rails idioms:
|
|
95
111
|
|
96
112
|
Although `Stitches.configuration` is global, do not depend directly on that in your logic. Instead, allow all classes to receive a configuration object in their constructor. This makes the classes easier to deal with and change, without incurring much of a real cost to development. Global symbols suck, but are convienient. This is how you make the most of it.
|
97
113
|
|
114
|
+
Also, the integration test does a lot of "testing the implementation", but since Rails generators are notorious for silently
|
115
|
+
failing with a successful result, we have to make sure that the various `inject_into_file` calls are actually working. Do not do
|
116
|
+
any fancy refactors here, just keep it up to date.
|
117
|
+
|
98
118
|
---
|
99
119
|
|
100
120
|
Provided with love by your friends at [Stitch Fix Engineering](http://technology.stitchfix.com)
|
data/build-matrix.json
ADDED
data/lib/stitches.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module Stitches
|
4
|
+
class AddDeprecationGenerator < Rails::Generators::Base
|
5
|
+
source_root(File.expand_path(File.join(File.dirname(__FILE__),"generator_files")))
|
6
|
+
|
7
|
+
desc "Adds deprecation support to an app creates with an older version of stitches"
|
8
|
+
def add_deprecation
|
9
|
+
inject_into_file "app/controllers/api/api_controller.rb", after: /^class.*$/ do<<-CODE
|
10
|
+
include Stitches::Deprecation
|
11
|
+
CODE
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -12,10 +12,12 @@ module Stitches
|
|
12
12
|
|
13
13
|
desc "Bootstraps your API service with a basic ping controller and spec to ensure everything is setup properly"
|
14
14
|
def bootstrap_api
|
15
|
-
inject_into_file "Gemfile", after: /^gem ["
|
15
|
+
inject_into_file "Gemfile", after: /^gem ['"]rails['"].*$/ do<<-GEM
|
16
16
|
|
17
17
|
gem "apitome"
|
18
18
|
gem "responders"
|
19
|
+
gem "rspec_api_documentation", group: [ :development, :test ]
|
20
|
+
gem "capybara", group: [ :development, :test ]
|
19
21
|
GEM
|
20
22
|
end
|
21
23
|
inject_into_file "config/routes.rb", before: /^end/ do<<-ROUTES
|
@@ -51,12 +53,6 @@ mount api_docs, at: "docs"
|
|
51
53
|
template "spec/features/api_spec.rb.erb", "spec/features/api_spec.rb"
|
52
54
|
copy_file "spec/acceptance/ping_v1_spec.rb", "spec/acceptance/ping_v1_spec.rb"
|
53
55
|
|
54
|
-
inject_into_file "Gemfile", after: /^group :test, :development do.*$/ do<<-GEM
|
55
|
-
|
56
|
-
gem "rspec_api_documentation"
|
57
|
-
gem "capybara"
|
58
|
-
GEM
|
59
|
-
end
|
60
56
|
run 'bundle install'
|
61
57
|
|
62
58
|
migration_template "db/migrate/enable_uuid_ossp_extension.rb", "db/migrate/enable_uuid_ossp_extension.rb"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Stitches
|
2
|
+
module Deprecation
|
3
|
+
# Indicate that a previously-deprecated endpoint is now gone
|
4
|
+
def gone!
|
5
|
+
head 410
|
6
|
+
end
|
7
|
+
|
8
|
+
# Indicate that this endpoint is deprecated and will go away on the given date.
|
9
|
+
#
|
10
|
+
# gon_on: - date, as a string, when this endpoint will go away
|
11
|
+
# block - the contents of the endpoint
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# def show
|
16
|
+
# deprecated gone_on: "2019-04-09" do
|
17
|
+
# render widgets: { Widget.find(params[:id]) }
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
def deprecated(gone_on:,&block)
|
21
|
+
response.set_header("Sunset",Date.parse(gone_on).in_time_zone("GMT").midnight.strftime("%a, %e %b %Y %H:%M:%S %Z"))
|
22
|
+
Rails.logger.info("Deprecated endpoint #{request.method} #{request.fullpath} requested by #{current_user.id}")
|
23
|
+
block.()
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
class Api::ApiController < ActionController::Base
|
2
|
+
include Stitches::Deprecation
|
2
3
|
rescue_from ActiveRecord::RecordNotFound do |exception|
|
3
4
|
respond_to do |type|
|
4
5
|
type.json { render json: { errors: Stitches::Errors.new([ Stitches::Error.new(code: "not_found", message: exception.message) ]) }, status: 404 }
|
data/lib/stitches/spec.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
# Use this to test that an HTTP response is properly "410/Gone"
|
2
|
+
#
|
3
|
+
# The object you expect on is generally `self`, because this is the object on which
|
4
|
+
# rspec_api_documentation allows you to call `status`
|
5
|
+
#
|
6
|
+
# get "/api/widgets" do
|
7
|
+
# it "has been removed" do
|
8
|
+
# expect(self).to be_gone
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
RSpec::Matchers.define :be_gone do
|
12
|
+
match do |rspec_api_documentation_context|
|
13
|
+
rspec_api_documentation_context.status == 410
|
14
|
+
end
|
15
|
+
failure_message do |rspec_api_documentation_context|
|
16
|
+
"Expected HTTP status to be 410/Gone, but it was #{rspec_api_documentation_context.status}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class DeprecationAnalysis
|
2
|
+
attr_reader :expected_sunset_value, :status, :sunset_value
|
3
|
+
def initialize(rspec_api_documentation_context, gone_on)
|
4
|
+
gone_on_date = Date.parse(gone_on)
|
5
|
+
if gone_on_date > Date.today
|
6
|
+
@expecting_sunset = true
|
7
|
+
@expected_sunset_value = gone_on_date.in_time_zone("GMT").midnight.strftime("%a, %e %b %Y %H:%M:%S %Z")
|
8
|
+
@sunset_header_set = rspec_api_documentation_context.response_headers["Sunset"].present?
|
9
|
+
@sunset_value = rspec_api_documentation_context.response_headers["Sunset"]
|
10
|
+
@sunset_header_match = @expected_sunset_value == @sunset_value
|
11
|
+
else
|
12
|
+
@expecting_gone = true
|
13
|
+
@status = rspec_api_documentation_context.status
|
14
|
+
@gone = @status == 410
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def expecting_sunset?; !!@expecting_sunset; end
|
19
|
+
def sunset_header_set?; !!@sunset_header_set; end
|
20
|
+
def sunset_header_match?; !!@sunset_header_match; end
|
21
|
+
def gone?; !!@gone; end
|
22
|
+
end
|
23
|
+
|
24
|
+
RSpec::Matchers.define :show_deprecation do |gone_on:|
|
25
|
+
match do |rspec_api_documentation_context|
|
26
|
+
analysis = DeprecationAnalysis.new(rspec_api_documentation_context,gone_on)
|
27
|
+
if analysis.expecting_sunset?
|
28
|
+
analysis.sunset_header_match?
|
29
|
+
else
|
30
|
+
analysis.gone?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
failure_message do |rspec_api_documentation_context|
|
34
|
+
analysis = DeprecationAnalysis.new(rspec_api_documentation_context,gone_on)
|
35
|
+
if analysis.expecting_sunset?
|
36
|
+
if analysis.sunset_header_set?
|
37
|
+
"Expected the Sunset header to be #{analysis.expected_sunset_value}, but was '#{analysis.sunset_value}'"
|
38
|
+
else
|
39
|
+
"No Sunset header was set. Use the deprecation method from Stitches::Deprecation in the controller method to deprecate this action"
|
40
|
+
end
|
41
|
+
else
|
42
|
+
"Since the deprecation date has passed, expected HTTP status to be 410/Gone, but it was #{analysis.status}. Re-implement the endpoint using `gone!` and then change the test to expect it to `be_gone`"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
data/lib/stitches/version.rb
CHANGED
data/lib/stitches_norailtie.rb
CHANGED
@@ -12,7 +12,9 @@ require 'stitches/render_timestamps_in_iso8601_in_json'
|
|
12
12
|
require 'stitches/error'
|
13
13
|
require 'stitches/errors'
|
14
14
|
require 'stitches/api_generator'
|
15
|
+
require 'stitches/add_deprecation_generator'
|
15
16
|
require 'stitches/add_enabled_to_api_clients_generator'
|
16
17
|
require 'stitches/api_version_constraint'
|
17
18
|
require 'stitches/api_key'
|
19
|
+
require 'stitches/deprecation'
|
18
20
|
require 'stitches/valid_mime_type'
|
data/owners.json
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
|
3
|
+
describe Stitches::Deprecation do
|
4
|
+
let(:response) {
|
5
|
+
double("response").tap { |r|
|
6
|
+
allow(r).to receive(:set_header)
|
7
|
+
}
|
8
|
+
}
|
9
|
+
let(:request) { double("request", method: "PUT", fullpath: "/foo/bar?blah") }
|
10
|
+
let(:api_client) { double("ApiClient", id: 99) }
|
11
|
+
let(:fake_controller) {
|
12
|
+
double("Controller", response: response, request: request, current_user: api_client).extend(described_class).tap { |controller|
|
13
|
+
allow(controller).to receive(:head)
|
14
|
+
}
|
15
|
+
}
|
16
|
+
describe "#gone" do
|
17
|
+
it "sends an HTTP 410" do
|
18
|
+
fake_controller.gone!
|
19
|
+
expect(fake_controller).to have_received(:head).with(410)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#deprecated" do
|
24
|
+
let(:logger) { double("logger") }
|
25
|
+
before do
|
26
|
+
allow(Rails).to receive(:logger).and_return(logger)
|
27
|
+
allow(logger).to receive(:info)
|
28
|
+
end
|
29
|
+
it "sets the Sunset header to the date given in GMT" do
|
30
|
+
fake_controller.deprecated(gone_on: "2018-01-01") {}
|
31
|
+
expect(response).to have_received(:set_header).with("Sunset","Mon, 1 Jan 2018 00:00:00 GMT")
|
32
|
+
end
|
33
|
+
it "logs about the request and current API key id" do
|
34
|
+
fake_controller.deprecated(gone_on: "2018-01-01") {}
|
35
|
+
expect(logger).to have_received(:info).with(/deprecated.*#{Regexp.escape(request.method)}.*#{Regexp.escape(request.fullpath)}.*#{Regexp.escape(api_client.id.to_s)}/i)
|
36
|
+
end
|
37
|
+
it "executes and returns the block" do
|
38
|
+
block_executed = false
|
39
|
+
result = fake_controller.deprecated(gone_on: "2018-01-01") do
|
40
|
+
block_executed = true
|
41
|
+
42
|
42
|
+
end
|
43
|
+
aggregate_failures do
|
44
|
+
expect(result).to eq(42)
|
45
|
+
expect(block_executed).to eq(true)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "fileutils"
|
3
|
+
require "open3"
|
4
|
+
|
5
|
+
RSpec.describe "Adding Stitches to a New Rails App" do
|
6
|
+
let(:work_dir) { Dir.mktmpdir }
|
7
|
+
let(:rails_app_name) { "swamp-thing" }
|
8
|
+
|
9
|
+
def run(command)
|
10
|
+
stdout, stderr, stat = Open3.capture3(command)
|
11
|
+
success = stat.success? && stdout !~ /Could not find generator/im
|
12
|
+
|
13
|
+
if ENV["DEBUG"] == 'true' || !success
|
14
|
+
$stdout.puts stdout
|
15
|
+
$stderr.puts stderr
|
16
|
+
end
|
17
|
+
unless success
|
18
|
+
raise "'#{command}' failed"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
around(:each) do |example|
|
23
|
+
rails_new = [
|
24
|
+
"rails new #{rails_app_name}",
|
25
|
+
"--skip-yarn",
|
26
|
+
"--skip-git",
|
27
|
+
"--skip-keeps",
|
28
|
+
"--skip-action-mailer",
|
29
|
+
"--skip-active-storage",
|
30
|
+
"--skip-action-cable",
|
31
|
+
"--skip-spring",
|
32
|
+
"--skip-listen",
|
33
|
+
"--skip-coffee",
|
34
|
+
"--skip-javascript",
|
35
|
+
"--skip-turbolinks",
|
36
|
+
"--skip-bootsnap",
|
37
|
+
"--no-rc",
|
38
|
+
"--skip-bundle",
|
39
|
+
].join(" ")
|
40
|
+
FileUtils.chdir work_dir do
|
41
|
+
run rails_new
|
42
|
+
FileUtils.chdir rails_app_name do
|
43
|
+
example.run
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "works as described in the README" do
|
49
|
+
run "bin/rails generate rspec:install"
|
50
|
+
run "bin/rails generate apitome:install"
|
51
|
+
run "bin/rails generate stitches:api"
|
52
|
+
|
53
|
+
rails_root = Pathname(work_dir) / rails_app_name
|
54
|
+
|
55
|
+
# Yuck! So much duplication! BUT: Rails app templates have a notoriously silent failure mode, so mostly
|
56
|
+
# what this is doing is ensuring that the generator inserted stuff when asked and that the very basics of what happens
|
57
|
+
# during generation are there. It's gross, and I'm sorry.
|
58
|
+
#
|
59
|
+
# It's also in one big block because making a new rails app and running the generator multiple times seems bad.
|
60
|
+
aggregate_failures do
|
61
|
+
expect(File.exist?(rails_root / "app" / "controllers" / "api" / "api_controller.rb")).to eq(true)
|
62
|
+
expect(rails_root / "Gemfile").to contain_gem("apitome")
|
63
|
+
expect(rails_root / "Gemfile").to contain_gem("responders")
|
64
|
+
expect(rails_root / "Gemfile").to contain_gem("rspec_api_documentation")
|
65
|
+
expect(rails_root / "Gemfile").to contain_gem("capybara")
|
66
|
+
expect(rails_root / "config" / "routes.rb").to have_route(namespace: :api, module_scope: :v1, resource: 'ping')
|
67
|
+
expect(rails_root / "config" / "routes.rb").to have_route(namespace: :api, module_scope: :v2, resource: 'ping')
|
68
|
+
expect(rails_root / "config" / "routes.rb").to have_mounted_engine("Apitome::Engine")
|
69
|
+
migrations = Dir["#{rails_root}/db/migrate/*.rb"].sort
|
70
|
+
expect(migrations.size).to eq(2)
|
71
|
+
expect(migrations[0]).to match(/\/\d+_enable_uuid_ossp_extension.rb/)
|
72
|
+
expect(migrations[1]).to match(/\/\d+_create_api_clients.rb/)
|
73
|
+
expect(File.read(rails_root / "spec" / "rails_helper.rb")).to include("config.include RSpec::Rails::RequestExampleGroup, type: :feature")
|
74
|
+
expect(File.read(rails_root / "spec" / "rails_helper.rb")).to include("require 'stitches/spec'")
|
75
|
+
expect(File.read(rails_root / "spec" / "rails_helper.rb")).to include("require 'rspec_api_documentation'")
|
76
|
+
expect(File.read(rails_root / "config" / "initializers" / "apitome.rb")).to include("config.mount_at = nil")
|
77
|
+
expect(File.read(rails_root / "config" / "initializers" / "apitome.rb")).to include("config.title = 'Service Documentation'")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "inserts the deprecation module into ApiController" do
|
82
|
+
run "bin/rails generate rspec:install"
|
83
|
+
run "bin/rails generate apitome:install"
|
84
|
+
run "bin/rails generate stitches:api"
|
85
|
+
|
86
|
+
rails_root = Pathname(work_dir) / rails_app_name
|
87
|
+
api_controller = rails_root / "app" / "controllers" / "api" / "api_controller.rb"
|
88
|
+
|
89
|
+
api_controller_contents = File.read(api_controller).split(/\n/)
|
90
|
+
File.open(api_controller,"w") do |file|
|
91
|
+
api_controller_contents.each do |line|
|
92
|
+
file.puts line unless line =~ /Stitches::Deprecation/
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
run "bin/rails generate stitches:add_deprecation"
|
97
|
+
|
98
|
+
aggregate_failures do
|
99
|
+
expect(File.read(api_controller)).to include("include Stitches::Deprecation")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class RoutesFileAnalysis
|
104
|
+
attr_reader :routes_file
|
105
|
+
def initialize(routes_file, namespace: nil, module_scope: nil, resource: nil, mounted_engine: nil)
|
106
|
+
@routes_file = File.read(routes_file).split(/\n/)
|
107
|
+
@found_namespace = false
|
108
|
+
@found_module = false
|
109
|
+
@found_resource = false
|
110
|
+
@found_engine = false
|
111
|
+
@engine_mounted = false
|
112
|
+
|
113
|
+
@routes_file.each do |line|
|
114
|
+
if line =~ /namespace :#{namespace} do/
|
115
|
+
@found_namespace = true
|
116
|
+
end
|
117
|
+
if @found_namespace && line =~ /^\s*scope module: :#{module_scope}, constraints: Stitches::ApiVersionConstraint/
|
118
|
+
@found_module = true
|
119
|
+
end
|
120
|
+
if @found_module && line =~ /^\s*resource\s+['"]#{resource}["']/
|
121
|
+
@found_resource = true
|
122
|
+
end
|
123
|
+
if line =~ /api_docs = Rack::Auth::Basic.new\(#{mounted_engine}/
|
124
|
+
@found_engine = true
|
125
|
+
end
|
126
|
+
if @found_engine && line =~ /mount api_docs/
|
127
|
+
@engine_mounted = true
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def found_namespace?
|
133
|
+
@found_namespace
|
134
|
+
end
|
135
|
+
|
136
|
+
def found_module?
|
137
|
+
@found_module
|
138
|
+
end
|
139
|
+
|
140
|
+
def found_resource?
|
141
|
+
@found_resource
|
142
|
+
end
|
143
|
+
|
144
|
+
def found_engine?
|
145
|
+
@found_engine
|
146
|
+
end
|
147
|
+
|
148
|
+
def engine_mounted?
|
149
|
+
@engine_mounted
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
RSpec::Matchers.define :have_mounted_engine do |engine_name|
|
154
|
+
match do |routes_file|
|
155
|
+
analysis = RoutesFileAnalysis.new(routes_file, mounted_engine: engine_name)
|
156
|
+
analysis.engine_mounted?
|
157
|
+
end
|
158
|
+
failure_message do |routes_file|
|
159
|
+
analysis = RoutesFileAnalysis.new(routes_file, mounted_engine: engine_name)
|
160
|
+
error = if analysis.found_engine?
|
161
|
+
"Found engine #{engine_name}, but it's not mounted"
|
162
|
+
else
|
163
|
+
"Didn't find engine #{engine_name}"
|
164
|
+
end
|
165
|
+
|
166
|
+
error + "\n#{File.read(analysis.routes_file.join("\n"))}"
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
RSpec::Matchers.define :have_route do |namespace:, module_scope:, resource:|
|
171
|
+
match do |routes_file|
|
172
|
+
analysis = RoutesFileAnalysis.new(routes_file, namespace: namespace, module_scope: module_scope, resource: resource)
|
173
|
+
analysis.found_resource? && analysis.found_module? && analysis.found_namespace?
|
174
|
+
end
|
175
|
+
|
176
|
+
failure_message do |routes_file|
|
177
|
+
analysis = RoutesFileAnalysis.new(routes_file, namespace: namespace, module_scope: module_scope, resource: resource)
|
178
|
+
error = if analysis.found_namespace?
|
179
|
+
if analysis.found_module?
|
180
|
+
"Could not find resource '#{resource}'"
|
181
|
+
else
|
182
|
+
"Could not find module '#{scope_module}'"
|
183
|
+
end
|
184
|
+
else
|
185
|
+
"Could not find namespace '#{namespace}'"
|
186
|
+
end
|
187
|
+
error + "\n#{File.read(analysis.routes_file.join("\n"))}"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
RSpec::Matchers.define :contain_gem do |gem_name|
|
191
|
+
match do |gemfile|
|
192
|
+
File.read(gemfile).split(/\n/).any? { |line|
|
193
|
+
line =~ /^\s*gem [\"\']#{gem_name}[\"\']/
|
194
|
+
}
|
195
|
+
end
|
196
|
+
failure_message do |gemfile|
|
197
|
+
"#{gem_name} not found in #{gemfile}:\n#{File.read(gemfile)}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
require 'stitches/spec'
|
3
|
+
|
4
|
+
describe "be_gone" do
|
5
|
+
it "passes the test if the http status was 410" do
|
6
|
+
rspec_api_documentation_context = double("a test using rspec_api_documentation", status: 410)
|
7
|
+
expect(rspec_api_documentation_context).to be_gone
|
8
|
+
end
|
9
|
+
it "fails the test if the http status was not 401" do
|
10
|
+
rspec_api_documentation_context = double("a test using rspec_api_documentation", status: 200)
|
11
|
+
begin
|
12
|
+
expect(rspec_api_documentation_context).to be_gone
|
13
|
+
fail "Expected the matcher to fail"
|
14
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
15
|
+
expect(e.message).to match(/expected http status.*410.*200/i)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
require 'stitches/spec'
|
3
|
+
|
4
|
+
describe "show_deprecation" do
|
5
|
+
context "when the expiration date is in the future" do
|
6
|
+
it "passes if the sunset header is set properly" do
|
7
|
+
rspec_api_documentation_context = double(
|
8
|
+
"a test using rspec_api_documentation",
|
9
|
+
status: 200,
|
10
|
+
response_headers: { "Sunset" => "Mon, 13 Dec 2038 00:00:00 GMT" }
|
11
|
+
)
|
12
|
+
|
13
|
+
expect(rspec_api_documentation_context).to show_deprecation(gone_on: "2038-12-13")
|
14
|
+
end
|
15
|
+
it "fails if the sunset header is not set" do
|
16
|
+
begin
|
17
|
+
rspec_api_documentation_context = double( "a test using rspec_api_documentation", status: 200, response_headers: {})
|
18
|
+
expect(rspec_api_documentation_context).to show_deprecation(gone_on: "2038-12-13")
|
19
|
+
fail "Expected matcher to fail"
|
20
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
21
|
+
expect(e.message).to match(/No Sunset header was set/i)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
it "fails if the sunset header is the wrong date" do
|
25
|
+
begin
|
26
|
+
rspec_api_documentation_context = double(
|
27
|
+
"a test using rspec_api_documentation",
|
28
|
+
status: 200,
|
29
|
+
response_headers: { "Sunset" => "Tuesday, 14 Dec 2038 00:00:00 GMT" }
|
30
|
+
)
|
31
|
+
expect(rspec_api_documentation_context).to show_deprecation(gone_on: "2038-12-13")
|
32
|
+
fail "Expected matcher to fail"
|
33
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
34
|
+
expect(e.message).to match(/Expected the Sunset header to be Mon, 13 Dec 2038 00:00:00 GMT, but was 'Tuesday, 14 Dec 2038 00:00:00 GMT/i)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
context "when the expiration date is in the past" do
|
39
|
+
it "passes if the http status is 410/gone" do
|
40
|
+
rspec_api_documentation_context = double("a test using rspec_api_documentation", status: 410)
|
41
|
+
expect(rspec_api_documentation_context).to show_deprecation(gone_on: "2011-01-01")
|
42
|
+
end
|
43
|
+
|
44
|
+
it "fails if the status is not 410/gone" do
|
45
|
+
rspec_api_documentation_context = double("a test using rspec_api_documentation", status: 200)
|
46
|
+
begin
|
47
|
+
expect(rspec_api_documentation_context).to show_deprecation(gone_on: "2011-01-01")
|
48
|
+
fail "Expected matcher to fail"
|
49
|
+
rescue RSpec::Expectations::ExpectationNotMetError => e
|
50
|
+
expect(e.message).to match(/deprecation date has passed.*410.*using `gone\!`/i)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/stitches.gemspec
CHANGED
@@ -17,10 +17,13 @@ Gem::Specification.new do |s|
|
|
17
17
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
18
|
s.executables = `git ls-files -- exe/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
|
+
|
20
21
|
s.add_runtime_dependency("rails")
|
21
22
|
s.add_runtime_dependency("pg")
|
22
|
-
s.add_development_dependency("rake")
|
23
23
|
s.add_runtime_dependency("rspec", ">= 3")
|
24
24
|
s.add_runtime_dependency("rspec-rails", "~> 3")
|
25
25
|
s.add_runtime_dependency("apitome")
|
26
|
+
|
27
|
+
s.add_development_dependency("rake")
|
28
|
+
s.add_development_dependency("rspec_junit_formatter")
|
26
29
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stitches
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.6.0.RC1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stitch Fix Engineering
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2018-06-11 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rails
|
@@ -41,20 +41,6 @@ dependencies:
|
|
41
41
|
- - ">="
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: '0'
|
44
|
-
- !ruby/object:Gem::Dependency
|
45
|
-
name: rake
|
46
|
-
requirement: !ruby/object:Gem::Requirement
|
47
|
-
requirements:
|
48
|
-
- - ">="
|
49
|
-
- !ruby/object:Gem::Version
|
50
|
-
version: '0'
|
51
|
-
type: :development
|
52
|
-
prerelease: false
|
53
|
-
version_requirements: !ruby/object:Gem::Requirement
|
54
|
-
requirements:
|
55
|
-
- - ">="
|
56
|
-
- !ruby/object:Gem::Version
|
57
|
-
version: '0'
|
58
44
|
- !ruby/object:Gem::Dependency
|
59
45
|
name: rspec
|
60
46
|
requirement: !ruby/object:Gem::Requirement
|
@@ -97,6 +83,34 @@ dependencies:
|
|
97
83
|
- - ">="
|
98
84
|
- !ruby/object:Gem::Version
|
99
85
|
version: '0'
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: rake
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
type: :development
|
94
|
+
prerelease: false
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
- !ruby/object:Gem::Dependency
|
101
|
+
name: rspec_junit_formatter
|
102
|
+
requirement: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
type: :development
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
100
114
|
description: You'll be in stitches at how easy it is to create a service at Stitch
|
101
115
|
Fix
|
102
116
|
email:
|
@@ -108,6 +122,7 @@ executables: []
|
|
108
122
|
extensions: []
|
109
123
|
extra_rdoc_files: []
|
110
124
|
files:
|
125
|
+
- ".circleci/config.yml"
|
111
126
|
- ".gitignore"
|
112
127
|
- ".ruby-gemset"
|
113
128
|
- ".ruby-version"
|
@@ -115,16 +130,22 @@ files:
|
|
115
130
|
- CODE_OF_CONDUCT.md
|
116
131
|
- CONTRIBUTING.md
|
117
132
|
- Gemfile
|
118
|
-
- Gemfile.
|
133
|
+
- Gemfile.rails-4.2
|
134
|
+
- Gemfile.rails-5.0
|
135
|
+
- Gemfile.rails-5.1
|
136
|
+
- Gemfile.rails-5.2
|
119
137
|
- LICENSE.txt
|
120
138
|
- README.md
|
121
139
|
- Rakefile
|
140
|
+
- build-matrix.json
|
122
141
|
- lib/stitches.rb
|
142
|
+
- lib/stitches/add_deprecation_generator.rb
|
123
143
|
- lib/stitches/add_enabled_to_api_clients_generator.rb
|
124
144
|
- lib/stitches/api_generator.rb
|
125
145
|
- lib/stitches/api_key.rb
|
126
146
|
- lib/stitches/api_version_constraint.rb
|
127
147
|
- lib/stitches/configuration.rb
|
148
|
+
- lib/stitches/deprecation.rb
|
128
149
|
- lib/stitches/error.rb
|
129
150
|
- lib/stitches/errors.rb
|
130
151
|
- lib/stitches/generator_files/app/controllers/api.rb
|
@@ -145,19 +166,26 @@ files:
|
|
145
166
|
- lib/stitches/render_timestamps_in_iso8601_in_json.rb
|
146
167
|
- lib/stitches/spec.rb
|
147
168
|
- lib/stitches/spec/api_clients.rb
|
169
|
+
- lib/stitches/spec/be_gone.rb
|
148
170
|
- lib/stitches/spec/be_iso_8601_utc_encoded.rb
|
149
171
|
- lib/stitches/spec/have_api_error.rb
|
172
|
+
- lib/stitches/spec/show_deprecation.rb
|
150
173
|
- lib/stitches/spec/test_headers.rb
|
151
174
|
- lib/stitches/valid_mime_type.rb
|
152
175
|
- lib/stitches/version.rb
|
153
176
|
- lib/stitches/whitelisting_middleware.rb
|
154
177
|
- lib/stitches_norailtie.rb
|
178
|
+
- owners.json
|
155
179
|
- spec/api_key_spec.rb
|
156
180
|
- spec/api_version_constraint_spec.rb
|
157
181
|
- spec/configuration_spec.rb
|
182
|
+
- spec/deprecation_spec.rb
|
158
183
|
- spec/error_spec.rb
|
159
184
|
- spec/errors_spec.rb
|
185
|
+
- spec/integration/add_to_rails_app_spec.rb
|
186
|
+
- spec/spec/be_gone_spec.rb
|
160
187
|
- spec/spec/have_api_error_spec.rb
|
188
|
+
- spec/spec/show_deprecation_spec.rb
|
161
189
|
- spec/spec_helper.rb
|
162
190
|
- spec/valid_mime_type_spec.rb
|
163
191
|
- stitches.gemspec
|
@@ -176,12 +204,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
176
204
|
version: '0'
|
177
205
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
178
206
|
requirements:
|
179
|
-
- - "
|
207
|
+
- - ">"
|
180
208
|
- !ruby/object:Gem::Version
|
181
|
-
version:
|
209
|
+
version: 1.3.1
|
182
210
|
requirements: []
|
183
211
|
rubyforge_project:
|
184
|
-
rubygems_version: 2.
|
212
|
+
rubygems_version: 2.7.6
|
185
213
|
signing_key:
|
186
214
|
specification_version: 4
|
187
215
|
summary: You'll be in stitches at how easy it is to create a service at Stitch Fix
|
@@ -189,8 +217,12 @@ test_files:
|
|
189
217
|
- spec/api_key_spec.rb
|
190
218
|
- spec/api_version_constraint_spec.rb
|
191
219
|
- spec/configuration_spec.rb
|
220
|
+
- spec/deprecation_spec.rb
|
192
221
|
- spec/error_spec.rb
|
193
222
|
- spec/errors_spec.rb
|
223
|
+
- spec/integration/add_to_rails_app_spec.rb
|
224
|
+
- spec/spec/be_gone_spec.rb
|
194
225
|
- spec/spec/have_api_error_spec.rb
|
226
|
+
- spec/spec/show_deprecation_spec.rb
|
195
227
|
- spec/spec_helper.rb
|
196
228
|
- spec/valid_mime_type_spec.rb
|
data/Gemfile.lock
DELETED
@@ -1,154 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
stitches (3.5.0)
|
5
|
-
apitome
|
6
|
-
pg
|
7
|
-
rails
|
8
|
-
rspec (>= 3)
|
9
|
-
rspec-rails (~> 3)
|
10
|
-
|
11
|
-
GEM
|
12
|
-
remote: https://www.rubygems.org/
|
13
|
-
specs:
|
14
|
-
actioncable (5.1.3)
|
15
|
-
actionpack (= 5.1.3)
|
16
|
-
nio4r (~> 2.0)
|
17
|
-
websocket-driver (~> 0.6.1)
|
18
|
-
actionmailer (5.1.3)
|
19
|
-
actionpack (= 5.1.3)
|
20
|
-
actionview (= 5.1.3)
|
21
|
-
activejob (= 5.1.3)
|
22
|
-
mail (~> 2.5, >= 2.5.4)
|
23
|
-
rails-dom-testing (~> 2.0)
|
24
|
-
actionpack (5.1.3)
|
25
|
-
actionview (= 5.1.3)
|
26
|
-
activesupport (= 5.1.3)
|
27
|
-
rack (~> 2.0)
|
28
|
-
rack-test (~> 0.6.3)
|
29
|
-
rails-dom-testing (~> 2.0)
|
30
|
-
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
31
|
-
actionview (5.1.3)
|
32
|
-
activesupport (= 5.1.3)
|
33
|
-
builder (~> 3.1)
|
34
|
-
erubi (~> 1.4)
|
35
|
-
rails-dom-testing (~> 2.0)
|
36
|
-
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
37
|
-
activejob (5.1.3)
|
38
|
-
activesupport (= 5.1.3)
|
39
|
-
globalid (>= 0.3.6)
|
40
|
-
activemodel (5.1.3)
|
41
|
-
activesupport (= 5.1.3)
|
42
|
-
activerecord (5.1.3)
|
43
|
-
activemodel (= 5.1.3)
|
44
|
-
activesupport (= 5.1.3)
|
45
|
-
arel (~> 8.0)
|
46
|
-
activesupport (5.1.3)
|
47
|
-
concurrent-ruby (~> 1.0, >= 1.0.2)
|
48
|
-
i18n (~> 0.7)
|
49
|
-
minitest (~> 5.1)
|
50
|
-
tzinfo (~> 1.1)
|
51
|
-
apitome (0.1.0)
|
52
|
-
kramdown
|
53
|
-
railties
|
54
|
-
rspec_api_documentation
|
55
|
-
arel (8.0.0)
|
56
|
-
builder (3.2.3)
|
57
|
-
concurrent-ruby (1.0.5)
|
58
|
-
diff-lcs (1.3)
|
59
|
-
erubi (1.6.1)
|
60
|
-
globalid (0.4.0)
|
61
|
-
activesupport (>= 4.2.0)
|
62
|
-
i18n (0.8.6)
|
63
|
-
kramdown (1.14.0)
|
64
|
-
loofah (2.0.3)
|
65
|
-
nokogiri (>= 1.5.9)
|
66
|
-
mail (2.6.6)
|
67
|
-
mime-types (>= 1.16, < 4)
|
68
|
-
method_source (0.8.2)
|
69
|
-
mime-types (3.1)
|
70
|
-
mime-types-data (~> 3.2015)
|
71
|
-
mime-types-data (3.2016.0521)
|
72
|
-
mini_portile2 (2.2.0)
|
73
|
-
minitest (5.10.3)
|
74
|
-
mustache (1.0.5)
|
75
|
-
nio4r (2.1.0)
|
76
|
-
nokogiri (1.8.0)
|
77
|
-
mini_portile2 (~> 2.2.0)
|
78
|
-
pg (0.21.0)
|
79
|
-
rack (2.0.3)
|
80
|
-
rack-test (0.6.3)
|
81
|
-
rack (>= 1.0)
|
82
|
-
rails (5.1.3)
|
83
|
-
actioncable (= 5.1.3)
|
84
|
-
actionmailer (= 5.1.3)
|
85
|
-
actionpack (= 5.1.3)
|
86
|
-
actionview (= 5.1.3)
|
87
|
-
activejob (= 5.1.3)
|
88
|
-
activemodel (= 5.1.3)
|
89
|
-
activerecord (= 5.1.3)
|
90
|
-
activesupport (= 5.1.3)
|
91
|
-
bundler (>= 1.3.0)
|
92
|
-
railties (= 5.1.3)
|
93
|
-
sprockets-rails (>= 2.0.0)
|
94
|
-
rails-dom-testing (2.0.3)
|
95
|
-
activesupport (>= 4.2.0)
|
96
|
-
nokogiri (>= 1.6)
|
97
|
-
rails-html-sanitizer (1.0.3)
|
98
|
-
loofah (~> 2.0)
|
99
|
-
railties (5.1.3)
|
100
|
-
actionpack (= 5.1.3)
|
101
|
-
activesupport (= 5.1.3)
|
102
|
-
method_source
|
103
|
-
rake (>= 0.8.7)
|
104
|
-
thor (>= 0.18.1, < 2.0)
|
105
|
-
rake (12.0.0)
|
106
|
-
rspec (3.6.0)
|
107
|
-
rspec-core (~> 3.6.0)
|
108
|
-
rspec-expectations (~> 3.6.0)
|
109
|
-
rspec-mocks (~> 3.6.0)
|
110
|
-
rspec-core (3.6.0)
|
111
|
-
rspec-support (~> 3.6.0)
|
112
|
-
rspec-expectations (3.6.0)
|
113
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
114
|
-
rspec-support (~> 3.6.0)
|
115
|
-
rspec-mocks (3.6.0)
|
116
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
117
|
-
rspec-support (~> 3.6.0)
|
118
|
-
rspec-rails (3.6.0)
|
119
|
-
actionpack (>= 3.0)
|
120
|
-
activesupport (>= 3.0)
|
121
|
-
railties (>= 3.0)
|
122
|
-
rspec-core (~> 3.6.0)
|
123
|
-
rspec-expectations (~> 3.6.0)
|
124
|
-
rspec-mocks (~> 3.6.0)
|
125
|
-
rspec-support (~> 3.6.0)
|
126
|
-
rspec-support (3.6.0)
|
127
|
-
rspec_api_documentation (5.0.0)
|
128
|
-
activesupport (>= 3.0.0)
|
129
|
-
mustache (~> 1.0, >= 0.99.4)
|
130
|
-
rspec (~> 3.0)
|
131
|
-
sprockets (3.7.1)
|
132
|
-
concurrent-ruby (~> 1.0)
|
133
|
-
rack (> 1, < 3)
|
134
|
-
sprockets-rails (3.2.0)
|
135
|
-
actionpack (>= 4.0)
|
136
|
-
activesupport (>= 4.0)
|
137
|
-
sprockets (>= 3.0.0)
|
138
|
-
thor (0.20.0)
|
139
|
-
thread_safe (0.3.6)
|
140
|
-
tzinfo (1.2.3)
|
141
|
-
thread_safe (~> 0.1)
|
142
|
-
websocket-driver (0.6.5)
|
143
|
-
websocket-extensions (>= 0.1.0)
|
144
|
-
websocket-extensions (0.1.2)
|
145
|
-
|
146
|
-
PLATFORMS
|
147
|
-
ruby
|
148
|
-
|
149
|
-
DEPENDENCIES
|
150
|
-
rake
|
151
|
-
stitches!
|
152
|
-
|
153
|
-
BUNDLED WITH
|
154
|
-
1.15.3
|