├── .browserslistrc ├── .dockerignore ├── .env.example ├── .github ├── dependabot.yml └── workflows │ └── rails.yml ├── .gitignore ├── .mergify.yml ├── .overcommit.yml ├── .rubocop.yml ├── .ruby-version ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Makefile ├── README.md ├── Rakefile ├── app.json ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ └── .keep │ └── stylesheets │ │ ├── application.css │ │ ├── appointments.scss │ │ └── bootstrap_and_overrides.css ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── application_controller.rb │ ├── appointments_controller.rb │ └── concerns │ │ └── .keep ├── helpers │ ├── application_helper.rb │ └── appointments_helper.rb ├── javascript │ ├── channels │ │ ├── consumer.js │ │ └── index.js │ ├── packs │ │ └── application.js │ └── stylesheets │ │ └── application.scss ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── appointment.rb │ └── concerns │ │ └── .keep └── views │ ├── appointments │ ├── _form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── index.json.jbuilder │ ├── new.html.erb │ ├── show.html.erb │ ├── show.json.jbuilder │ └── welcome.html.erb │ └── layouts │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb ├── babel.config.js ├── bin ├── bundle ├── delayed_job ├── rails ├── rake ├── setup ├── spring ├── update ├── webpack ├── webpack-dev-server └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── 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 │ ├── delayed_job_config.rb │ ├── dotenv.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ └── wrap_parameters.rb ├── locales │ ├── en.bootstrap.yml │ └── en.yml ├── puma.rb ├── routes.rb ├── spring.rb ├── storage.yml ├── webpack │ ├── development.js │ ├── environment.js │ ├── production.js │ └── test.js └── webpacker.yml ├── db ├── migrate │ └── 20190121114109_create_delayed_jobs.rb ├── schema.rb └── seeds.rb ├── docker-compose.yml ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── storage └── .keep ├── test ├── application_system_test_case.rb ├── controllers │ ├── .keep │ └── appointments_controller_test.rb ├── fixtures │ ├── .keep │ ├── appointments.yml │ └── files │ │ └── .keep ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ └── appointment_test.rb ├── system │ └── .keep └── test_helper.rb ├── tmp └── .keep └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Your Twilio Account SID. Get a free account at twilio.com/try-twilio 2 | TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX 3 | # Your Twilio Auth Token. You can get it at twilio.com/console 4 | TWILIO_AUTH_TOKEN=yourAuthToken 5 | # The Twilio phone number you want to use to send SMS. Get one in the Twilio Console 6 | TWILIO_NUMBER=+12223334444 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | ignore: 9 | - dependency-name: bootsnap 10 | versions: 11 | - 1.6.0 12 | - 1.7.3 13 | - dependency-name: twilio-ruby 14 | versions: 15 | - 5.46.1 16 | -------------------------------------------------------------------------------- /.github/workflows/rails.yml: -------------------------------------------------------------------------------- 1 | name: Rails CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | os: [ubuntu-latest, macos-latest, windows-latest] 11 | ruby: [ '2.6' ] 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | - name: Install SQLite (Ubuntu) 17 | if: runner.os == 'Linux' 18 | run: sudo apt-get update && sudo apt-get install -yqq libsqlite3-dev 19 | - name: Install SQLite (OSX) 20 | if: runner.os == 'macOs' 21 | run: brew install sqlite3 22 | - name: Install SQLite (Windows) 23 | if: runner.os == 'Windows' 24 | uses: crazy-max/ghaction-chocolatey@v1 25 | with: 26 | args: install sqlite 27 | - name: Setup Ruby ${{ matrix.ruby }} 28 | uses: actions/setup-ruby@v1 29 | with: 30 | ruby-version: ${{ matrix.ruby }} 31 | - name: Ruby gem cache 32 | uses: actions/cache@v1 33 | with: 34 | path: vendor/bundle 35 | key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} 36 | restore-keys: | 37 | ${{ runner.os }}-gems- 38 | - name: Install SQLite gem (Windows) 39 | if: runner.os == 'Windows' 40 | run: gem install sqlite3 --platform=ruby -- --with-sqlite3-include=/c:/ProgramData/ProgramData/lib/SQLite/tools 41 | - name: Install gems 42 | run: | 43 | gem install bundler 44 | bundle config path vendor/bundle 45 | bundle install --jobs 4 --retry 3 46 | - name: Setup Node 47 | uses: actions/setup-node@v1 48 | with: 49 | node-version: 12.13.1 50 | - name: Install packages 51 | run: | 52 | npm install 53 | - name: Run tests 54 | run: | 55 | cp .env.example .env 56 | bundle exec rails db:setup 57 | bundle exec rails test 58 | env: 59 | RAILS_ENV: test 60 | -------------------------------------------------------------------------------- /.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 all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | # Ignore uploaded files in development 17 | /storage/* 18 | !/storage/.keep 19 | 20 | /node_modules 21 | /yarn-error.log 22 | 23 | /public/assets 24 | .byebug_history 25 | 26 | # Ignore master key for decrypting credentials and more. 27 | /config/master.key 28 | 29 | .env 30 | db/development.sqlite3 31 | db/test.sqlite3 32 | vendor/bundle 33 | 34 | .tool-versions 35 | 36 | /public/packs 37 | /public/packs-test 38 | /yarn-error.log 39 | yarn-debug.log* 40 | .yarn-integrity 41 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: automatic merge for Dependabot pull requests 3 | conditions: 4 | - author=dependabot-preview[bot] 5 | - status-success=build (ubuntu-latest, 2.6) 6 | - status-success=build (macos-latest, 2.6) 7 | - status-success=build (windows-latest, 2.6) 8 | actions: 9 | merge: 10 | method: squash 11 | -------------------------------------------------------------------------------- /.overcommit.yml: -------------------------------------------------------------------------------- 1 | gemfile: 'Gemfile' 2 | PreCommit: 3 | # Style Check 4 | RuboCop: 5 | enabled: true 6 | command: ['bundle', 'exec', 'rubocop', '-a'] 7 | on_warn: fail 8 | 9 | # Dependency Check 10 | BundleCheck: 11 | enabled: true 12 | on_warn: fail 13 | 14 | # Migration Check 15 | RailsSchemaUpToDate: 16 | enabled: true 17 | on_warn: fail 18 | 19 | # Checks for hard tabs in files 20 | HardTabs: 21 | enabled: true 22 | on_warn: fail 23 | 24 | PrePush: 25 | # Unit & Integration TEST 26 | RSpec: 27 | enabled: true 28 | command: ['bundle', 'exec', 'rspec'] 29 | on_warn: fail 30 | 31 | BundleInstall: 32 | enabled: true 33 | on_warn: fail -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - rubocop-rails 3 | - rubocop-rspec 4 | 5 | Layout/LineLength: 6 | IgnoredPatterns: ['(\A|\s)#'] 7 | 8 | Style/Documentation: 9 | Enabled: false 10 | 11 | Style/ClassAndModuleChildren: 12 | Enabled: false 13 | 14 | Rails/FilePath: 15 | EnforcedStyle: arguments 16 | 17 | Rails/DynamicFindBy: 18 | Enabled: false 19 | 20 | Metrics/MethodLength: 21 | Enabled: false 22 | 23 | Metrics/BlockLength: 24 | Enabled: false 25 | 26 | Style/HashEachMethods: 27 | Enabled: true 28 | 29 | Style/HashTransformKeys: 30 | Enabled: true 31 | 32 | Style/HashTransformValues: 33 | Enabled: true 34 | 35 | RSpec/EmptyExampleGroup: 36 | Enabled: false 37 | 38 | RSpec/DescribeClass: 39 | Enabled: false 40 | 41 | RSpec/ExampleLength: 42 | Enabled: false 43 | 44 | RSpec/MultipleExpectations: 45 | Enabled: false -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.6.5 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at open-source@twilio.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Twilio 2 | 3 | All third party contributors acknowledge that any contributions they provide will be made under the same open source license that the open source project is provided under. 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.6 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY Gemfile ./ 6 | 7 | COPY Makefile ./ 8 | 9 | COPY package.json ./ 10 | 11 | # Install a Javascript environment in the container to avoid ExecJS::RuntimeUnavailable 12 | RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \ 13 | && apt install -y nodejs 14 | 15 | RUN make install 16 | 17 | COPY . . 18 | 19 | RUN make database 20 | 21 | EXPOSE 3000 22 | 23 | CMD ["sh", "-c","./bin/delayed_job start && make serve"] 24 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 5 | 6 | ruby '~> 2.6' 7 | 8 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 9 | gem 'rails', '~> 6.1.4' 10 | # Use sqlite3 as the database for Active Record 11 | gem 'sqlite3', '~> 1.4' 12 | # Use Puma as the app server 13 | gem 'puma', '~> 5.4' 14 | # Use SCSS for stylesheets 15 | gem 'sass-rails', '>= 6' 16 | # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker 17 | gem 'webpacker', '~> 5.4' 18 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 19 | gem 'turbolinks', '~> 5' 20 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 21 | gem 'jbuilder', '~> 2.11' 22 | 23 | # Reduces boot times through caching; required in config/boot.rb 24 | 25 | # Use CoffeeScript for .coffee assets and views 26 | gem 'coffee-rails', '~> 5.0' 27 | 28 | gem 'rails-controller-testing' 29 | 30 | # Use jquery as the JavaScript library 31 | gem 'jquery-rails' 32 | 33 | gem 'bootstrap', '~> 5.0' 34 | # Use bootstrap themes 35 | gem 'twitter-bootstrap-rails', :git => 'git://github.com/seyhunak/twitter-bootstrap-rails.git' 36 | 37 | # Use delayed job for running background jobs 38 | gem 'delayed_job_active_record' 39 | 40 | # Need daemons to start delayed_job 41 | gem 'daemons' 42 | 43 | gem 'bootsnap', '>= 1.4.2', require: false 44 | 45 | group :development, :test do 46 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 47 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 48 | 49 | gem 'dotenv-rails', '~> 2.7' 50 | end 51 | 52 | group :development do 53 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code. 54 | gem 'listen', '>= 3.0.5', '< 3.7' 55 | gem 'web-console', '>= 3.3.0' 56 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 57 | gem 'spring' 58 | gem 'spring-watcher-listen', '~> 2.0.0' 59 | 60 | gem 'overcommit', '~> 0.58.0', require: false 61 | gem 'rubocop', '~> 1.18.4', require: false 62 | gem 'rubocop-rails', '~> 2.11', require: false 63 | end 64 | 65 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 66 | gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] 67 | 68 | gem 'twilio-ruby', '~> 5.57' 69 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/seyhunak/twitter-bootstrap-rails.git 3 | revision: a9a5b41735cdb37e0dcb878eb25dcdf6174412cd 4 | specs: 5 | twitter-bootstrap-rails (4.0.0) 6 | actionpack (>= 5.0, < 7.0) 7 | execjs (~> 2.7) 8 | less-rails (>= 3.0, < 5.0) 9 | railties (>= 5.0, < 7.0) 10 | 11 | GEM 12 | remote: https://rubygems.org/ 13 | specs: 14 | actioncable (6.1.4) 15 | actionpack (= 6.1.4) 16 | activesupport (= 6.1.4) 17 | nio4r (~> 2.0) 18 | websocket-driver (>= 0.6.1) 19 | actionmailbox (6.1.4) 20 | actionpack (= 6.1.4) 21 | activejob (= 6.1.4) 22 | activerecord (= 6.1.4) 23 | activestorage (= 6.1.4) 24 | activesupport (= 6.1.4) 25 | mail (>= 2.7.1) 26 | actionmailer (6.1.4) 27 | actionpack (= 6.1.4) 28 | actionview (= 6.1.4) 29 | activejob (= 6.1.4) 30 | activesupport (= 6.1.4) 31 | mail (~> 2.5, >= 2.5.4) 32 | rails-dom-testing (~> 2.0) 33 | actionpack (6.1.4) 34 | actionview (= 6.1.4) 35 | activesupport (= 6.1.4) 36 | rack (~> 2.0, >= 2.0.9) 37 | rack-test (>= 0.6.3) 38 | rails-dom-testing (~> 2.0) 39 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 40 | actiontext (6.1.4) 41 | actionpack (= 6.1.4) 42 | activerecord (= 6.1.4) 43 | activestorage (= 6.1.4) 44 | activesupport (= 6.1.4) 45 | nokogiri (>= 1.8.5) 46 | actionview (6.1.4) 47 | activesupport (= 6.1.4) 48 | builder (~> 3.1) 49 | erubi (~> 1.4) 50 | rails-dom-testing (~> 2.0) 51 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 52 | activejob (6.1.4) 53 | activesupport (= 6.1.4) 54 | globalid (>= 0.3.6) 55 | activemodel (6.1.4) 56 | activesupport (= 6.1.4) 57 | activerecord (6.1.4) 58 | activemodel (= 6.1.4) 59 | activesupport (= 6.1.4) 60 | activestorage (6.1.4) 61 | actionpack (= 6.1.4) 62 | activejob (= 6.1.4) 63 | activerecord (= 6.1.4) 64 | activesupport (= 6.1.4) 65 | marcel (~> 1.0.0) 66 | mini_mime (>= 1.1.0) 67 | activesupport (6.1.4) 68 | concurrent-ruby (~> 1.0, >= 1.0.2) 69 | i18n (>= 1.6, < 2) 70 | minitest (>= 5.1) 71 | tzinfo (~> 2.0) 72 | zeitwerk (~> 2.3) 73 | ast (2.4.2) 74 | autoprefixer-rails (10.2.5.1) 75 | execjs (> 0) 76 | bindex (0.8.1) 77 | bootsnap (1.7.7) 78 | msgpack (~> 1.0) 79 | bootstrap (5.0.1) 80 | autoprefixer-rails (>= 9.1.0) 81 | popper_js (>= 2.9.2, < 3) 82 | sassc-rails (>= 2.0.0) 83 | builder (3.2.4) 84 | byebug (11.1.3) 85 | childprocess (4.1.0) 86 | coffee-rails (5.0.0) 87 | coffee-script (>= 2.2.0) 88 | railties (>= 5.2.0) 89 | coffee-script (2.4.1) 90 | coffee-script-source 91 | execjs 92 | coffee-script-source (1.12.2) 93 | commonjs (0.2.7) 94 | concurrent-ruby (1.1.9) 95 | crass (1.0.6) 96 | daemons (1.4.0) 97 | delayed_job (4.1.9) 98 | activesupport (>= 3.0, < 6.2) 99 | delayed_job_active_record (4.1.6) 100 | activerecord (>= 3.0, < 6.2) 101 | delayed_job (>= 3.0, < 5) 102 | dotenv (2.7.6) 103 | dotenv-rails (2.7.6) 104 | dotenv (= 2.7.6) 105 | railties (>= 3.2) 106 | erubi (1.10.0) 107 | execjs (2.8.1) 108 | faraday (1.5.1) 109 | faraday-em_http (~> 1.0) 110 | faraday-em_synchrony (~> 1.0) 111 | faraday-excon (~> 1.1) 112 | faraday-httpclient (~> 1.0.1) 113 | faraday-net_http (~> 1.0) 114 | faraday-net_http_persistent (~> 1.1) 115 | faraday-patron (~> 1.0) 116 | multipart-post (>= 1.2, < 3) 117 | ruby2_keywords (>= 0.0.4) 118 | faraday-em_http (1.0.0) 119 | faraday-em_synchrony (1.0.0) 120 | faraday-excon (1.1.0) 121 | faraday-httpclient (1.0.1) 122 | faraday-net_http (1.0.1) 123 | faraday-net_http_persistent (1.2.0) 124 | faraday-patron (1.0.0) 125 | ffi (1.15.3) 126 | globalid (0.4.2) 127 | activesupport (>= 4.2.0) 128 | i18n (1.8.10) 129 | concurrent-ruby (~> 1.0) 130 | iniparse (1.5.0) 131 | jbuilder (2.11.2) 132 | activesupport (>= 5.0.0) 133 | jquery-rails (4.4.0) 134 | rails-dom-testing (>= 1, < 3) 135 | railties (>= 4.2.0) 136 | thor (>= 0.14, < 2.0) 137 | jwt (2.2.3) 138 | less (2.6.0) 139 | commonjs (~> 0.2.7) 140 | less-rails (4.0.0) 141 | actionpack (>= 4) 142 | less (~> 2.6.0) 143 | sprockets (>= 2) 144 | listen (3.6.0) 145 | rb-fsevent (~> 0.10, >= 0.10.3) 146 | rb-inotify (~> 0.9, >= 0.9.10) 147 | loofah (2.10.0) 148 | crass (~> 1.0.2) 149 | nokogiri (>= 1.5.9) 150 | mail (2.7.1) 151 | mini_mime (>= 0.1.1) 152 | marcel (1.0.1) 153 | method_source (1.0.0) 154 | mini_mime (1.1.0) 155 | mini_portile2 (2.5.3) 156 | minitest (5.14.4) 157 | msgpack (1.4.2) 158 | multipart-post (2.1.1) 159 | nio4r (2.5.7) 160 | nokogiri (1.11.7) 161 | mini_portile2 (~> 2.5.0) 162 | racc (~> 1.4) 163 | overcommit (0.58.0) 164 | childprocess (>= 0.6.3, < 5) 165 | iniparse (~> 1.4) 166 | rexml (~> 3.2) 167 | parallel (1.20.1) 168 | parser (3.0.2.0) 169 | ast (~> 2.4.1) 170 | popper_js (2.9.2) 171 | puma (5.4.0) 172 | nio4r (~> 2.0) 173 | racc (1.5.2) 174 | rack (2.2.3) 175 | rack-proxy (0.7.0) 176 | rack 177 | rack-test (1.1.0) 178 | rack (>= 1.0, < 3) 179 | rails (6.1.4) 180 | actioncable (= 6.1.4) 181 | actionmailbox (= 6.1.4) 182 | actionmailer (= 6.1.4) 183 | actionpack (= 6.1.4) 184 | actiontext (= 6.1.4) 185 | actionview (= 6.1.4) 186 | activejob (= 6.1.4) 187 | activemodel (= 6.1.4) 188 | activerecord (= 6.1.4) 189 | activestorage (= 6.1.4) 190 | activesupport (= 6.1.4) 191 | bundler (>= 1.15.0) 192 | railties (= 6.1.4) 193 | sprockets-rails (>= 2.0.0) 194 | rails-controller-testing (1.0.5) 195 | actionpack (>= 5.0.1.rc1) 196 | actionview (>= 5.0.1.rc1) 197 | activesupport (>= 5.0.1.rc1) 198 | rails-dom-testing (2.0.3) 199 | activesupport (>= 4.2.0) 200 | nokogiri (>= 1.6) 201 | rails-html-sanitizer (1.3.0) 202 | loofah (~> 2.3) 203 | railties (6.1.4) 204 | actionpack (= 6.1.4) 205 | activesupport (= 6.1.4) 206 | method_source 207 | rake (>= 0.13) 208 | thor (~> 1.0) 209 | rainbow (3.0.0) 210 | rake (13.0.6) 211 | rb-fsevent (0.11.0) 212 | rb-inotify (0.10.1) 213 | ffi (~> 1.0) 214 | regexp_parser (2.1.1) 215 | rexml (3.2.5) 216 | rubocop (1.18.4) 217 | parallel (~> 1.10) 218 | parser (>= 3.0.0.0) 219 | rainbow (>= 2.2.2, < 4.0) 220 | regexp_parser (>= 1.8, < 3.0) 221 | rexml 222 | rubocop-ast (>= 1.8.0, < 2.0) 223 | ruby-progressbar (~> 1.7) 224 | unicode-display_width (>= 1.4.0, < 3.0) 225 | rubocop-ast (1.8.0) 226 | parser (>= 3.0.1.1) 227 | rubocop-rails (2.11.3) 228 | activesupport (>= 4.2.0) 229 | rack (>= 1.1) 230 | rubocop (>= 1.7.0, < 2.0) 231 | ruby-progressbar (1.11.0) 232 | ruby2_keywords (0.0.5) 233 | sass-rails (6.0.0) 234 | sassc-rails (~> 2.1, >= 2.1.1) 235 | sassc (2.4.0) 236 | ffi (~> 1.9) 237 | sassc-rails (2.1.2) 238 | railties (>= 4.0.0) 239 | sassc (>= 2.0) 240 | sprockets (> 3.0) 241 | sprockets-rails 242 | tilt 243 | semantic_range (3.0.0) 244 | spring (2.1.1) 245 | spring-watcher-listen (2.0.1) 246 | listen (>= 2.7, < 4.0) 247 | spring (>= 1.2, < 3.0) 248 | sprockets (4.0.2) 249 | concurrent-ruby (~> 1.0) 250 | rack (> 1, < 3) 251 | sprockets-rails (3.2.2) 252 | actionpack (>= 4.0) 253 | activesupport (>= 4.0) 254 | sprockets (>= 3.0.0) 255 | sqlite3 (1.4.2) 256 | thor (1.1.0) 257 | tilt (2.0.10) 258 | turbolinks (5.2.1) 259 | turbolinks-source (~> 5.2) 260 | turbolinks-source (5.2.0) 261 | twilio-ruby (5.57.1) 262 | faraday (>= 0.9, < 2.0) 263 | jwt (>= 1.5, <= 2.5) 264 | nokogiri (>= 1.6, < 2.0) 265 | tzinfo (2.0.4) 266 | concurrent-ruby (~> 1.0) 267 | unicode-display_width (2.0.0) 268 | web-console (4.1.0) 269 | actionview (>= 6.0.0) 270 | activemodel (>= 6.0.0) 271 | bindex (>= 0.4.0) 272 | railties (>= 6.0.0) 273 | webpacker (5.4.0) 274 | activesupport (>= 5.2) 275 | rack-proxy (>= 0.6.1) 276 | railties (>= 5.2) 277 | semantic_range (>= 2.3.0) 278 | websocket-driver (0.7.5) 279 | websocket-extensions (>= 0.1.0) 280 | websocket-extensions (0.1.5) 281 | zeitwerk (2.4.2) 282 | 283 | PLATFORMS 284 | ruby 285 | 286 | DEPENDENCIES 287 | bootsnap (>= 1.4.2) 288 | bootstrap (~> 5.0) 289 | byebug 290 | coffee-rails (~> 5.0) 291 | daemons 292 | delayed_job_active_record 293 | dotenv-rails (~> 2.7) 294 | jbuilder (~> 2.11) 295 | jquery-rails 296 | listen (>= 3.0.5, < 3.7) 297 | overcommit (~> 0.58.0) 298 | puma (~> 5.4) 299 | rails (~> 6.1.4) 300 | rails-controller-testing 301 | rubocop (~> 1.18.4) 302 | rubocop-rails (~> 2.11) 303 | sass-rails (>= 6) 304 | spring 305 | spring-watcher-listen (~> 2.0.0) 306 | sqlite3 (~> 1.4) 307 | turbolinks (~> 5) 308 | twilio-ruby (~> 5.57) 309 | twitter-bootstrap-rails! 310 | tzinfo-data 311 | web-console (>= 3.3.0) 312 | webpacker (~> 5.4) 313 | 314 | RUBY VERSION 315 | ruby 2.6.5p114 316 | 317 | BUNDLED WITH 318 | 2.1.4 319 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Twilio Inc. 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 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: install database serve 2 | 3 | install: 4 | bundle install; \ 5 | npm install; 6 | 7 | database: 8 | bundle exec rails db:setup 9 | 10 | serve: 11 | bundle exec rails s --binding 0.0.0.0 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Twilio 3 | 4 | 5 | # Appointment Reminders with Ruby on Rails and Twilio 6 | 7 | [![Actions Status](https://github.com/TwilioDevEd/appointment-reminders-rails/workflows/Rails%20CI/badge.svg)](https://github.com/TwilioDevEd/appointment-reminders-rails/actions) 8 | 9 | > This template is part of Twilio CodeExchange. If you encounter any issues with this code, please open an issue at [github.com/twilio-labs/code-exchange/issues](https://github.com/twilio-labs/code-exchange/issues). 10 | 11 | ## About 12 | 13 | Make sure your customers show up for their scheduled appointments with automated 14 | reminders. Deliver reminders via SMS text messages that don't get ignored like 15 | your e-mails. 16 | 17 | [Read the full tutorial here!](https://www.twilio.com/docs/tutorials/walkthrough/appointment-reminders/ruby/rails) 18 | 19 | Implementations in other languages: 20 | 21 | | .NET | Java | Python| PHP | Node | 22 | | :--- | :--- | :---- | :-- | :--- | 23 | | [Done](https://github.com/TwilioDevEd/appointment-reminders-csharp) | [Done](https://github.com/TwilioDevEd/appointment-reminders-java) | [Done](https://github.com/TwilioDevEd/appointment-reminders-django) | [Done](https://github.com/TwilioDevEd/appointment-reminders-laravel) | [Done](https://github.com/twilio-labs/sample-appointment-reminders-node) | 24 | 25 | ## Set up 26 | 27 | ### Requirements 28 | - [Ruby](https://www.ruby-lang.org/) **2.6.x** version. 29 | - [Sqlite](https://www.sqlite.org/index.html). 30 | - [Node.js](https://nodejs.org/en/) **10.x** or **12.x** version. 31 | - A Twilio account - [sign up](https://www.twilio.com/try-twilio) 32 | 33 | ### Twilio Account Settings 34 | 35 | This application should give you a ready-made starting point for writing your own application. 36 | Before we begin, we need to collect all the config values we need to run the application: 37 | 38 | | Config Value | Description | 39 | | :----------- | :---------- | 40 | | TWILIO_ACCOUNT_SID | Your primary Twilio account identifier - find this [in the Console](https://www.twilio.com/console). | 41 | | TWILIO_AUTH_TOKEN | Used to authenticate - [just like the above, you'll find this here](https://www.twilio.com/console). | 42 | | TWILIO_NUMBER | A Twilio phone number in [E.164 format](https://en.wikipedia.org/wiki/E.164) - you can [get one here](https://www.twilio.com/console/phone-numbers/incoming) | 43 | 44 | ### Local development 45 | 46 | This project is built using [Ruby on Rails](http://rubyonrails.org/) Framework. 47 | 48 | 1. First clone this repository and `cd` into it. 49 | 50 | ```bash 51 | git clone git@github.com:TwilioDevEd/appointment-reminders-rails.git 52 | cd appointment-reminders-rails 53 | ``` 54 | 55 | 2. Install the dependencies, the following command will install gems and Node dependencies. 56 | 57 | ```bash 58 | make install 59 | ``` 60 | 61 | 3. Copy the `.env.example` file to `.env`, and edit it including your credentials 62 | for the Twilio API (found at https://www.twilio.com/console/account/settings). 63 | You will also need a [Twilio Number](https://www.twilio.com/console/phone-numbers/incoming). 64 | 65 | ```bash 66 | cp .env.example .env 67 | ``` 68 | See [Twilio Account Settings](#twilio-account-settings) to locate the necessary environment variables. 69 | 70 | 4. Create the database and run migrations. 71 | 72 | ```bash 73 | make database 74 | ``` 75 | 76 | At this point you are ready to run the code: 77 | 78 | 5. First start the delayed jobs deamon, in the root execute the following command: 79 | 80 | ```bash 81 | ./bin/delayed_job start 82 | ``` 83 | 84 | You can `tail` the log for this process: 85 | 86 | ```bash 87 | tail -f log/delayed_job.log 88 | ``` 89 | 90 | 6. Then start the development server: 91 | 92 | ```bash 93 | make serve 94 | ``` 95 | 96 | 7. Check it out at [http://localhost:3000](http://localhost:3000). 97 | 98 | ### Docker 99 | 100 | If you have [Docker](https://www.docker.com/) already installed on your machine, you can use our `docker-compose.yml` to setup your project. 101 | 102 | 1. Make sure you have the project cloned. 103 | 2. Setup the `.env` file as outlined in the [Local Development](#local-development) steps. 104 | 3. Run `docker-compose up`. 105 | 106 | ### Test 107 | 108 | You can run the tests locally by typing: 109 | ```bash 110 | $ bundle exec rails test 111 | ``` 112 | 113 | ### Cloud deployment 114 | 115 | Additionally to trying out this application locally, you can deploy it to a variety of host services. Here is a small selection of them. 116 | 117 | Please be aware that some of these might charge you for the usage or might make the source code for this application visible to the public. When in doubt research the respective hosting service first. 118 | 119 | | Service | | 120 | | :-------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 121 | | [Heroku](https://www.heroku.com/) | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) | 122 | 123 | ## Resources 124 | 125 | - The CodeExchange repository can be found [here](https://github.com/twilio-labs/code-exchange/). 126 | 127 | ## Contributing 128 | 129 | This template is open source and welcomes contributions. All contributions are subject to our [Code of Conduct](https://github.com/twilio-labs/.github/blob/master/CODE_OF_CONDUCT.md). 130 | 131 | [Visit the project on GitHub](https://github.com/twilio-labs/sample-template-nodejs) 132 | 133 | ## License 134 | 135 | [MIT](http://www.opensource.org/licenses/mit-license.html) 136 | 137 | ## Disclaimer 138 | 139 | No warranty expressed or implied. Software is as is. 140 | 141 | [twilio]: https://www.twilio.com 142 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require_relative 'config/application' 7 | 8 | Rails.application.load_tasks 9 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Appointment Reminders powered by Twilio", 3 | "description": "A template for getting started with Appointment Reminders using Twilio and Ruby on Rails.", 4 | "website": "https://twilio.com", 5 | "logo": "https://www.twilio.com/marketing/bundles/marketing/img/favicons/favicon_114.png", 6 | "success_url": "/", 7 | "addons": [], 8 | "keywords": ["rails", "twilio"], 9 | "env": { 10 | "TWILIO_ACCOUNT_SID": { 11 | "description": "Your Twilio Account SID. Get a free account at twilio.com/try-twilio", 12 | "value": "" 13 | }, 14 | "TWILIO_AUTH_TOKEN": { 15 | "description": "Your Twilio Auth Token. You can get it at twilio.com/console", 16 | "value": "" 17 | }, 18 | "TWILIO_NUMBER": { 19 | "description": "The Twilio phone number you want to use to send SMS. Get one in the Twilio Console", 20 | "value": "+12345678910" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/appointments.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the Appointments controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bootstrap_and_overrides.css: -------------------------------------------------------------------------------- 1 | /* 2 | =require twitter-bootstrap-static/bootstrap 3 | 4 | Static version of css will use Glyphicons sprites by default 5 | =require twitter-bootstrap-static/sprites 6 | */ -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Channel < ActionCable::Channel::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationCable 4 | class Connection < ActionCable::Connection::Base 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | # Prevent CSRF attacks by raising an exception. 5 | # For APIs, you may want to use :null_session instead. 6 | protect_from_forgery with: :exception 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/appointments_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class AppointmentsController < ApplicationController 4 | before_action :find_appointment, only: [:show, :edit, :update, :destroy] 5 | 6 | # GET /appointments 7 | # GET /appointments.json 8 | def index 9 | @appointments = Appointment.all 10 | if @appointments.length.zero? 11 | flash[:alert] = 'You have no appointments. Create one now to get started.' 12 | end 13 | end 14 | 15 | # GET /appointments/1 16 | # GET /appointments/1.json 17 | def show 18 | end 19 | 20 | # GET /appointments/new 21 | def new 22 | @appointment = Appointment.new 23 | @min_date = DateTime.now 24 | end 25 | 26 | # GET /appointments/1/edit 27 | def edit 28 | end 29 | 30 | # POST /appointments 31 | # POST /appointments.json 32 | def create 33 | Time.zone = appointment_params[:time_zone] 34 | @appointment = Appointment.new(appointment_params) 35 | 36 | respond_to do |format| 37 | if @appointment.save 38 | format.html { redirect_to @appointment, notice: 'Appointment was successfully created.' } 39 | format.json { render :show, status: :created, location: @appointment } 40 | else 41 | format.html { render :new } 42 | format.json { render json: @appointment.errors, status: :unprocessable_entity } 43 | end 44 | end 45 | end 46 | 47 | # PATCH/PUT /appointments/1 48 | # PATCH/PUT /appointments/1.json 49 | def update 50 | respond_to do |format| 51 | if @appointment.update(appointment_params) 52 | format.html { redirect_to @appointment, notice: 'Appointment was successfully updated.' } 53 | format.json { render :show, status: :ok, location: @appointment } 54 | else 55 | format.html { render :edit } 56 | format.json { render json: @appointment.errors, status: :unprocessable_entity } 57 | end 58 | end 59 | end 60 | 61 | # DELETE /appointments/1 62 | # DELETE /appointments/1.json 63 | def destroy 64 | @appointment.destroy 65 | respond_to do |format| 66 | format.html { redirect_to appointments_url, notice: 'Appointment was successfully destroyed.' } 67 | format.json { head :no_content } 68 | end 69 | end 70 | 71 | private 72 | # Use callbacks to share common setup or constraints between actions. 73 | # See above ---> before_action :set_appointment, only: [:show, :edit, :update, :destroy] 74 | def find_appointment 75 | @appointment = Appointment.find(params[:id]) 76 | end 77 | 78 | # Never trust parameters from the scary internet, only allow the white list through. 79 | def appointment_params 80 | params.require(:appointment).permit(:name, :phone_number, :time, :time_zone) 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module ApplicationHelper 4 | end 5 | -------------------------------------------------------------------------------- /app/helpers/appointments_helper.rb: -------------------------------------------------------------------------------- 1 | module AppointmentsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/javascript/channels/consumer.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 | import { createConsumer } from "@rails/actioncable" 5 | 6 | export default createConsumer() 7 | -------------------------------------------------------------------------------- /app/javascript/channels/index.js: -------------------------------------------------------------------------------- 1 | // Load all the channels within this directory and all subdirectories. 2 | // Channel files must be named *_channel.js. 3 | 4 | const channels = require.context('.', true, /_channel\.js$/) 5 | channels.keys().forEach(channels) 6 | -------------------------------------------------------------------------------- /app/javascript/packs/application.js: -------------------------------------------------------------------------------- 1 | // This file is automatically compiled by Webpack, along with any other files 2 | // present in this directory. You're encouraged to place your actual application logic in 3 | // a relevant structure within app/javascript and only use these pack files to reference 4 | // that code so it'll be compiled. 5 | 6 | require("@rails/ujs").start() 7 | require("turbolinks").start() 8 | require("@rails/activestorage").start() 9 | require("channels") 10 | import "bootstrap" 11 | import "../stylesheets/application" 12 | 13 | // Uncomment to copy all static images under ../images to the output folder and reference 14 | // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) 15 | // or the `imagePath` JavaScript helper below. 16 | // 17 | // const images = require.context('../images', true) 18 | // const imagePath = (name) => images(name, true) 19 | -------------------------------------------------------------------------------- /app/javascript/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | @import "bootstrap"; 2 | 3 | footer { 4 | bottom: 10px; 5 | text-align: center; 6 | 7 | i { 8 | color:#ff0000; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationJob < ActiveJob::Base 4 | # Automatically retry jobs that encountered a deadlock 5 | # retry_on ActiveRecord::Deadlocked 6 | 7 | # Most jobs are safe to ignore if the underlying records are no longer available 8 | # discard_on ActiveJob::DeserializationError 9 | end 10 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationMailer < ActionMailer::Base 4 | default from: 'from@example.com' 5 | layout 'mailer' 6 | end 7 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationRecord < ActiveRecord::Base 4 | self.abstract_class = true 5 | end 6 | -------------------------------------------------------------------------------- /app/models/appointment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Appointment < ActiveRecord::Base 4 | validates :name, presence: true 5 | validates :phone_number, presence: true 6 | validates :time, presence: true 7 | 8 | after_create :reminder 9 | 10 | # Notify our appointment attendee X minutes before the appointment time 11 | def reminder 12 | @twilio_number = ENV['TWILIO_NUMBER'] 13 | account_sid = ENV['TWILIO_ACCOUNT_SID'] 14 | @client = Twilio::REST::Client.new account_sid, ENV['TWILIO_AUTH_TOKEN'] 15 | time_str = ((self.time).localtime).strftime("%I:%M%p on %b. %d, %Y") 16 | body = "Hi #{self.name}. Just a reminder that you have an appointment coming up at #{time_str}." 17 | message = @client.messages.create( 18 | :from => @twilio_number, 19 | :to => self.phone_number, 20 | :body => body, 21 | ) 22 | end 23 | 24 | def when_to_run 25 | minutes_before_appointment = 30.minutes 26 | time - minutes_before_appointment 27 | end 28 | 29 | handle_asynchronously :reminder, :run_at => Proc.new { |i| i.when_to_run } 30 | end 31 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/views/appointments/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @appointment, :html => { :class => "form-horizontal appointment" } do |f| %> 2 | 3 | <% if @appointment.errors.any? %> 4 |
5 |
6 |

