├── .dockerignore ├── .gitignore ├── .rspec ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── README.md ├── Rakefile ├── _assignments ├── assignment_01.md ├── assignment_02.md ├── assignment_03.md ├── assignment_04.md ├── assignment_05.md ├── assignment_06.md ├── assignment_07.md ├── assignment_08.md ├── assignment_09.md ├── assignment_10.md ├── assignment_11.md ├── assignment_12.md ├── assignment_13.md └── assignment_14.md ├── _examples ├── Dockerfile.with_postgres_client ├── Dockerfile.with_server_pid ├── Dockerfile.with_spring ├── docker-compose.yml ├── docker-compose.yml.with_bind_mount ├── docker-compose.yml.with_sidekiq ├── docker-compose.yml.with_spring ├── docker-compose.yml.with_tty ├── script │ ├── bootstrap │ ├── console │ ├── server │ ├── setup │ ├── test │ └── update ├── wait-for-postgres └── webpacker │ ├── package.json │ └── yarn.lock ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── application.js │ │ ├── cable.js │ │ └── channels │ │ │ └── .keep │ └── stylesheets │ │ └── application.scss ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── activities_controller.rb │ ├── application_controller.rb │ ├── archived_todos_controller.rb │ ├── concerns │ │ └── .keep │ └── todos_controller.rb ├── helpers │ └── application_helper.rb ├── javascript │ ├── packs │ │ └── application.js │ └── src │ │ ├── _dark_theme.scss │ │ ├── application.scss │ │ └── bootstrap_and_rails.js ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── activity.rb │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── score.rb │ └── todo.rb ├── services │ └── score_calculator.rb └── views │ ├── activities │ ├── _todo_created.html.erb │ ├── _todo_deleted.html.erb │ ├── _todo_marked_as_active.html.erb │ ├── _todo_marked_as_complete.html.erb │ └── index.html.erb │ ├── layouts │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ └── todos │ ├── _form.html.erb │ ├── index.html.erb │ └── update.js ├── bin ├── bundle ├── rails ├── rake ├── rspec ├── setup ├── spring ├── update └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── database.yml.postgres ├── database.yml.sqlite ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── puma.rb ├── routes.rb ├── spring.rb └── storage.yml ├── db ├── development.sqlite3 ├── migrate │ ├── 20190721025643_create_todos.rb │ ├── 20190721162926_add_archived_to_todos.rb │ ├── 20190721171220_create_activities.rb │ └── 20190721200733_create_scores.rb ├── schema.rb ├── seeds.rb └── test.sqlite3 ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package.json ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── spec ├── models │ ├── activity_spec.rb │ └── todo_spec.rb ├── rails_helper.rb ├── requests │ ├── activities_spec.rb │ ├── archived_todos_spec.rb │ └── todos_spec.rb ├── services │ └── score_calculator_spec.rb └── spec_helper.rb ├── storage └── .keep ├── tmp └── .keep └── vendor └── .keep /.dockerignore: -------------------------------------------------------------------------------- 1 | /log/* 2 | /tmp/* 3 | /node_modules 4 | /.bundle 5 | /public/assets 6 | /_*/ 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore uploaded files in development 21 | /storage/* 22 | !/storage/.keep 23 | 24 | /node_modules 25 | /yarn-error.log 26 | 27 | /public/assets 28 | .byebug_history 29 | 30 | # Ignore master key for decrypting credentials and more. 31 | /config/master.key 32 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby "2.6.3" 5 | 6 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 7 | gem "rails", "~> 5.2.3" 8 | # Use sqlite3 as the database for Active Record 9 | gem "sqlite3" 10 | # We will use pg later on to replace sqlite 11 | gem "pg" 12 | # Use Puma as the app server 13 | gem "puma", "~> 3.11" 14 | # Use SCSS for stylesheets 15 | gem "sass-rails", "~> 5.0" 16 | # Use Uglifier as compressor for JavaScript assets 17 | gem "uglifier", ">= 1.3.0" 18 | # See https://github.com/rails/execjs#readme for more supported runtimes 19 | # gem 'mini_racer', platforms: :ruby 20 | 21 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 22 | gem "turbolinks", "~> 5" 23 | # Use CoffeeScript for .coffee assets and views 24 | # gem 'coffee-rails', '~> 4.2' 25 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 26 | # gem 'jbuilder', '~> 2.5' 27 | # Use Redis adapter to run Action Cable in production 28 | # gem 'redis', '~> 4.0' 29 | # Use ActiveModel has_secure_password 30 | # gem 'bcrypt', '~> 3.1.7' 31 | 32 | # Reduces boot times through caching; required in config/boot.rb 33 | gem "bootsnap", ">= 1.1.0", require: false 34 | 35 | # Twitter bootstrap 36 | gem "bootstrap", "~> 4.3.1" 37 | gem "jquery-rails" 38 | 39 | group :development, :test do 40 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 41 | gem "byebug", platforms: [:mri, :mingw, :x64_mingw] 42 | gem "rspec-rails" 43 | gem "spring-commands-rspec" 44 | gem "standard" 45 | end 46 | 47 | group :development do 48 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 49 | gem "web-console", ">= 3.3.0" 50 | gem "listen", ">= 3.0.5", "< 3.2" 51 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 52 | gem "spring" 53 | gem "spring-watcher-listen", "~> 2.0.0" 54 | end 55 | 56 | # We won't run any integration tests for now 57 | # group :test do 58 | # # Adds support for Capybara system testing and selenium driver 59 | # gem 'capybara', '>= 2.15' 60 | # gem 'selenium-webdriver' 61 | # # Easy installation and use of chromedriver to run system tests with Chrome 62 | # gem 'chromedriver-helper' 63 | # end 64 | 65 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 66 | # gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 67 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.2.3) 5 | actionpack (= 5.2.3) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailer (5.2.3) 9 | actionpack (= 5.2.3) 10 | actionview (= 5.2.3) 11 | activejob (= 5.2.3) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.2.3) 15 | actionview (= 5.2.3) 16 | activesupport (= 5.2.3) 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.2.3) 22 | activesupport (= 5.2.3) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.2.3) 28 | activesupport (= 5.2.3) 29 | globalid (>= 0.3.6) 30 | activemodel (5.2.3) 31 | activesupport (= 5.2.3) 32 | activerecord (5.2.3) 33 | activemodel (= 5.2.3) 34 | activesupport (= 5.2.3) 35 | arel (>= 9.0) 36 | activestorage (5.2.3) 37 | actionpack (= 5.2.3) 38 | activerecord (= 5.2.3) 39 | marcel (~> 0.3.1) 40 | activesupport (5.2.3) 41 | concurrent-ruby (~> 1.0, >= 1.0.2) 42 | i18n (>= 0.7, < 2) 43 | minitest (~> 5.1) 44 | tzinfo (~> 1.1) 45 | arel (9.0.0) 46 | ast (2.4.0) 47 | autoprefixer-rails (9.6.1) 48 | execjs 49 | bindex (0.8.1) 50 | bootsnap (1.4.4) 51 | msgpack (~> 1.0) 52 | bootstrap (4.3.1) 53 | autoprefixer-rails (>= 9.1.0) 54 | popper_js (>= 1.14.3, < 2) 55 | sassc-rails (>= 2.0.0) 56 | builder (3.2.3) 57 | byebug (11.0.1) 58 | concurrent-ruby (1.1.5) 59 | crass (1.0.4) 60 | diff-lcs (1.3) 61 | erubi (1.8.0) 62 | execjs (2.7.0) 63 | ffi (1.11.1) 64 | globalid (0.4.2) 65 | activesupport (>= 4.2.0) 66 | i18n (1.6.0) 67 | concurrent-ruby (~> 1.0) 68 | jaro_winkler (1.5.3) 69 | jquery-rails (4.3.5) 70 | rails-dom-testing (>= 1, < 3) 71 | railties (>= 4.2.0) 72 | thor (>= 0.14, < 2.0) 73 | listen (3.1.5) 74 | rb-fsevent (~> 0.9, >= 0.9.4) 75 | rb-inotify (~> 0.9, >= 0.9.7) 76 | ruby_dep (~> 1.2) 77 | loofah (2.2.3) 78 | crass (~> 1.0.2) 79 | nokogiri (>= 1.5.9) 80 | mail (2.7.1) 81 | mini_mime (>= 0.1.1) 82 | marcel (0.3.3) 83 | mimemagic (~> 0.3.2) 84 | method_source (0.9.2) 85 | mimemagic (0.3.3) 86 | mini_mime (1.0.2) 87 | mini_portile2 (2.4.0) 88 | minitest (5.11.3) 89 | msgpack (1.3.0) 90 | nio4r (2.4.0) 91 | nokogiri (1.10.3) 92 | mini_portile2 (~> 2.4.0) 93 | parallel (1.17.0) 94 | parser (2.6.3.0) 95 | ast (~> 2.4.0) 96 | pg (1.1.4) 97 | popper_js (1.14.5) 98 | puma (3.12.1) 99 | rack (2.0.7) 100 | rack-test (1.1.0) 101 | rack (>= 1.0, < 3) 102 | rails (5.2.3) 103 | actioncable (= 5.2.3) 104 | actionmailer (= 5.2.3) 105 | actionpack (= 5.2.3) 106 | actionview (= 5.2.3) 107 | activejob (= 5.2.3) 108 | activemodel (= 5.2.3) 109 | activerecord (= 5.2.3) 110 | activestorage (= 5.2.3) 111 | activesupport (= 5.2.3) 112 | bundler (>= 1.3.0) 113 | railties (= 5.2.3) 114 | sprockets-rails (>= 2.0.0) 115 | rails-dom-testing (2.0.3) 116 | activesupport (>= 4.2.0) 117 | nokogiri (>= 1.6) 118 | rails-html-sanitizer (1.0.4) 119 | loofah (~> 2.2, >= 2.2.2) 120 | railties (5.2.3) 121 | actionpack (= 5.2.3) 122 | activesupport (= 5.2.3) 123 | method_source 124 | rake (>= 0.8.7) 125 | thor (>= 0.19.0, < 2.0) 126 | rainbow (3.0.0) 127 | rake (12.3.2) 128 | rb-fsevent (0.10.3) 129 | rb-inotify (0.10.0) 130 | ffi (~> 1.0) 131 | rspec-core (3.8.2) 132 | rspec-support (~> 3.8.0) 133 | rspec-expectations (3.8.4) 134 | diff-lcs (>= 1.2.0, < 2.0) 135 | rspec-support (~> 3.8.0) 136 | rspec-mocks (3.8.1) 137 | diff-lcs (>= 1.2.0, < 2.0) 138 | rspec-support (~> 3.8.0) 139 | rspec-rails (3.8.2) 140 | actionpack (>= 3.0) 141 | activesupport (>= 3.0) 142 | railties (>= 3.0) 143 | rspec-core (~> 3.8.0) 144 | rspec-expectations (~> 3.8.0) 145 | rspec-mocks (~> 3.8.0) 146 | rspec-support (~> 3.8.0) 147 | rspec-support (3.8.2) 148 | rubocop (0.72.0) 149 | jaro_winkler (~> 1.5.1) 150 | parallel (~> 1.10) 151 | parser (>= 2.6) 152 | rainbow (>= 2.2.2, < 4.0) 153 | ruby-progressbar (~> 1.7) 154 | unicode-display_width (>= 1.4.0, < 1.7) 155 | rubocop-performance (1.4.0) 156 | rubocop (>= 0.71.0) 157 | ruby-progressbar (1.10.1) 158 | ruby_dep (1.5.0) 159 | sass (3.7.4) 160 | sass-listen (~> 4.0.0) 161 | sass-listen (4.0.0) 162 | rb-fsevent (~> 0.9, >= 0.9.4) 163 | rb-inotify (~> 0.9, >= 0.9.7) 164 | sass-rails (5.0.7) 165 | railties (>= 4.0.0, < 6) 166 | sass (~> 3.1) 167 | sprockets (>= 2.8, < 4.0) 168 | sprockets-rails (>= 2.0, < 4.0) 169 | tilt (>= 1.1, < 3) 170 | sassc (2.0.1) 171 | ffi (~> 1.9) 172 | rake 173 | sassc-rails (2.1.2) 174 | railties (>= 4.0.0) 175 | sassc (>= 2.0) 176 | sprockets (> 3.0) 177 | sprockets-rails 178 | tilt 179 | spring (2.1.0) 180 | spring-commands-rspec (1.0.4) 181 | spring (>= 0.9.1) 182 | spring-watcher-listen (2.0.1) 183 | listen (>= 2.7, < 4.0) 184 | spring (>= 1.2, < 3.0) 185 | sprockets (3.7.2) 186 | concurrent-ruby (~> 1.0) 187 | rack (> 1, < 3) 188 | sprockets-rails (3.2.1) 189 | actionpack (>= 4.0) 190 | activesupport (>= 4.0) 191 | sprockets (>= 3.0.0) 192 | sqlite3 (1.4.1) 193 | standard (0.1.0) 194 | rubocop (~> 0.72.0) 195 | rubocop-performance (~> 1.4.0) 196 | thor (0.20.3) 197 | thread_safe (0.3.6) 198 | tilt (2.0.9) 199 | turbolinks (5.2.0) 200 | turbolinks-source (~> 5.2) 201 | turbolinks-source (5.2.0) 202 | tzinfo (1.2.5) 203 | thread_safe (~> 0.1) 204 | uglifier (4.1.20) 205 | execjs (>= 0.3.0, < 3) 206 | unicode-display_width (1.6.0) 207 | web-console (3.7.0) 208 | actionview (>= 5.0) 209 | activemodel (>= 5.0) 210 | bindex (>= 0.4.0) 211 | railties (>= 5.0) 212 | websocket-driver (0.7.1) 213 | websocket-extensions (>= 0.1.0) 214 | websocket-extensions (0.1.4) 215 | 216 | PLATFORMS 217 | ruby 218 | 219 | DEPENDENCIES 220 | bootsnap (>= 1.1.0) 221 | bootstrap (~> 4.3.1) 222 | byebug 223 | jquery-rails 224 | listen (>= 3.0.5, < 3.2) 225 | pg 226 | puma (~> 3.11) 227 | rails (~> 5.2.3) 228 | rspec-rails 229 | sass-rails (~> 5.0) 230 | spring 231 | spring-commands-rspec 232 | spring-watcher-listen (~> 2.0.0) 233 | sqlite3 234 | standard 235 | turbolinks (~> 5) 236 | uglifier (>= 1.3.0) 237 | web-console (>= 3.3.0) 238 | 239 | RUBY VERSION 240 | ruby 2.6.3p62 241 | 242 | BUNDLED WITH 243 | 2.0.2 244 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Julian Fahrer 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 | # Dockerizing Rails - A supercharged development process 2 | 3 | ## Preparing for the workshop 4 | Please make sure to install 5 | * Docker - https://hub.docker.com/search?q=&type=edition&offering=community 6 | * Git - https://git-scm.com/downloads 7 | 8 | Make sure to create a Docker ID if you don't have one already: https://hub.docker.com/signup 9 | 10 | Clone this repository so that we can iterate on it later in the workshop: 11 | ``` 12 | git clone https://github.com/jfahrer/dockerizing_rails 13 | ``` 14 | 15 | ### Testing your Docker installation 16 | Run the following command on you system: 17 | ``` 18 | docker version 19 | ``` 20 | 21 | The output of the command should look similar to this: 22 | ``` 23 | Client: Docker Engine - Community 24 | Version: 18.09.2 25 | API version: 1.39 26 | Go version: go1.10.8 27 | Git commit: 6247962 28 | Built: Sun Feb 10 04:12:39 2019 29 | OS/Arch: darwin/amd64 30 | Experimental: false 31 | 32 | Server: Docker Engine - Community 33 | Engine: 34 | Version: 18.09.2 35 | API version: 1.39 (minimum version 1.12) 36 | Go version: go1.10.6 37 | Git commit: 6247962 38 | Built: Sun Feb 10 04:13:06 2019 39 | OS/Arch: linux/amd64 40 | Experimental: true 41 | ``` 42 | 43 | The important parts here are 44 | * `Version` for the `Client` and `Server` should be `18.09.0` or higher 45 | Please update your Docker installation if you are on an older version. 46 | * Under `Server: Docker Engine - Community` make sure that the `OS/Arch` says `linux/amd64`. 47 | If you are seeing `Windows` in there, please make sure to switch you Docker installation to run Linux: https://docs.docker.com/docker-for-windows/#switch-between-windows-and-linux-containers 48 | 49 | ### Verifying Docker Compose version 50 | Run the following command on you system: 51 | ``` 52 | docker-compose -v 53 | ``` 54 | 55 | The output of the command should look similar to this: 56 | ``` 57 | docker-compose version 1.23.2, build 1110ad01 58 | ``` 59 | 60 | If you are running an older Version of Docker Compose or you are getting a `command not found`, please follow the installation instructions for Docker Compose: https://docs.docker.com/compose/install/ 61 | 62 | 63 | ### Pre-loading images 64 | To save time and bandwidth throughout the workshop, I recommend that you download the container "images" that we will use beforehand. Don't worry if you don't know what an image is yet - we will go over that in the workshop. All you have to do is execute the following commands: 65 | 66 | ``` 67 | docker image pull ubuntu:18.04 68 | docker image pull jfahrer/ruby:2.6.3-alpine3.10-ser 69 | docker image pull redis:5.0 70 | docker image pull postgres:10.6-alpine 71 | ``` 72 | 73 | The output will look similar to this: 74 | ``` 75 | 2.6.3-alpine3.10-ser: Pulling from jfahrer/ruby 76 | bdf0201b3a05: Pull complete 77 | 67a4a175230f: Pull complete 78 | 5b688ca58800: Pull complete 79 | 68bfb7317906: Pull complete 80 | e95e2e8e402a: Pull complete 81 | 5e7827d9c7e8: Pull complete 82 | 4507d0429dd7: Pull complete 83 | 7cba2dc1349a: Pull complete 84 | 61e576b94017: Pull complete 85 | 951629f33eb5: Pull complete 86 | Digest: sha256:b1a4210e93b94e5a09a6e9c8f44c8f0a2aef03c520d6268faa20261c55d6d2b7 87 | Status: Downloaded newer image for jfahrer/ruby:2.6.3-alpine3.10-ser 88 | ``` 89 | 90 | # Assignments 91 | Throughout the workshop you will complete the following assignments: 92 | 93 | * [Assignment 1 - Hello World](_assignments/assignment_01.md) 94 | * [Assignment 2 - Your first image](_assignments/assignment_02.md) 95 | * [Assignment 3 - Running Rails](_assignments/assignment_03.md) 96 | * [Assignment 4 - Talking to a service](_assignments/assignment_04.md) 97 | * [Assignment 5 - Integrating Postgres](_assignments/assignment_05.md) 98 | * [Assignment 6 - Utilizing layers](_assignments/assignment_06.md) 99 | * [Assignment 7 - Glueing things together](_assignments/assignment_07.md) 100 | * [Assignment 8 - Iterating](_assignments/assignment_08.md) 101 | * [Assignment 9 - Debugging](_assignments/assignment_09.md) 102 | * [Assignment 9 - Integrating Sidekiq](_assignments/assignment_10.md) 103 | * [Assignment 11 - Installing Webpacker](_assignments/assignment_11.md) 104 | * [Assignment 12 - Keeping things running with Spring](_assignments/assignment_12.md) 105 | * [Assignment 13 - Scripts To Rule Them All](_assignments/assignment_13.md) 106 | * [Assignment 14 - A seamless experience](_assignments/assignment_14.md) 107 | 108 | # TLDR - I just want to see a dockerized Rails app 109 | Check out the [`dockerized`](https://github.com/jfahrer/dockerizing_rails/tree/dockerized) branch and run `script/setup` to start the application. 110 | 111 | # Learning more 112 | ## Additional topics 113 | There are more topics that didn't quite fit into the workshop. 114 | * Running the application as a different user 115 | * Advanced caching mechanisms for gems and libraries 116 | * Making use of environment variables to change the development environment 117 | * Integration testing with Capybara/Cypress 118 | * Utilizing CI/CD 119 | * Preparing your image for production 120 | * Deploying to Kubernetes 121 | And much more. 122 | 123 | Sign up at [RailsWithDocker.com](https://RailsWithDocker.com) and I'll shoot you an email as soon as the material is ready. 124 | 125 | ## Resources 126 | * https://LearnDocker.online: Free resource with 11+ hours of video content teaching you everything I know about Docker and Containers. 127 | * https://RailsWithDocker.com: Free online resource teaching you how to dockerize a Rails app. It covers everything you learned in this workshop *and* more. 128 | * https://docs.docker.com: The official Docker documentation 129 | 130 | ## Doing more 131 | Nothing better than practicing your newly acquired skills. I suggest that you dockerize one of you applications or extend the demo app with some additional functionality. Here are some ideas: 132 | * Paginate activities and todos 133 | * Add a filter to display archived todos: There is currently no way to show archived todos 134 | * Add a search engine: Use Elasticsearch, Solr, or another searchengine to make todos searchable 135 | * Soft delete todos: We currently allow users to delete todos from the database 136 | * Add a Sidekiq monitor (https://github.com/mperham/sidekiq/wiki/Monitoring): Try to run the monitor in as its own Rack app in its own container 137 | 138 | 139 | # Contributions 140 | Special thanks to [@hjhart](https://github.com/hjhart) and [@palexvs](https://github.com/palexvs) for all their help with this workshop. 141 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /_assignments/assignment_01.md: -------------------------------------------------------------------------------- 1 | # Assignment 1 - Hello World 2 | To run your first container, please execute the following command on your machine: 3 | ``` 4 | docker container run hello-world 5 | ``` 6 | 7 | The output of the command should look similar to this: 8 | ``` 9 | Unable to find image 'hello-world:latest' locally 10 | latest: Pulling from library/hello-world 11 | 1b930d010525: Pull complete 12 | Digest: sha256:2557e3c07ed1e38f26e389462d03ed943586f744621577a99efb77324b0fe535 13 | Status: Downloaded newer image for hello-world:latest 14 | 15 | Hello from Docker! 16 | This message shows that your installation appears to be working correctly. 17 | 18 | To generate this message, Docker took the following steps: 19 | 1. The Docker client contacted the Docker daemon. 20 | 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. 21 | (amd64) 22 | 3. The Docker daemon created a new container from that image which runs the 23 | executable that produces the output you are currently reading. 24 | 4. The Docker daemon streamed that output to the Docker client, which sent it 25 | to your terminal. 26 | 27 | To try something more ambitious, you can run an Ubuntu container with: 28 | $ docker run -it ubuntu bash 29 | 30 | Share images, automate workflows, and more with a free Docker ID: 31 | https://hub.docker.com/ 32 | 33 | For more examples and ideas, visit: 34 | https://docs.docker.com/get-started/ 35 | ``` 36 | 37 | [Back to the overview](../README.md#assignments) 38 | -------------------------------------------------------------------------------- /_assignments/assignment_02.md: -------------------------------------------------------------------------------- 1 | # Assignment 2 - Your first image 2 | In this section, you will create your first container image. Please start by creating an empty directory and change into this directory 3 | ``` 4 | mkdir first_image 5 | cd first_image 6 | ``` 7 | 8 | ### Building the image 9 | In this directory you will create the `Dockerfile` for your first image. Copy and paste the following content: 10 | ```Dockerfile 11 | FROM ubuntu:18.04 12 | 13 | RUN apt-get update && apt-get install -y cowsay 14 | 15 | CMD /usr/games/cowsay "Containers are awesome!" 16 | ``` 17 | 18 | Now, from within directory containing the `Dockerfile`, you can build the container image: 19 | ``` 20 | docker image build -t your_docker_id/cowsay:v1 . 21 | ``` 22 | 23 | The output of the command will look similar to this (note that you might not see the "Pulling from …" part if you followed the getting started part).: 24 | ``` 25 | Sending build context to Docker daemon 2.048kB 26 | Step 1/3 : FROM ubuntu:18.04 27 | 18.04: Pulling from library/ubuntu 28 | 6cf436f81810: Pull complete 29 | 987088a85b96: Pull complete 30 | b4624b3efe06: Pull complete 31 | d42beb8ded59: Pull complete 32 | Digest: sha256:7a47ccc3bbe8a451b500d2b53104868b46d60ee8f5b35a24b41a86077c650210 33 | Status: Downloaded newer image for ubuntu:18.04 34 | ---> 47b19964fb50 35 | Step 2/3 : RUN apt-get update && apt-get install -y cowsay 36 | ---> Running in 28d176829ae1 37 | Get:1 http://security.ubuntu.com/ubuntu bionic-security InRelease [88.7 kB] 38 | Get:2 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB] 39 | Get:3 http://security.ubuntu.com/ubuntu bionic-security/main amd64 Packages [339 kB] 40 | Get:4 http://archive.ubuntu.com/ubuntu bionic-updates InRelease [88.7 kB] 41 | Get:5 http://archive.ubuntu.com/ubuntu bionic-backports InRelease [74.6 kB] 42 | Get:6 http://archive.ubuntu.com/ubuntu bionic/universe amd64 Packages [11.3 MB] 43 | Get:7 http://security.ubuntu.com/ubuntu bionic-security/multiverse amd64 Packages [3451 B] 44 | Get:8 http://security.ubuntu.com/ubuntu bionic-security/universe amd64 Packages [147 kB] 45 | Get:9 http://archive.ubuntu.com/ubuntu bionic/main amd64 Packages [1344 kB] 46 | Get:10 http://archive.ubuntu.com/ubuntu bionic/restricted amd64 Packages [13.5 kB] 47 | Get:11 http://archive.ubuntu.com/ubuntu bionic/multiverse amd64 Packages [186 kB] 48 | Get:12 http://archive.ubuntu.com/ubuntu bionic-updates/multiverse amd64 Packages [6955 B] 49 | Get:13 http://archive.ubuntu.com/ubuntu bionic-updates/restricted amd64 Packages [10.7 kB] 50 | Get:14 http://archive.ubuntu.com/ubuntu bionic-updates/universe amd64 Packages [929 kB] 51 | Get:15 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 Packages [679 kB] 52 | Get:16 http://archive.ubuntu.com/ubuntu bionic-backports/universe amd64 Packages [4690 B] 53 | Fetched 15.5 MB in 6s (2466 kB/s) 54 | Reading package lists... 55 | Reading package lists... 56 | Building dependency tree... 57 | Reading state information... 58 | The following additional packages will be installed: 59 | libgdbm-compat4 libgdbm5 libperl5.26 libtext-charwidth-perl netbase perl 60 | perl-modules-5.26 61 | Suggested packages: 62 | filters cowsay-off gdbm-l10n perl-doc libterm-readline-gnu-perl 63 | | libterm-readline-perl-perl make 64 | The following NEW packages will be installed: 65 | cowsay libgdbm-compat4 libgdbm5 libperl5.26 libtext-charwidth-perl netbase 66 | perl perl-modules-5.26 67 | 0 upgraded, 8 newly installed, 0 to remove and 2 not upgraded. 68 | Need to get 6563 kB of archives. 69 | After this operation, 41.7 MB of additional disk space will be used. 70 | Get:1 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 perl-modules-5.26 all 5.26.1-6ubuntu0.3 [2763 kB] 71 | Get:2 http://archive.ubuntu.com/ubuntu bionic/main amd64 libgdbm5 amd64 1.14.1-6 [26.0 kB] 72 | Get:3 http://archive.ubuntu.com/ubuntu bionic/main amd64 libgdbm-compat4 amd64 1.14.1-6 [6084 B] 73 | Get:4 http://archive.ubuntu.com/ubuntu bionic-updates/main amd64 libperl5.26 amd64 5.26.1-6ubuntu0.3 [3527 kB] 74 | [... SNIP ...] 75 | Unpacking cowsay (3.03+dfsg2-4) ... 76 | Setting up perl-modules-5.26 (5.26.1-6ubuntu0.3) ... 77 | Setting up libgdbm5:amd64 (1.14.1-6) ... 78 | Processing triggers for libc-bin (2.27-3ubuntu1) ... 79 | Setting up libtext-charwidth-perl (0.04-7.1) ... 80 | Setting up libgdbm-compat4:amd64 (1.14.1-6) ... 81 | Setting up netbase (5.4) ... 82 | Setting up libperl5.26:amd64 (5.26.1-6ubuntu0.3) ... 83 | Setting up perl (5.26.1-6ubuntu0.3) ... 84 | Setting up cowsay (3.03+dfsg2-4) ... 85 | Processing triggers for libc-bin (2.27-3ubuntu1) ... 86 | Removing intermediate container 28d176829ae1 87 | ---> c14904ec9828 88 | Step 3/3 : CMD /usr/games/cowsay "Containers are awesome!" 89 | ---> Running in 487bea9e4c74 90 | Removing intermediate container 487bea9e4c74 91 | ---> 760f3018b10b 92 | Successfully built 760f3018b10b 93 | Successfully tagged your_docker_id/cowsay:v1 94 | ``` 95 | 96 | 97 | After the build process finishes, you can verify that the build was successful and that image exists on your system: 98 | ``` 99 | docker image ls # to list all images 100 | docker image ls your_docker_id/cowsay # to list images in the `your_docker_id/cowsay` repository 101 | ``` 102 | 103 | ### Using the image 104 | We can now run a container based on this image: 105 | ``` 106 | docker container run your_docker_id/cowsay:v1 107 | ``` 108 | 109 | You should see they cow saying `Containers are awesome!`: 110 | ``` 111 | _________________________ 112 | < Containers are awesome! > 113 | ------------------------- 114 | \ ^__^ 115 | \ (oo)\_______ 116 | (__)\ )\/\ 117 | ||----w | 118 | || || 119 | ``` 120 | 121 | 122 | ### Running other commands 123 | Time to play around for a bit! We can execute arbitrary commands in a container - all we have to do is to __append the command after the image name__. For example: 124 | ``` 125 | docker container run your_docker_id/cowsay:v1 echo "Running the echo command in the cowsay image" 126 | ``` 127 | 128 | This will print `Running the echo command in the cowsay image`. The output was produced by the `echo` executable that is part of the `your_docker_id/cowsay:v1` image. In fact, we "inherited" this executable from the `ubuntu:18.04` image. 129 | 130 | ### Missing commands 131 | The executable for whatever command we run in the container must be present in the container image. This means even with Ruby installed on our local system, __the following command will fail__: 132 | ``` 133 | docker container run your_docker_id/cowsay:v1 ruby -v 134 | ``` 135 | 136 | ### Interactive programs 137 | Let's try something else and start an interactive program like a shell in a container. We already know that we can specify which command we want to run in the container. However, for interactive applications like a shell, we have to specify the `-it` flags *before* the image name: 138 | ``` 139 | docker container run -it your_docker_id/cowsay:v1 bash 140 | ``` 141 | 142 | This command will open a shell prompt in a new container. Try it out - you will be able to navigate the containers file system and execute arbitrary commands as well. For example: 143 | ``` 144 | uname -a 145 | ls -l /usr/bin 146 | whoami 147 | ``` 148 | 149 | Once you are done, you can exit the shell and quit the container by pressing `Ctrl-D` 150 | 151 | [Back to the overview](../README.md#assignments) 152 | -------------------------------------------------------------------------------- /_assignments/assignment_03.md: -------------------------------------------------------------------------------- 1 | # Assignment 3 - Running Rails 2 | 3 | For this and the remaining assignments, we will work with the demo application that is part of this repository. Make sure to __*change into the directory that you cloned this repository into*__ (see [here](../README.md#getting-started)). 4 | 5 | ## The Dockerfile 6 | Just as before, we will create a `Dockerfile` **inside the demo applications directory**: 7 | 8 | ```Dockerfile 9 | FROM jfahrer/ruby:2.6.3-alpine3.10-ser 10 | 11 | RUN apk add --update --no-cache \ 12 | bash \ 13 | build-base \ 14 | nodejs \ 15 | sqlite-dev \ 16 | tzdata \ 17 | postgresql-dev 18 | 19 | RUN gem install bundler:2.0.2 20 | 21 | WORKDIR /usr/src/app 22 | 23 | COPY . . 24 | 25 | RUN bundle install 26 | 27 | CMD ["rails", "console"] 28 | ``` 29 | 30 | For convenience, I already put a `.dockerignore` file in place. Similar to a `.gitignore` file, the files and directories listed in the `.dockerignore` file will be ignored when copying data into the container image. Feel free to open the `.dockerignore` file and take a look. 31 | 32 | With the `Dockerfile` in place we can go ahead and build the image: 33 | ``` 34 | docker image build -t your_docker_id/rails_app:v1 . 35 | ``` 36 | 37 | Once the command completed successfully, we should be able to start a rails console in a container: 38 | ``` 39 | docker container run -it your_docker_id/rails_app:v1 40 | ``` 41 | 42 | Feel free to play around with the Rails environment for a bit. You should for example be able to create some records in the database: 43 | ``` 44 | Todo.create!(title: "laundry") 45 | ``` 46 | 47 | Press `Ctrl-D` to quit the rails console and terminate the container. 48 | 49 | > **Note**: Wondering why you didn't have to create/migrate the database? The data is currently stored in SQLite database which is part of this repository and hence part of the container image as well. 50 | 51 | 52 | ## Running the tests 53 | As we've seen before, we can run arbitrary commands in the context of the container image by appending the command after the image name. We can use this technique to execute the test suite using `rspec`: 54 | ``` 55 | docker container run -it your_docker_id/rails_app:v1 rspec 56 | ``` 57 | 58 | You will see that __one of the tests will fail__ - that is OK and expected! You will get the time to fix the failing test later in the workshop. 59 | 60 | > **Note**:*__ We don't need the `-it` flags here because we don't run an interactive program like the Rails console. However, we will only see the colors in the output with the `-t` flag. As there is no harm in adding those flags, we will keep doing this from there on even if we start non-interactive programs. 61 | 62 | ## Finding out what is going on 63 | The Docker CLI is pretty straight forward. To get more information about what is possible, try just typing `docker`. 64 | You will see a list of `Commands` and `Management Commands`. To get more information about a command, just append `--help`. For example 65 | ``` 66 | docker container run --help 67 | ``` 68 | 69 | Here are a few useful commands that you should try out before moving on to the next exercise. 70 | ``` 71 | docker container ls # List all running containers 72 | docker container ls -a # List all containers - running or not 73 | docker container rm # Delete a container. Use the `-f` flag to delete a running container 74 | docker container stop # Stop a running container 75 | docker container kill # Kill a running container 76 | ``` 77 | 78 | Go ahead an try them out! It might make sense to use a second terminal to run the `stop` / `kill` / `ls` / `rm` commands while you keep a container running in the other terminal. 79 | 80 | ## Cleaning up 81 | Now that we are done, it is time to clean up a little bit. 82 | 83 | If you care about any of the containers on your system, delete just the ones you don't need using `docker container ls -a` and `docker container rm`. 84 | 85 | If you never used Docker before or don't care about any of the containers on your system, you can run: 86 | 87 | ``` 88 | docker container prune 89 | ``` 90 | 91 | *__!!Attention!!__* This will delete all stopped containers on your system. 92 | 93 | 94 | # What changed 95 | You can find our changes in the [`initial_dockerfile`](https://github.com/jfahrer/dockerizing_rails/tree/initial_dockerfile) branch. [Compare it](https://github.com/jfahrer/dockerizing_rails/compare/initial_dockerfile) to the previous branch to see what changed. 96 | 97 | [Back to the overview](../README.md#assignments) 98 | -------------------------------------------------------------------------------- /_assignments/assignment_04.md: -------------------------------------------------------------------------------- 1 | # Assignment 4 - Talking to a service 2 | In order to be able to interact with our Rails application over http, we need to publish port `3000`. The following command should do the trick: 3 | ``` 4 | docker container run -it -p 3000:3000 your_docker_id/rails_app:v1 rails s -b 0.0.0.0 5 | ``` 6 | 7 | As we've seen in prior assignments, we just have to append the command (`rails s`) after the image name. The `-p 3000:3000` flag makes sure that traffic that is received by the Docker Host on port 3000 is forwarded to the container on port 3000. 8 | 9 | Open the browser and try to navigate to http://localhost:3000. You should see the Web UI of our demo application. 10 | 11 | Back on you command line you should see the output of the Rails server. 12 | 13 | Go ahead and try to create a few books via the web interface. 14 | 15 | Once you are done, you can terminate Rails and the container by pressing `Ctrl-C`. 16 | 17 | > **Note**: Accessing a service inside a container will NOT work of the service only listens on `localhost`. In other words, you have to make sure that the service listens on the outwards facing ethernet interfaces of the container. We told our Rails application to listen on `0.0.0.0` which is an alias for all interfaces. 18 | 19 | > **Note**: When using `-p 3000:3000` you instruct Docker to listen on all interfaces on your local machine. That means that your rails service will be accessible from other machines. Not what you want? Try `-p 127.0.0.1:3000:3000` instead to only listen on `localhost`. 20 | 21 | ## Enhancing the Dockerfile 22 | Let's make some changes to our Dockerfile to make our life a little easier and safe us some typing. **Before we get started, make sure to terminate any running containers**. 23 | 24 | Here is the updated version of the Dockerfile: 25 | ```Dockerfile 26 | FROM jfahrer/ruby:2.6.3-alpine3.10-ser 27 | 28 | RUN apk add --update --no-cache \ 29 | bash \ 30 | build-base \ 31 | nodejs \ 32 | sqlite-dev \ 33 | tzdata \ 34 | postgresql-dev 35 | 36 | RUN gem install bundler:2.0.2 37 | 38 | WORKDIR /usr/src/app 39 | 40 | COPY . . 41 | 42 | RUN bundle install 43 | 44 | EXPOSE 3000 45 | 46 | CMD ["rails", "server", "-b", "0.0.0.0"] 47 | ``` 48 | 49 | New here is the `EXPOSE` instructions. `EXPOSE` adds metadata to the image that tells Docker that the service provided by this image will listen on port 3000. This is not required, but it allows other users to properly run the container and publish the correct port. 50 | 51 | We also changed the `CMD` instruction. The `rails server` with the required arguments is not the defeault command that will be executed when a container based on the image starts. 52 | 53 | With the changes in place, we have to rebuild our image: 54 | ``` 55 | docker image build -t your_docker_id/rails_app:v1 . 56 | ``` 57 | 58 | Now we can start a container without specifying that we want to run `rails server`: 59 | ``` 60 | docker container run -it -p 3000:3000 your_docker_id/rails_app:v1 61 | ``` 62 | 63 | ## Things to try 64 | * What will happen if you try to start a second container while the first one is still running? 65 | * Stop the current container by pressing `Ctrl-C`. Then start a new container with the same command. Is the data still there? 66 | * Try to figure out how to revive the old container that you stopped earlier. `docker container --help` is your friend. Do you see the data again? 67 | 68 | ## Troubleshooting 69 | If you receive an error message similar to this one: 70 | ``` 71 | docker: Error response from daemon: driver failed programming external connectivity on endpoint elastic_robinson (1e9b864796702b7909a3247a04809fe053885367b152d2385ce7ddeab364b6d5): Bind for 0.0.0.0:3000 failed: port is already allocated. 72 | ERRO[0000] error waiting for container: context canceled 73 | ``` 74 | You are already running something on port 3000. Either stop the service that is bound to port 3000 or change the local port: 75 | ``` 76 | docker container run -it -p 3001:3000 your_docker_id/rails_app:v1 rails s -b 0.0.0.0 77 | ``` 78 | 79 | 80 | # What changed 81 | You can find our changes in the [`rails_server`](https://github.com/jfahrer/dockerizing_rails/tree/rails_server) branch. [Compare it](https://github.com/jfahrer/dockerizing_rails/compare/initial_dockerfile...rails_server) to the previous branch to see what changed. 82 | 83 | [Back to the overview](../README.md#assignments) 84 | -------------------------------------------------------------------------------- /_assignments/assignment_05.md: -------------------------------------------------------------------------------- 1 | # Assignment 5 - integrating Postgres 2 | Before we start, let's clean up. Make sure to stop all containers and _optionally_ delete them. Either use `docker container ls -a` and `docker container rm` OR `docker container prune` (*if you don't care about any of the containers on your system*) 3 | 4 | ## Configuring the application 5 | Currently, our Rails app is using SQLite as a DBMS. We're going to switch to PostgresSQL and need to update our `database.yml`. An updated `database.yml` can be found in the `config` directory 6 | ``` 7 | cp config/database.yml.postgres config/database.yml 8 | ``` 9 | 10 | Make sure to take a look at the updated `database.yml`. As you can see, we are utilizing environment variables to configure our connection to PostgreSQL. 11 | 12 | Since we made changes to the source code of our application, __we need to rebuild the image__: 13 | ``` 14 | docker image build -t your_docker_id/rails_app:v1 . 15 | ``` 16 | 17 | ## Running Postgres 18 | Now we can start our Postgres instance: 19 | ``` 20 | docker container run --name pg -d \ 21 | -v ws-pg-data:/var/lib/postgresql/data \ 22 | -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=secret postgres:10.6-alpine 23 | ``` 24 | 25 | Let's dig a little into the flags we used to start the container: 26 | * The `--name` flag gives our container a name. This way we can easily start/stop it and reference it from other containers. 27 | * `-d` starts the container in `detatched` mode. That means we won't be attached to `STDOUT` of the container. Try playing around `docker container attach` and `docker container logs` to see what is going on. Don't forget the `--help` flag if you want to find out more about those commands 28 | * With `-v ws-pg-data:/var/lib/postgresql/data`, we make sure that the data that Postgres writes to disk is persisted outside of the container. Docker will create a volume named `ws-pg-data` and mount it into the container. 29 | * `-e` allows us to pass in environment variables that will be available to our container. We use this flag to configure the credentials for Postgres. 30 | 31 | Make sure that the container is up and running by running the `docker container ls` command. You should see a container with the name `pg` in the output: 32 | ``` 33 | $ docker container ls 34 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 35 | 66bbf21aedc0 postgres:10.6-alpine "docker-entrypoint.s…" 17 seconds ago Up 16 seconds 5432/tcp pg 36 | ``` 37 | 38 | 39 | ### Talking to the service 40 | Now that we have our Postgres container up and running, we need to start our Rails application. We already built the new image with the correct `database.yml` in place. If you take a quick look at the `database.yml`, you will see that we set the connection information via environment variables. We need to pass the correct environment variables to our `docker container run` command using the `-e` flag: 41 | ``` 42 | docker container run -it -p 127.0.0.1:3000:3000 --link pg \ 43 | -e POSTGRES_HOST=pg -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=secret \ 44 | your_docker_id/rails_app:v1 45 | ``` 46 | 47 | The environment variables that we pass in are used to configure the connection to our Postgres database. These environment variables are read in out `database.yml`. 48 | 49 | You might also have spotted the `--link` flag in our command. This flag allows us to talk to our Postgres container by its name `pg`. 50 | 51 | > **Note**: We don't need to tell Docker which command we want to execute in the container because we made `rails server` the default in the prior assignment. 52 | 53 | > **Note**: `--link` is deprecated and we will learn about a better mechanism later in the workshop. However, it is the easiest way to get the app up and running for now. 54 | 55 | If you open your browser and go to http://localhost:3000, you will see that we need to migrate our database. So let's do that in another container (in another shell): 56 | ``` 57 | docker container run -it --link pg \ 58 | -e POSTGRES_HOST=pg -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=secret \ 59 | your_docker_id/rails_app:v1 rake db:create db:migrate 60 | ``` 61 | 62 | With the database schema in place you should be able to to interact with our application through the web interface. 63 | 64 | Feel free to terminate your Rails and Postgres containers and restart them. Your data will still be there! 65 | 66 | Let's start by stopping Postgres 67 | ``` 68 | docker container stop pg 69 | docker container rm pg 70 | ``` 71 | 72 | The volume `ws-pg-data` will still be there. You can verify this via: 73 | ``` 74 | docker volume ls 75 | ``` 76 | 77 | ### Bonus 78 | * Try to run the test suite with the Postgres connection in place. 79 | * Start a Rails console in a second container and create some records. They should show up in the Web UI. 80 | 81 | ## Troubleshooting 82 | Things are going wrong? Let's make them work! Here are a couple of things to try 83 | * Make sure you copied the database.yml AND rebuild the image? 84 | * Remove the Postgres container and volume: 85 | ``` 86 | docker container rm -f pg 87 | docker volume rm ws-pg-data 88 | ``` 89 | 90 | And then try it again 91 | 92 | * Check the logs for the Postgres container 93 | ``` 94 | docker container logs pg 95 | ``` 96 | 97 | * Check if the expected containers are up and running 98 | ``` 99 | docker container ls -a 100 | docker volume ls 101 | ``` 102 | 103 | * Delete all the containers and volumes and start over (__be careful__ - this will do exactly that): 104 | ``` 105 | docker container ls -a | xargs docker container rm 106 | docker volume ls | xargs docker volume rm 107 | ``` 108 | 109 | # What changed 110 | You can find our changes in the [`integrating_postgres`](https://github.com/jfahrer/dockerizing_rails/tree/integrating_postgres) branch. [Compare it](https://github.com/jfahrer/dockerizing_rails/compare/rails_server...integrating_postgres) to the previous branch to see what changed. 111 | 112 | [Back to the overview](../README.md#assignments) 113 | -------------------------------------------------------------------------------- /_assignments/assignment_06.md: -------------------------------------------------------------------------------- 1 | # Assignment 6 - Utilizing layers 2 | In order to speed up our build process, we should take advantage of Docker's build cache. Right now we re-install all gems whenever we make a change to our source code and rebuild the image. That takes time and is annoying. We can easily work around this by copying the `Gemfile` and `Gemfile.lock` separately. Change your Dockerfile so that it looks more like this: 3 | ```Dockerfile 4 | FROM jfahrer/ruby:2.6.3-alpine3.10-ser 5 | 6 | RUN apk add --update --no-cache \ 7 | bash \ 8 | build-base \ 9 | nodejs \ 10 | sqlite-dev \ 11 | tzdata \ 12 | postgresql-dev 13 | 14 | RUN gem install bundler:2.0.2 15 | 16 | WORKDIR /usr/src/app 17 | 18 | COPY Gemfile Gemfile.lock ./ 19 | 20 | RUN bundle install 21 | 22 | COPY . . 23 | 24 | EXPOSE 3000 25 | 26 | CMD ["rails", "server", "-b", "0.0.0.0"] 27 | ``` 28 | 29 | The key change here is that we copy our `Gemfile` and `Gemfile.lock` separately and run bundle install /before/ we copy all of the source code. As long as the two haven't changed, we can use the intermediate image from the build cache. 30 | 31 | Rebuild the image with the changes in place: 32 | ``` 33 | docker image build -t your_docker_id/rails_app:v1 . 34 | ``` 35 | 36 | To make sure it works, let's make a small change to our source code. Simply add or change some text in `app/views/books/index.html.erb` and then rebuild the image. You should see that the `RUN bundle install` instruction is not executed but retrieved from the cache. 37 | 38 | ## Bonus 39 | * Try invalidating the build cache by adding an empty line to your `Gemfile` and then rebuild the image. 40 | 41 | 42 | # What changed 43 | You can find our changes in the [`utilizing_layers`](https://github.com/jfahrer/dockerizing_rails/tree/utilizing_layers) branch. [Compare it](https://github.com/jfahrer/dockerizing_rails/compare/integrating_postgres...utilizing_layers) to the previous branch to see what changed. 44 | 45 | [Back to the overview](../README.md#assignments) 46 | -------------------------------------------------------------------------------- /_assignments/assignment_07.md: -------------------------------------------------------------------------------- 1 | # Assignment 7 - Glueing things together 2 | 3 | ## Preparing the Compose file 4 | Now that we have a clean environment, let's put the `docker-compose.yml` in place: 5 | ``` 6 | cp _examples/docker-compose.yml . 7 | ``` 8 | 9 | Take some time to get familiar with the content of the file. 10 | 11 | ## Starting the services 12 | Open the file and make the changes in the appropriate places. This should just be the name of the image that will be used. Once you are done, go ahead and start the environment with: 13 | ``` 14 | docker-compose up -d 15 | ``` 16 | 17 | The `-d` flag will start all services in the background. This allows us to keep working in our current shell. 18 | 19 | We can make sure that all services are running with 20 | ``` 21 | docker-compose ps 22 | ``` 23 | You should see two containers. The `State` column should say `Up`. 24 | 25 | In order to see what our containers tell us on `STDOUT` we can use the following command: 26 | ``` 27 | docker-compose logs 28 | ``` 29 | 30 | We can also append some additional flags to the `docker-compose logs` command. Use the `--help` option to find out more. Getting to know the `docker-compose logs` command will be __very help for debugging any issues__ that come up. 31 | 32 | ## Getting the app up and running 33 | Try browsing http://localhost:3000 - you will see that the database has not been migrated. That makes sense, we just created a whole new set of containers and volumes. 34 | 35 | > **Note**: Docker Compose automatically prefixes all resources with the project name (the name of the current directory per default). 36 | 37 | So let's run the migrations. Just as with the Docker CLI, we can run arbitrary commands in the context of a service: 38 | ``` 39 | docker-compose run --rm app rake db:create db:migrate db:test:prepare 40 | ``` 41 | 42 | Now the web application should work as expected. The `--rm` in the command deletes the container right after it terminates. This is useful because we don't have to clean up after ourselves. Go ahead and browse http://localhost:3000 to verify it works as expected. 43 | 44 | Let's try some other things - this time we will run the test suite: 45 | ``` 46 | docker-compose run --rm app rspec 47 | ``` 48 | 49 | And we can easily start a rails console as well: 50 | ``` 51 | docker-compose run --rm app rails c 52 | ``` 53 | 54 | > **Note**: We don't have to specify the `-it` flags with `docker-compose run`. Docker Compose takes care of that for us automatically when using the `run` command. 55 | 56 | 57 | ### Shutting things down 58 | You can stop everything by running 59 | ``` 60 | docker-compose down 61 | ``` 62 | 63 | This will delete the container, but keep the volumes intact. Use the `-v` flag if you also want to delete the volumes defined in the `docker-compose.yml`. 64 | 65 | ## Building images 66 | Docker Compose also enables us to build our images with a single command: 67 | ``` 68 | docker-compose build 69 | ``` 70 | 71 | Docker Compose will build the image for the service `app` because we specified the `build` directive. It automatically tags the image with the name specified by the `image` directive. 72 | 73 | We can combine building the image with the `up` command as well: 74 | ``` 75 | docker-compose up -d --build 76 | ``` 77 | 78 | ### Bonus 79 | * What will happen if you just run the following command? Any idea why? 80 | ``` 81 | docker-compose run --rm app 82 | ``` 83 | * Import seed data by running `rake db:seed` 84 | * What happens when you shut down the services with `docker-compose down` and then run the test suite? 85 | 86 | 87 | # What changed 88 | You can find our changes in the [`glueing_things_together`](https://github.com/jfahrer/dockerizing_rails/tree/glueing_things_together) branch. [Compare it](https://github.com/jfahrer/dockerizing_rails/compare/utilizing_layers...glueing_things_together) to the previous branch to see what changed. 89 | 90 | [Back to the overview](../README.md#assignments) 91 | -------------------------------------------------------------------------------- /_assignments/assignment_08.md: -------------------------------------------------------------------------------- 1 | # Assignment 8 - Iterating 2 | Rebuilding the image every time we make changes is tedious and slows us down in our development workflow. But Docker wouldn't be Docker if we couldn't work around this problem. The solution here are so called bind mounts. A bind mount allows us to mount a local file or directory into the containers file system. For the Docker CLI, we can specify the bind mount using the `-v` flag. With Docker Compose, we simply add the definition the `docker-compose.yml` via the `volumes` directive. Here is an example: 3 | 4 | ```yaml 5 | app: 6 | image: your_docker_id/rails_app:v1 7 | build: 8 | context: . 9 | environment: 10 | - POSTGRES_HOST=pg 11 | - POSTGRES_USER=postgres 12 | - POSTGRES_PASSWORD=secret 13 | - RAILS_ENV 14 | volumes: 15 | - ./:/usr/src/app:cached 16 | ports: 17 | - 127.0.0.1:3000:3000 18 | ``` 19 | 20 | The key part here is the `volumes` definition: 21 | ```yaml 22 | volumes: 23 | - ./:/usr/src/app:cached 24 | ``` 25 | 26 | We instruct Docker Compose to mount the current local working directory (`./`) to the `/usr/src/app` directory in the container. The `/usr/src/app` directory in the image contains a copy of our source code. We essentially just "replace" the content with what is currently on our local file system. 27 | 28 | > **Note**: The `cached` options will [increase the performance](https://docs.docker.com/docker-for-mac/osxfs-caching/) of the bind mount on MacOS. Unlike on Linux, there is some overhead when using bind mounts on MacOS. Remember, Linux containers need to run on Linux and Docker will setup a virtual machine running Linux on your Mac. Getting the data from your Mac into the virtual machine requires a shared file system called [`osxfs`](https://docs.docker.com/docker-for-mac/osxfs/). There are significant overheads to guaranteeing perfect consistency and the `cached` options looses up the those guarantees. We don't require perfect consistency for our use case: Mounting our source code into the container. 29 | 30 | In addition to the bind mount we will also add a volume for our `tmp/` directory. This is not strictly required but recommended for the following reasons: 31 | * On MacOS a volume will be a lot faster than a bind mount. Since we Rails will read and write temporary and cache data to `tmp/` we want to give ourselves maximum speed. 32 | * We don't usually access anything in the `tmp/` directory locally, so there is no reason to write the data back to our Docker Host. 33 | * [Bootsnap](https://github.com/Shopify/bootsnap) doesn't seem to [play nice](https://github.com/Shopify/bootsnap/issues/177) with the `cached` option and using a dedicated volume solves this problem for us. 34 | 35 | In our `app` service definition we will mount a volume to `/usr/src/app/tmp`: 36 | ```yaml 37 | volumes: 38 | - ./:/usr/src/app:cached 39 | - tmp:/usr/src/app/tmp 40 | ``` 41 | 42 | Since we are using a named volume we also have to add it to the `volumes` section at the bottom of our `docker-compose.yml`: 43 | ```yaml 44 | volumes: 45 | pg-data: 46 | tmp: 47 | ``` 48 | 49 | There is one more change we have to make to our `Dockerfile`. Since every container has its own process tree by default, the Rails server will always be PID 1. Puma, our Web Server, stores a PID-file in `tmp/`. This leads to issues when we restart our container: Puma will see the PID file and because there is already a process with the PID 1 (Puma itself), Puma will refuse to start and exit. We can work around this issue by appending the `--pid=/tmp/server.pid` flag to `rails server`. In order to do that we will change the `CMD` instruction in the `Dockerfile` like so: 50 | 51 | ```Dockerfile 52 | CMD ["rails", "server", "-b", "0.0.0.0", "--pid=/tmp/server.pid"] 53 | ``` 54 | 55 | Check out `_examples/docker-compose.yml.with_bind_mount` and `_examples/Dockerfile.with_server_pid` for complete examples. 56 | 57 | 58 | We can now restart our containers and rebuild the image in one go with: 59 | ``` 60 | docker-compose up -d --build 61 | ``` 62 | 63 | Docker Compose will pick up our changes and re-create the container for our `app` service and create the new `tmp` volume. Make sure that everything is running with `docker-compose ps` and use `docker-compose logs` to troubleshoot any issues. 64 | 65 | With the bind mount in place, we can start iterating on our application. Here are a few things you can try: 66 | * Make some changes to `app/views/todos/index.html.erb` or `app/views/todos/_form.html.erb`. You can for example change a copy or the class of a submit button (try `btn-secondary` instead of `btn-primary`) 67 | * Create a new model using the Rails generators 68 | * Add a `presence` validation to `app/models/activity.rb` for the `data` field. 69 | 70 | You should see the changes being reflected in the already running containers without the need to rebuild or restart anything. 71 | 72 | # What changed 73 | You can find our changes in the [`iterating`](https://github.com/jfahrer/dockerizing_rails/tree/iterating) branch. [Compare it](https://github.com/jfahrer/dockerizing_rails/compare/glueing_things_together...iterating) to the previous branch to see what changed. 74 | 75 | [Back to the overview](../README.md#assignments) 76 | -------------------------------------------------------------------------------- /_assignments/assignment_09.md: -------------------------------------------------------------------------------- 1 | # Assignment 9 - Debugging 2 | 3 | Now that we can iterate quickly on our application, it is time to fix the failing spec! 4 | 5 | Let's rerun the specs with `docker-compose run --rm app rspec spec/` and look at the error message for the failing spec: 6 | ``` 7 | 1) Todos PATCH /todos/:id with valid params creates an entry in the activity log 8 | Failure/Error: 9 | expect { 10 | patch todo_path(todo), params: {id: todo.to_param, todo: new_attributes} 11 | }.to change(Activity, :count).by(1) 12 | 13 | expected `Activity.count` to have changed by 1, but was changed by 0 14 | # ./spec/requests/todos_spec.rb:123:in `block (4 levels) in
' 15 | ``` 16 | 17 | It tells us that updating a todo (e.g. marking a todo as complete) does not create an entry in our activity log. We can confirm this is an actual problem by: 18 | * Opening the browser and heading to http://localhost:3000 19 | * Creating a todo 20 | * Marking the todo as complete 21 | * Heading to the activity log 22 | 23 | In the activity log we can see that we only have an entry for the creation of our todo but not for marking it as complete. 24 | 25 | ## What now? 26 | One way to troubleshoot this problem is to use a debugger like `byebug`. Let's open the file `./app/controllers/todos_controller.rb` and place a `byebug` in the first line of the `update` action: 27 | 28 | ``` 29 | # PATCH/PUT /todos/1 30 | def update 31 | byebug 32 | if @todo.update(update_params) && @todo.completed_changed? 33 | activity_name = @todo.completed ? "todo_marked_as_complete" : "todo_marked_as_active" 34 | ... 35 | ``` 36 | 37 | If we now rerun the failing spec with `docker-compose run --rm app rspec ./spec/requests/todos_spec.rb:123` we will be presented with a `byebug` prompt: 38 | 39 | ``` 40 | Run options: include {:locations=>{"./spec/requests/todos_spec.rb"=>[123]}} 41 | 42 | [18, 27] in /usr/src/app/app/controllers/todos_controller.rb 43 | 18: end 44 | 19: 45 | 20: # PATCH/PUT /todos/1 46 | 21: def update 47 | 22: byebug 48 | => 23: if @todo.update(update_params) && @todo.completed_changed? 49 | 24: activity_name = @todo.completed ? "todo_marked_as_complete" : "todo_marked_as_active" 50 | 25: Activity.create(name: activity_name, data: {id: @todo.id, title: @todo.title}) 51 | 26: end 52 | 27: 53 | ``` 54 | 55 | If we execute the individual parts of the conditional, we can see that `@todo.update(update_params)` returns `true` but `@todo.completed_changed?` returns `false`. The `update_params` however contain `{"completed"=>"true"}` and we know the todo was not completed prior based on our test setup. So the issue must be withing the `@todo.completed_changed?`. If we inspect `@todo.changes` we get back an empty hash which indicates that there are no changes. 56 | 57 | ``` 58 | Run options: include {:locations=>{"./spec/requests/todos_spec.rb"=>[123]}} 59 | 60 | [18, 27] in /usr/src/app/app/controllers/todos_controller.rb 61 | 18: end 62 | 19: 63 | 20: # PATCH/PUT /todos/1 64 | 21: def update 65 | 22: byebug 66 | => 23: if @todo.update(update_params) && @todo.completed_changed? 67 | 24: activity_name = @todo.completed ? "todo_marked_as_complete" : "todo_marked_as_active" 68 | 25: Activity.create(name: activity_name, data: {id: @todo.id, title: @todo.title}) 69 | 26: end 70 | 27: 71 | (byebug) @todo.update(update_params) 72 | true 73 | (byebug) @todo.completed_changed? 74 | false 75 | (byebug) update_params 76 | "true"} permitted: true> 77 | (byebug) @todo.changes 78 | {} 79 | ``` 80 | 81 | This happens because the `update` call actually persists the changes to the database and also clears the `changes` hash. That makes sense because we now deal with a fully persisted todo again. However, Rails also gives us `@todo.previous_changes`! In the previous changes we can see that `completed` changes from `false` to `true`. 82 | ``` 83 | (byebug) @todo.previous_changes 84 | {"completed"=>[false, true], "updated_at"=>[Fri, 26 Jul 2019 16:37:47 UTC +00:00, Fri, 26 Jul 2019 16:37:51 UTC +00:00]} 85 | ``` 86 | 87 | That means that `@todo.completed_previously_changed?` should return true - and it does. 88 | ``` 89 | (byebug) @todo.completed_previously_changed? 90 | true 91 | ``` 92 | 93 | To make our test pass we can simply replace `@todo.completed_changed?` with `@todo.completed_previously_changed?`. 94 | 95 | But before we make the change, let's try to get a `byebug` session from an actual web request. To do that we need to switch back to our browser and make a todo as completed or active. 96 | 97 | What should happen is that the Rails server stop and opens a `byebug` - but as you can tell, this doesn't work. The request is processed and http response is returned to our browser. This happens because by default the contains that are started with `docker-compose up` (as well as `docker-compose start` and `docker-compose restart`) don't have a tty attached and `byebug` can't hence not start the session. 98 | 99 | We've seen that opening a byebug session works when we use `docker-compose run`. It does because Docker Compose will automatically attach a pseudo-tty for us and attach `STDIN` as well. So let's start the Rails server with `docker-compose run` instead. We have to first shut down the current app service, otherwise we will not be able to start a new Rails server because port 3000 is already in use. 100 | ``` 101 | docker-compose stop app 102 | ``` 103 | 104 | Now we can start the Rails server witch `docker-compose run`: 105 | ``` 106 | docker-compose run --rm -p 127.0.0.1:3000:3000 app 107 | ``` 108 | 109 | Since we defined the port mapping in our `docker-compose.yml` and `rails server` we could also use the `--service-ports` flag instead of `-p`: 110 | ``` 111 | docker-compose run --rm --service-ports app 112 | ``` 113 | 114 | If we now mark another todo as complete or active we will be dropped into a `byebug` session. 115 | 116 | We already know how to fix the issue. Go ahead and make the code change and make sure that the test passes. 117 | 118 | ## Making it work without stopping the server 119 | Depending on your style of working, starting and stopping the container whenever we want to start a `byebug` session might be tedious. The good news is that there is another way! We can add the following settings to our `app` service definition in the `docker-compose.yml`: 120 | 121 | ```yaml 122 | tty: true 123 | stdin_open: true 124 | ``` 125 | 126 | These to flags do what `docker-compose run` does for us automatically: They will attach a pseudo-tty to the container and keep `STDIN` open. You can find a complete example in `_examples/docker-compose.yml.with_tty`. 127 | 128 | With these flags in place we can now restart our containers with 129 | ``` 130 | docker-compose up -d 131 | ``` 132 | 133 | If we now place another byebug in the controller action and update a todo, we will see that the browser hangs. We can also see that a `byebug` session was opened if we run 134 | ``` 135 | docker-compose logs --tail 25 app 136 | ``` 137 | 138 | The question is, how can we get into the session so that we can start typing commands? The answer is *by attaching to the container*. We can use the `docker attach` command to do that. 139 | 140 | We first need to find the name of the running container with 141 | ``` 142 | docker-compose ps 143 | ``` 144 | 145 | In the output we will copy the name of the container for the `app` service - `dockerizing_rails_app_1` in our case: 146 | ``` 147 | Name Command State Ports 148 | ------------------------------------------------------------------------------------------- 149 | dockerizing_rails_app_1 rails server -b 0.0.0.0 -- ... Up 127.0.0.1:3000->3000/tcp 150 | dockerizing_rails_pg_1 docker-entrypoint.sh postgres Up 5432/tcp 151 | ``` 152 | 153 | Now that we have the container we can attach to it: 154 | ``` 155 | docker attach dockerizing_rails_app_1 156 | ``` 157 | 158 | And there we go! If you press `ENTER` you will see that you are in a `byebug` session, just as before. 159 | 160 | If we end the session by typing `continue`, we will see the Rails log on our screen - we are still attached to the container. In order to detach from the container we can use the key sequence `Ctrl-p` `Ctrl-q`. We could also press `Ctrl-c`, but that would terminate the container and we would have to restart the service. 161 | 162 | > **Note**: The naming conventions of Docker Compose makes it pretty straight forward to "guess". I also recommend using command-line completion for Docker and [Docker compose](https://docs.docker.com/compose/completion/) 163 | 164 | ## There is more 165 | Another very useful command you should be aware of is `docker stats`. It shows you CPU, memory, disk and network usage of your containers. The fantastic thing here is that you have isolated statistics for each part of our application! 166 | 167 | And of course the is `docker-compose logs`. We've already used it, but I want to say a few more words. With these commands we have easy access to the logs of each part of our applications - independent of the language or type of service we are running. To ensure that this pattern works, your have to send the all logs of your applications to `STDOUT` and `STDERR`. These streams are picked up by docker and you can access them with `docker logs` and `docker-compose logs`. Writing to logfiles is discouraged in the container landscape. If whatever you are running in the container does not support sending logs to `STDOUT` or `STDERR`, write the logs to `/dev/stdout` or `/dev/stderr` files instead. This way they will end up on the respective streams. 168 | 169 | And last but not least, you can use `docker exec` and `docker-compose exec` to start a separate process in already running container: 170 | ``` 171 | docker-compose exec app bash 172 | ``` 173 | 174 | This will will open a shell in the running container for `app` service. This means that you now can use or install additional tooling to debug your application in an isolated environment. 175 | 176 | # What changed 177 | You can find our changes in the [`debugging`](https://github.com/jfahrer/dockerizing_rails/tree/debugging) branch. [Compare it](https://github.com/jfahrer/dockerizing_rails/compare/iterating...debugging) to the previous branch to see what changed. 178 | 179 | [Back to the overview](../README.md#assignments) 180 | -------------------------------------------------------------------------------- /_assignments/assignment_10.md: -------------------------------------------------------------------------------- 1 | # Assignment 10 - Integrating Sidekiq 2 | To further optimize the our application, we will integrate [Sidekiq](https://github.com/mperham/sidekiq) and move the expensive score generation operation into a background job. In the `TodosController` (`app/controllers/todos_controller.rb`) we currently update the users scores after each action via a `after_action`: 3 | ```ruby 4 | after_action :update_scores, only: [:create, :update, :destroy] 5 | ``` 6 | 7 | The `update_scores` method uses the `ScoreCalculator` to update the scores: 8 | ```ruby 9 | def update_scores 10 | ScoreCalculator.call(Date.today) 11 | end 12 | ``` 13 | 14 | The goal is to make the call to `ScoreCalculator.call(Date.today)` asynchronous since there is no good reason to do it inline with when the user updates the todo-list. 15 | 16 | ## Adding the Gem 17 | As we would do with a non-dockerized application, we start by adding the Sidekiq gem to our `Gemfile`: 18 | 19 | ```ruby 20 | gem "sidekiq", "~> 5.2.7" 21 | ``` 22 | 23 | The next step would be to run `bundle install`. This is somewhat problematic with our current setup. We have a discrete build step in which we build the container image. In this step we run `bundle install`. The `Gemfile.lock` does not yet have an entry for Sidekiq. If we would run `docker image build`, the `Gemfile.lock` would be updated as part of the build process of the image. However, only the file that is part of the image would be updated. Our local `Gemfile.lock` stays unchanged. That means over time we might end up with a different version of Sidekiq in our image. 24 | 25 | We can work around this by creating the `Gemfile.lock` using a container to run bundle: 26 | ``` 27 | docker-compose run --rm app bundle 28 | ``` 29 | 30 | With the `Gemfile.lock` in place we can now build the image and can rest assured that we will always use the locked version of Sidekiq. 31 | 32 | There are still a few problems tho: 33 | * We have to run 2 commands in order to run install images 34 | * Installing gems takes a long time because if the `Gemfile` or `Gemfile.lock` changes, the `RUN` instruction in our `Dockerfile` will be executed in the context of a "blank" ruby installation. Hence all gems in the `Gemfile` have to be installed. 35 | * Switching between branches with different `Gemfiles`s becomes tedious. Every time we switch, we have to build the Docker image again. 36 | 37 | To ensure a better experience we are going to use a concept we already learned about - a volume. We will use the volume to store a copy of our gems. Doing this allows us to skip building the image and just `bundle` as we would do with a non-dockerized ruby application. 38 | 39 | Let's a volume `gems` to our `app` service definition: 40 | ```yaml 41 | volumes: 42 | - ./:/usr/src/app:cached 43 | - tmp:/usr/src/app/tmp 44 | - gems:/usr/local/bundle 45 | 46 | ``` 47 | 48 | And just like in the prior examples, we also have to add the volume to the `volumes` section: 49 | ```yaml 50 | volumes: 51 | pg-data: 52 | tmp: 53 | gems: 54 | ``` 55 | 56 | From here on we can just run `docker-compose run --rm app bundle` to bundle our gems. Even switching between different branches will work seamlessly since the all gems we install over time will be persisted in the volume. 57 | 58 | ## Setting up Sidekiq 59 | Now to the actual Sidekiq integration. Let's start by creating an initializer. Here is an example `config/initializers/sidekiq.rb`: 60 | ```ruby 61 | redis_url = "redis://#{ENV.fetch('REDIS_HOST', 'localhost')}/:#{ENV.fetch('REDIS_PORT', '6379')}/#{ENV.fetch('REDIS_DB', '0')}" 62 | 63 | Sidekiq.configure_server do |config| 64 | config.redis = { url: redis_url } 65 | end 66 | 67 | Sidekiq.configure_client do |config| 68 | config.redis = { url: redis_url } 69 | end 70 | ``` 71 | 72 | In [12factor](https://12factor.net/) manner we use environment variables to configure Sidekiq. We also make sure to fallback to default values in case the environment variables are not set. This also ensures that someone can develop the application without using Docker. We also applied these principles for the Postgres setup. Take another look at the `config/database.yml` if you want to refresh your memory. This pattern and the other 11 factors are great for containerized applications - both in development and production. 73 | 74 | 75 | In order to make Sidekiq play nice with our test suite, we have to require `sidekiq/testing` and tell Sidekiq to run jobs inline in `spec/rails_helper.rb`: 76 | ```ruby 77 | require 'sidekiq/testing' 78 | 79 | Sidekiq::Testing.inline! 80 | ``` 81 | 82 | You can copy and paste those lines right bellow the `# Add additional requires below this line. Rails is not loaded until this point!` comment in `spec/rails_helper.rb`. 83 | 84 | ## Adding the service 85 | Thanks to Docker and Compose, adding additional services to our Rails application becomes a breeze. All we have to do is: 86 | * Add a service to our `docker-compose.yml` to run Redis: 87 | ```yaml 88 | redis: 89 | image: redis:5.0 90 | volumes: 91 | - redis-data:/data 92 | ``` 93 | 94 | * Add the `redis-data` volume to the volumes section: 95 | ```yaml 96 | volumes: 97 | pg-data: 98 | redis-data: 99 | tmp: 100 | gems: 101 | ``` 102 | Redis will persist its data to `/data` when the service is stopped and read the backup on startup to restore it. By mounting a volume to `/data` we ensure that we keep the data around even when the container is deleted. 103 | 104 | * Add a service to our `docker-compose.yml` to run Sidekiq: 105 | ```yaml 106 | sidekiq: 107 | image: your_docker_id/rails_app:v1 108 | command: ["sidekiq"] 109 | volumes: 110 | - ./:/usr/src/app 111 | - tmp:/usr/src/app/tmp 112 | - gems:/usr/local/bundle 113 | environment: 114 | - POSTGRES_HOST=pg 115 | - POSTGRES_USER=postgres 116 | - POSTGRES_PASSWORD=secret 117 | - REDIS_HOST=redis 118 | - RAILS_ENV 119 | tty: true 120 | stdin_open: true 121 | ``` 122 | 123 | The environment section of the `sidekiq` service is mostly identical with the one from the `app` service. There is one additional environment variable that we set: `REDIS_HOST=redis`. This environment variable is used in `config/initializers/sidekiq.rb` to configure Sidekiq. We could also specify `REDIS_PORT` and `REDIS_DB`, but since we are using the default values, there is no need to. However, we do have to add the `REDIS_HOST` environment variable to our `app` service so that Rails can enqueue jobs: 124 | ```yaml 125 | - REDIS_HOST=redis 126 | ``` 127 | 128 | We can omit the `build` directive for the `sidekiq` service since we use the same image as the `app` service. Docker Compose will build the image for `app` and then just re-use it for `sidekiq`. Check out the `_examples/docker-compose.yml.with_sidekiq` for a complete example. 129 | 130 | 131 | ## Creating the job 132 | Now we have to write the code for the actual job. It will simply call `ScoreCalculator.call`. 133 | 134 | So let's create `app/jobs/score_generation_job.rb`: 135 | ```ruby 136 | class ScoreGenerationJob 137 | include Sidekiq::Worker 138 | 139 | def perform(date_string) 140 | ScoreCalculator.call(date_string.to_date) 141 | end 142 | end 143 | ``` 144 | 145 | And then we update the `update_scores` method in `app/controllers/todos_controller.rb` to look like this: 146 | ```ruby 147 | def update_scores 148 | ScoreGenerationJob.perform_async(Date.today) 149 | end 150 | ``` 151 | 152 | So instead of calling the `ScoreCalculator` directly, we will enqueue a Sidekiq job that will do this for us. 153 | 154 | Once you're done with the changes, restart the `app` service and run the specs to make sure everything works as expected: 155 | ``` 156 | docker-compose restart app 157 | docker-compose run --rm app rspec 158 | ``` 159 | 160 | ## Running Sidekiq 161 | With the configuration in place, we can now spin up our services with `docker-compose up -d`. As always, make sure that all the services are up and running with `docker-compose ps`. 162 | 163 | If everything looks good, let's keep an eye on the Sidekiq logs with: 164 | ``` 165 | docker-compose logs -f sidekiq 166 | ``` 167 | 168 | Add some todos and mark them as complete. You should see that jobs are being processed in the Sidekiq logs. 169 | 170 | 171 | # What changed 172 | You can find our changes in the [`sidekiq`](https://github.com/jfahrer/dockerizing_rails/tree/sidekiq) branch. [Compare it](https://github.com/jfahrer/dockerizing_rails/compare/debugging...sidekiq) to the previous branch to see what changed. 173 | 174 | [Back to the overview](../README.md#assignments) 175 | -------------------------------------------------------------------------------- /_assignments/assignment_11.md: -------------------------------------------------------------------------------- 1 | # Assignment 11 - Installing Webpacker 2 | 3 | Okay, now let's get webpacker working with rails 5.2.3 4 | 5 | ## Set up Webpacker 6 | 7 | Tasks to complete solution: 8 | 9 | - Add the most recent version of webpacker gem as a bundler dependency 10 | - Add yarn via `apk` in the docker image 11 | - Mount a volume for the `node_modules` directory to `/usr/src/app/node_modules` 12 | - Install webpacker by running the rake task: `webpacker:install` 13 | - Add the packs to the application.html.erb layout: 14 | ```erb 15 | <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 16 | <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> 17 | ``` 18 | - Navigate to localhost:3000 and open your web console. 19 | - If you see "Hello World from Webpacker!", it worked! 20 | - If you got this far, there are more exercises in the "[Using Webpacker](#using-webpacker)" section 21 | 22 | > **Note**: Step-by-step solutions can be found below. All the steps above should excercise previously learned concepts. Give it a shot, then look below for help! 23 | 24 | Let's install the most recent version of webpacker by adding it the to the Gemfile. 25 | 26 | ``` 27 | gem 'webpacker', '~> 4.x' 28 | ``` 29 | 30 | Using what we learned from the sidekiq gem installation, let's reinstall. 31 | 32 | ``` 33 | docker-compose run --rm app bundle 34 | ``` 35 | 36 | In order to install webpacker, we'll need to add `yarn` to the Docker image. Add this to the `Dockerfile` 37 | 38 | ``` 39 | RUN apk add --update --no-cache \ 40 | bash \ 41 | build-base \ 42 | nodejs \ 43 | sqlite-dev \ 44 | tzdata \ 45 | postgresql-dev \ 46 | yarn 47 | ``` 48 | 49 | And rebuild the image: 50 | 51 | ``` 52 | docker-compose build app 53 | ``` 54 | 55 | Now that we have yarn installed, we can run the `webpacker:install` rails rake task. 56 | 57 | ``` 58 | docker-compose run --rm app bundle exec rails webpacker:install 59 | ``` 60 | 61 | If you run a `git status`, You'll see quite a few files were created and then `yarn install` was run. 62 | 63 | But wait! If you look in your root directory you'll notice that a node_modules directory has been installed in your local directory. That's where yarn stores all of it's dependencies defined in the `package.json` – similar to where Bundler stores its gems. It is about 18k files and 120M of `node_modules`. We want to make sure that we don't COPY all those files when we rebuild the image, so think about how you have fixed a similar problem previously. 64 | 65 | To clean up, let's remove the `node_modules` directory that was just created (`rm -rf node_modules`). 66 | 67 | Add a volume called `node_modules` and mount it to `/usr/src/app/node_modules`. 68 | 69 | > **Note**: We have tried many different times to get the node_modules extracted to another directory (e.g. `/usr/local/node_modules`) but it never works properly, unfortunately. So this is second best. Report back if you find a way of doing this. 70 | 71 | ```yaml 72 | app: 73 | volumes: 74 | - ./:/usr/src/app:cached 75 | - tmp:/usr/src/app/tmp 76 | - gems:/usr/local/bundle 77 | - node_modules:/usr/src/app/node_modules 78 | 79 | ... 80 | 81 | sidekiq: 82 | volumes: 83 | - ./:/usr/src/app 84 | - tmp:/usr/src/app/tmp 85 | - gems:/usr/local/bundle 86 | - node_modules:/usr/src/app/node_modules 87 | 88 | ... 89 | 90 | volumes: 91 | pg-data: 92 | redis-data: 93 | tmp: 94 | gems: 95 | node_modules: 96 | ``` 97 | 98 | Let’s try booting up the server again. 99 | 100 | ``` 101 | docker-compose up app 102 | ``` 103 | 104 | > **Note**: We’ll see another error. It’s because we removed the node_modules directory – now that we have a volume mounted let’s install those again. 105 | 106 | ``` 107 | docker-compose run --rm app yarn install 108 | ``` 109 | 110 | And let’s boot the server up again. 111 | 112 | ``` 113 | docker-compose up app 114 | ``` 115 | 116 | And also notice on your local disk, the `node_modules` directory exists but now it is empty! 117 | 118 | Okay! We also need to revisit the `Dockerfile` to run `yarn install` so that if other engineers on your team pull the app they don’t run into the same yarn install error we just saw. 119 | 120 | Add these lines after we install our gems: 121 | 122 | ```Dockerfile 123 | 124 | COPY package.json yarn.lock ./ 125 | 126 | RUN yarn install 127 | 128 | ``` 129 | 130 | ```bash 131 | docker-compose build app 132 | ``` 133 | 134 | Okay, now you are all set up and so will other engineers that install the project! 135 | 136 | In order to actually load the webpacks that are being generated, found in `app/javascript/packs`, let's add stylesheets and javascript files to the application layout in `application.html.erb` 137 | 138 | ```erb 139 | <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 140 | <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> 141 | ``` 142 | 143 | And now if you reload localhost:3000 you'll notice a `console.log` letting you know that webpack compiled assets are being loaded on the page! 144 | 145 | ## Using Webpacker 146 | 147 | Let’s replace EVERYTHING in the asset pipeline with webpacker (starting with bootstrap) 148 | 149 | ``` 150 | docker-compose run --rm app yarn add bootstrap jquery popper.js 151 | ``` 152 | 153 | And also let’s add in rails-ujs and turbolinks. 154 | 155 | ``` 156 | docker-compose run --rm app yarn add rails-ujs turbolinks 157 | ``` 158 | 159 | This adds it to your package.json file, and also locks the dependencies in the yarn.lock file. 160 | 161 | 162 | We will need to expose some bootstrap dependencies on the `window` object in javascript, so let's add the following code to `config/webpack/environment.js` 163 | 164 | ```javascript 165 | const { environment } = require('@rails/webpacker') 166 | 167 | const webpack = require("webpack") 168 | 169 | environment.plugins.append("Provide", new webpack.ProvidePlugin({ 170 | $: 'jquery', 171 | jQuery: 'jquery', 172 | Popper: ['popper.js', 'default'] 173 | })) 174 | module.exports = environment 175 | ``` 176 | 177 | And now we can import those dependencies, that facilitates bootstrap, rails-ujs, turbolinks by placing the following code within our `app/javascript/packs/application.js` 178 | 179 | ``` 180 | import "../src/bootstrap_and_rails"; 181 | ``` 182 | 183 | Check out the code inside `app/javascript/src/bootstrap_and_rails.js` to see how we're using webpacker. 184 | 185 | Now that we've replaced everything in the asset pipeline, we can remove the other javascript and stylesheet imports inside our `application.html.erb` layout. 186 | 187 | ``` 188 | // Delete me! 189 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 190 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> 191 | ``` 192 | 193 | Now refresh the page and you can see that dark mode is now working! Sprockets has been completely replaced by webpacker now. 194 | 195 | ## Bonus 196 | 197 | Notice how each render takes quite a bit of time? Rails is generating those assets when the web request is issued. 198 | 199 | We can generate those assets as soon as the javascript is saved using the `./bin/webpack-dev-server` process. 200 | 201 | As an exercise for the reader, let’s extract the `bin/webpack-dev-server` process into a separate docker container. 202 | 203 | > **Hint**: After running `webpacker:install` this message is presented to the user. 204 | 205 | ``` 206 | You need to allow webpack-dev-server host as allowed origin for connect-src. 207 | This can be done in Rails 5.2+ for development environment in the CSP initializer 208 | config/initializers/content_security_policy.rb with a snippet like this: 209 | policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? 210 | ``` 211 | 212 | > **Super-hint**: [webpacker/docker.md at master · rails/webpacker · GitHub](~https://github.com/rails/webpacker/blob/master/docs/docker.md~) 213 | 214 | # What changed 215 | 216 | You can find our changes in the [`webpacker`](~https://github.com/jfahrer/dockerizing_rails/tree/webpacker~) branch. [Compare it](~https://github.com/jfahrer/dockerizing_rails/compare/sidekiq...webpacker~) to the previous branch to see what changed. 217 | 218 | [Back to the overview](~../README.md#assignments~) 219 | -------------------------------------------------------------------------------- /_assignments/assignment_12.md: -------------------------------------------------------------------------------- 1 | # Assignment 12 - Keeping things running with Spring 2 | 3 | Utilizing the power of [Spring](https://github.com/rails/spring) and pre-loading your application also works in Dockerland and speed up our test runs. Just like we did with Sidekiq, we will add another service for Spring to separate the concerns into different containers. We will run one service for Rails Server, one for Sidekiq and another one for Spring. I tend to use the service name `app` for Spring for and `web` for the Rails Server. 4 | 5 | ```yaml 6 | app: 7 | image: your_docker_id/rails_app:v1 8 | build: 9 | context: . 10 | command: ["spring", "server"] 11 | environment: 12 | - POSTGRES_HOST=pg 13 | - POSTGRES_USER=postgres 14 | - POSTGRES_PASSWORD=secret 15 | - REDIS_HOST=redis 16 | - RAILS_ENV 17 | volumes: 18 | - ./:/usr/src/app:cached 19 | - tmp:/usr/src/app/tmp 20 | - gems:/usr/local/bundle 21 | - node_modules:/usr/src/app/node_modules 22 | 23 | web: 24 | image: your_docker_id/rails_app:v1 25 | environment: 26 | - POSTGRES_HOST=pg 27 | - POSTGRES_USER=postgres 28 | - POSTGRES_PASSWORD=secret 29 | - REDIS_HOST=redis 30 | - RAILS_ENV 31 | volumes: 32 | - ./:/usr/src/app:cached 33 | - tmp:/usr/src/app/tmp 34 | - gems:/usr/local/bundle 35 | - node_modules:/usr/src/app/node_modules 36 | ports: 37 | - 127.0.0.1:3000:3000 38 | tty: true 39 | stdin_open: true 40 | ``` 41 | 42 | We changed the `app` service to start Spring with `spring server` and we also removed the published ports. The new service `web` will take care of making the Rails server available to us. Since we use the same image for all three services, we only need to build it once. Hence only our `app` service has the `build` directive set. 43 | 44 | 45 | We also have to make sure that our springified binstubs are in our path by adding the following line to our `Dockerfile` right before the `CMD` instruction: 46 | 47 | ```Dockerfile 48 | ENV PATH=./bin:$PATH 49 | ``` 50 | 51 | This way we will automatically use `./bin/rails` and `./bin/rspec` if we execute `rails` or `rspec` inside a container. The same is true for the other binstubs in the `bin/` directory. The binstubs are setup to utilize Spring. 52 | 53 | Check out `_examples/docker-compose.yml.with_spring` and `_examples/Dockerfile.with_spring` for complete examples. 54 | 55 | Let's rebuild the image and restart your services: 56 | ``` 57 | docker-compose build app 58 | docker-compose up -d 59 | ``` 60 | 61 | > **Note**: Run the command a second time if you get an error - depending on the order in which things happen the app container might still be holding port 3000 open. 62 | 63 | 64 | Instead of using `docker-compose run` we can now use `docker-compose exec` and send arbitrary shell commands to a running container of the `app` service: 65 | ``` 66 | docker-compose exec app rspec 67 | docker-compose exec rake db:migrate 68 | docker-compose exec rails console 69 | ``` 70 | 71 | Unlike `docker-compose run`, `docker-compose exec` will not spin up a brand new container every time we execute the command. Instead the command is executed in an already running container. That means that the commands we send to the container can now benefit from Spring pre-loading the application. 72 | 73 | Try restarting the `app` service with `docker-compose restart app` and running the test suite afterwards. The first time you run the test suite it will take a short while before rspec starts. If you run the test suite for a second time, rspec should start almost immediately thanks to Spring. 74 | 75 | 76 | From here on, web ui will be made available via the `web` service. So if we now want to read the logs for the Rails server, we need to run: 77 | ``` 78 | docker-compose logs --tail 25 -f web 79 | ``` 80 | 81 | # What changed 82 | You can find our changes in the [`spring`](https://github.com/jfahrer/dockerizing_rails/tree/spring) branch. [Compare it](https://github.com/jfahrer/dockerizing_rails/compare/webpacker...spring) to the previous branch to see what changed. 83 | 84 | [Back to the overview](../README.md#assignments) 85 | -------------------------------------------------------------------------------- /_assignments/assignment_13.md: -------------------------------------------------------------------------------- 1 | # Assignment 13 - Scripts To Rule Them All 2 | 3 | The [Scripts To Rule Them All](https://github.com/github/scripts-to-rule-them-all) pattern comes from GitHub. It is a set of scripts with normalized names that can be used to get a project up and running in a repeatable and reliable fashion. 4 | 5 | The purpose of the assignment is to demonstrate that Docker allows us to easily write scripts that get our environment into a working state. The exact usage and naming of the scripts is ultimately up to you. Coming up with a standard and sticking to it has the advantage that we don't have to look into the README when we switch between projects. 6 | 7 | I already prepared a set of scripts for us. You can copy them from `_examples/script`: 8 | ``` 9 | cp -rp _examples/script ./script 10 | ``` 11 | 12 | We won't go into the inner mechanics of the scripts. They are all pretty straight forward shell scripts that execute `docker` and `docker-compose`. Instead, let's talk about what each script is intended to do: 13 | * `script/bootstrap`: Prepares the runtime environment for the application. This entails building the image and install gems and node modules. 14 | * `script/update` is responsible for updating the dependencies of the application. The script makes sure that Redis and Postgres are up and running, migrates the database, and updates the node modules and gems by calling `bootstrap`. 15 | * `script/setup` is responsible for setting up the whole environment. It should be called after the initial clone or when a full reset of the environment is required. `setup` calls `update`. 16 | * `script/test` will start all services and run the est suite of the application. `test` calls `update` unless the environment variable `FAST` is set. 17 | * `script/server` will start all services and tail the logs of the Rails server and Sidekiq. `server` calls `update` unless the environment variable `FAST` is set. 18 | * `script/console` will start all services and open a Rails console. `console` calls `update` unless the environment variable `FAST` is set. 19 | 20 | Whoever needs to work on this project just needs to have **bash and Docker** installed, clone the repository and execute `script/setup`. Since the scripts only execute commands in containers, we know that they **will yield the expected results**! The same pattern can be utilized for other dockerized and non-dockerized applications as well. The great thing is that developers don't have to understand the inner mechanics of the application to interact with it. They just have to know which script to execute based on what we are trying to achieve. 21 | 22 | ## What we have to do 23 | One tricky thing, when it comes to fully automating the setup of an application, is dealing with external dependencies. They might not being ready to use when you need them. For example it might take Postgres a while to boot. Since we want to script the setup of our application, we need to add steps to make sure our external dependencies are up and running before we use them. 24 | 25 | Our set scripts solve this by using a tool called `pg_isready` that is shipped with Postgres. It can be used to determine if Postgres server is up and running. Since we don't want to introduce additional dependencies besides bash and Docker, we are going to run `pg_isready` in container. To do so, we have to install it in our Docker image. Go ahead and add `postgresql-client` to the list of packages that we install with `apk add`: 26 | ``` 27 | RUN apk add --update --no-cache \ 28 | bash \ 29 | build-base \ 30 | nodejs \ 31 | sqlite-dev \ 32 | tzdata \ 33 | postgresql-dev \ 34 | postgresql-client \ 35 | yarn 36 | ``` 37 | You can find a complete example in `_examples/Dockerfile.with_postgres_client`. 38 | 39 | To make calling `pg_isready` easier, we are going to wrap it in another script. We are going to call it `wait-for-postgres` and place it in `bin/`. I already prepared the script and you can just copy it: 40 | ``` 41 | cp -rp _examples/wait-for-postgres ./bin 42 | ``` 43 | 44 | > **Note**: We place the `wait-for-postgres` script in `bin/` because it is intended to be executed within the application context. That means we make it part of the application and run it in containers. Everything in `script/` is not part of the actual application. Those are helpers that we use to interact with our application. Everything in `script/` is intended to be run on the Docker Host. 45 | 46 | The script uses the `POSTGRES_HOST` environment variable that we pass to the our containers to connect to Postgres. `pg_isready` will yield a unsuccessful exit code if it can't connect to Postgres. The script simply executes `pg_isready` until it returns successful. 47 | 48 | Within our Scripts To Rule them All, `wait-for-postgres` is used like this: 49 | ``` 50 | docker-compose run --rm app bin/wait-for-postgres 51 | ``` 52 | 53 | Go ahead and try the scripts! 54 | 55 | # What changed 56 | You can find our changes in the [`scripts_to_rule_them_all`](https://github.com/jfahrer/dockerizing_rails/tree/scripts_to_rule_them_all) branch. [Compare it](https://github.com/jfahrer/dockerizing_rails/compare/spring...scripts_to_rule_them_all) to the previous branch to see what changed. 57 | 58 | [Back to the overview](../README.md#assignments) 59 | -------------------------------------------------------------------------------- /_assignments/assignment_14.md: -------------------------------------------------------------------------------- 1 | # Assignment 13 - A seamless experience 2 | 3 | ## Customizing your experience 4 | You might have realized that the `rails console` is not preserving our history. On your local system you might also use an `.irbrc`, shell customizations or other rc-files to enrich your development experience. These are all things that are currently missing in our containers. Not an issue tho - we can simply bind mount our rc-files into our containers! 5 | 6 | ``` 7 | volumes: 8 | - ./.irbrc:/root/.irbrc 9 | ``` 10 | 11 | The `.irbrc` might look something like this: 12 | ``` 13 | require 'irb/completion' 14 | require 'irb/ext/save-history' 15 | IRB.conf[:SAVE_HISTORY] = 1000 16 | IRB.conf[:HISTORY_FILE] = "/usr/src/app/.irb-history" 17 | ``` 18 | 19 | And we can use environment variables in our `docker-compose.yml` to change the behaviour of containers as well. For example we can set the history file used by `bash` using the `HISTFILE` environment variable: 20 | 21 | ``` 22 | HISTFILE=/usr/src/app/.bash_history 23 | ``` 24 | 25 | > **Note**: You might want to add `.bash_history` and `.irb_history` to your `.gitignore`. 26 | 27 | > **Note**: The rc-files don't have the live in the repo - you can place them anywhere on your file system. You can even use [environment variables](https://docs.docker.com/compose/environment-variables/) within the `docker-compose.yml` to reference them. For example `${HOME}/.irbrc:/root/.irbrc`. 28 | 29 | 30 | ## Streamlining the execution of commands 31 | When it comes to developing applications with Docker and Compose, we often have to make a few choice before running a command. Should we use `exec` or `run`? And which service do we have to specify for the command at hand? Here are a few examples: 32 | * `docker-compose exec --rm app rspec`: We run the test suite using `exec` because we want to utilize Spring running provided the bye `app` service. 33 | * `docker-compose run --rm bundle`: We install gems using `run` because missing gems might prevent our application from starting and `exec` won't work. 34 | * `docker-compose exec pg psql -U postgres`: We use `exec` to start a `psql` session for our Postgres Database. We want to use `exec` here because Postgres has to be running in order to connect to it. It also allows us to omit the hostname of the database server since Postgres is running on "localhost" in the context of the `pg` service. 35 | * `docker-compose exec redis redis-cli`: We use `exec` to connect to Redis via the `redis-cli`. We have to use the `redis` service here because our other containers won't have `redis-cli` installed. Using `exec` instead of `run` allows us to omit the hostname and connect to "localhost" in the context of the `redis` service. 36 | 37 | This list is neither complete nor will it be identical across projects. This is where [Donner](https://github.com/codetales/donner) is helpful. Donner allows you to define how to execute given commands in a simple yaml-config. Instead of having to decide whether ot use `run` vs `exec` and which service to use, you can just execute 38 | * `donner run rspec` 39 | * `donner run bundle` 40 | * `donner run psql -U postgres` 41 | * `donner run redis-cli` 42 | 43 | Donner will figure out how to execute the command for you based on the config. 44 | 45 | Donner even allows you to set aliases for the specified commands. This way all you have to type is `rspec` or `psql -U postgres`! 46 | 47 | Check out the [README](https://github.com/codetales/donner) and integrate Donner into your project if you are interested. 48 | 49 | 50 | ## Accelerating bind mounts on MacOS 51 | If you are a MacOS user, you might notice that interacting with your applications seams slower compared to a version that runs locally. This is due to the overhead that comes with [`osxfs`](https://docs.docker.com/docker-for-mac/osxfs/), a shared file system that allows you to bind mount file and folders into Docker containers. 52 | 53 | To work around this, we can use [Blitz](https://github.com/codetales/blitz), a zero dependency source code synchronizer for Docker for Mac. Instead of relying on `osxfs` and bind mounts to get your source code into your containers, Blitz synchronises files and folder from you Mac with a Doocker Volume. That means that your containers can access the files at native speed! 54 | 55 | Check out the [README](https://github.com/codetales/blitz) and try setting it up if you are using MacOS. 56 | 57 | # What changed 58 | You can find our changes in the [`scripts_to_rule_them_all`](https://github.com/jfahrer/dockerizing_rails/tree/seamless) branch. [Compare it](https://github.com/jfahrer/dockerizing_rails/compare/scripts_to_rule_them_all...seamless_development) to the previous branch to see what changed. 59 | 60 | [Back to the overview](../README.md#assignments) 61 | -------------------------------------------------------------------------------- /_examples/Dockerfile.with_postgres_client: -------------------------------------------------------------------------------- 1 | FROM jfahrer/ruby:2.6.3-alpine3.10-ser 2 | 3 | RUN apk add --update --no-cache \ 4 | bash \ 5 | build-base \ 6 | nodejs \ 7 | sqlite-dev \ 8 | tzdata \ 9 | postgresql-dev \ 10 | postgresql-client \ 11 | yarn 12 | 13 | RUN gem install bundler:2.0.2 14 | 15 | WORKDIR /usr/src/app 16 | 17 | COPY Gemfile Gemfile.lock ./ 18 | 19 | RUN bundle install 20 | 21 | COPY package.json yarn.lock ./ 22 | 23 | RUN yarn install 24 | 25 | COPY . . 26 | 27 | EXPOSE 3000 28 | 29 | ENV PATH=./bin:$PATH 30 | 31 | CMD ["rails", "server", "-b", "0.0.0.0", "--pid=/tmp/server.pid"] 32 | -------------------------------------------------------------------------------- /_examples/Dockerfile.with_server_pid: -------------------------------------------------------------------------------- 1 | FROM jfahrer/ruby:2.6.3-alpine3.10-ser 2 | 3 | RUN apk add --update --no-cache \ 4 | bash \ 5 | build-base \ 6 | nodejs \ 7 | sqlite-dev \ 8 | tzdata \ 9 | postgresql-dev 10 | 11 | RUN gem install bundler:2.0.2 12 | 13 | WORKDIR /usr/src/app 14 | 15 | COPY Gemfile Gemfile.lock ./ 16 | 17 | RUN bundle install 18 | 19 | COPY . . 20 | 21 | EXPOSE 3000 22 | 23 | CMD ["rails", "server", "-b", "0.0.0.0", "--pid=/tmp/server.pid"] 24 | -------------------------------------------------------------------------------- /_examples/Dockerfile.with_spring: -------------------------------------------------------------------------------- 1 | FROM jfahrer/ruby:2.6.3-alpine3.10-ser 2 | 3 | RUN apk add --update --no-cache \ 4 | bash \ 5 | build-base \ 6 | nodejs \ 7 | sqlite-dev \ 8 | tzdata \ 9 | postgresql-dev \ 10 | yarn 11 | 12 | RUN gem install bundler:2.0.2 13 | 14 | WORKDIR /usr/src/app 15 | 16 | COPY Gemfile Gemfile.lock ./ 17 | 18 | RUN bundle install 19 | 20 | COPY package.json yarn.lock ./ 21 | 22 | RUN yarn install 23 | 24 | COPY . . 25 | 26 | EXPOSE 3000 27 | 28 | ENV PATH=./bin:$PATH 29 | 30 | CMD ["rails", "server", "-b", "0.0.0.0", "--pid=/tmp/server.pid"] 31 | -------------------------------------------------------------------------------- /_examples/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | app: 5 | image: your_docker_id/rails_app:v1 6 | build: 7 | context: . 8 | environment: 9 | - POSTGRES_HOST=pg 10 | - POSTGRES_USER=postgres 11 | - POSTGRES_PASSWORD=secret 12 | - RAILS_ENV 13 | ports: 14 | - 127.0.0.1:3000:3000 15 | 16 | pg: 17 | image: postgres:10.6-alpine 18 | volumes: 19 | - pg-data:/var/lib/postgresql/data 20 | environment: 21 | - POSTGRES_USER=postgres 22 | - POSTGRES_PASSWORD=secret 23 | 24 | volumes: 25 | pg-data: 26 | -------------------------------------------------------------------------------- /_examples/docker-compose.yml.with_bind_mount: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | app: 5 | image: your_docker_id/rails_app:v1 6 | build: 7 | context: . 8 | environment: 9 | - POSTGRES_HOST=pg 10 | - POSTGRES_USER=postgres 11 | - POSTGRES_PASSWORD=secret 12 | - RAILS_ENV 13 | volumes: 14 | - ./:/usr/src/app:cached 15 | - tmp:/usr/src/app/tmp 16 | ports: 17 | - 127.0.0.1:3000:3000 18 | 19 | pg: 20 | image: postgres:10.6-alpine 21 | volumes: 22 | - pg-data:/var/lib/postgresql/data 23 | environment: 24 | - POSTGRES_USER=postgres 25 | - POSTGRES_PASSWORD=secret 26 | 27 | volumes: 28 | pg-data: 29 | tmp: 30 | -------------------------------------------------------------------------------- /_examples/docker-compose.yml.with_sidekiq: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | app: 5 | image: your_docker_id/rails_app:v1 6 | build: 7 | context: . 8 | environment: 9 | - POSTGRES_HOST=pg 10 | - POSTGRES_USER=postgres 11 | - POSTGRES_PASSWORD=secret 12 | - REDIS_HOST=redis 13 | - RAILS_ENV 14 | volumes: 15 | - ./:/usr/src/app:cached 16 | - tmp:/usr/src/app/tmp 17 | - gems:/usr/local/bundle 18 | ports: 19 | - 127.0.0.1:3000:3000 20 | tty: true 21 | stdin_open: true 22 | 23 | sidekiq: 24 | image: your_docker_id/rails_app:v1 25 | command: ["sidekiq"] 26 | volumes: 27 | - ./:/usr/src/app 28 | - tmp:/usr/src/app/tmp 29 | - gems:/usr/local/bundle 30 | environment: 31 | - POSTGRES_HOST=pg 32 | - POSTGRES_USER=postgres 33 | - POSTGRES_PASSWORD=secret 34 | - REDIS_HOST=redis 35 | - RAILS_ENV 36 | tty: true 37 | stdin_open: true 38 | 39 | pg: 40 | image: postgres:10.6-alpine 41 | volumes: 42 | - pg-data:/var/lib/postgresql/data 43 | environment: 44 | - POSTGRES_USER=postgres 45 | - POSTGRES_PASSWORD=secret 46 | 47 | redis: 48 | image: redis:5.0 49 | volumes: 50 | - redis-data:/data 51 | 52 | volumes: 53 | pg-data: 54 | redis-data: 55 | tmp: 56 | gems: 57 | -------------------------------------------------------------------------------- /_examples/docker-compose.yml.with_spring: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | app: 5 | image: your_docker_id/rails_app:v1 6 | build: 7 | context: . 8 | command: ["spring", "server"] 9 | environment: 10 | - POSTGRES_HOST=pg 11 | - POSTGRES_USER=postgres 12 | - POSTGRES_PASSWORD=secret 13 | - REDIS_HOST=redis 14 | - RAILS_ENV 15 | volumes: 16 | - ./:/usr/src/app:cached 17 | - tmp:/usr/src/app/tmp 18 | - gems:/usr/local/bundle 19 | - node_modules:/usr/src/app/node_modules 20 | 21 | web: 22 | image: your_docker_id/rails_app:v1 23 | environment: 24 | - POSTGRES_HOST=pg 25 | - POSTGRES_USER=postgres 26 | - POSTGRES_PASSWORD=secret 27 | - REDIS_HOST=redis 28 | - RAILS_ENV 29 | volumes: 30 | - ./:/usr/src/app:cached 31 | - tmp:/usr/src/app/tmp 32 | - gems:/usr/local/bundle 33 | - node_modules:/usr/src/app/node_modules 34 | ports: 35 | - 127.0.0.1:3000:3000 36 | tty: true 37 | stdin_open: true 38 | 39 | sidekiq: 40 | image: your_docker_id/rails_app:v1 41 | build: 42 | context: . 43 | command: ["sidekiq"] 44 | volumes: 45 | - ./:/usr/src/app 46 | - tmp:/usr/src/app/tmp 47 | - gems:/usr/local/bundle 48 | - node_modules:/usr/src/app/node_modules 49 | environment: 50 | - POSTGRES_HOST=pg 51 | - POSTGRES_USER=postgres 52 | - POSTGRES_PASSWORD=secret 53 | - REDIS_HOST=redis 54 | - RAILS_ENV 55 | 56 | pg: 57 | image: postgres:10.6-alpine 58 | volumes: 59 | - pg-data:/var/lib/postgresql/data 60 | environment: 61 | - POSTGRES_USER=postgres 62 | - POSTGRES_PASSWORD=secret 63 | 64 | redis: 65 | image: redis:5.0 66 | volumes: 67 | - redis-data:/data 68 | 69 | volumes: 70 | pg-data: 71 | redis-data: 72 | tmp: 73 | gems: 74 | node_modules: 75 | -------------------------------------------------------------------------------- /_examples/docker-compose.yml.with_tty: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | app: 5 | image: your_docker_id/rails_app:v1 6 | build: 7 | context: . 8 | environment: 9 | - POSTGRES_HOST=pg 10 | - POSTGRES_USER=postgres 11 | - POSTGRES_PASSWORD=secret 12 | - RAILS_ENV 13 | volumes: 14 | - ./:/usr/src/app:cached 15 | - tmp:/usr/src/app/tmp 16 | ports: 17 | - 127.0.0.1:3000:3000 18 | tty: true 19 | stdin_open: true 20 | 21 | pg: 22 | image: postgres:10.6-alpine 23 | volumes: 24 | - pg-data:/var/lib/postgresql/data 25 | environment: 26 | - POSTGRES_USER=postgres 27 | - POSTGRES_PASSWORD=secret 28 | 29 | volumes: 30 | pg-data: 31 | tmp: 32 | -------------------------------------------------------------------------------- /_examples/script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")/.." 3 | 4 | set -ex 5 | 6 | if [ -z "${SKIP_BUILD}" ]; then 7 | docker-compose build --pull 8 | fi 9 | 10 | docker-compose run --rm app bin/bundle 11 | docker-compose run --rm app yarn 12 | -------------------------------------------------------------------------------- /_examples/script/console: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")/.." 3 | 4 | set -ex 5 | 6 | if [ -z "${FAST}" ]; then 7 | script/update 8 | fi 9 | 10 | docker-compose up -d 11 | docker-compose exec app rails console 12 | -------------------------------------------------------------------------------- /_examples/script/server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")/.." 3 | 4 | set -ex 5 | 6 | if [ -z "${FAST}" ]; then 7 | script/update 8 | fi 9 | 10 | docker-compose up -d 11 | docker-compose logs -f --tail 50 web sidekiq 12 | -------------------------------------------------------------------------------- /_examples/script/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")/.." 3 | 4 | set -ex 5 | 6 | docker-compose down -v 7 | 8 | docker-compose build app 9 | 10 | docker-compose up -d pg 11 | 12 | docker-compose run --rm app bin/wait-for-postgres 13 | docker-compose run --rm app bin/rake db:create 14 | 15 | SKIP_BUILD=1 script/update 16 | 17 | docker-compose run --rm app bin/rake db:seed 18 | -------------------------------------------------------------------------------- /_examples/script/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")/.." 3 | 4 | set -ex 5 | 6 | if [ -z "${FAST}" ]; then 7 | script/update 8 | fi 9 | 10 | docker-compose up -d 11 | docker-compose exec app bin/rspec spec/ 12 | -------------------------------------------------------------------------------- /_examples/script/update: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")/.." 3 | 4 | set -ex 5 | 6 | script/bootstrap 7 | 8 | docker-compose up -d pg redis 9 | docker-compose run --rm app bin/wait-for-postgres 10 | docker-compose run --rm app bin/rake db:migrate 11 | 12 | docker-compose up -d 13 | -------------------------------------------------------------------------------- /_examples/wait-for-postgres: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo -n Waiting for postgres to start... 4 | while ! pg_isready -h ${POSTGRES_HOST:-localhost} > /dev/null; do 5 | sleep 0.5; echo -n .; done 6 | echo done 7 | -------------------------------------------------------------------------------- /_examples/webpacker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dockerizing_rails", 3 | "private": true, 4 | "dependencies": { 5 | "@rails/webpacker": "^4.0.7" 6 | }, 7 | "devDependencies": { 8 | "webpack-dev-server": "^3.7.2" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /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/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/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, or any plugin's 5 | // 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 rails-ujs 14 | //= require activestorage 15 | //= require turbolinks 16 | //= require jquery3 17 | //= require popper 18 | //= require bootstrap-sprockets 19 | //= require_tree . 20 | -------------------------------------------------------------------------------- /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/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap"; 2 | 3 | td.todo-checkbox { 4 | width: 10px; 5 | } 6 | -------------------------------------------------------------------------------- /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/activities_controller.rb: -------------------------------------------------------------------------------- 1 | class ActivitiesController < ApplicationController 2 | def index 3 | @activities = Activity.order(created_at: :desc).limit(25) 4 | @scores = Score.order(date: :desc).limit(7) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/controllers/archived_todos_controller.rb: -------------------------------------------------------------------------------- 1 | class ArchivedTodosController < ApplicationController 2 | def create 3 | Todo.completed.update_all(archived_at: Time.now) 4 | 5 | redirect_to todos_path 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/todos_controller.rb: -------------------------------------------------------------------------------- 1 | class TodosController < ApplicationController 2 | before_action :set_todo, only: [:update, :destroy] 3 | after_action :update_scores, only: [:create, :update, :destroy] 4 | 5 | # GET /todos 6 | def index 7 | @todos = Todo.public_send(current_filter || :all_todos).ordered 8 | end 9 | 10 | # POST /todos 11 | def create 12 | @todo = Todo.new(create_params) 13 | if @todo.save 14 | Activity.create(name: "todo_created", data: {id: @todo.id, title: @todo.title}) 15 | end 16 | 17 | redirect_to todos_path(filter: current_filter) 18 | end 19 | 20 | # PATCH/PUT /todos/1 21 | def update 22 | if @todo.update(update_params) && @todo.completed_changed? 23 | activity_name = @todo.completed ? "todo_marked_as_complete" : "todo_marked_as_active" 24 | Activity.create(name: activity_name, data: {id: @todo.id, title: @todo.title}) 25 | end 26 | 27 | respond_to do |format| 28 | format.html { redirect_to todos_path(filter: current_filter) } 29 | format.js 30 | end 31 | end 32 | 33 | # DELETE /todos/1 34 | def destroy 35 | if @todo.destroy 36 | Activity.create(name: "todo_deleted", data: {id: @todo.id, title: @todo.title}) 37 | end 38 | 39 | redirect_to todos_url(filter: current_filter) 40 | end 41 | 42 | private 43 | 44 | # Use callbacks to share common setup or constraints between actions. 45 | def set_todo 46 | @todo = Todo.find(params[:id]) 47 | end 48 | 49 | # Only allow a trusted parameter "white list" through. 50 | def create_params 51 | params.require(:todo).permit(:title) 52 | end 53 | 54 | def update_params 55 | params.require(:todo).permit(:completed) 56 | end 57 | 58 | def update_scores 59 | ScoreCalculator.call(Date.today) 60 | end 61 | 62 | def current_filter 63 | @filter ||= begin 64 | filter = params[:filter] 65 | allowed_filters = ["active", "completed"] 66 | allowed_filters.include?(filter) ? filter : nil 67 | end 68 | end 69 | 70 | helper_method :current_filter 71 | end 72 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console:0 */ 2 | // This file is automatically compiled by Webpack, along with any other files 3 | // present in this directory. You're encouraged to place your actual application logic in 4 | // a relevant structure within app/javascript and only use these pack files to reference 5 | // that code so it'll be compiled. 6 | // 7 | // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate 8 | // layout file, like app/views/layouts/application.html.erb 9 | 10 | 11 | // Uncomment to copy all static images under ../images to the output folder and reference 12 | // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) 13 | // or the `imagePath` JavaScript helper below. 14 | // 15 | // const images = require.context('../images', true) 16 | // const imagePath = (name) => images(name, true) 17 | 18 | console.log("Hello World from Webpacker"); 19 | -------------------------------------------------------------------------------- /app/javascript/src/_dark_theme.scss: -------------------------------------------------------------------------------- 1 | // Quick shout out to 2 | // Darkly 4.3.1 3 | // Bootswatch 4 | 5 | // 6 | // Color system 7 | // 8 | 9 | $white: #fff !default; 10 | $gray-100: #f8f9fa !default; 11 | $gray-200: #ebebeb !default; 12 | $gray-300: #dee2e6 !default; 13 | $gray-400: #ced4da !default; 14 | $gray-500: #adb5bd !default; 15 | $gray-600: #999 !default; 16 | $gray-700: #444 !default; 17 | $gray-800: #303030 !default; 18 | $gray-900: #222 !default; 19 | $black: #000 !default; 20 | 21 | $blue: #375a7f !default; 22 | $indigo: #6610f2 !default; 23 | $purple: #6f42c1 !default; 24 | $pink: #e83e8c !default; 25 | $red: #e74c3c !default; 26 | $orange: #fd7e14 !default; 27 | $yellow: #f39c12 !default; 28 | $green: #00bc8c !default; 29 | $teal: #20c997 !default; 30 | $cyan: #3498db !default; 31 | 32 | $primary: $blue !default; 33 | $secondary: $gray-700 !default; 34 | $success: $green !default; 35 | $info: $cyan !default; 36 | $warning: $yellow !default; 37 | $danger: $red !default; 38 | $light: $gray-600 !default; 39 | $dark: $gray-800 !default; 40 | 41 | $yiq-contrasted-threshold: 175 !default; 42 | 43 | // Body 44 | 45 | $body-bg: $gray-900 !default; 46 | $body-color: $white !default; 47 | 48 | // Links 49 | 50 | $link-color: $success !default; 51 | 52 | // Fonts 53 | -------------------------------------------------------------------------------- /app/javascript/src/application.scss: -------------------------------------------------------------------------------- 1 | .dark { 2 | @import "dark_theme"; 3 | @import "~bootstrap/scss/bootstrap"; 4 | } 5 | 6 | @import "~bootstrap/scss/bootstrap"; 7 | -------------------------------------------------------------------------------- /app/javascript/src/bootstrap_and_rails.js: -------------------------------------------------------------------------------- 1 | import Rails from "rails-ujs"; 2 | import Turbolinks from "turbolinks"; 3 | 4 | Rails.start(); 5 | Turbolinks.start(); 6 | 7 | import "./application.scss"; 8 | import "bootstrap"; 9 | 10 | $(document).ready(function() { 11 | $("#nightModeSwitch").change(function() { 12 | $("html").toggleClass("dark"); 13 | $("nav").toggleClass("navbar-light"); 14 | $("nav").toggleClass("navbar-dark"); 15 | $("nav").toggleClass("bg-light"); 16 | $("nav").toggleClass("bg-dark"); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /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/activity.rb: -------------------------------------------------------------------------------- 1 | class Activity < ApplicationRecord 2 | VALID_NAMES = %w[todo_marked_as_complete todo_marked_as_active todo_created todo_deleted] 3 | serialize :data, Hash 4 | 5 | validates :name, inclusion: {in: VALID_NAMES} 6 | end 7 | -------------------------------------------------------------------------------- /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/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/score.rb: -------------------------------------------------------------------------------- 1 | class Score < ApplicationRecord 2 | validates :date, :points, presence: true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/todo.rb: -------------------------------------------------------------------------------- 1 | class Todo < ApplicationRecord 2 | scope :ordered, -> { order(id: :asc) } 3 | scope :completed, -> { where(archived_at: nil, completed: true) } 4 | scope :active, -> { where(archived_at: nil, completed: false) } 5 | scope :all_todos, -> { where(archived_at: nil) } 6 | 7 | validates :title, presence: true 8 | 9 | def title=(title) 10 | write_attribute(:title, title.to_s.strip) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/services/score_calculator.rb: -------------------------------------------------------------------------------- 1 | class ScoreCalculator 2 | POINTS = { 3 | todo_created: 1, 4 | todo_deleted: -1, 5 | todo_marked_as_complete: 5, 6 | todo_marked_as_active: -5, 7 | } 8 | 9 | def self.call(date) 10 | total_points = POINTS.sum { |activity_name, points| 11 | Activity.where(created_at: [date.beginning_of_day..date.end_of_day], name: activity_name).count * points 12 | } 13 | 14 | score = Score.find_or_create_by(date: date) { |score| 15 | score.points = 0 16 | } 17 | score.update!(points: total_points) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/views/activities/_todo_created.html.erb: -------------------------------------------------------------------------------- 1 | '<%= data[:title] %>' has been created 2 | -------------------------------------------------------------------------------- /app/views/activities/_todo_deleted.html.erb: -------------------------------------------------------------------------------- 1 | '<%= data[:title] %>' has been deleted 2 | -------------------------------------------------------------------------------- /app/views/activities/_todo_marked_as_active.html.erb: -------------------------------------------------------------------------------- 1 | '<%= data[:title] %>' has been marked as active 2 | -------------------------------------------------------------------------------- /app/views/activities/_todo_marked_as_complete.html.erb: -------------------------------------------------------------------------------- 1 | '<%= data[:title] %>' has been marked as completed 2 | -------------------------------------------------------------------------------- /app/views/activities/index.html.erb: -------------------------------------------------------------------------------- 1 |

