├── .ctags
├── .gitignore
├── .hound.yml
├── .rspec
├── .ruby-version
├── Gemfile
├── Gemfile.lock
├── LICENSE.md
├── README.md
├── Rakefile
├── app.json
├── app
├── assets
│ ├── config
│ │ └── manifest.js
│ ├── images
│ │ └── .keep
│ ├── javascripts
│ │ ├── application.js
│ │ ├── cable.js
│ │ └── channels
│ │ │ └── .keep
│ └── stylesheets
│ │ ├── application.scss
│ │ ├── base
│ │ ├── _base.scss
│ │ ├── _buttons.scss
│ │ ├── _forms.scss
│ │ ├── _layout.scss
│ │ ├── _lists.scss
│ │ ├── _media.scss
│ │ ├── _tables.scss
│ │ ├── _typography.scss
│ │ └── _variables.scss
│ │ └── refills
│ │ └── _flashes.scss
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
├── controllers
│ ├── application_controller.rb
│ └── concerns
│ │ └── .keep
├── helpers
│ ├── application_helper.rb
│ └── flashes_helper.rb
├── jobs
│ └── application_job.rb
├── mailers
│ └── application_mailer.rb
├── models
│ ├── application_record.rb
│ ├── concerns
│ │ └── .keep
│ ├── invitation.rb
│ ├── magazine.rb
│ ├── person.rb
│ ├── team.rb
│ └── user.rb
└── views
│ ├── application
│ ├── _analytics.html.erb
│ ├── _css_overrides.html.erb
│ ├── _flashes.html.erb
│ └── _javascript.html.erb
│ ├── layouts
│ ├── application.html.erb
│ ├── mailer.html.erb
│ └── mailer.text.erb
│ └── pages
│ └── .keep
├── bin
├── bundle
├── delayed_job
├── deploy
├── rails
├── rake
├── rspec
├── setup
├── setup_review_app
├── spring
└── update
├── config.ru
├── config
├── application.rb
├── boot.rb
├── cable.yml
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── application_controller_renderer.rb
│ ├── assets.rb
│ ├── backtrace_silencers.rb
│ ├── cookies_serializer.rb
│ ├── errors.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── json_encoding.rb
│ ├── mime_types.rb
│ ├── new_framework_defaults.rb
│ ├── rack_mini_profiler.rb
│ ├── session_store.rb
│ ├── simple_form.rb
│ └── wrap_parameters.rb
├── locales
│ ├── en.yml
│ └── simple_form.en.yml
├── puma.rb
├── routes.rb
├── secrets.yml
├── smtp.rb
└── spring.rb
├── db
├── migrate
│ ├── 20160712171429_create_delayed_jobs.rb
│ ├── 20160712183756_create_people.rb
│ ├── 20160712200146_create_subscriptions_and_magazines.rb
│ ├── 20160714142622_create_teams.rb
│ ├── 20160714142640_create_users.rb
│ └── 20160714143007_create_invitations.rb
├── schema.rb
└── seeds.rb
├── lib
├── assets
│ └── .keep
├── tasks
│ ├── .keep
│ ├── bundler_audit.rake
│ └── dev.rake
└── templates
│ └── erb
│ └── scaffold
│ └── _form.html.erb
├── public
├── 404.html
├── 422.html
├── 500.html
├── apple-touch-icon-precomposed.png
├── apple-touch-icon.png
├── favicon.ico
└── robots.txt
├── spec
├── factories.rb
├── models
│ └── invitation_spec.rb
├── rails_helper.rb
├── spec_helper.rb
└── support
│ └── factory_bot.rb
└── vendor
└── assets
├── javascripts
└── .keep
└── stylesheets
└── .keep
/.ctags:
--------------------------------------------------------------------------------
1 | --recurse=yes
2 | --exclude=vendor
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | !.keep
2 | *.DS_Store
3 | *.swo
4 | *.swp
5 | /.bundle
6 | /.env.local
7 | /coverage/*
8 | /db/*.sqlite3
9 | /log/*
10 | /public/system
11 | /public/assets
12 | /tags
13 | /tmp/*
14 |
--------------------------------------------------------------------------------
/.hound.yml:
--------------------------------------------------------------------------------
1 | # See https://houndci.com/configuration for help.
2 | haml:
3 | # config_file: .haml-style.yml
4 | enabled: true
5 | javascript:
6 | # config_file: .javascript-style.json
7 | enabled: true
8 | # ignore_file: .javascript_ignore
9 | ruby:
10 | # config_file: .ruby-style.yml
11 | enabled: true
12 | scss:
13 | # config_file: .scss-style.yml
14 | enabled: true
15 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.6.10
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | ruby "2.6.10"
4 |
5 | gem "autoprefixer-rails"
6 | gem "delayed_job_active_record"
7 | gem "flutie"
8 | gem "honeybadger"
9 | gem "jquery-rails"
10 | gem "normalize-rails", "~> 3.0.0"
11 | gem "pg"
12 | gem "puma"
13 | gem "rack-canonical-host"
14 | gem "rails", "~> 5.0.0"
15 | gem "recipient_interceptor"
16 | gem "sass-rails", "~> 5.0"
17 | gem "simple_form"
18 | gem "sprockets", ">= 3.0.0"
19 | gem "sprockets-es6"
20 | gem "suspenders"
21 | gem "title"
22 | gem "uglifier"
23 |
24 | group :development do
25 | gem "listen"
26 | gem "spring", "~> 1.7", ">= 1.7.2"
27 | gem "spring-commands-rspec"
28 | gem "web-console"
29 | end
30 |
31 | group :development, :test do
32 | gem "awesome_print"
33 | gem "bullet"
34 | gem "bundler-audit", ">= 0.5.0", require: false
35 | gem "dotenv-rails"
36 | gem "factory_bot_rails"
37 | gem "pry-byebug"
38 | gem "pry-rails"
39 | gem "refills"
40 | gem "rspec-rails"
41 | end
42 |
43 | group :development, :staging do
44 | gem "rack-mini-profiler", require: false
45 | end
46 |
47 | group :test do
48 | gem "webdrivers"
49 | gem "database_cleaner"
50 | gem "formulaic"
51 | gem "launchy"
52 | gem "shoulda-matchers"
53 | gem "simplecov", require: false
54 | gem "timecop"
55 | gem "webmock"
56 | end
57 |
58 | group :staging, :production do
59 | gem "rack-timeout"
60 | gem "rails_stdout_logging"
61 | end
62 |
63 | gem 'high_voltage'
64 | gem 'bourbon', '5.0.0.beta.6'
65 | gem 'neat', '~> 1.8.0'
66 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | actioncable (5.0.7.2)
5 | actionpack (= 5.0.7.2)
6 | nio4r (>= 1.2, < 3.0)
7 | websocket-driver (~> 0.6.1)
8 | actionmailer (5.0.7.2)
9 | actionpack (= 5.0.7.2)
10 | actionview (= 5.0.7.2)
11 | activejob (= 5.0.7.2)
12 | mail (~> 2.5, >= 2.5.4)
13 | rails-dom-testing (~> 2.0)
14 | actionpack (5.0.7.2)
15 | actionview (= 5.0.7.2)
16 | activesupport (= 5.0.7.2)
17 | rack (~> 2.0)
18 | rack-test (~> 0.6.3)
19 | rails-dom-testing (~> 2.0)
20 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
21 | actionview (5.0.7.2)
22 | activesupport (= 5.0.7.2)
23 | builder (~> 3.1)
24 | erubis (~> 2.7.0)
25 | rails-dom-testing (~> 2.0)
26 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
27 | activejob (5.0.7.2)
28 | activesupport (= 5.0.7.2)
29 | globalid (>= 0.3.6)
30 | activemodel (5.0.7.2)
31 | activesupport (= 5.0.7.2)
32 | activerecord (5.0.7.2)
33 | activemodel (= 5.0.7.2)
34 | activesupport (= 5.0.7.2)
35 | arel (~> 7.0)
36 | activesupport (5.0.7.2)
37 | concurrent-ruby (~> 1.0, >= 1.0.2)
38 | i18n (>= 0.7, < 2)
39 | minitest (~> 5.1)
40 | tzinfo (~> 1.1)
41 | addressable (2.8.0)
42 | public_suffix (>= 2.0.2, < 5.0)
43 | arel (7.1.4)
44 | autoprefixer-rails (10.4.2.0)
45 | execjs (~> 2)
46 | awesome_print (1.9.2)
47 | babel-source (5.8.35)
48 | babel-transpiler (0.7.0)
49 | babel-source (>= 4.0, < 6)
50 | execjs (~> 2.0)
51 | bindex (0.8.1)
52 | bitters (1.4.0)
53 | bourbon (>= 5.0.0.beta.6)
54 | sass (~> 3.4)
55 | thor (~> 0.19)
56 | bourbon (5.0.0.beta.6)
57 | sass (~> 3.4.22)
58 | thor (~> 0.19.1)
59 | builder (3.2.4)
60 | bullet (7.0.1)
61 | activesupport (>= 3.0.0)
62 | uniform_notifier (~> 1.11)
63 | bundler-audit (0.7.0.1)
64 | bundler (>= 1.2.0, < 3)
65 | thor (>= 0.18, < 2)
66 | byebug (11.1.3)
67 | capybara (3.36.0)
68 | addressable
69 | matrix
70 | mini_mime (>= 0.1.3)
71 | nokogiri (~> 1.8)
72 | rack (>= 1.6.0)
73 | rack-test (>= 0.6.3)
74 | regexp_parser (>= 1.5, < 3.0)
75 | xpath (~> 3.2)
76 | childprocess (4.1.0)
77 | coderay (1.1.3)
78 | concurrent-ruby (1.1.10)
79 | crack (0.4.5)
80 | rexml
81 | crass (1.0.6)
82 | database_cleaner (2.0.1)
83 | database_cleaner-active_record (~> 2.0.0)
84 | database_cleaner-active_record (2.0.1)
85 | activerecord (>= 5.a)
86 | database_cleaner-core (~> 2.0.0)
87 | database_cleaner-core (2.0.1)
88 | delayed_job (4.1.10)
89 | activesupport (>= 3.0, < 8.0)
90 | delayed_job_active_record (4.1.7)
91 | activerecord (>= 3.0, < 8.0)
92 | delayed_job (>= 3.0, < 5)
93 | diff-lcs (1.5.0)
94 | docile (1.4.0)
95 | dotenv (2.7.6)
96 | dotenv-rails (2.7.6)
97 | dotenv (= 2.7.6)
98 | railties (>= 3.2)
99 | erubis (2.7.0)
100 | execjs (2.8.1)
101 | factory_bot (6.2.1)
102 | activesupport (>= 5.0.0)
103 | factory_bot_rails (6.2.0)
104 | factory_bot (~> 6.2.0)
105 | railties (>= 5.0.0)
106 | ffi (1.15.5)
107 | flutie (2.2.0)
108 | formulaic (0.4.1)
109 | activesupport
110 | capybara
111 | i18n
112 | globalid (1.0.1)
113 | activesupport (>= 5.0)
114 | hashdiff (1.0.1)
115 | high_voltage (3.1.2)
116 | honeybadger (4.12.1)
117 | i18n (1.12.0)
118 | concurrent-ruby (~> 1.0)
119 | jquery-rails (4.4.0)
120 | rails-dom-testing (>= 1, < 3)
121 | railties (>= 4.2.0)
122 | thor (>= 0.14, < 2.0)
123 | launchy (2.5.0)
124 | addressable (~> 2.7)
125 | listen (3.7.1)
126 | rb-fsevent (~> 0.10, >= 0.10.3)
127 | rb-inotify (~> 0.9, >= 0.9.10)
128 | loofah (2.19.1)
129 | crass (~> 1.0.2)
130 | nokogiri (>= 1.5.9)
131 | mail (2.7.1)
132 | mini_mime (>= 0.1.1)
133 | matrix (0.4.2)
134 | method_source (1.0.0)
135 | mini_mime (1.1.2)
136 | mini_portile2 (2.8.1)
137 | minitest (5.17.0)
138 | neat (1.8.0)
139 | sass (>= 3.3)
140 | thor (~> 0.19)
141 | nio4r (2.5.8)
142 | nokogiri (1.13.10)
143 | mini_portile2 (~> 2.8.0)
144 | racc (~> 1.4)
145 | normalize-rails (3.0.3)
146 | pg (1.3.5)
147 | pry (0.13.1)
148 | coderay (~> 1.1)
149 | method_source (~> 1.0)
150 | pry-byebug (3.9.0)
151 | byebug (~> 11.0)
152 | pry (~> 0.13.0)
153 | pry-rails (0.3.9)
154 | pry (>= 0.10.4)
155 | public_suffix (4.0.7)
156 | puma (5.6.4)
157 | nio4r (~> 2.0)
158 | racc (1.6.2)
159 | rack (2.2.6.2)
160 | rack-canonical-host (1.1.0)
161 | addressable (> 0, < 3)
162 | rack (>= 1.0.0, < 3)
163 | rack-mini-profiler (3.0.0)
164 | rack (>= 1.2.0)
165 | rack-test (0.6.3)
166 | rack (>= 1.0)
167 | rack-timeout (0.6.0)
168 | rails (5.0.7.2)
169 | actioncable (= 5.0.7.2)
170 | actionmailer (= 5.0.7.2)
171 | actionpack (= 5.0.7.2)
172 | actionview (= 5.0.7.2)
173 | activejob (= 5.0.7.2)
174 | activemodel (= 5.0.7.2)
175 | activerecord (= 5.0.7.2)
176 | activesupport (= 5.0.7.2)
177 | bundler (>= 1.3.0)
178 | railties (= 5.0.7.2)
179 | sprockets-rails (>= 2.0.0)
180 | rails-dom-testing (2.0.3)
181 | activesupport (>= 4.2.0)
182 | nokogiri (>= 1.6)
183 | rails-html-sanitizer (1.4.2)
184 | loofah (~> 2.3)
185 | rails_stdout_logging (0.0.5)
186 | railties (5.0.7.2)
187 | actionpack (= 5.0.7.2)
188 | activesupport (= 5.0.7.2)
189 | method_source
190 | rake (>= 0.8.7)
191 | thor (>= 0.18.1, < 2.0)
192 | rake (13.0.6)
193 | rb-fsevent (0.11.1)
194 | rb-inotify (0.10.1)
195 | ffi (~> 1.0)
196 | recipient_interceptor (0.3.1)
197 | mail
198 | refills (0.2.0)
199 | regexp_parser (2.3.0)
200 | rexml (3.2.5)
201 | rspec-core (3.11.0)
202 | rspec-support (~> 3.11.0)
203 | rspec-expectations (3.11.0)
204 | diff-lcs (>= 1.2.0, < 2.0)
205 | rspec-support (~> 3.11.0)
206 | rspec-mocks (3.11.1)
207 | diff-lcs (>= 1.2.0, < 2.0)
208 | rspec-support (~> 3.11.0)
209 | rspec-rails (4.1.2)
210 | actionpack (>= 4.2)
211 | activesupport (>= 4.2)
212 | railties (>= 4.2)
213 | rspec-core (~> 3.10)
214 | rspec-expectations (~> 3.10)
215 | rspec-mocks (~> 3.10)
216 | rspec-support (~> 3.10)
217 | rspec-support (3.11.0)
218 | rubyzip (2.3.2)
219 | sass (3.4.25)
220 | sass-rails (5.0.7)
221 | railties (>= 4.0.0, < 6)
222 | sass (~> 3.1)
223 | sprockets (>= 2.8, < 4.0)
224 | sprockets-rails (>= 2.0, < 4.0)
225 | tilt (>= 1.1, < 3)
226 | selenium-webdriver (4.1.0)
227 | childprocess (>= 0.5, < 5.0)
228 | rexml (~> 3.2, >= 3.2.5)
229 | rubyzip (>= 1.2.2)
230 | shoulda-matchers (4.5.1)
231 | activesupport (>= 4.2.0)
232 | simple_form (5.0.3)
233 | actionpack (>= 5.0)
234 | activemodel (>= 5.0)
235 | simplecov (0.21.2)
236 | docile (~> 1.1)
237 | simplecov-html (~> 0.11)
238 | simplecov_json_formatter (~> 0.1)
239 | simplecov-html (0.12.3)
240 | simplecov_json_formatter (0.1.4)
241 | spring (1.7.2)
242 | spring-commands-rspec (1.0.4)
243 | spring (>= 0.9.1)
244 | sprockets (3.7.2)
245 | concurrent-ruby (~> 1.0)
246 | rack (> 1, < 3)
247 | sprockets-es6 (0.9.2)
248 | babel-source (>= 5.8.11)
249 | babel-transpiler
250 | sprockets (>= 3.0.0)
251 | sprockets-rails (3.2.2)
252 | actionpack (>= 4.0)
253 | activesupport (>= 4.0)
254 | sprockets (>= 3.0.0)
255 | suspenders (1.42.0)
256 | bitters (~> 1.3)
257 | bundler (~> 1.3)
258 | rails (~> 5.0.0)
259 | thor (0.19.4)
260 | thread_safe (0.3.6)
261 | tilt (2.0.10)
262 | timecop (0.9.5)
263 | title (0.0.8)
264 | i18n
265 | rails (>= 3.1)
266 | tzinfo (1.2.10)
267 | thread_safe (~> 0.1)
268 | uglifier (4.2.0)
269 | execjs (>= 0.3.0, < 3)
270 | uniform_notifier (1.16.0)
271 | web-console (3.7.0)
272 | actionview (>= 5.0)
273 | activemodel (>= 5.0)
274 | bindex (>= 0.4.0)
275 | railties (>= 5.0)
276 | webdrivers (5.0.0)
277 | nokogiri (~> 1.6)
278 | rubyzip (>= 1.3.0)
279 | selenium-webdriver (~> 4.0)
280 | webmock (3.14.0)
281 | addressable (>= 2.8.0)
282 | crack (>= 0.3.2)
283 | hashdiff (>= 0.4.0, < 2.0.0)
284 | websocket-driver (0.6.5)
285 | websocket-extensions (>= 0.1.0)
286 | websocket-extensions (0.1.5)
287 | xpath (3.2.0)
288 | nokogiri (~> 1.8)
289 |
290 | PLATFORMS
291 | ruby
292 |
293 | DEPENDENCIES
294 | autoprefixer-rails
295 | awesome_print
296 | bourbon (= 5.0.0.beta.6)
297 | bullet
298 | bundler-audit (>= 0.5.0)
299 | database_cleaner
300 | delayed_job_active_record
301 | dotenv-rails
302 | factory_bot_rails
303 | flutie
304 | formulaic
305 | high_voltage
306 | honeybadger
307 | jquery-rails
308 | launchy
309 | listen
310 | neat (~> 1.8.0)
311 | normalize-rails (~> 3.0.0)
312 | pg
313 | pry-byebug
314 | pry-rails
315 | puma
316 | rack-canonical-host
317 | rack-mini-profiler
318 | rack-timeout
319 | rails (~> 5.0.0)
320 | rails_stdout_logging
321 | recipient_interceptor
322 | refills
323 | rspec-rails
324 | sass-rails (~> 5.0)
325 | shoulda-matchers
326 | simple_form
327 | simplecov
328 | spring (~> 1.7, >= 1.7.2)
329 | spring-commands-rspec
330 | sprockets (>= 3.0.0)
331 | sprockets-es6
332 | suspenders
333 | timecop
334 | title
335 | uglifier
336 | web-console
337 | webdrivers
338 | webmock
339 |
340 | RUBY VERSION
341 | ruby 2.6.10p210
342 |
343 | BUNDLED WITH
344 | 1.17.3
345 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2015-2022 [thoughtbot, inc.](http://thoughtbot.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the “Software”), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Fundamentals of TDD / Tdd Spec Cleanup Exercise
2 |
3 | Hey there! We're [thoughtbot](https://thoughtbot.com), a design and
4 | development consultancy that brings your digital product ideas to life.
5 | We also love to share what we learn.
6 |
7 | This coding exercise comes from [Upcase](https://thoughtbot.com/upcase),
8 | the online learning platform we run. It's part of the
9 | [Fundamentals of TDD](https://thoughtbot.com/upcase/fundamentals-of-tdd) course and is just one small sample of all
10 | the great material available on Upcase, so be sure to visit and check out the rest.
11 |
12 | ## Exercise Intro
13 |
14 | Test code deserves the same care and effort as we put into writing our production code. In this exercise you'll take a messy test and refactor it to better capture the intent and tell a good story.
15 |
16 | ## Instructions
17 |
18 | To start, you'll want to clone and run the setup script for the repo
19 |
20 | git clone git@github.com:thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise.git
21 | cd tdd-spec-cleanup-exercise
22 | bin/setup
23 |
24 | Your goal is to refactor the provided spec to help it tell a good story. Currently the spec makes heavy use of features like `let` and `before`, obscuring the behavior and assertions in each of the specs. Instead, we want each spec to stand on its own, telling a small clear story about the behavior of our model
25 |
26 | 1. Edit the spec in `spec/models/invitation_spec.rb`.
27 | 2. Refactor the spec, cleaning up and clarifying things _without_ changing the test or model behavior
28 | 3. As with any good refactoring, your tests should remain green throughout. Be sure to check with:
29 |
30 | ```
31 | $ bin/rspec spec/models/invitation_spec.rb
32 | ```
33 |
34 | **Your goal is to refactor the specs in that file to clarify the behavior and tell a good story**
35 |
36 | - Keep the 4 phase test model in mind (likely you'll only need 3 as tear down is automated)
37 | - Feel free to extract helper methods to abstract away details while keeping setup explicit to the spec
38 |
39 | ## Tips and Tricks
40 |
41 | Revisit the video on [Telling a Story with Your Tests](https://thoughtbot.com/upcase/videos/telling-a-story-with-your-tests) for tips on how to tackle this cleanup.
42 |
43 | ## Forum Discussion
44 |
45 | If you find yourself stuck, be sure to check out the associated
46 | [Upcase Forum discussion](https://forum.upcase.com)
47 | for this exercise to see what other folks have said.
48 |
49 | ## Next Steps
50 |
51 | When you've finished the exercise, head on back to the
52 | [Fundamentals of TDD](https://thoughtbot.com/upcase/fundamentals-of-tdd) course to find the next exercise,
53 | or explore any of the other great content on
54 | [Upcase](https://thoughtbot.com/upcase).
55 |
56 | ## License
57 |
58 | tdd-spec-cleanup-exercise is Copyright © 2015-2022 thoughtbot, inc. It is free software,
59 | and may be redistributed under the terms specified in the
60 | [LICENSE](/LICENSE.md) file.
61 |
62 | ## Credits
63 |
64 | 
65 |
66 | This exercise is maintained and funded by
67 | [thoughtbot, inc](http://thoughtbot.com/community).
68 |
69 | The names and logos for Upcase and thoughtbot are registered trademarks of
70 | thoughtbot, inc.
71 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 | task(:default).clear
8 | task default: [:spec]
9 |
10 | if defined? RSpec
11 | task(:spec).clear
12 | RSpec::Core::RakeTask.new(:spec) do |t|
13 | t.verbose = false
14 | end
15 | end
16 |
17 | task default: "bundler:audit"
18 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"tdd-example",
3 | "scripts":{},
4 | "env":{
5 | "APPLICATION_HOST":{
6 | "required":true
7 | },
8 | "EMAIL_RECIPIENTS":{
9 | "required":true
10 | },
11 | "HEROKU_APP_NAME": {
12 | "required":true
13 | },
14 | "HEROKU_PARENT_APP_NAME": {
15 | "required":true
16 | },
17 | "RACK_ENV":{
18 | "required":true
19 | },
20 | "SECRET_KEY_BASE":{
21 | "generator":"secret"
22 | },
23 | "SMTP_ADDRESS":{
24 | "required":true
25 | },
26 | "SMTP_DOMAIN":{
27 | "required":true
28 | },
29 | "SMTP_PASSWORD":{
30 | "required":true
31 | },
32 | "SMTP_USERNAME":{
33 | "required":true
34 | }
35 | },
36 | "addons":[
37 | "heroku-postgresql"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/app/assets/images/.keep
--------------------------------------------------------------------------------
/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // compiled file. JavaScript code in this file should be added after the last require_* statement.
9 | //
10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11 | // about supported directives.
12 | //
13 | //= require jquery
14 | //= require jquery_ujs
15 | //= require turbolinks
16 | //= require_tree .
17 |
--------------------------------------------------------------------------------
/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the rails generate channel command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/app/assets/javascripts/channels/.keep
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.scss:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | @import "normalize-rails";
4 |
5 | @import "bourbon";
6 | @import "neat";
7 |
8 | @import "base/base";
9 | @import "refills/flashes";
10 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_base.scss:
--------------------------------------------------------------------------------
1 | // Bitters 1.3.2
2 | // http://bitters.bourbon.io
3 | // Copyright 2013-2015 thoughtbot, inc.
4 | // MIT License
5 |
6 | @import "variables";
7 |
8 | @import "buttons";
9 | @import "forms";
10 | @import "layout";
11 | @import "lists";
12 | @import "media";
13 | @import "tables";
14 | @import "typography";
15 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_buttons.scss:
--------------------------------------------------------------------------------
1 | #{$all-buttons} {
2 | appearance: none;
3 | background-color: $action-color;
4 | border: 0;
5 | border-radius: $base-border-radius;
6 | color: #fff;
7 | cursor: pointer;
8 | display: inline-block;
9 | font-family: $base-font-family;
10 | font-size: $base-font-size;
11 | -webkit-font-smoothing: antialiased;
12 | font-weight: 600;
13 | line-height: 1;
14 | padding: $small-spacing $base-spacing;
15 | text-decoration: none;
16 | transition: background-color $base-duration $base-timing;
17 | user-select: none;
18 | vertical-align: middle;
19 | white-space: nowrap;
20 |
21 | &:hover,
22 | &:focus {
23 | background-color: shade($action-color, 20%);
24 | color: #fff;
25 | }
26 |
27 | &:disabled {
28 | cursor: not-allowed;
29 | opacity: 0.5;
30 |
31 | &:hover {
32 | background-color: $action-color;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_forms.scss:
--------------------------------------------------------------------------------
1 | fieldset {
2 | background-color: transparent;
3 | border: 0;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | legend {
9 | font-weight: 600;
10 | margin-bottom: $small-spacing / 2;
11 | padding: 0;
12 | }
13 |
14 | label {
15 | display: block;
16 | font-weight: 600;
17 | margin-bottom: $small-spacing / 2;
18 | }
19 |
20 | input,
21 | select,
22 | textarea {
23 | display: block;
24 | font-family: $base-font-family;
25 | font-size: $base-font-size;
26 | }
27 |
28 | #{$all-text-inputs},
29 | select[multiple] {
30 | background-color: $base-background-color;
31 | border: $base-border;
32 | border-radius: $base-border-radius;
33 | box-shadow: $form-box-shadow;
34 | box-sizing: border-box;
35 | margin-bottom: $small-spacing;
36 | padding: $base-spacing / 3;
37 | transition: border-color $base-duration $base-timing;
38 | width: 100%;
39 |
40 | &:hover {
41 | border-color: shade($base-border-color, 20%);
42 | }
43 |
44 | &:focus {
45 | border-color: $action-color;
46 | box-shadow: $form-box-shadow-focus;
47 | outline: none;
48 | }
49 |
50 | &:disabled {
51 | background-color: shade($base-background-color, 5%);
52 | cursor: not-allowed;
53 |
54 | &:hover {
55 | border: $base-border;
56 | }
57 | }
58 |
59 | &::placeholder {
60 | color: $medium-gray;
61 | }
62 | }
63 |
64 | textarea {
65 | resize: vertical;
66 | }
67 |
68 | [type="search"] {
69 | appearance: none;
70 | }
71 |
72 | [type="checkbox"],
73 | [type="radio"] {
74 | display: inline;
75 | margin-right: $small-spacing / 2;
76 | }
77 |
78 | [type="file"] {
79 | margin-bottom: $small-spacing;
80 | width: 100%;
81 | }
82 |
83 | select {
84 | margin-bottom: $small-spacing;
85 | max-width: 100%;
86 | width: auto;
87 | }
88 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_layout.scss:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | }
4 |
5 | *,
6 | *::before,
7 | *::after {
8 | box-sizing: inherit;
9 | }
10 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_lists.scss:
--------------------------------------------------------------------------------
1 | ul,
2 | ol {
3 | list-style-type: none;
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | dl {
9 | margin: 0;
10 | }
11 |
12 | dt {
13 | font-weight: 600;
14 | margin: 0;
15 | }
16 |
17 | dd {
18 | margin: 0;
19 | }
20 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_media.scss:
--------------------------------------------------------------------------------
1 | figure {
2 | margin: 0;
3 | }
4 |
5 | img,
6 | picture {
7 | margin: 0;
8 | max-width: 100%;
9 | }
10 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_tables.scss:
--------------------------------------------------------------------------------
1 | table {
2 | border-collapse: collapse;
3 | margin: $small-spacing 0;
4 | table-layout: fixed;
5 | width: 100%;
6 | }
7 |
8 | th {
9 | border-bottom: 1px solid shade($base-border-color, 25%);
10 | font-weight: 600;
11 | padding: $small-spacing 0;
12 | text-align: left;
13 | }
14 |
15 | td {
16 | border-bottom: $base-border;
17 | padding: $small-spacing 0;
18 | }
19 |
20 | tr,
21 | td,
22 | th {
23 | vertical-align: middle;
24 | }
25 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_typography.scss:
--------------------------------------------------------------------------------
1 | body {
2 | color: $base-font-color;
3 | font-family: $base-font-family;
4 | font-size: $base-font-size;
5 | line-height: $base-line-height;
6 | }
7 |
8 | h1,
9 | h2,
10 | h3,
11 | h4,
12 | h5,
13 | h6 {
14 | font-family: $heading-font-family;
15 | font-size: modular-scale(1);
16 | line-height: $heading-line-height;
17 | margin: 0 0 $small-spacing;
18 | }
19 |
20 | p {
21 | margin: 0 0 $small-spacing;
22 | }
23 |
24 | a {
25 | color: $action-color;
26 | text-decoration: none;
27 | transition: color $base-duration $base-timing;
28 |
29 | &:active,
30 | &:focus,
31 | &:hover {
32 | color: shade($action-color, 25%);
33 | }
34 | }
35 |
36 | hr {
37 | border-bottom: $base-border;
38 | border-left: 0;
39 | border-right: 0;
40 | border-top: 0;
41 | margin: $base-spacing 0;
42 | }
43 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/base/_variables.scss:
--------------------------------------------------------------------------------
1 | // Breakpoints
2 | $medium-screen: 600px;
3 | $large-screen: 900px;
4 |
5 | // Typography
6 | $base-font-family: $font-stack-system;
7 | $heading-font-family: $base-font-family;
8 |
9 | // Font Sizes
10 | $base-font-size: 1em;
11 |
12 | // Line height
13 | $base-line-height: 1.5;
14 | $heading-line-height: 1.2;
15 |
16 | // Other Sizes
17 | $base-border-radius: 3px;
18 | $base-spacing: $base-line-height * 1em;
19 | $small-spacing: $base-spacing / 2;
20 | $base-z-index: 0;
21 |
22 | // Colors
23 | $blue: #1565c0;
24 | $dark-gray: #333;
25 | $medium-gray: #999;
26 | $light-gray: #ddd;
27 |
28 | // Font Colors
29 | $base-font-color: $dark-gray;
30 | $action-color: $blue;
31 |
32 | // Border
33 | $base-border-color: $light-gray;
34 | $base-border: 1px solid $base-border-color;
35 |
36 | // Background Colors
37 | $base-background-color: #fff;
38 | $secondary-background-color: tint($base-border-color, 75%);
39 |
40 | // Forms
41 | $form-box-shadow: inset 0 1px 3px rgba(#000, 0.06);
42 | $form-box-shadow-focus: $form-box-shadow, 0 0 5px adjust-color($action-color, $lightness: -5%, $alpha: -0.3);
43 |
44 | // Animations
45 | $base-duration: 150ms;
46 | $base-timing: ease;
47 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/refills/_flashes.scss:
--------------------------------------------------------------------------------
1 | $base-spacing: 1.5em !default;
2 | $alert-color: #fff6bf !default;
3 | $error-color: #fbe3e4 !default;
4 | $notice-color: #e5edf8 !default;
5 | $success-color: #e6efc2 !default;
6 |
7 | @mixin flash($color) {
8 | background-color: $color;
9 | color: darken($color, 60%);
10 | display: block;
11 | font-weight: 600;
12 | margin-bottom: $base-spacing / 2;
13 | padding: $base-spacing / 2;
14 | text-align: center;
15 |
16 | a {
17 | color: darken($color, 70%);
18 |
19 | &:focus,
20 | &:hover {
21 | color: darken($color, 90%);
22 | }
23 | }
24 | }
25 |
26 | .flash-alert {
27 | @include flash($alert-color);
28 | }
29 |
30 | .flash-error {
31 | @include flash($error-color);
32 | }
33 |
34 | .flash-notice {
35 | @include flash($notice-color);
36 | }
37 |
38 | .flash-success {
39 | @include flash($success-color);
40 | }
41 |
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery with: :exception
3 | end
4 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/helpers/flashes_helper.rb:
--------------------------------------------------------------------------------
1 | module FlashesHelper
2 | def user_facing_flashes
3 | flash.to_hash.slice("alert", "error", "notice", "success")
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/app/models/concerns/.keep
--------------------------------------------------------------------------------
/app/models/invitation.rb:
--------------------------------------------------------------------------------
1 | class Invitation < ApplicationRecord
2 | belongs_to :user
3 | belongs_to :team
4 |
5 | validates :team, presence: true
6 | validates :user, presence: true
7 |
8 | after_save :mark_user_as_invited
9 |
10 | def event_log_statement
11 | if !new_record?
12 | invitation_description
13 | elsif valid?
14 | "PENDING - #{invitation_description}"
15 | else
16 | "INVALID"
17 | end
18 | end
19 |
20 | private
21 |
22 | def invitation_description
23 | "#{user.email} invited to #{team.name}"
24 | end
25 |
26 | def mark_user_as_invited
27 | user.update(invited: true)
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/app/models/magazine.rb:
--------------------------------------------------------------------------------
1 | class Magazine < ApplicationRecord
2 | end
3 |
--------------------------------------------------------------------------------
/app/models/person.rb:
--------------------------------------------------------------------------------
1 | class Person < ApplicationRecord
2 | end
3 |
--------------------------------------------------------------------------------
/app/models/team.rb:
--------------------------------------------------------------------------------
1 | class Team < ApplicationRecord
2 | belongs_to :owner, class_name: User
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | class User < ApplicationRecord
2 | belongs_to :team
3 | end
4 |
--------------------------------------------------------------------------------
/app/views/application/_analytics.html.erb:
--------------------------------------------------------------------------------
1 | <% if ENV["SEGMENT_KEY"] %>
2 |
7 | <% end %>
8 |
--------------------------------------------------------------------------------
/app/views/application/_css_overrides.html.erb:
--------------------------------------------------------------------------------
1 | <% if Rails.env.test? %>
2 |
7 | <% end %>
8 |
--------------------------------------------------------------------------------
/app/views/application/_flashes.html.erb:
--------------------------------------------------------------------------------
1 | <% if flash.any? %>
2 |
3 | <% user_facing_flashes.each do |key, value| -%>
4 |
<%= value %>
5 | <% end -%>
6 |
7 | <% end %>
8 |
--------------------------------------------------------------------------------
/app/views/application/_javascript.html.erb:
--------------------------------------------------------------------------------
1 | <%= javascript_include_tag :application %>
2 |
3 | <%= yield :javascript %>
4 |
5 | <%= render "analytics" %>
6 |
7 | <% if Rails.env.test? %>
8 | <%= javascript_tag do %>
9 | $.fx.off = true;
10 | $.ajaxSetup({ async: false });
11 | <% end %>
12 | <% end %>
13 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | <%#
8 | Configure default and controller-, and view-specific titles in
9 | config/locales/en.yml. For more see:
10 | https://github.com/calebthompson/title#usage
11 | %>
12 | <%= title %>
13 | <%= stylesheet_link_tag :application, media: "all" %>
14 | <%= csrf_meta_tags %>
15 |
16 |
17 | <%= render "flashes" -%>
18 | <%= yield %>
19 | <%= render "javascript" %>
20 | <%= render "css_overrides" %>
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/app/views/pages/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/app/views/pages/.keep
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/bin/delayed_job:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
4 | require 'delayed/command'
5 | Delayed::Command.new(ARGV).daemonize
6 |
--------------------------------------------------------------------------------
/bin/deploy:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Run this script to deploy the app to Heroku.
4 |
5 | set -e
6 |
7 | branch="$(git symbolic-ref HEAD --short)"
8 | target="${1:-staging}"
9 |
10 | git push "$target" "$branch:master"
11 | heroku run rake db:migrate --exit-code --remote "$target"
12 | heroku restart --remote "$target"
13 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../config/application', __dir__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require_relative '../config/boot'
8 | require 'rake'
9 | Rake.application.run
10 |
--------------------------------------------------------------------------------
/bin/rspec:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | require 'bundler/setup'
8 | load Gem.bin_path('rspec-core', 'rspec')
9 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Set up Rails app. Run this script immediately after cloning the codebase.
4 | # https://github.com/thoughtbot/guides/tree/master/protocol
5 |
6 | # Exit if any subcommand fails
7 | set -e
8 |
9 | # Set up Ruby dependencies via Bundler
10 | gem install bundler --conservative
11 | bundle check || bundle install
12 |
13 | # Set up database and add any development seed data
14 | bin/rake dev:prime
15 |
16 | # Add binstubs to PATH via export PATH=".git/safe/../../bin:$PATH" in ~/.zshenv
17 | mkdir -p .git/safe
18 |
19 | # Only if this isn't CI
20 | # if [ -z "$CI" ]; then
21 | # fi
22 |
--------------------------------------------------------------------------------
/bin/setup_review_app:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Run this script to set up a review app's database and worker dyno
4 |
5 | set -e
6 |
7 | if [ -z "$1" ]; then
8 | printf "You must provide a review app (same as the pull request) id.\n"
9 | exit 64
10 | fi
11 |
12 | heroku pg:backups restore \
13 | `heroku pg:backups public-url -a tdd-example-staging` \
14 | DATABASE_URL \
15 | --confirm tdd-example-staging-pr-$1 \
16 | --app tdd-example-staging-pr-$1
17 | heroku run rake db:migrate --exit-code --app tdd-example-staging-pr-$1
18 | heroku ps:scale worker=1 --app tdd-example-staging-pr-$1
19 | heroku restart --app tdd-example-staging-pr-$1
20 |
--------------------------------------------------------------------------------
/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | # This file loads spring without using Bundler, in order to be fast.
4 | # It gets overwritten when you run the `spring binstub` command.
5 |
6 | unless defined?(Spring)
7 | require 'rubygems'
8 | require 'bundler'
9 |
10 | if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m))
11 | Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) }
12 | gem 'spring', match[1]
13 | require 'spring/binstub'
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a way to update your development environment automatically.
15 | # Add necessary update steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | puts "\n== Updating database =="
22 | system! 'bin/rails db:migrate'
23 |
24 | puts "\n== Removing old logs and tempfiles =="
25 | system! 'bin/rails log:clear tmp:clear'
26 |
27 | puts "\n== Restarting application server =="
28 | system! 'bin/rails restart'
29 | end
30 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative 'boot'
2 | require 'rails/all'
3 | Bundler.require(*Rails.groups)
4 | module TddExample
5 | class Application < Rails::Application
6 | config.assets.quiet = true
7 | config.generators do |generate|
8 | generate.helper false
9 | generate.javascript_engine false
10 | generate.request_specs false
11 | generate.routing_specs false
12 | generate.stylesheets false
13 | generate.test_framework :rspec
14 | generate.view_specs false
15 | end
16 | config.action_controller.action_on_unpermitted_parameters = :raise
17 | config.active_job.queue_adapter = :delayed_job
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: redis://localhost:6379/1
10 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | development: &default
2 | adapter: postgresql
3 | database: tdd_example_development
4 | encoding: utf8
5 | min_messages: warning
6 | pool: <%= Integer(ENV.fetch("DB_POOL", 5)) %>
7 | reaping_frequency: <%= Integer(ENV.fetch("DB_REAPING_FREQUENCY", 10)) %>
8 | timeout: 5000
9 |
10 | test:
11 | <<: *default
12 | database: tdd_example_test
13 |
14 | production: &deploy
15 | encoding: utf8
16 | min_messages: warning
17 | pool: <%= [Integer(ENV.fetch("MAX_THREADS", 5)), Integer(ENV.fetch("DB_POOL", 5))].max %>
18 | timeout: 5000
19 | url: <%= ENV.fetch("DATABASE_URL", "") %>
20 |
21 | staging: *deploy
22 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | require_relative 'application'
2 | Rails.application.initialize!
3 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | config.cache_classes = false
3 | config.eager_load = false
4 | config.consider_all_requests_local = true
5 | if Rails.root.join('tmp/caching-dev.txt').exist?
6 | config.action_controller.perform_caching = true
7 | config.cache_store = :memory_store
8 | config.public_file_server.headers = {
9 | 'Cache-Control' => 'public, max-age=172800'
10 | }
11 | else
12 | config.action_controller.perform_caching = false
13 | config.cache_store = :null_store
14 | end
15 | config.action_mailer.raise_delivery_errors = true
16 | config.after_initialize do
17 | Bullet.enable = true
18 | Bullet.bullet_logger = true
19 | Bullet.rails_logger = true
20 | end
21 | config.action_mailer.delivery_method = :file
22 | config.action_mailer.perform_caching = false
23 | config.active_support.deprecation = :log
24 | config.active_record.migration_error = :page_load
25 | config.assets.debug = true
26 | config.assets.quiet = true
27 | config.action_view.raise_on_missing_translations = true
28 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
29 | config.action_mailer.default_url_options = { host: "localhost:3000" }
30 | end
31 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | require Rails.root.join("config/smtp")
2 | Rails.application.configure do
3 | if ENV.fetch("HEROKU_APP_NAME", "").include?("staging-pr-")
4 | ENV["APPLICATION_HOST"] = ENV["HEROKU_APP_NAME"] + ".herokuapp.com"
5 | end
6 | config.middleware.use Rack::CanonicalHost, ENV.fetch("APPLICATION_HOST")
7 | config.cache_classes = true
8 | config.eager_load = true
9 | config.consider_all_requests_local = false
10 | config.action_controller.perform_caching = true
11 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
12 | config.assets.js_compressor = :uglifier
13 | config.assets.compile = false
14 | config.action_controller.asset_host = ENV.fetch("ASSET_HOST", ENV.fetch("APPLICATION_HOST"))
15 | config.log_level = :debug
16 | config.log_tags = [ :request_id ]
17 | config.action_mailer.perform_caching = false
18 | config.action_mailer.delivery_method = :smtp
19 | config.action_mailer.smtp_settings = SMTP_SETTINGS
20 | config.i18n.fallbacks = true
21 | config.active_support.deprecation = :notify
22 | config.log_formatter = ::Logger::Formatter.new
23 | if ENV["RAILS_LOG_TO_STDOUT"].present?
24 | logger = ActiveSupport::Logger.new(STDOUT)
25 | logger.formatter = config.log_formatter
26 | config.logger = ActiveSupport::TaggedLogging.new(logger)
27 | end
28 | config.active_record.dump_schema_after_migration = false
29 | config.middleware.use Rack::Deflater
30 | config.static_cache_control = "public, max-age=31557600"
31 | config.action_mailer.default_url_options = { host: ENV.fetch("APPLICATION_HOST") }
32 | end
33 | Rack::Timeout.timeout = (ENV["RACK_TIMEOUT"] || 10).to_i
34 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | config.cache_classes = true
3 | config.eager_load = false
4 | config.public_file_server.enabled = true
5 | config.public_file_server.headers = {
6 | 'Cache-Control' => 'public, max-age=3600'
7 | }
8 | config.consider_all_requests_local = true
9 | config.action_controller.perform_caching = false
10 | config.action_dispatch.show_exceptions = false
11 | config.action_controller.allow_forgery_protection = false
12 | config.action_mailer.perform_caching = false
13 | config.action_mailer.delivery_method = :test
14 | config.active_support.deprecation = :stderr
15 | config.action_view.raise_on_missing_translations = true
16 | config.assets.raise_runtime_errors = true
17 | config.action_mailer.default_url_options = { host: "www.example.com" }
18 | config.active_job.queue_adapter = :inline
19 | end
20 |
--------------------------------------------------------------------------------
/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ApplicationController.renderer.defaults.merge!(
4 | # http_host: 'example.org',
5 | # https: false
6 | # )
7 |
--------------------------------------------------------------------------------
/config/initializers/assets.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Version of your assets, change this if you want to expire all your assets.
4 | Rails.application.config.assets.version = (ENV["ASSETS_VERSION"] || "1.0")
5 |
6 | # Add additional assets to the asset load path
7 | # Rails.application.config.assets.paths << Emoji.images_path
8 |
9 | # Precompile additional assets.
10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
11 | # Rails.application.config.assets.precompile += %w( search.js )
12 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
6 |
--------------------------------------------------------------------------------
/config/initializers/errors.rb:
--------------------------------------------------------------------------------
1 | require "net/http"
2 | require "net/smtp"
3 |
4 | # Example:
5 | # begin
6 | # some http call
7 | # rescue *HTTP_ERRORS => error
8 | # notify_hoptoad error
9 | # end
10 |
11 | HTTP_ERRORS = [
12 | EOFError,
13 | Errno::ECONNRESET,
14 | Errno::EINVAL,
15 | Net::HTTPBadResponse,
16 | Net::HTTPHeaderSyntaxError,
17 | Net::ProtocolError,
18 | Timeout::Error,
19 | ]
20 |
21 | SMTP_SERVER_ERRORS = [
22 | IOError,
23 | Net::SMTPAuthenticationError,
24 | Net::SMTPServerBusy,
25 | Net::SMTPUnknownError,
26 | Timeout::Error,
27 | ]
28 |
29 | SMTP_CLIENT_ERRORS = [
30 | Net::SMTPFatalError,
31 | Net::SMTPSyntaxError,
32 | ]
33 |
34 | SMTP_ERRORS = SMTP_SERVER_ERRORS + SMTP_CLIENT_ERRORS
35 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Configure sensitive parameters which will be filtered from the log file.
4 | Rails.application.config.filter_parameters += [:password]
5 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/config/initializers/json_encoding.rb:
--------------------------------------------------------------------------------
1 | ActiveSupport::JSON::Encoding.time_precision = 0
2 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/config/initializers/new_framework_defaults.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # This file contains migration options to ease your Rails 5.0 upgrade.
4 | #
5 | # Read the Rails 5.0 release notes for more info on each option.
6 |
7 | # Enable per-form CSRF tokens. Previous versions had false.
8 | Rails.application.config.action_controller.per_form_csrf_tokens = true
9 |
10 | # Enable origin-checking CSRF mitigation. Previous versions had false.
11 | Rails.application.config.action_controller.forgery_protection_origin_check = true
12 |
13 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
14 | # Previous versions had false.
15 | ActiveSupport.to_time_preserves_timezone = true
16 |
17 | # Require `belongs_to` associations by default. Previous versions had false.
18 | Rails.application.config.active_record.belongs_to_required_by_default = true
19 |
20 | # Do not halt callback chains when a callback returns false. Previous versions had true.
21 | ActiveSupport.halt_callback_chains_on_return_false = false
22 |
23 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false.
24 | Rails.application.config.ssl_options = { hsts: { subdomains: true } }
25 |
--------------------------------------------------------------------------------
/config/initializers/rack_mini_profiler.rb:
--------------------------------------------------------------------------------
1 | if ENV["RACK_MINI_PROFILER"].to_i > 0
2 | require "rack-mini-profiler"
3 |
4 | Rack::MiniProfilerRails.initialize!(Rails.application)
5 | end
6 |
--------------------------------------------------------------------------------
/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Rails.application.config.session_store :cookie_store, key: '_tdd_example_session'
4 |
--------------------------------------------------------------------------------
/config/initializers/simple_form.rb:
--------------------------------------------------------------------------------
1 | # Use this setup block to configure all options available in SimpleForm.
2 | SimpleForm.setup do |config|
3 | # Wrappers are used by the form builder to generate a
4 | # complete input. You can remove any component from the
5 | # wrapper, change the order or even add your own to the
6 | # stack. The options given below are used to wrap the
7 | # whole input.
8 | config.wrappers :default, class: :input,
9 | hint_class: :field_with_hint, error_class: :field_with_errors do |b|
10 | ## Extensions enabled by default
11 | # Any of these extensions can be disabled for a
12 | # given input by passing: `f.input EXTENSION_NAME => false`.
13 | # You can make any of these extensions optional by
14 | # renaming `b.use` to `b.optional`.
15 |
16 | # Determines whether to use HTML5 (:email, :url, ...)
17 | # and required attributes
18 | b.use :html5
19 |
20 | # Calculates placeholders automatically from I18n
21 | # You can also pass a string as f.input placeholder: "Placeholder"
22 | b.use :placeholder
23 |
24 | ## Optional extensions
25 | # They are disabled unless you pass `f.input EXTENSION_NAME => true`
26 | # to the input. If so, they will retrieve the values from the model
27 | # if any exists. If you want to enable any of those
28 | # extensions by default, you can change `b.optional` to `b.use`.
29 |
30 | # Calculates maxlength from length validations for string inputs
31 | b.optional :maxlength
32 |
33 | # Calculates pattern from format validations for string inputs
34 | b.optional :pattern
35 |
36 | # Calculates min and max from length validations for numeric inputs
37 | b.optional :min_max
38 |
39 | # Calculates readonly automatically from readonly attributes
40 | b.optional :readonly
41 |
42 | ## Inputs
43 | b.use :label_input
44 | b.use :hint, wrap_with: { tag: :span, class: :hint }
45 | b.use :error, wrap_with: { tag: :span, class: :error }
46 |
47 | ## full_messages_for
48 | # If you want to display the full error message for the attribute, you can
49 | # use the component :full_error, like:
50 | #
51 | # b.use :full_error, wrap_with: { tag: :span, class: :error }
52 | end
53 |
54 | # The default wrapper to be used by the FormBuilder.
55 | config.default_wrapper = :default
56 |
57 | # Define the way to render check boxes / radio buttons with labels.
58 | # Defaults to :nested for bootstrap config.
59 | # inline: input + label
60 | # nested: label > input
61 | config.boolean_style = :nested
62 |
63 | # Default class for buttons
64 | config.button_class = 'btn'
65 |
66 | # Method used to tidy up errors. Specify any Rails Array method.
67 | # :first lists the first message for each field.
68 | # Use :to_sentence to list all errors for each field.
69 | # config.error_method = :first
70 |
71 | # Default tag used for error notification helper.
72 | config.error_notification_tag = :div
73 |
74 | # CSS class to add for error notification helper.
75 | config.error_notification_class = 'error_notification'
76 |
77 | # ID to add for error notification helper.
78 | # config.error_notification_id = nil
79 |
80 | # Series of attempts to detect a default label method for collection.
81 | # config.collection_label_methods = [ :to_label, :name, :title, :to_s ]
82 |
83 | # Series of attempts to detect a default value method for collection.
84 | # config.collection_value_methods = [ :id, :to_s ]
85 |
86 | # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none.
87 | # config.collection_wrapper_tag = nil
88 |
89 | # You can define the class to use on all collection wrappers. Defaulting to none.
90 | # config.collection_wrapper_class = nil
91 |
92 | # You can wrap each item in a collection of radio/check boxes with a tag,
93 | # defaulting to :span.
94 | # config.item_wrapper_tag = :span
95 |
96 | # You can define a class to use in all item wrappers. Defaulting to none.
97 | # config.item_wrapper_class = nil
98 |
99 | # How the label text should be generated altogether with the required text.
100 | # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" }
101 |
102 | # You can define the class to use on all labels. Default is nil.
103 | # config.label_class = nil
104 |
105 | # You can define the default class to be used on forms. Can be overriden
106 | # with `html: { :class }`. Defaulting to none.
107 | # config.default_form_class = nil
108 |
109 | # You can define which elements should obtain additional classes
110 | # config.generate_additional_classes_for = [:wrapper, :label, :input]
111 |
112 | # Whether attributes are required by default (or not). Default is true.
113 | # config.required_by_default = true
114 |
115 | # Tell browsers whether to use the native HTML5 validations (novalidate form option).
116 | # These validations are enabled in SimpleForm's internal config but disabled by default
117 | # in this configuration, which is recommended due to some quirks from different browsers.
118 | # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations,
119 | # change this configuration to true.
120 | config.browser_validations = false
121 |
122 | # Collection of methods to detect if a file type was given.
123 | # config.file_methods = [ :mounted_as, :file?, :public_filename ]
124 |
125 | # Custom mappings for input types. This should be a hash containing a regexp
126 | # to match as key, and the input type that will be used when the field name
127 | # matches the regexp as value.
128 | # config.input_mappings = { /count/ => :integer }
129 |
130 | # Custom wrappers for input types. This should be a hash containing an input
131 | # type as key and the wrapper that will be used for all inputs with specified type.
132 | # config.wrapper_mappings = { string: :prepend }
133 |
134 | # Namespaces where SimpleForm should look for custom input classes that
135 | # override default inputs.
136 | # config.custom_inputs_namespaces << "CustomInputs"
137 |
138 | # Default priority for time_zone inputs.
139 | # config.time_zone_priority = nil
140 |
141 | # Default priority for country inputs.
142 | # config.country_priority = nil
143 |
144 | # When false, do not use translations for labels.
145 | # config.translate_labels = true
146 |
147 | # Automatically discover new inputs in Rails' autoload path.
148 | # config.inputs_discovery = true
149 |
150 | # Cache SimpleForm inputs discovery
151 | # config.cache_discovery = !Rails.env.development?
152 |
153 | # Default class for inputs
154 | # config.input_class = nil
155 |
156 | # Define the default class of the input wrapper of the boolean input.
157 | config.boolean_label_class = 'checkbox'
158 |
159 | # Defines if the default input wrapper class should be included in radio
160 | # collection wrappers.
161 | # config.include_default_input_wrapper_class = true
162 |
163 | # Defines which i18n scope will be used in Simple Form.
164 | # config.i18n_scope = 'simple_form'
165 | end
166 |
--------------------------------------------------------------------------------
/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | date:
3 | formats:
4 | default:
5 | "%m/%d/%Y"
6 | with_weekday:
7 | "%a %m/%d/%y"
8 |
9 | time:
10 | formats:
11 | default:
12 | "%a, %b %-d, %Y at %r"
13 | date:
14 | "%b %-d, %Y"
15 | short:
16 | "%B %d"
17 |
18 | titles:
19 | application: Tdd example
20 |
--------------------------------------------------------------------------------
/config/locales/simple_form.en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | simple_form:
3 | "yes": 'Yes'
4 | "no": 'No'
5 | required:
6 | text: 'required'
7 | mark: '*'
8 | # You can uncomment the line below if you need to overwrite the whole required html.
9 | # When using html, text and mark won't be used.
10 | # html: '*'
11 | error_notification:
12 | default_message: "Please review the problems below:"
13 | # Examples
14 | # labels:
15 | # defaults:
16 | # password: 'Password'
17 | # user:
18 | # new:
19 | # email: 'E-mail to sign in.'
20 | # edit:
21 | # email: 'E-mail.'
22 | # hints:
23 | # defaults:
24 | # username: 'User name to sign in.'
25 | # password: 'No special characters, please.'
26 | # include_blanks:
27 | # defaults:
28 | # age: 'Rather not say'
29 | # prompts:
30 | # defaults:
31 | # age: 'Select your age'
32 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server
2 |
3 | # The environment variable WEB_CONCURRENCY may be set to a default value based
4 | # on dyno size. To manually configure this value use heroku config:set
5 | # WEB_CONCURRENCY.
6 | #
7 | # Increasing the number of workers will increase the amount of resting memory
8 | # your dynos use. Increasing the number of threads will increase the amount of
9 | # potential bloat added to your dynos when they are responding to heavy
10 | # requests.
11 | #
12 | # Starting with a low number of workers and threads provides adequate
13 | # performance for most applications, even under load, while maintaining a low
14 | # risk of overusing memory.
15 | workers Integer(ENV.fetch("WEB_CONCURRENCY", 2))
16 | threads_count = Integer(ENV.fetch("MAX_THREADS", 2))
17 | threads(threads_count, threads_count)
18 |
19 | preload_app!
20 |
21 | rackup DefaultRackup
22 | environment ENV.fetch("RACK_ENV", "development")
23 |
24 | on_worker_boot do
25 | # Worker specific setup for Rails 4.1+
26 | # See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
27 | ActiveRecord::Base.establish_connection
28 | end
29 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | end
3 |
--------------------------------------------------------------------------------
/config/secrets.yml:
--------------------------------------------------------------------------------
1 | default: &default
2 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
3 |
4 | development:
5 | <<: *default
6 |
7 | test:
8 | <<: *default
9 |
10 | staging:
11 | <<: *default
12 |
13 | production:
14 | <<: *default
15 |
--------------------------------------------------------------------------------
/config/smtp.rb:
--------------------------------------------------------------------------------
1 | SMTP_SETTINGS = {
2 | address: ENV.fetch("SMTP_ADDRESS"), # example: "smtp.sendgrid.net"
3 | authentication: :plain,
4 | domain: ENV.fetch("SMTP_DOMAIN"), # example: "heroku.com"
5 | enable_starttls_auto: true,
6 | password: ENV.fetch("SMTP_PASSWORD"),
7 | port: "587",
8 | user_name: ENV.fetch("SMTP_USERNAME")
9 | }
10 |
11 | if ENV["EMAIL_RECIPIENTS"].present?
12 | Mail.register_interceptor RecipientInterceptor.new(ENV["EMAIL_RECIPIENTS"])
13 | end
14 |
--------------------------------------------------------------------------------
/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w(
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ).each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/db/migrate/20160712171429_create_delayed_jobs.rb:
--------------------------------------------------------------------------------
1 | class CreateDelayedJobs < ActiveRecord::Migration
2 | def self.up
3 | create_table :delayed_jobs, force: true do |table|
4 | table.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of the queue
5 | table.integer :attempts, default: 0, null: false # Provides for retries, but still fail eventually.
6 | table.text :handler, null: false # YAML-encoded string of the object that will do work
7 | table.text :last_error # reason for last failure (See Note below)
8 | table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
9 | table.datetime :locked_at # Set when a client is working on this object
10 | table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
11 | table.string :locked_by # Who is working on this object (if locked)
12 | table.string :queue # The name of the queue this job is in
13 | table.timestamps null: true
14 | end
15 |
16 | add_index :delayed_jobs, [:priority, :run_at], name: "delayed_jobs_priority"
17 | end
18 |
19 | def self.down
20 | drop_table :delayed_jobs
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/db/migrate/20160712183756_create_people.rb:
--------------------------------------------------------------------------------
1 | class CreatePeople < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :people do |t|
4 | t.string :name
5 |
6 | t.timestamps
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/db/migrate/20160712200146_create_subscriptions_and_magazines.rb:
--------------------------------------------------------------------------------
1 | class CreateSubscriptionsAndMagazines < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :magazines do |t|
4 | t.string :name
5 | t.boolean :color
6 | t.boolean :large
7 | end
8 |
9 | create_table :subscriptions do |t|
10 | t.references :person, foreign_key: true
11 | t.references :magazine, foreign_key: true
12 | t.datetime :start_on
13 | t.datetime :end_on
14 |
15 | t.timestamps
16 | end
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/db/migrate/20160714142622_create_teams.rb:
--------------------------------------------------------------------------------
1 | class CreateTeams < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :teams do |t|
4 | t.string :name
5 | t.integer :owner_id
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/migrate/20160714142640_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :users do |t|
4 | t.string :email
5 | t.boolean :invited
6 | t.references :team, foreign_key: true
7 |
8 | t.timestamps
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20160714143007_create_invitations.rb:
--------------------------------------------------------------------------------
1 | class CreateInvitations < ActiveRecord::Migration[5.0]
2 | def change
3 | create_table :invitations do |t|
4 | t.references :user, foreign_key: true
5 | t.references :team, foreign_key: true
6 |
7 | t.timestamps
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 20160714143007) do
14 |
15 | # These are extensions that must be enabled in order to support this database
16 | enable_extension "plpgsql"
17 |
18 | create_table "delayed_jobs", force: :cascade do |t|
19 | t.integer "priority", default: 0, null: false
20 | t.integer "attempts", default: 0, null: false
21 | t.text "handler", null: false
22 | t.text "last_error"
23 | t.datetime "run_at"
24 | t.datetime "locked_at"
25 | t.datetime "failed_at"
26 | t.string "locked_by"
27 | t.string "queue"
28 | t.datetime "created_at"
29 | t.datetime "updated_at"
30 | t.index ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree
31 | end
32 |
33 | create_table "invitations", force: :cascade do |t|
34 | t.integer "user_id"
35 | t.integer "team_id"
36 | t.datetime "created_at", null: false
37 | t.datetime "updated_at", null: false
38 | t.index ["team_id"], name: "index_invitations_on_team_id", using: :btree
39 | t.index ["user_id"], name: "index_invitations_on_user_id", using: :btree
40 | end
41 |
42 | create_table "magazines", force: :cascade do |t|
43 | t.string "name"
44 | t.boolean "color"
45 | t.boolean "large"
46 | end
47 |
48 | create_table "people", force: :cascade do |t|
49 | t.string "name"
50 | t.datetime "created_at", null: false
51 | t.datetime "updated_at", null: false
52 | end
53 |
54 | create_table "subscriptions", force: :cascade do |t|
55 | t.integer "person_id"
56 | t.integer "magazine_id"
57 | t.datetime "start_on"
58 | t.datetime "end_on"
59 | t.datetime "created_at", null: false
60 | t.datetime "updated_at", null: false
61 | t.index ["magazine_id"], name: "index_subscriptions_on_magazine_id", using: :btree
62 | t.index ["person_id"], name: "index_subscriptions_on_person_id", using: :btree
63 | end
64 |
65 | create_table "teams", force: :cascade do |t|
66 | t.string "name"
67 | t.integer "owner_id"
68 | t.datetime "created_at", null: false
69 | t.datetime "updated_at", null: false
70 | end
71 |
72 | create_table "users", force: :cascade do |t|
73 | t.string "email"
74 | t.boolean "invited"
75 | t.integer "team_id"
76 | t.datetime "created_at", null: false
77 | t.datetime "updated_at", null: false
78 | t.index ["team_id"], name: "index_users_on_team_id", using: :btree
79 | end
80 |
81 | add_foreign_key "invitations", "teams"
82 | add_foreign_key "invitations", "users"
83 | add_foreign_key "subscriptions", "magazines"
84 | add_foreign_key "subscriptions", "people"
85 | add_foreign_key "users", "teams"
86 | end
87 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
7 | # Character.create(name: 'Luke', movie: movies.first)
8 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/lib/assets/.keep
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/lib/tasks/.keep
--------------------------------------------------------------------------------
/lib/tasks/bundler_audit.rake:
--------------------------------------------------------------------------------
1 | if Rails.env.development? || Rails.env.test?
2 | require "bundler/audit/cli"
3 |
4 | namespace :bundler do
5 | desc "Updates the ruby-advisory-db and runs audit"
6 | task :audit do
7 | %w(update check).each do |command|
8 | Bundler::Audit::CLI.start [command]
9 | end
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/tasks/dev.rake:
--------------------------------------------------------------------------------
1 | if Rails.env.development? || Rails.env.test?
2 | require "factory_bot"
3 |
4 | namespace :dev do
5 | desc "Sample data for local development environment"
6 | task prime: "db:setup" do
7 | include FactoryBot::Syntax::Methods
8 |
9 | # create(:user, email: "user@example.com", password: "password")
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/templates/erb/scaffold/_form.html.erb:
--------------------------------------------------------------------------------
1 | <%%= simple_form_for(@<%= singular_table_name %>) do |f| %>
2 | <%%= f.error_notification %>
3 |
4 |
5 | <%- attributes.each do |attribute| -%>
6 | <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %>
7 | <%- end -%>
8 |
9 |
10 |
11 | <%%= f.button :submit %>
12 |
13 | <%% end %>
14 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | The page you were looking for doesn't exist (404)
8 |
9 |
58 |
59 |
60 |
61 |
62 |
63 |
The page you were looking for doesn't exist.
64 |
You may have mistyped the address or the page may have moved.
65 |
66 |
If you are the application owner check the logs for more information.
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | The change you wanted was rejected (422)
8 |
9 |
58 |
59 |
60 |
61 |
62 |
63 |
The change you wanted was rejected.
64 |
Maybe you tried to change something you didn't have access to.
65 |
66 |
If you are the application owner check the logs for more information.
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | We're sorry, but something went wrong (500)
8 |
9 |
58 |
59 |
60 |
61 |
62 |
63 |
We're sorry, but something went wrong.
64 |
65 |
If you are the application owner check the logs for more information.
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/spec/factories.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 |
3 | factory :invitation do
4 | user nil
5 | team nil
6 | end
7 |
8 | factory :user do
9 | email { "MyString" }
10 | invited { false }
11 | team nil
12 | end
13 |
14 | factory :team do
15 | name { "MyString" }
16 | end
17 |
18 | factory :magazine do
19 | name { "The Ruby Times" }
20 | end
21 |
22 | factory :person do
23 | end
24 |
25 | factory :subscription do
26 | person
27 | magazine
28 | end
29 |
30 | end
31 |
--------------------------------------------------------------------------------
/spec/models/invitation_spec.rb:
--------------------------------------------------------------------------------
1 | require "rails_helper"
2 |
3 | RSpec.describe Invitation do
4 | let(:invitation) { build(:invitation, team: team, user: new_user) }
5 | let(:new_user) { create(:user, email: "rookie@example.com") }
6 | let(:team) { create(:team, name: "A fine team") }
7 | let(:team_owner) { create(:user) }
8 |
9 | before do
10 | team.update!(owner: team_owner)
11 | team_owner.update!(team: team)
12 | end
13 |
14 | describe "callbacks" do
15 | describe "after_save" do
16 | context "with valid data" do
17 | it "invites the user" do
18 | invitation.save
19 | expect(new_user).to be_invited
20 | end
21 | end
22 |
23 | context "with invalid data" do
24 | before do
25 | invitation.team = nil
26 | invitation.save
27 | end
28 |
29 | it "does not save the invitation" do
30 | expect(invitation).not_to be_valid
31 | expect(invitation).to be_new_record
32 | end
33 |
34 | it "does not mark the user as invited" do
35 | expect(new_user).not_to be_invited
36 | end
37 | end
38 | end
39 | end
40 |
41 | describe "#event_log_statement" do
42 | context "when the record is saved" do
43 | before do
44 | invitation.save
45 | end
46 |
47 | it "include the name of the team" do
48 | log_statement = invitation.event_log_statement
49 | expect(log_statement).to include("A fine team")
50 | end
51 |
52 | it "include the email of the invitee" do
53 | log_statement = invitation.event_log_statement
54 | expect(log_statement).to include("rookie@example.com")
55 | end
56 | end
57 |
58 | context "when the record is not saved but valid" do
59 | it "includes the name of the team" do
60 | log_statement = invitation.event_log_statement
61 | expect(log_statement).to include("A fine team")
62 | end
63 |
64 | it "includes the email of the invitee" do
65 | log_statement = invitation.event_log_statement
66 | expect(log_statement).to include("rookie@example.com")
67 | end
68 |
69 | it "includes the word 'PENDING'" do
70 | log_statement = invitation.event_log_statement
71 | expect(log_statement).to include("PENDING")
72 | end
73 | end
74 |
75 | context "when the record is not saved and not valid" do
76 | it "includes INVALID" do
77 | invitation.user = nil
78 | log_statement = invitation.event_log_statement
79 | expect(log_statement).to include("INVALID")
80 | end
81 | end
82 | end
83 | end
84 |
--------------------------------------------------------------------------------
/spec/rails_helper.rb:
--------------------------------------------------------------------------------
1 | ENV["RACK_ENV"] = "test"
2 |
3 | require File.expand_path("../../config/environment", __FILE__)
4 | abort("DATABASE_URL environment variable is set") if ENV["DATABASE_URL"]
5 |
6 | require "rspec/rails"
7 | require "support/factory_bot"
8 |
9 | FactoryBot.reload # Without this factories do not get registered
10 |
11 | Dir[Rails.root.join("spec/support/**/*.rb")].sort.each { |file| require file }
12 |
13 | module Features
14 | # Extend this module in spec/support/features/*.rb
15 | include Formulaic::Dsl
16 | end
17 |
18 | RSpec.configure do |config|
19 | config.include Features, type: :feature
20 | config.infer_base_class_for_anonymous_controllers = false
21 | config.infer_spec_type_from_file_location!
22 | config.use_transactional_fixtures = false
23 | end
24 |
25 | ActiveRecord::Migration.maintain_test_schema!
26 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | if ENV.fetch("COVERAGE", false)
2 | require "simplecov"
3 |
4 | if ENV["CIRCLE_ARTIFACTS"]
5 | dir = File.join(ENV["CIRCLE_ARTIFACTS"], "coverage")
6 | SimpleCov.coverage_dir(dir)
7 | end
8 |
9 | SimpleCov.start "rails"
10 | end
11 |
12 | require "webmock/rspec"
13 |
14 | # http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
15 | RSpec.configure do |config|
16 | config.expect_with :rspec do |expectations|
17 | expectations.syntax = :expect
18 | end
19 |
20 | config.mock_with :rspec do |mocks|
21 | mocks.syntax = :expect
22 | mocks.verify_partial_doubles = true
23 | end
24 |
25 | config.example_status_persistence_file_path = "tmp/rspec_examples.txt"
26 | config.order = :random
27 | end
28 |
29 | WebMock.disable_net_connect!(allow_localhost: true)
30 |
--------------------------------------------------------------------------------
/spec/support/factory_bot.rb:
--------------------------------------------------------------------------------
1 | RSpec.configure do |config|
2 | config.include FactoryBot::Syntax::Methods
3 | end
4 |
--------------------------------------------------------------------------------
/vendor/assets/javascripts/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/vendor/assets/javascripts/.keep
--------------------------------------------------------------------------------
/vendor/assets/stylesheets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thoughtbot-upcase-exercises/tdd-spec-cleanup-exercise/dbfbb100773ec60a9d152c2e36c0640a635e3bd5/vendor/assets/stylesheets/.keep
--------------------------------------------------------------------------------