<%= pluralize(@appointment.errors.count, "error") %> prohibited this appointment from being saved:

7 |
8 |
9 | 14 |
15 |
16 | <% end %> 17 | 18 |
19 | <%= f.label :name, :class => 'control-label col-lg-2' %> 20 |
21 | <%= f.text_field :name, :class => 'form-control' %> 22 |
23 | <%=f.error_span(:name) %> 24 |
25 |
26 | <%= f.label :phone_number, :class => 'control-label col-lg-2' %> 27 |
28 | <%= f.text_field :phone_number, :class => 'form-control' %> 29 |
30 | <%=f.error_span(:phone_number) %> 31 |
32 |
33 | <%= f.label :time, "Time and Date", :class => 'control-label col-lg-2' %> 34 | 35 |
36 | <%= time_select :appointment, :time, {:class => "form-control" } %> 37 |
38 |
39 | <%= date_select :appointment, :time, {:class => "form-control" } %> 40 |
41 |
42 | <%= f.time_zone_select :time_zone, ActiveSupport::TimeZone.all.sort, default: "Pacific Time (US & Canada)" %> 43 |
44 | <%=f.error_span(:time) %> 45 |
46 | 47 |
48 |
49 | <%= f.submit nil, :class => 'btn btn-primary' %> 50 | <%= link_to t('.cancel', :default => t("helpers.links.cancel")), 51 | appointments_path, :class => 'btn btn-default' %> 52 |
53 |
54 | 55 | <% end %> 56 | -------------------------------------------------------------------------------- /app/views/appointments/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%- model_class = Appointment -%> 2 | 5 | <%= render :partial => 'form' %> 6 | -------------------------------------------------------------------------------- /app/views/appointments/index.html.erb: -------------------------------------------------------------------------------- 1 | <%- model_class = Appointment -%> 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | <% @appointments.each do |appointment| %> 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 34 | <% end %> 35 | 36 |
<%= model_class.human_attribute_name(:id) %><%= model_class.human_attribute_name(:name) %><%= model_class.human_attribute_name(:phone_number) %><%= model_class.human_attribute_name(:time) %><%= model_class.human_attribute_name(:created_at) %><%=t '.actions', :default => t("helpers.actions") %>
<%= link_to appointment.id, appointment_path(appointment) %><%= appointment.name %><%= appointment.phone_number %><%= appointment.time %><%=l appointment.created_at %> 25 | <%= link_to "Edit", 26 | edit_appointment_path(appointment), :class => 'btn btn-default btn-xs' %> 27 | <%= link_to "Delete", 28 | appointment_path(appointment), 29 | :method => :delete, 30 | :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) }, 31 | :class => 'btn btn-xs btn-danger' %> 32 |
37 | 38 | <%= link_to "New", 39 | new_appointment_path, 40 | :class => 'btn btn-primary' %> 41 | -------------------------------------------------------------------------------- /app/views/appointments/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array!(@appointments) do |appointment| 2 | json.extract! appointment, :id, :name, :phone_number, :time 3 | json.url appointment_url(appointment, format: :json) 4 | end 5 | -------------------------------------------------------------------------------- /app/views/appointments/new.html.erb: -------------------------------------------------------------------------------- 1 | <%- model_class = Appointment -%> 2 | 5 | <%= render :partial => 'form' %> 6 | -------------------------------------------------------------------------------- /app/views/appointments/show.html.erb: -------------------------------------------------------------------------------- 1 | <%- model_class = Appointment -%> 2 | 5 | 6 |
7 |
<%= model_class.human_attribute_name(:name) %>:
8 |
<%= @appointment.name %>
9 |
<%= model_class.human_attribute_name(:phone_number) %>:
10 |
<%= @appointment.phone_number %>
11 |
<%= model_class.human_attribute_name(:time) %>:
12 |
<%= ((@appointment.time).in_time_zone(@appointment.time_zone)).strftime("%I:%M%p") %>
13 |
14 | 15 | <%= link_to t('.back', :default => t("helpers.links.back")), 16 | appointments_path, :class => 'btn btn-default' %> 17 | <%= link_to t('.edit', :default => t("helpers.links.edit")), 18 | edit_appointment_path(@appointment), :class => 'btn btn-default' %> 19 | <%= link_to t('.destroy', :default => t("helpers.links.destroy")), 20 | appointment_path(@appointment), 21 | :method => 'delete', 22 | :data => { :confirm => t('.confirm', :default => t("helpers.links.confirm", :default => 'Are you sure?')) }, 23 | :class => 'btn btn-danger' %> 24 | -------------------------------------------------------------------------------- /app/views/appointments/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @appointment, :id, :name, :phone_number, :time, :created_at, :updated_at 2 | -------------------------------------------------------------------------------- /app/views/appointments/welcome.html.erb: -------------------------------------------------------------------------------- 1 |