2 | 3 |
4 |
5 | <% if @scores.any? %> 6 |

Your scores

7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <% @scores.each do |score| %> 17 | 18 | 19 | 20 | 21 | <% end %> 22 | 23 |
DatePoints
<%= score.date %><%= score.points %>
24 | <% end %> 25 |
26 |
27 | 28 |

29 | 30 |
31 |
32 |

Logs

33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | <% @activities.each do |activity| %> 43 | 44 | 45 | 46 | 47 | <% end %> 48 | 49 |
WhenWhat
<%= activity.created_at %><%= render activity.name, data: activity.data %>
50 |
51 |
52 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dockerizing Rails 5 | <%= csrf_meta_tags %> 6 | <%= csp_meta_tag %> 7 | 8 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 9 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> 10 | 11 | 12 | 13 | 14 | 15 |
16 | 32 | 33 | <%= yield %> 34 | 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /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/todos/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: todo, local: true, class: "form") do |form| %> 2 | <%= hidden_field_tag :filter, current_filter %> 3 | 4 |
5 | <%= form.text_field :title, class: "form-control", placeholder: "What needs do be done?" %> 6 |
7 | 8 |
9 | <%= form.submit "Add", class: "btn btn-primary" %> 10 |
11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/views/todos/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 | 6 | 7 | 8 | 17 | 18 | 19 | 20 | 21 | <% @todos.each do |todo| %> 22 | 23 | 24 | 25 | 26 | 27 | <% end %> 28 | 29 |
9 | 16 |
<%= check_box_tag dom_id(todo, :checkbox), nil, todo.completed?, data: { url: todo_path(todo, filter: current_filter, todo: { completed: !todo.completed }), remote: :true, method: :patch } %> <%= todo.title %> <%= link_to 'Delete', todo_path(todo, filter: current_filter), method: :delete, data: { confirm: 'Are you sure?' } %>
30 |
31 | 32 |
33 |

34 | 35 | <%= render 'form', todo: Todo.new %> 36 | 37 |

38 | 39 | <%= link_to 'Archive completed todos', archived_todos_path(filter: current_filter), method: :post, data: { confirm: 'Are you sure?' } %> 40 | 41 |
42 |
43 | 44 | -------------------------------------------------------------------------------- /app/views/todos/update.js: -------------------------------------------------------------------------------- 1 | Turbolinks.visit(location.toString()); 2 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /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 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a starting point to setup your application. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | # puts "\n== Copying sample files ==" 24 | # unless File.exist?('config/database.yml') 25 | # cp 'config/database.yml.sample', 'config/database.yml' 26 | # end 27 | 28 | puts "\n== Preparing database ==" 29 | system! 'bin/rails db:setup' 30 | 31 | puts "\n== Removing old logs and tempfiles ==" 32 | system! 'bin/rails log:clear tmp:clear' 33 | 34 | puts "\n== Restarting application server ==" 35 | system! 'bin/rails restart' 36 | end 37 | -------------------------------------------------------------------------------- /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 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == 'spring' } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | puts "\n== Updating database ==" 24 | system! 'bin/rails db:migrate' 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! 'bin/rails log:clear tmp:clear' 28 | 29 | puts "\n== Restarting application server ==" 30 | system! 'bin/rails restart' 31 | end 32 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | begin 5 | exec "yarnpkg", *ARGV 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /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 | 3 | require "rails/all" 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module DockerizingRails 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 5.2 13 | 14 | # Settings in config/environments/* take precedence over those specified here. 15 | # Application configuration can go into files in config/initializers 16 | # -- all .rb files in that directory are automatically loaded after loading 17 | # the framework and any gems in your application. 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 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: dockerizing_rails_production 11 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | 9K5dvkgW1udyCfjXCCd9O3Ni65INkUwVDd6B8b51GT9UxEKHrnvr1GypYuUMXueyLf3PQ79hmw5UiNDMuQn1AQbMsLJRq1GojjaW55foMCUwx899mxHJR4en1l0Hw2+fpRG/WRffzsPxluOJFUGEjNv1LOz6m4WIjtEwMxynVWIrEcBVI9JVPH4874bN9M1YXK1DovgQWGZx6p9mVm9IoGOjlVK/Av0BDxs0zecaRZ3/EVae2g+g2QLFgW0K1latKK6jqumm8IVwMUIuLUFEqDo0ojCCb7r1qh/u87sa93lvnphQdgBKRyhkCRoWv6MzXboBw5rKNr8PQew+TrWakcUgD/68J45H0C6LhRX6SlemlMXL8VrfW3ZF0dsDZNRHdp1jg4ddENtWJ8US5pQu0tAQKYS8HPU2YP4b--Tw7DSoKujymZRNBc--1kbwCmQhsBiR80iwOojNaw== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /config/database.yml.postgres: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | encoding: unicode 4 | # For details on connection pooling, see Rails configuration guide 5 | # http://guides.rubyonrails.org/configuring.html#database-pooling 6 | pool: <%= ENV.fetch('RAILS_MAX_THREADS') { 5 } %> 7 | 8 | username: <%= ENV.fetch('POSTGRES_USER', 'postgres') %> 9 | password: <%= ENV.fetch('POSTGRES_PASSWORD', '') %> 10 | host: <%= ENV.fetch('POSTGRES_HOST', 'localhost') %> 11 | port: <%= ENV.fetch('POSTGRES_PORT', 5432) %> 12 | 13 | development: 14 | <<: *default 15 | database: dockerizing_rails_development 16 | 17 | test: 18 | <<: *default 19 | database: dockerizing_rails_test 20 | 21 | production: 22 | <<: *default 23 | database: dockerizing_rails_production 24 | -------------------------------------------------------------------------------- /config/database.yml.sqlite: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | # Run rails dev:cache to toggle caching. 17 | if Rails.root.join("tmp", "caching-dev.txt").exist? 18 | config.action_controller.perform_caching = true 19 | 20 | config.cache_store = :memory_store 21 | config.public_file_server.headers = { 22 | "Cache-Control" => "public, max-age=#{2.days.to_i}", 23 | } 24 | else 25 | config.action_controller.perform_caching = false 26 | 27 | config.cache_store = :null_store 28 | end 29 | 30 | # Store uploaded files on the local file system (see config/storage.yml for options) 31 | config.active_storage.service = :local 32 | 33 | # Don't care if the mailer can't send. 34 | config.action_mailer.raise_delivery_errors = false 35 | 36 | config.action_mailer.perform_caching = false 37 | 38 | # Print deprecation notices to the Rails logger. 39 | config.active_support.deprecation = :log 40 | 41 | # Raise an error on page load if there are pending migrations. 42 | config.active_record.migration_error = :page_load 43 | 44 | # Highlight code that triggered database queries in logs. 45 | config.active_record.verbose_query_logs = true 46 | 47 | # Debug mode disables concatenation and preprocessing of assets. 48 | # This option may cause significant delays in view rendering with a large 49 | # number of complex assets. 50 | config.assets.debug = true 51 | 52 | # Suppress logger output for asset requests. 53 | config.assets.quiet = true 54 | 55 | # Raises error for missing translations 56 | # config.action_view.raise_on_missing_translations = true 57 | 58 | # Use an evented file watcher to asynchronously detect changes in source code, 59 | # routes, locales, etc. This feature depends on the listen gem. 60 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 61 | end 62 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 19 | # config.require_master_key = true 20 | 21 | # Disable serving static files from the `/public` folder by default since 22 | # Apache or NGINX already handles this. 23 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 33 | 34 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 35 | # config.action_controller.asset_host = 'http://assets.example.com' 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 40 | 41 | # Store uploaded files on the local file system (see config/storage.yml for options) 42 | config.active_storage.service = :local 43 | 44 | # Mount Action Cable outside main process or domain 45 | # config.action_cable.mount_path = nil 46 | # config.action_cable.url = 'wss://example.com/cable' 47 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 48 | 49 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 50 | # config.force_ssl = true 51 | 52 | # Use the lowest log level to ensure availability of diagnostic information 53 | # when problems arise. 54 | config.log_level = :debug 55 | 56 | # Prepend all log lines with the following tags. 57 | config.log_tags = [:request_id] 58 | 59 | # Use a different cache store in production. 60 | # config.cache_store = :mem_cache_store 61 | 62 | # Use a real queuing backend for Active Job (and separate queues per environment) 63 | # config.active_job.queue_adapter = :resque 64 | # config.active_job.queue_name_prefix = "dockerizing_rails_#{Rails.env}" 65 | 66 | config.action_mailer.perform_caching = false 67 | 68 | # Ignore bad email addresses and do not raise email delivery errors. 69 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 70 | # config.action_mailer.raise_delivery_errors = false 71 | 72 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 73 | # the I18n.default_locale when a translation cannot be found). 74 | config.i18n.fallbacks = true 75 | 76 | # Send deprecation notices to registered listeners. 77 | config.active_support.deprecation = :notify 78 | 79 | # Use default logging formatter so that PID and timestamp are not suppressed. 80 | config.log_formatter = ::Logger::Formatter.new 81 | 82 | # Use a different logger for distributed setups. 83 | # require 'syslog/logger' 84 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 85 | 86 | if ENV["RAILS_LOG_TO_STDOUT"].present? 87 | logger = ActiveSupport::Logger.new(STDOUT) 88 | logger.formatter = config.log_formatter 89 | config.logger = ActiveSupport::TaggedLogging.new(logger) 90 | end 91 | 92 | # Do not dump schema after migrations. 93 | config.active_record.dump_schema_after_migration = false 94 | end 95 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | "Cache-Control" => "public, max-age=#{1.hour.to_i}", 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | 31 | # Store uploaded files on the local file system in a temporary directory 32 | config.active_storage.service = :test 33 | 34 | config.action_mailer.perform_caching = false 35 | 36 | # Tell Action Mailer not to deliver emails to the real world. 37 | # The :test delivery method accumulates sent emails in the 38 | # ActionMailer::Base.deliveries array. 39 | config.action_mailer.delivery_method = :test 40 | 41 | # Print deprecation notices to the stderr. 42 | config.active_support.deprecation = :stderr 43 | 44 | # Raises error for missing translations 45 | # config.action_view.raise_on_missing_translations = true 46 | end 47 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /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 = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join("node_modules") 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /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/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | 19 | # If you are using UJS then enable automatic nonce generation 20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 21 | 22 | # Report CSP violations to a specified URI 23 | # For further information see the following documentation: 24 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 25 | # Rails.application.config.content_security_policy_report_only = true 26 | -------------------------------------------------------------------------------- /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/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/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/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 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. 30 | # 31 | # preload_app! 32 | 33 | # Allow puma to be restarted by `rails restart` command. 34 | plugin :tmp_restart 35 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :todos, except: [:new, :edit, :show] 3 | resources :archived_todos, only: [:create] 4 | resources :activities, only: [:index] 5 | 6 | root to: "todos#index" 7 | end 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /db/development.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/db/development.sqlite3 -------------------------------------------------------------------------------- /db/migrate/20190721025643_create_todos.rb: -------------------------------------------------------------------------------- 1 | class CreateTodos < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :todos do |t| 4 | t.string :title 5 | t.boolean :completed, default: false 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20190721162926_add_archived_to_todos.rb: -------------------------------------------------------------------------------- 1 | class AddArchivedToTodos < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :todos, :archived_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20190721171220_create_activities.rb: -------------------------------------------------------------------------------- 1 | class CreateActivities < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :activities do |t| 4 | t.string :name 5 | t.text :data 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20190721200733_create_scores.rb: -------------------------------------------------------------------------------- 1 | class CreateScores < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :scores do |t| 4 | t.date :date, unique: true 5 | t.integer :points 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: 2019_07_21_200733) do 14 | 15 | create_table "activities", force: :cascade do |t| 16 | t.string "name" 17 | t.text "data" 18 | t.datetime "created_at", null: false 19 | t.datetime "updated_at", null: false 20 | end 21 | 22 | create_table "scores", force: :cascade do |t| 23 | t.date "date" 24 | t.integer "points" 25 | t.datetime "created_at", null: false 26 | t.datetime "updated_at", null: false 27 | end 28 | 29 | create_table "todos", force: :cascade do |t| 30 | t.string "title" 31 | t.boolean "completed", default: false 32 | t.datetime "created_at", null: false 33 | t.datetime "updated_at", null: false 34 | t.datetime "archived_at" 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /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 | 9 | Todo.create!(title: "Laundry", created_at: 51.hours.ago) 10 | Todo.create!(title: "Bring the trash out", completed: true, created_at: 51.3.hours.ago) 11 | Todo.create!(title: "Shopping", created_at: 1.hours.ago) 12 | 13 | Activity.create!([ 14 | {name: "todo_created", data: {title: "Laundry"}, created_at: 51.hours.ago}, 15 | {name: "todo_created", data: {title: "Bring the trash out"}, created_at: 51.3.hours.ago}, 16 | {name: "todo_marked_as_complete", data: {title: "Bring the trash out"}, created_at: 48.1.hours.ago}, 17 | {name: "todo_created", data: {title: "Pick the kids up from soccer"}, created_at: 25.3.hours.ago}, 18 | {name: "todo_deleted", data: {title: "Pick the kids up from soccer"}, created_at: 24.0.hours.ago}, 19 | {name: "todo_created", data: {title: "Shopping"}, created_at: 1.hours.ago}, 20 | ]) 21 | 22 | 0.upto(3) do |i| 23 | date = Date.today - i.days 24 | ScoreCalculator.call(date) 25 | end 26 | -------------------------------------------------------------------------------- /db/test.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/db/test.sqlite3 -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dockerizing_rails", 3 | "private": true, 4 | "dependencies": {} 5 | } 6 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/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 | -------------------------------------------------------------------------------- /spec/models/activity_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Activity, type: :model do 4 | it "required a valid name" do 5 | invalid_activity = Activity.create(name: "invalid", data: {title: "foo"}) 6 | valid_activity = Activity.create(name: Activity::VALID_NAMES.sample, data: {title: "foo"}) 7 | 8 | expect(invalid_activity).not_to be_valid 9 | expect(valid_activity).to be_valid 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/models/todo_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe Todo, type: :model do 4 | it "requires a title" do 5 | todo = Todo.create(title: nil) 6 | expect(todo).not_to be_valid 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | require "spec_helper" 3 | ENV["RAILS_ENV"] ||= "test" 4 | require File.expand_path("../../config/environment", __FILE__) 5 | # Prevent database truncation if the environment is production 6 | abort("The Rails environment is running in production mode!") if Rails.env.production? 7 | require "rspec/rails" 8 | # Add additional requires below this line. Rails is not loaded until this point! 9 | 10 | # Requires supporting ruby files with custom matchers and macros, etc, in 11 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 12 | # run as spec files by default. This means that files in spec/support that end 13 | # in _spec.rb will both be required and run as specs, causing the specs to be 14 | # run twice. It is recommended that you do not name files matching this glob to 15 | # end with _spec.rb. You can configure this pattern with the --pattern 16 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 17 | # 18 | # The following line is provided for convenience purposes. It has the downside 19 | # of increasing the boot-up time by auto-requiring all files in the support 20 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 21 | # require only the support files necessary. 22 | # 23 | # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f } 24 | 25 | # Checks for pending migrations and applies them before tests are run. 26 | # If you are not using ActiveRecord, you can remove these lines. 27 | begin 28 | ActiveRecord::Migration.maintain_test_schema! 29 | rescue ActiveRecord::PendingMigrationError => e 30 | puts e.to_s.strip 31 | exit 1 32 | end 33 | RSpec.configure do |config| 34 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 35 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 36 | 37 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 38 | # examples within a transaction, remove the following line or assign false 39 | # instead of true. 40 | config.use_transactional_fixtures = true 41 | 42 | # RSpec Rails can automatically mix in different behaviours to your tests 43 | # based on their file location, for example enabling you to call `get` and 44 | # `post` in specs under `spec/controllers`. 45 | # 46 | # You can disable this behaviour by removing the line below, and instead 47 | # explicitly tag your specs with their type, e.g.: 48 | # 49 | # RSpec.describe UsersController, :type => :controller do 50 | # # ... 51 | # end 52 | # 53 | # The different available types are documented in the features, such as in 54 | # https://relishapp.com/rspec/rspec-rails/docs 55 | config.infer_spec_type_from_file_location! 56 | 57 | # Filter lines from Rails gems in backtraces. 58 | config.filter_rails_from_backtrace! 59 | # arbitrary gems may also be filtered via: 60 | # config.filter_gems_from_backtrace("gem name") 61 | end 62 | -------------------------------------------------------------------------------- /spec/requests/activities_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Acitivites", type: :request do 4 | describe "GET /activities" do 5 | it "lists activities" do 6 | Activity::VALID_NAMES.each do |name| 7 | Activity.create(name: name, data: {title: "laundry"}) 8 | end 9 | 10 | get activities_path 11 | 12 | expect(response).to have_http_status(200) 13 | expect(response.body).to include("laundry") 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/requests/archived_todos_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "ArchivedTodos", type: :request do 4 | describe "POST /archived_todos" do 5 | it "archives all complted todos" do 6 | completed_todos = [ 7 | Todo.create(title: "laundry", completed: true), 8 | Todo.create(title: "shopping", completed: true), 9 | ] 10 | active_todo = Todo.create(title: "cleaning", completed: false) 11 | 12 | post archived_todos_path 13 | expect(response).to redirect_to(todos_path) 14 | 15 | expect(completed_todos.each(&:reload).map(&:archived_at)).not_to include(nil) 16 | expect(active_todo.reload.archived_at).to be_nil 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/requests/todos_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe "Todos", type: :request do 4 | let(:valid_attributes) { 5 | {title: "laundry"} 6 | } 7 | 8 | let(:invalid_attributes) { 9 | {title: ""} 10 | } 11 | 12 | describe "GET /todos" do 13 | it "lists all todos" do 14 | Todo.create!(title: "laundry") 15 | Todo.create!(title: "shopping") 16 | 17 | get todos_path 18 | 19 | expect(response).to have_http_status(200) 20 | expect(response.body).to include("laundry") 21 | expect(response.body).to include("shopping") 22 | end 23 | 24 | context "with the filter" do 25 | before do 26 | Todo.create!(title: "laundry", completed: false) 27 | Todo.create!(title: "shopping", completed: true) 28 | end 29 | 30 | context "set to completed" do 31 | it "only returns completed todos" do 32 | get todos_path, params: {filter: "completed"} 33 | expect(response).to have_http_status(200) 34 | 35 | expect(response.body).not_to include("laundry") 36 | expect(response.body).to include("shopping") 37 | end 38 | end 39 | 40 | context "set to active" do 41 | it "only returns active todos" do 42 | get todos_path, params: {filter: "active"} 43 | expect(response).to have_http_status(200) 44 | 45 | expect(response.body).to include("laundry") 46 | expect(response.body).not_to include("shopping") 47 | end 48 | end 49 | 50 | context "set to all" do 51 | it "returns all todos" do 52 | get todos_path, params: {filter: "all"} 53 | expect(response).to have_http_status(200) 54 | 55 | expect(response.body).to include("laundry") 56 | expect(response.body).to include("shopping") 57 | end 58 | end 59 | end 60 | end 61 | 62 | describe "POST /todos" do 63 | context "with valid params" do 64 | it "creates a new Todo" do 65 | expect { 66 | post todos_path, params: {todo: valid_attributes} 67 | }.to change(Todo, :count).by(1) 68 | expect(response).to redirect_to(todos_path) 69 | end 70 | 71 | it "creates an entry in the activity log" do 72 | expect { 73 | post todos_path, params: {todo: valid_attributes} 74 | }.to change(Activity, :count).by(1).and change(Score, :count).by(1) 75 | end 76 | end 77 | 78 | context "with invalid params" do 79 | it "redirects to the todo list" do 80 | post todos_path, params: {todo: invalid_attributes} 81 | 82 | expect(response).to redirect_to(todos_path) 83 | end 84 | 85 | it "does not create an acitivity log entry" do 86 | expect { 87 | post todos_path, params: {todo: invalid_attributes} 88 | }.not_to change(Activity, :count) 89 | end 90 | end 91 | 92 | context "with a filter set" do 93 | it "keeps the filter when redirecting" do 94 | Todo.create! valid_attributes 95 | 96 | post todos_path, params: {filter: "active", todo: valid_attributes} 97 | 98 | expect(response).to redirect_to(todos_path(filter: "active")) 99 | end 100 | end 101 | end 102 | 103 | describe "PATCH /todos/:id" do 104 | context "with valid params" do 105 | let(:new_attributes) { 106 | {completed: true} 107 | } 108 | 109 | it "updates the requested todo" do 110 | todo = Todo.create! valid_attributes 111 | expect(todo.completed).to be_falsy 112 | 113 | patch todo_path(todo), params: {id: todo.to_param, todo: new_attributes} 114 | todo.reload 115 | 116 | expect(response).to redirect_to(todos_path) 117 | expect(todo.completed).to be_truthy 118 | end 119 | 120 | it "creates an entry in the activity log" do 121 | todo = Todo.create! valid_attributes 122 | 123 | expect { 124 | patch todo_path(todo), params: {id: todo.to_param, todo: new_attributes} 125 | }.to change(Activity, :count).by(1).and change(Score, :count).by(1) 126 | end 127 | end 128 | 129 | context "with invalid params" do 130 | it "redirects to the todos" do 131 | todo = Todo.create! valid_attributes 132 | 133 | patch todo_path(todo), params: {id: todo.to_param, todo: invalid_attributes} 134 | expect(response).to redirect_to(todos_path) 135 | end 136 | 137 | it "does not create an acitivity log entry" do 138 | todo = Todo.create! valid_attributes 139 | 140 | expect { 141 | patch todo_path(todo), params: {id: todo.to_param, todo: invalid_attributes} 142 | }.to_not change(Activity, :count) 143 | end 144 | end 145 | 146 | context "with a filter set" do 147 | it "keeps the filter when redirecting" do 148 | todo = Todo.create! valid_attributes 149 | 150 | patch todo_path(todo), params: {filter: "active", id: todo.to_param, todo: invalid_attributes} 151 | 152 | expect(response).to redirect_to(todos_path(filter: "active")) 153 | end 154 | end 155 | end 156 | 157 | describe "DELETE /todos/:id" do 158 | it "destroys the requested todo" do 159 | todo = Todo.create! valid_attributes 160 | 161 | expect { 162 | delete todo_path(todo), params: {id: todo.to_param} 163 | }.to change(Todo, :count).by(-1) 164 | expect(response).to redirect_to(todos_path) 165 | end 166 | 167 | it "creates an entry in the activity log" do 168 | todo = Todo.create! valid_attributes 169 | 170 | expect { 171 | delete todo_path(todo), params: {id: todo.to_param} 172 | }.to change(Activity, :count).by(1).and change(Score, :count).by(1) 173 | end 174 | 175 | context "with a filter set" do 176 | it "keeps the filter when redirecting" do 177 | todo = Todo.create! valid_attributes 178 | 179 | delete todo_path(todo), params: {filter: "active", id: todo.to_param} 180 | 181 | expect(response).to redirect_to(todos_path(filter: "active")) 182 | end 183 | end 184 | end 185 | end 186 | -------------------------------------------------------------------------------- /spec/services/score_calculator_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe ScoreCalculator do 4 | describe ".call" do 5 | it "generates the total score for a given date" do 6 | start_time = Time.now.beginning_of_day 7 | 8 | Activity.create!(name: "todo_created", data: {title: "old todo"}, created_at: 48.hours.ago) 9 | Activity.create!(name: "todo_created", data: {title: "actual todo"}, created_at: start_time + 1.hour) 10 | Activity.create!(name: "todo_created", data: {title: "deleted todo"}, created_at: start_time + 1.hour) 11 | Activity.create!(name: "todo_deleted", data: {title: "deleted todo"}, created_at: start_time + 1.hour) 12 | Activity.create!(name: "todo_marked_as_complete", data: {title: "actual todo"}, created_at: start_time + 2.hour) 13 | Activity.create!(name: "todo_marked_as_active", data: {title: "actual todo"}, created_at: start_time + 3.hour) 14 | Activity.create!(name: "todo_marked_as_complete", data: {title: "actual todo"}, created_at: start_time + 4.hour) 15 | 16 | expect { 17 | described_class.call(start_time.to_date) 18 | }.to change(Score, :count).by(1) 19 | expect(Score.last.points).to eq(6) 20 | end 21 | 22 | it "updates existing scores" do 23 | date = Date.today 24 | score = Score.create(date: date, points: 0) 25 | Activity.create!(name: "todo_created", data: {title: "actual todo"}, created_at: date.beginning_of_day) 26 | 27 | expect { 28 | described_class.call(date) 29 | }.not_to change(Score, :count) 30 | expect(score.reload.points).to eq(1) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 16 | RSpec.configure do |config| 17 | # rspec-expectations config goes here. You can use an alternate 18 | # assertion/expectation library such as wrong or the stdlib/minitest 19 | # assertions if you prefer. 20 | config.expect_with :rspec do |expectations| 21 | # This option will default to `true` in RSpec 4. It makes the `description` 22 | # and `failure_message` of custom matchers include text for helper methods 23 | # defined using `chain`, e.g.: 24 | # be_bigger_than(2).and_smaller_than(4).description 25 | # # => "be bigger than 2 and smaller than 4" 26 | # ...rather than: 27 | # # => "be bigger than 2" 28 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 29 | end 30 | 31 | # rspec-mocks config goes here. You can use an alternate test double 32 | # library (such as bogus or mocha) by changing the `mock_with` option here. 33 | config.mock_with :rspec do |mocks| 34 | # Prevents you from mocking or stubbing a method that does not exist on 35 | # a real object. This is generally recommended, and will default to 36 | # `true` in RSpec 4. 37 | mocks.verify_partial_doubles = true 38 | end 39 | 40 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 41 | # have no way to turn it off -- the option exists only for backwards 42 | # compatibility in RSpec 3). It causes shared context metadata to be 43 | # inherited by the metadata hash of host groups and examples, rather than 44 | # triggering implicit auto-inclusion in groups with matching metadata. 45 | config.shared_context_metadata_behavior = :apply_to_host_groups 46 | 47 | # The settings below are suggested to provide a good initial experience 48 | # with RSpec, but feel free to customize to your heart's content. 49 | # # This allows you to limit a spec run to individual examples or groups 50 | # # you care about by tagging them with `:focus` metadata. When nothing 51 | # # is tagged with `:focus`, all examples get run. RSpec also provides 52 | # # aliases for `it`, `describe`, and `context` that include `:focus` 53 | # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 54 | # config.filter_run_when_matching :focus 55 | # 56 | # # Allows RSpec to persist some state between runs in order to support 57 | # # the `--only-failures` and `--next-failure` CLI options. We recommend 58 | # # you configure your source control system to ignore this file. 59 | # config.example_status_persistence_file_path = "spec/examples.txt" 60 | # 61 | # # Limits the available syntax to the non-monkey patched syntax that is 62 | # # recommended. For more details, see: 63 | # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 64 | # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 65 | # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 66 | # config.disable_monkey_patching! 67 | # 68 | # # Many RSpec users commonly either run the entire suite or an individual 69 | # # file, and it's useful to allow more verbose output when running an 70 | # # individual spec file. 71 | # if config.files_to_run.one? 72 | # # Use the documentation formatter for detailed output, 73 | # # unless a formatter has already been configured 74 | # # (e.g. via a command-line flag). 75 | # config.default_formatter = "doc" 76 | # end 77 | # 78 | # # Print the 10 slowest examples and example groups at the 79 | # # end of the spec run, to help surface which specs are running 80 | # # particularly slow. 81 | # config.profile_examples = 10 82 | # 83 | # # Run specs in random order to surface order dependencies. If you find an 84 | # # order dependency and want to debug it, you can fix the order by providing 85 | # # the seed, which is printed after each run. 86 | # # --seed 1234 87 | # config.order = :random 88 | # 89 | # # Seed global randomization in this process using the `--seed` CLI option. 90 | # # Setting this allows you to use `--seed` to deterministically reproduce 91 | # # test failures related to randomization by passing the same `--seed` value 92 | # # as the one that triggered the failure. 93 | # Kernel.srand config.seed 94 | end 95 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/storage/.keep -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/tmp/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jfahrer/dockerizing_rails/dedc81dd2d73c0139d57ba226a315fc5252d893f/vendor/.keep --------------------------------------------------------------------------------