Appointment Reminders with Ruby on Rails

2 | 3 |

This demo application will show you how to implement SMS appointment reminders with <%= link_to "Twilio", "http://twilio.com" %> in Ruby on Rails. 4 | 5 |

<%= link_to "Create a new Appointment", new_appointment_path, :class => 'btn btn-primary btn-xs' %> to see how it works!

-------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example App 5 | <%= csrf_meta_tags %> 6 | <%= csp_meta_tag %> 7 | 8 | <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 9 | <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> 10 | 11 | 12 | 13 | 23 |
24 |
25 |
26 | <%= yield %> 27 |
28 |
29 |
30 | 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | var validEnv = ['development', 'test', 'production'] 3 | var currentEnv = api.env() 4 | var isDevelopmentEnv = api.env('development') 5 | var isProductionEnv = api.env('production') 6 | var isTestEnv = api.env('test') 7 | 8 | if (!validEnv.includes(currentEnv)) { 9 | throw new Error( 10 | 'Please specify a valid `NODE_ENV` or ' + 11 | '`BABEL_ENV` environment variables. Valid values are "development", ' + 12 | '"test", and "production". Instead, received: ' + 13 | JSON.stringify(currentEnv) + 14 | '.' 15 | ) 16 | } 17 | 18 | return { 19 | presets: [ 20 | isTestEnv && [ 21 | '@babel/preset-env', 22 | { 23 | targets: { 24 | node: 'current' 25 | } 26 | } 27 | ], 28 | (isProductionEnv || isDevelopmentEnv) && [ 29 | '@babel/preset-env', 30 | { 31 | forceAllTransforms: true, 32 | useBuiltIns: 'entry', 33 | corejs: 3, 34 | modules: false, 35 | exclude: ['transform-typeof-symbol'] 36 | } 37 | ] 38 | ].filter(Boolean), 39 | plugins: [ 40 | 'babel-plugin-macros', 41 | '@babel/plugin-syntax-dynamic-import', 42 | isTestEnv && 'babel-plugin-dynamic-import-node', 43 | '@babel/plugin-transform-destructuring', 44 | [ 45 | '@babel/plugin-proposal-class-properties', 46 | { 47 | loose: true 48 | } 49 | ], 50 | [ 51 | '@babel/plugin-proposal-object-rest-spread', 52 | { 53 | useBuiltIns: true 54 | } 55 | ], 56 | [ 57 | '@babel/plugin-transform-runtime', 58 | { 59 | helpers: false, 60 | regenerator: true, 61 | corejs: false 62 | } 63 | ], 64 | [ 65 | '@babel/plugin-transform-regenerator', 66 | { 67 | async: false 68 | } 69 | ] 70 | ].filter(Boolean) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_version 64 | @bundler_version ||= 65 | env_var_version || cli_arg_version || 66 | lockfile_version 67 | end 68 | 69 | def bundler_requirement 70 | return "#{Gem::Requirement.default}.a" unless bundler_version 71 | 72 | bundler_gem_version = Gem::Version.new(bundler_version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /bin/delayed_job: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment')) 4 | require 'delayed/command' 5 | Delayed::Command.new(ARGV).daemonize 6 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path('..', __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a starting point to setup your application. 13 | # Add necessary setup steps to this file. 14 | 15 | puts '== Installing dependencies ==' 16 | system! 'gem install bundler --conservative' 17 | system('bundle check') || system!('bundle install') 18 | 19 | # Install JavaScript dependencies if using Yarn 20 | # system('bin/yarn') 21 | 22 | # puts "\n== Copying sample files ==" 23 | # unless File.exist?('config/database.yml') 24 | # cp 'config/database.yml.sample', 'config/database.yml' 25 | # end 26 | 27 | puts "\n== Preparing database ==" 28 | system! 'bin/rails db:setup' 29 | 30 | puts "\n== Removing old logs and tempfiles ==" 31 | system! 'bin/rails log:clear tmp:clear' 32 | 33 | puts "\n== Restarting application server ==" 34 | system! 'bin/rails restart' 35 | end 36 | -------------------------------------------------------------------------------- /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/webpack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/webpack_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::WebpackRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /bin/webpack-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" 4 | ENV["NODE_ENV"] ||= "development" 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::DevServerRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /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 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require_relative 'config/environment' 6 | 7 | run Rails.application 8 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'boot' 4 | 5 | require 'rails/all' 6 | 7 | # Require the gems listed in Gemfile, including any gems 8 | # you've limited to :test, :development, or :production. 9 | Bundler.require(*Rails.groups) 10 | 11 | module AppointmentRemindersRails 12 | class Application < Rails::Application 13 | # Initialize configuration defaults for originally generated Rails version. 14 | config.load_defaults 6.0 15 | 16 | # Settings in config/environments/* take precedence over those specified here. 17 | # Application configuration can go into files in config/initializers 18 | # -- all .rb files in that directory are automatically loaded after loading 19 | # the framework and any gems in your application. 20 | 21 | config.active_job.queue_adapter = :delayed_job 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 4 | 5 | require 'bundler/setup' # Set up gems listed in the Gemfile. 6 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 7 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: appointment_reminders_rails_production 11 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | ECzkMFm8W848suTTcp0w+63K01teC9l14+7O3XoOjNTthBVLfmSHm4R0Nrs3TpxO2I4m0KwaK15xs9m/yvqiMs9O1afNpHRPSDWkirE2DDuopBcQydw+Q9qmnSjkdysvKu/I/mDqWjcyWgQZdBEUYon+ZbBIzluAAIBlnvap+pGwhHlVQ30r827AI2aMil9IrnPJSpA2rV9DWSGkWFrYWeqIoZXeiKimALNmKR5K2EmC9S3Y9rkhvBdw/Wc6uIOYzY801xROQLQk2h20Uqo5+ottKno3OoDIVO5asd7mxFmnZyNSQw6q2vUCwntMg5ulTdmfNyA0L4J3/EH1QbiV4alDpguPJcaaKgRljhOGcwRHtDTeKm7tVfJrmRYY16YgpnJxDorIln96HK2cw0zRkrTaRlhNLHFUwrQF--D9oGnRiJ4ahmk7PO--X3syz4XUSfFVvpBGONiZDA== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 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 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative 'application' 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded on 7 | # every request. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable/disable caching. By default caching is disabled. 18 | # Run rails dev:cache to toggle caching. 19 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 20 | config.action_controller.perform_caching = true 21 | config.action_controller.enable_fragment_cache_logging = true 22 | 23 | config.cache_store = :memory_store 24 | config.public_file_server.headers = { 25 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 26 | } 27 | else 28 | config.action_controller.perform_caching = false 29 | 30 | config.cache_store = :null_store 31 | end 32 | 33 | # Store uploaded files on the local file system (see config/storage.yml for options). 34 | config.active_storage.service = :local 35 | 36 | # Don't care if the mailer can't send. 37 | config.action_mailer.raise_delivery_errors = false 38 | 39 | config.action_mailer.perform_caching = false 40 | 41 | # Print deprecation notices to the Rails logger. 42 | config.active_support.deprecation = :log 43 | 44 | # Raise an error on page load if there are pending migrations. 45 | config.active_record.migration_error = :page_load 46 | 47 | # Highlight code that triggered database queries in logs. 48 | config.active_record.verbose_query_logs = true 49 | 50 | # Debug mode disables concatenation and preprocessing of assets. 51 | # This option may cause significant delays in view rendering with a large 52 | # number of complex assets. 53 | config.assets.debug = true 54 | 55 | # Suppress logger output for asset requests. 56 | config.assets.quiet = true 57 | 58 | # Raises error for missing translations. 59 | # config.action_view.raise_on_missing_translations = true 60 | 61 | # Use an evented file watcher to asynchronously detect changes in source code, 62 | # routes, locales, etc. This feature depends on the listen gem. 63 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 64 | 65 | Rails.logger = Logger.new(STDOUT) 66 | Rails.logger.datetime_format = '%Y-%m-%d %H:%M:%S' 67 | 68 | # log formatter 69 | Rails.logger.formatter = proc do |severity, datetime, progname, msg| 70 | "#{datetime}, #{severity}: #{progname} #{msg} \n" 71 | end 72 | config.logger = ActiveSupport::Logger.new("log/#{Rails.env}.log") 73 | end 74 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Compress CSS using a preprocessor. 28 | # config.assets.css_compressor = :sass 29 | 30 | # Do not fallback to assets pipeline if a precompiled asset is missed. 31 | config.assets.compile = false 32 | 33 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 34 | # config.action_controller.asset_host = 'http://assets.example.com' 35 | 36 | # Specifies the header that your server uses for sending files. 37 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 38 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 39 | 40 | # Store uploaded files on the local file system (see config/storage.yml for options). 41 | config.active_storage.service = :local 42 | 43 | # Mount Action Cable outside main process or domain. 44 | # config.action_cable.mount_path = nil 45 | # config.action_cable.url = 'wss://example.com/cable' 46 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 47 | 48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 49 | # config.force_ssl = true 50 | 51 | # Use the lowest log level to ensure availability of diagnostic information 52 | # when problems arise. 53 | config.log_level = :debug 54 | 55 | # Prepend all log lines with the following tags. 56 | config.log_tags = [:request_id] 57 | 58 | # Use a different cache store in production. 59 | # config.cache_store = :mem_cache_store 60 | 61 | # Use a real queuing backend for Active Job (and separate queues per environment). 62 | # config.active_job.queue_adapter = :resque 63 | # config.active_job.queue_name_prefix = "appointment_reminders_rails_#{Rails.env}" 64 | 65 | config.action_mailer.perform_caching = false 66 | 67 | # Ignore bad email addresses and do not raise email delivery errors. 68 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 69 | # config.action_mailer.raise_delivery_errors = false 70 | 71 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 72 | # the I18n.default_locale when a translation cannot be found). 73 | config.i18n.fallbacks = true 74 | 75 | # Send deprecation notices to registered listeners. 76 | config.active_support.deprecation = :notify 77 | 78 | # Use default logging formatter so that PID and timestamp are not suppressed. 79 | config.log_formatter = ::Logger::Formatter.new 80 | 81 | # Use a different logger for distributed setups. 82 | # require 'syslog/logger' 83 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 84 | 85 | if ENV['RAILS_LOG_TO_STDOUT'].present? 86 | logger = ActiveSupport::Logger.new(STDOUT) 87 | logger.formatter = config.log_formatter 88 | config.logger = ActiveSupport::TaggedLogging.new(logger) 89 | end 90 | 91 | # Do not dump schema after migrations. 92 | config.active_record.dump_schema_after_migration = false 93 | 94 | # Inserts middleware to perform automatic connection switching. 95 | # The `database_selector` hash is used to pass options to the DatabaseSelector 96 | # middleware. The `delay` is used to determine how long to wait after a write 97 | # to send a subsequent read to the primary. 98 | # 99 | # The `database_resolver` class is used by the middleware to determine which 100 | # database is appropriate to use based on the time delay. 101 | # 102 | # The `database_resolver_context` class is used by the middleware to set 103 | # timestamps for the last write to the primary. The resolver uses the context 104 | # class timestamps to determine how long to wait before reading from the 105 | # replica. 106 | # 107 | # By default Rails will store a last write timestamp in the session. The 108 | # DatabaseSelector middleware is designed as such you can define your own 109 | # strategy for connection switching and pass that into the middleware through 110 | # these configuration options. 111 | # config.active_record.database_selector = { delay: 2.seconds } 112 | # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver 113 | # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session 114 | end 115 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | config.cache_classes = false 12 | 13 | # Do not eager load code on boot. This avoids loading your whole application 14 | # just for the purpose of running a single test. If you are using a tool that 15 | # preloads Rails for running tests, you may have to set it to true. 16 | config.eager_load = false 17 | 18 | # Configure public file server for tests with Cache-Control for performance. 19 | config.public_file_server.enabled = true 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 22 | } 23 | 24 | # Show full error reports and disable caching. 25 | config.consider_all_requests_local = true 26 | config.action_controller.perform_caching = false 27 | config.cache_store = :null_store 28 | 29 | # Raise exceptions instead of rendering exception templates. 30 | config.action_dispatch.show_exceptions = false 31 | 32 | # Disable request forgery protection in test environment. 33 | config.action_controller.allow_forgery_protection = false 34 | 35 | # Store uploaded files on the local file system in a temporary directory. 36 | config.active_storage.service = :test 37 | 38 | config.action_mailer.perform_caching = false 39 | 40 | # Tell Action Mailer not to deliver emails to the real world. 41 | # The :test delivery method accumulates sent emails in the 42 | # ActionMailer::Base.deliveries array. 43 | config.action_mailer.delivery_method = :test 44 | 45 | # Print deprecation notices to the stderr. 46 | config.active_support.deprecation = :stderr 47 | 48 | # Raises error for missing translations. 49 | # config.action_view.raise_on_missing_translations = true 50 | 51 | Rails.logger = Logger.new(STDOUT) 52 | Rails.logger.datetime_format = '%Y-%m-%d %H:%M:%S' 53 | 54 | # log formatter 55 | Rails.logger.formatter = proc do |severity, datetime, progname, msg| 56 | "#{datetime}, #{severity}: #{progname} #{msg} \n" 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # ActiveSupport::Reloader.to_prepare do 5 | # ApplicationController.renderer.defaults.merge!( 6 | # http_host: 'example.org', 7 | # https: false 8 | # ) 9 | # end 10 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Version of your assets, change this if you want to expire all your assets. 6 | Rails.application.config.assets.version = '1.0' 7 | 8 | # Add additional assets to the asset load path. 9 | # Rails.application.config.assets.paths << Emoji.images_path 10 | # Add Yarn node_modules folder to the asset load path. 11 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 12 | 13 | # Precompile additional assets. 14 | # application.js, application.css, and all non-JS/CSS in the app/assets 15 | # folder are already added. 16 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 17 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 5 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 6 | 7 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 8 | # Rails.backtrace_cleaner.remove_silencers! 9 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Define an application-wide content security policy 5 | # For further information see the following documentation 6 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 7 | 8 | # Rails.application.config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # If you are using webpack-dev-server then specify webpack-dev-server host 16 | # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? 17 | 18 | # # Specify URI for violation reports 19 | # # policy.report_uri "/csp-violation-report-endpoint" 20 | # end 21 | 22 | # If you are using UJS then enable automatic nonce generation 23 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 24 | 25 | # Set the nonce only to specific directives 26 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src) 27 | 28 | # Report CSP violations to a specified URI 29 | # For further information see the following documentation: 30 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 31 | # Rails.application.config.content_security_policy_report_only = true 32 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Specify a serializer for the signed and encrypted cookie jars. 6 | # Valid options are :json, :marshal, and :hybrid. 7 | Rails.application.config.action_dispatch.cookies_serializer = :json 8 | -------------------------------------------------------------------------------- /config/initializers/delayed_job_config.rb: -------------------------------------------------------------------------------- 1 | # config/initializers/delayed_job_config.rb 2 | Delayed::Worker.destroy_failed_jobs = false 3 | Delayed::Worker.sleep_delay = 60 4 | Delayed::Worker.max_attempts = 3 5 | Delayed::Worker.max_run_time = 5.minutes 6 | Delayed::Worker.read_ahead = 10 7 | Delayed::Worker.default_queue_name = 'default' 8 | Delayed::Worker.delay_jobs = !Rails.env.test? 9 | Delayed::Worker.raise_signal_exceptions = :term 10 | Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log')) 11 | -------------------------------------------------------------------------------- /config/initializers/dotenv.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Dotenv.require_keys( 4 | 'TWILIO_ACCOUNT_SID', 5 | 'TWILIO_AUTH_TOKEN', 6 | 'TWILIO_NUMBER' 7 | ) 8 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure sensitive parameters which will be filtered from the log file. 6 | Rails.application.config.filter_parameters += [:password] 7 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new inflection rules using the following format. Inflections 5 | # are locale specific, and you may define rules for as many different 6 | # locales as you wish. All of these examples are active by default: 7 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 8 | # inflect.plural /^(ox)$/i, '\1en' 9 | # inflect.singular /^(ox)en/i, '\1' 10 | # inflect.irregular 'person', 'people' 11 | # inflect.uncountable %w( fish sheep ) 12 | # end 13 | 14 | # These inflection rules are supported but not enabled by default: 15 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 16 | # inflect.acronym 'RESTful' 17 | # end 18 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new mime types for use in respond_to blocks: 5 | # Mime::Type.register "text/richtext", :rtf 6 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /config/locales/en.bootstrap.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | breadcrumbs: 6 | application: 7 | root: "Index" 8 | pages: 9 | pages: "Pages" 10 | helpers: 11 | actions: "Actions" 12 | links: 13 | back: "Back" 14 | cancel: "Cancel" 15 | confirm: "Are you sure?" 16 | destroy: "Delete" 17 | new: "New" 18 | edit: "Edit" 19 | titles: 20 | edit: "Edit %{model}" 21 | save: "Save %{model}" 22 | new: "New %{model}" 23 | delete: "Delete %{model}" 24 | -------------------------------------------------------------------------------- /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 https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Puma can serve each request in a thread from an internal thread pool. 4 | # The `threads` method setting takes two numbers: a minimum and maximum. 5 | # Any libraries that use thread pools should be configured to match 6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 7 | # and maximum; this matches the default thread size of Active Record. 8 | # 9 | max_threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 } 10 | min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count } 11 | threads min_threads_count, max_threads_count 12 | 13 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 14 | # 15 | port ENV.fetch('PORT') { 3000 } 16 | 17 | # Specifies the `environment` that Puma will run in. 18 | # 19 | environment ENV.fetch('RAILS_ENV') { 'development' } 20 | 21 | # Specifies the `pidfile` that Puma will use. 22 | pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' } 23 | 24 | # Specifies the number of `workers` to boot in clustered mode. 25 | # Workers are forked web server processes. If using threads and workers together 26 | # the concurrency of the application would be max `threads` * `workers`. 27 | # Workers do not work on JRuby or Windows (both of which do not support 28 | # processes). 29 | # 30 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 31 | 32 | # Use the `preload_app!` method when specifying a `workers` number. 33 | # This directive tells Puma to first boot the application and load code 34 | # before forking the application. This takes advantage of Copy On Write 35 | # process behavior so workers use less memory. 36 | # 37 | # preload_app! 38 | 39 | # Allow puma to be restarted by `rails restart` command. 40 | plugin :tmp_restart 41 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | resources :appointments 5 | root 'appointments#welcome' 6 | end 7 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Spring.watch( 4 | '.ruby-version', 5 | '.rbenv-vars', 6 | 'tmp/restart.txt', 7 | 'tmp/caching-dev.txt' 8 | ) 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpack/environment.js: -------------------------------------------------------------------------------- 1 | const { environment } = require('@rails/webpacker') 2 | 3 | const webpack = require('webpack') 4 | environment.plugins.append( 5 | 'Provide', 6 | new webpack.ProvidePlugin({ 7 | $: 'jquery', 8 | jQuery: 'jquery', 9 | Popper: ['popper.js', 'default'] 10 | }) 11 | ) 12 | 13 | module.exports = environment 14 | -------------------------------------------------------------------------------- /config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const environment = require('./environment') 4 | 5 | module.exports = environment.toWebpackConfig() 6 | -------------------------------------------------------------------------------- /config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpack-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/javascript 5 | source_entry_path: packs 6 | public_root_path: public 7 | public_output_path: packs 8 | cache_path: tmp/cache/webpacker 9 | check_yarn_integrity: false 10 | webpack_compile_output: true 11 | 12 | # Additional paths webpack should lookup modules 13 | # ['app/assets', 'engine/foo/app/assets'] 14 | resolved_paths: [] 15 | 16 | # Reload manifest.json on all requests so we reload latest compiled packs 17 | cache_manifest: false 18 | 19 | # Extract and emit a css file 20 | extract_css: false 21 | 22 | static_assets_extensions: 23 | - .jpg 24 | - .jpeg 25 | - .png 26 | - .gif 27 | - .tiff 28 | - .ico 29 | - .svg 30 | - .eot 31 | - .otf 32 | - .ttf 33 | - .woff 34 | - .woff2 35 | 36 | extensions: 37 | - .mjs 38 | - .js 39 | - .sass 40 | - .scss 41 | - .css 42 | - .module.sass 43 | - .module.scss 44 | - .module.css 45 | - .png 46 | - .svg 47 | - .gif 48 | - .jpeg 49 | - .jpg 50 | 51 | development: 52 | <<: *default 53 | compile: true 54 | 55 | # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules 56 | check_yarn_integrity: false 57 | 58 | # Reference: https://webpack.js.org/configuration/dev-server/ 59 | dev_server: 60 | https: false 61 | host: localhost 62 | port: 3035 63 | public: localhost:3035 64 | hmr: false 65 | # Inline should be set to true if using HMR 66 | inline: true 67 | overlay: true 68 | compress: true 69 | disable_host_check: true 70 | use_local_ip: false 71 | quiet: false 72 | pretty: false 73 | headers: 74 | 'Access-Control-Allow-Origin': '*' 75 | watch_options: 76 | ignored: '**/node_modules/**' 77 | 78 | 79 | test: 80 | <<: *default 81 | compile: true 82 | 83 | # Compile test packs to a separate directory 84 | public_output_path: packs-test 85 | 86 | production: 87 | <<: *default 88 | 89 | # Production depends on precompilation of packs prior to booting for performance. 90 | compile: false 91 | 92 | # Extract and emit a css file 93 | extract_css: true 94 | 95 | # Cache manifest.json for performance 96 | cache_manifest: true 97 | -------------------------------------------------------------------------------- /db/migrate/20190121114109_create_delayed_jobs.rb: -------------------------------------------------------------------------------- 1 | class CreateDelayedJobs < ActiveRecord::Migration[6.0] 2 | def self.up 3 | create_table :delayed_jobs, force: true do |table| 4 | table.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of the queue 5 | table.integer :attempts, default: 0, null: false # Provides for retries, but still fail eventually. 6 | table.text :handler, null: false # YAML-encoded string of the object that will do work 7 | table.text :last_error # reason for last failure (See Note below) 8 | table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future. 9 | table.datetime :locked_at # Set when a client is working on this object 10 | table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead) 11 | table.string :locked_by # Who is working on this object (if locked) 12 | table.string :queue # The name of the queue this job is in 13 | table.timestamps null: true 14 | end 15 | 16 | add_index :delayed_jobs, [:priority, :run_at], name: "delayed_jobs_priority" 17 | end 18 | 19 | def self.down 20 | drop_table :delayed_jobs 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /db/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_01_21_114109) do 14 | create_table "appointments", force: :cascade do |t| 15 | t.string "name" 16 | t.string "phone_number" 17 | t.datetime "time" 18 | t.datetime "created_at", null: false 19 | t.datetime "updated_at", null: false 20 | t.string "time_zone" 21 | end 22 | 23 | create_table "delayed_jobs", force: :cascade do |t| 24 | t.integer "priority", default: 0, null: false 25 | t.integer "attempts", default: 0, null: false 26 | t.text "handler", null: false 27 | t.text "last_error" 28 | t.datetime "run_at" 29 | t.datetime "locked_at" 30 | t.datetime "failed_at" 31 | t.string "locked_by" 32 | t.string "queue" 33 | t.datetime "created_at" 34 | t.datetime "updated_at" 35 | t.index ["priority", "run_at"], name: "delayed_jobs_priority" 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # This file should contain all the record creation needed to seed the database with its default values. 3 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 4 | # 5 | # Examples: 6 | # 7 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 8 | # Character.create(name: 'Luke', movie: movies.first) 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | app: 4 | build: . 5 | ports: 6 | - "3000:3000" 7 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "appointment-reminders-rails", 3 | "private": true, 4 | "dependencies": { 5 | "@rails/actioncable": "^6.0.0", 6 | "@rails/activestorage": "^6.0.0", 7 | "@rails/ujs": "^6.0.0", 8 | "@rails/webpacker": "4.2.2", 9 | "bootstrap": "^4.4.1", 10 | "jquery": "^3.4.1", 11 | "popper.js": "^1.16.1", 12 | "turbolinks": "^5.2.0" 13 | }, 14 | "version": "0.1.0", 15 | "devDependencies": { 16 | "webpack-dev-server": "^3.11.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import'), 4 | require('postcss-flexbugs-fixes'), 5 | require('postcss-preset-env')({ 6 | autoprefixer: { 7 | flexbox: 'no-2009' 8 | }, 9 | stage: 3 10 | }) 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /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/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/storage/.keep -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 5 | end 6 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/appointments_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AppointmentsControllerTest < ActionController::TestCase 4 | setup do 5 | @appointment = appointments(:one) 6 | end 7 | 8 | test "should get index" do 9 | get :index 10 | assert_response :success 11 | assert_not_nil assigns(:appointments) 12 | end 13 | 14 | test "should get new" do 15 | get :new 16 | assert_response :success 17 | end 18 | 19 | test "should create appointment" do 20 | assert_difference('Appointment.count') do 21 | Appointment.skip_callback(:create, :after, :reminder) 22 | post :create, params: {appointment: { name: @appointment.name, phone_number: @appointment.phone_number, time: @appointment.time }} 23 | end 24 | 25 | assert_redirected_to appointment_path(assigns(:appointment)) 26 | end 27 | 28 | test "should show appointment" do 29 | get :show, params: { id: @appointment } 30 | assert_response :success 31 | end 32 | 33 | test "should get edit" do 34 | get :edit, params: { id: @appointment } 35 | assert_response :success 36 | end 37 | 38 | test "should update appointment" do 39 | patch :update, params: { id: @appointment, appointment: { name: @appointment.name, phone_number: @appointment.phone_number, time: @appointment.time } } 40 | assert_redirected_to appointment_path(assigns(:appointment)) 41 | end 42 | 43 | test "should destroy appointment" do 44 | assert_difference('Appointment.count', -1) do 45 | delete :destroy, params: { id: @appointment } 46 | end 47 | 48 | assert_redirected_to appointments_path 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/test/fixtures/.keep -------------------------------------------------------------------------------- /test/fixtures/appointments.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | id: 1 5 | name: TestOne 6 | phone_number: "a_phone_number" 7 | time: 2018-03-12 10:33:18 8 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/test/helpers/.keep -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/test/models/.keep -------------------------------------------------------------------------------- /test/models/appointment_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class AppointmentTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/test/system/.keep -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require_relative '../config/environment' 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 7 | fixtures :all 8 | 9 | # Add more helper methods to be used by all tests here... 10 | end 11 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TwilioDevEd/appointment-reminders-rails/729741de628e8fc4b0c3decafeba2d824aeea9c3/tmp/.keep --------------------------------------------------------------------------------