├── .tool-versions ├── mise.toml ├── .gitignore ├── sass ├── .stylelintrc.json ├── sample.scss └── README.md ├── javascript-typescript ├── .eslintrc.json ├── sample.js └── README.md ├── python └── README.md ├── _template ├── how-to │ ├── do-something.md │ └── do-something-else.md └── README.md ├── rails ├── sample.rb ├── migration.rb ├── how-to │ ├── start_a_new_rails_app.md │ ├── feature_test_javascript_in_a_rails_app.md │ ├── seed-data.md │ └── deploy_a_rails_app_to_heroku.md └── README.md ├── erb ├── sample.html.erb └── README.md ├── CODE_OF_CONDUCT.md ├── .github └── workflows │ ├── linting.yml │ └── main.yml ├── testing-rspec ├── predicate_tests_spec.rb ├── acceptance_test_spec.rb ├── unit_test_spec.rb ├── avoid_let_spec.rb └── README.md ├── ruby ├── sample_2.rb ├── how-to │ └── release_a_ruby_gem.md ├── Use-an-opinionated-set-of-rules-for-Rubocop.md ├── Limit-use-of-conditional-modifiers-to-short-simple-cases.md ├── sample_1.rb ├── README.md └── .rubocop.yml ├── bash └── README.md ├── web └── README.md ├── .hound.yml ├── email └── README.md ├── relational-databases └── README.md ├── object-oriented-design └── README.md ├── package.json ├── android ├── README.md └── android_layout.xml ├── postgres └── README.md ├── security ├── protecting-personal-or-identifying-information.md ├── README.md └── application.md ├── open-source └── README.md ├── product-review └── README.md ├── CONTRIBUTING.md ├── swift ├── README.md └── sample.swift ├── lefthook.yml ├── web-performance └── README.md ├── testing-jest └── README.md ├── ios └── README.md ├── html └── README.md ├── general └── README.md ├── react-native └── README.md ├── shell └── README.md ├── graphql └── README.md ├── react └── README.md ├── README.md ├── tech-stack └── README.md ├── css └── README.md ├── git └── README.md ├── .markdownlint-cli2.jsonc ├── data └── README.md ├── code-review └── README.md └── accessibility └── README.md /.tool-versions: -------------------------------------------------------------------------------- 1 | node latest 2 | -------------------------------------------------------------------------------- /mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | node = "latest" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .obsidian 2 | 3 | node_modules 4 | -------------------------------------------------------------------------------- /sass/.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@thoughtbot/stylelint-config" 3 | } 4 | -------------------------------------------------------------------------------- /javascript-typescript/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@thoughtbot/eslint-config" 3 | } 4 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | # Python 2 | 3 | - Follow [PEP 8]. 4 | 5 | [pep 8]: http://www.python.org/dev/peps/pep-0008/ 6 | -------------------------------------------------------------------------------- /_template/how-to/do-something.md: -------------------------------------------------------------------------------- 1 | # How to Do Something 2 | 3 | This is an example how-to guide. Write anything you want here! 4 | -------------------------------------------------------------------------------- /_template/how-to/do-something-else.md: -------------------------------------------------------------------------------- 1 | # How to Do Something Else 2 | 3 | This is an example how-to guide. Write anything you want here! 4 | -------------------------------------------------------------------------------- /rails/sample.rb: -------------------------------------------------------------------------------- 1 | class SomeClass 2 | belongs_to :tree, class_name: "Plant" 3 | has_many :apples 4 | has_many :watermelons 5 | 6 | validates :name, presence: true, uniqueness: true 7 | end 8 | -------------------------------------------------------------------------------- /erb/sample.html.erb: -------------------------------------------------------------------------------- 1 | <%= short_method_call_that_fits_on_one_line arguments %> 2 | 3 | <%= link_to( 4 | some_object_with_a_long_name.title, 5 | parent_object_child_object_path(some_object_with_a_long_name), 6 | ) %> 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | By participating in this project, you agree to abide by the [thoughtbot code of 4 | conduct]. 5 | 6 | [thoughtbot code of conduct]: https://thoughtbot.com/open-source-code-of-conduct 7 | -------------------------------------------------------------------------------- /javascript-typescript/sample.js: -------------------------------------------------------------------------------- 1 | object = { spacing: true } 2 | 3 | class Cat { 4 | canBark() { 5 | return false; 6 | } 7 | } 8 | 9 | const somePerson = { 10 | name: 'Ralph', 11 | company: 'thoughtbot', 12 | }; 13 | -------------------------------------------------------------------------------- /.github/workflows/linting.yml: -------------------------------------------------------------------------------- 1 | on: [pull_request] 2 | 3 | jobs: 4 | lint: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v4 8 | - uses: DavidAnson/markdownlint-cli2-action@v19 9 | with: 10 | globs: | 11 | ./**/*.md 12 | -------------------------------------------------------------------------------- /testing-rspec/predicate_tests_spec.rb: -------------------------------------------------------------------------------- 1 | # Class under test: 2 | 3 | class Thing 4 | def awesome? 5 | true 6 | end 7 | end 8 | 9 | # RSpec test: 10 | 11 | describe Thing, "#awesome?" do 12 | it "is true" do 13 | thing = Thing.new 14 | 15 | expect(thing).to be_awesome 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /ruby/sample_2.rb: -------------------------------------------------------------------------------- 1 | # Include an href or to_param attribute when serializing models 2 | class PostSerializer < ActiveModel::Serializer 3 | attributes :id, :content, :to_param 4 | 5 | delegate :to_param, to: :object 6 | end 7 | 8 | FactoryBot.define do 9 | factory :event do 10 | start_on { 1.week.from_now } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /erb/README.md: -------------------------------------------------------------------------------- 1 | # ERB 2 | 3 | [Sample](sample.html.erb) 4 | 5 | - When wrapping long lines, keep the method name on the same line as the ERB 6 | interpolation operator and keep each method argument on its own line. 7 | - Use a trailing comma after each argument in a multi-line method call, 8 | including the last item. 9 | - Prefer double quotes for attributes. 10 | -------------------------------------------------------------------------------- /bash/README.md: -------------------------------------------------------------------------------- 1 | # Bash 2 | 3 | In addition to [shell](/shell/) best practices: 4 | 5 | - Prefer `${var,,}` and `${var^^}` over `tr` for changing case. 6 | - Prefer `${var//from/to}` over `sed` for simple string replacements. 7 | - Prefer `[[` over `test` or `[`. 8 | - Prefer process substitution over a pipe in `while read` loops. 9 | - Use `((` or `let`, not `$((` when you don't need the result 10 | -------------------------------------------------------------------------------- /rails/migration.rb: -------------------------------------------------------------------------------- 1 | class CreateClearanceUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.timestamps null: false 5 | t.string :email, null: false 6 | t.string :name, null: false, default: '' 7 | t.references :company 8 | end 9 | 10 | add_index :users, :email 11 | add_foreign_key :users, :company_id, on_delete: :restrict 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: update-templates 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | update-templates: 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | pages: write 15 | uses: thoughtbot/templates/.github/workflows/dynamic-readme.yaml@main 16 | secrets: 17 | token: ${{ secrets.GITHUB_TOKEN }} 18 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # Web 2 | 3 | - Avoid rendering delays caused by synchronous loading. 4 | 5 | - Use HTTPS instead of HTTP when linking to assets. 6 | 7 | - Prefer using a UTF-8 charset 8 | 9 | - Avoid targeting specific browsers and aim for [baseline feature support][] 10 | which means that features are widely available across major browsers. 11 | 12 | [baseline feature support]: https://web-platform-dx.github.io/web-features/ 13 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | coffeescript: 2 | enabled: false 3 | eslint: 4 | enabled: true 5 | config_file: javascript-typescript/.eslintrc.json 6 | version: 6.3.0 7 | haml: 8 | enabled: true 9 | config_file: haml/haml.yml 10 | javascript: 11 | enabled: false 12 | rubocop: 13 | enabled: true 14 | config_file: ruby/.rubocop.yml 15 | version: 0.72.0 16 | scss: 17 | enabled: false 18 | stylelint: 19 | enabled: true 20 | config_file: sass/.stylelintrc.json 21 | -------------------------------------------------------------------------------- /ruby/how-to/release_a_ruby_gem.md: -------------------------------------------------------------------------------- 1 | # How to Release a Ruby gem 2 | 3 | - Edit the `VERSION` constant. 4 | - Run `bundle install` to update `Gemfile.lock`. 5 | - Run the test suite. 6 | - Edit `NEWS`, `CHANGELOG`, or `README` files if relevant. 7 | - Commit changes. Use the convention "v2.1.0" in your commit message. 8 | - Run `rake release`, which tags the release, pushes the tag to GitHub, and 9 | pushes the gem to [RubyGems.org]. 10 | 11 | [rubygems.org]: https://rubygems.org/ 12 | -------------------------------------------------------------------------------- /testing-rspec/acceptance_test_spec.rb: -------------------------------------------------------------------------------- 1 | # spec/system/user_signs_up_spec.rb 2 | 3 | require "spec_helper" 4 | 5 | describe "User signs up" do 6 | it "signs up the user with valid details" do 7 | visit sign_up_path 8 | 9 | within_fieldset "Sign up" do 10 | fill_in "Email", with: "user@example.com" 11 | fill_in "Password", with: "Examp1ePa$$" 12 | click_button "Sign up" 13 | end 14 | 15 | expect(page).to have_button("Sign out") 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /email/README.md: -------------------------------------------------------------------------------- 1 | # Email 2 | 3 | - Use [SendGrid][] or [Amazon SES][] to deliver email in staging and production 4 | environments. 5 | 6 | - Use a tool like [ActionMailer Preview][] to look at each created or updated 7 | mailer view before merging. 8 | 9 | [actionmailer preview]: https://guides.rubyonrails.org/action_mailer_basics.html#previewing-and-testing-mailers 10 | [amazon ses]: https://thoughtbot.com/blog/deliver-email-with-amazon-ses-in-a-rails-app 11 | [sendgrid]: https://devcenter.heroku.com/articles/sendgrid 12 | -------------------------------------------------------------------------------- /rails/how-to/start_a_new_rails_app.md: -------------------------------------------------------------------------------- 1 | # How to Start a New Rails App 2 | 3 | We used to use [Suspenders](https://github.com/thoughtbot/suspenders) to start 4 | new Rails apps, but we're currently re-evaluating and upgrading it. 5 | 6 | In the meantime, to avoid potential conflicts when creating a new app with 7 | Suspenders, we recommend using the default Rails commands instead: 8 | 9 | ```sh 10 | rails new -d=postgresql 11 | ``` 12 | 13 | And adding the gems we find useful, like: 14 | factory_bot 15 | rspec-rails 16 | shoulda-matchers 17 | -------------------------------------------------------------------------------- /relational-databases/README.md: -------------------------------------------------------------------------------- 1 | # Relational Databases 2 | 3 | - [Index foreign keys]. 4 | - Constrain most columns as [`NOT NULL`]. 5 | - In a SQL view, only select columns you need (i.e., avoid `SELECT table.*`). 6 | - Use an `ORDER BY` clause on queries where the results will be displayed to a 7 | user, as queries without one may return results in a changing, arbitrary 8 | order. 9 | 10 | [index foreign keys]: https://thoughtbot.com/blog/a-grand-piano-for-your-violin 11 | [`not null`]: http://www.postgresql.org/docs/9.1/static/ddl-constraints.html#AEN2444 12 | -------------------------------------------------------------------------------- /object-oriented-design/README.md: -------------------------------------------------------------------------------- 1 | # Object-Oriented Design 2 | 3 | - Avoid global variables. 4 | - Avoid long parameter lists. 5 | - Limit dependencies of an object (entities an object depends on). 6 | - Limit an object's dependents (entities that depend on an object). 7 | - Prefer composition over inheritance. 8 | - Prefer small methods. Between one and five lines is best. 9 | - Prefer small classes with a single, well-defined responsibility. When a class 10 | exceeds 100 lines, it may be doing too many things. 11 | - [Tell, don't ask]. 12 | 13 | [tell, don't ask]: https://thoughtbot.com/blog/tell-dont-ask 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guides", 3 | "description": "[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)", 4 | "repository": { 5 | "type": "git", 6 | "url": "git+https://github.com/thoughtbot/guides.git" 7 | }, 8 | "bugs": { 9 | "url": "https://github.com/thoughtbot/guides/issues" 10 | }, 11 | "homepage": "https://github.com/thoughtbot/guides#readme", 12 | "dependencies": { 13 | "markdownlint-cli2": "^0.19.0" 14 | }, 15 | "scripts": { 16 | "lint": "markdownlint-cli2 \"./**/*.md\"" 17 | }, 18 | "devDependencies": { 19 | "lefthook": "^1.11.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/README.md: -------------------------------------------------------------------------------- 1 | # Android 2 | 3 | - Properties of views should be alphabetized, with the exception of `id`, 4 | `layout_width`, and `layout_height` which should be placed first in that 5 | order. 6 | - Use Kotlin for all new code. 7 | - Prefer pull request reviews from other Android developers but be open to 8 | reviews from iOS and React Native developers as well. 9 | - Prefer non-null types. 10 | - Use string resources for all user-visible text. 11 | - Prefer vector drawables over PNGs or JPEGs. 12 | - Prefer Model-View-ViewModel (MVVM) for your app architecture. 13 | - Prefer `.forEach` over the `for` keyword. 14 | - Document each `@SuppressLint` with a comment. 15 | -------------------------------------------------------------------------------- /android/android_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 19 | 20 | -------------------------------------------------------------------------------- /postgres/README.md: -------------------------------------------------------------------------------- 1 | # Postgres 2 | 3 | - Avoid multicolumn indexes. Postgres [combines multiple indexes] efficiently. 4 | Optimize later with a [compound index] if needed. 5 | - Consider a [partial index] for queries on booleans. 6 | - Avoid JSONB columns unless you have a strong reason to store an entire JSON 7 | document from an external source. 8 | - Use [uppercase for SQL key words and lowercase for SQL identifiers]. 9 | 10 | [uppercase for sql key words and lowercase for sql identifiers]: http://www.postgresql.org/docs/9.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS 11 | [combines multiple indexes]: http://www.postgresql.org/docs/9.1/static/indexes-bitmap-scans.html 12 | [compound index]: http://www.postgresql.org/docs/9.2/static/indexes-bitmap-scans.html 13 | [partial index]: http://www.postgresql.org/docs/9.1/static/indexes-partial.html 14 | -------------------------------------------------------------------------------- /security/protecting-personal-or-identifying-information.md: -------------------------------------------------------------------------------- 1 | # Protecting Personal or Identifying Information 2 | 3 | Data privacy and security should be made a priority when developing software in 4 | an effort to protect personal or identifying information. 5 | 6 | Data privacy and security is important for everyone. It is especially vital for individuals who, due to their backgrounds or circumstances, might be at a higher risk of harmful consequences from privacy violations. 7 | 8 | Examples include: 9 | 10 | - Survivors of domestic abuse or those who are trying to escape domestic abuse 11 | - LGBTQIA+ individuals who might be in an unsafe circumstance in relation to their identity, including if their housing is dependent on someone who might evict them because of it 12 | - Political dissidents, asylum seekers, targets of government-sanctioned 13 | violence 14 | - Undocumented immigrants 15 | -------------------------------------------------------------------------------- /rails/how-to/feature_test_javascript_in_a_rails_app.md: -------------------------------------------------------------------------------- 1 | # How to Feature-test JavaScript in a Rails App 2 | 3 | Use [capybara-webkit]. In your `Gemfile`: 4 | 5 | ```ruby 6 | gem "capybara-webkit" 7 | ``` 8 | 9 | In `spec/support/capybara_webkit.rb` (for RSpec): 10 | 11 | ```ruby 12 | Capybara.javascript_driver = :webkit 13 | 14 | Capybara::Webkit.configure do |config| 15 | config.block_unknown_urls 16 | end 17 | ``` 18 | 19 | When writing a spec, you must set the `:js` flag for that test to make use of 20 | capybara-webkit. For example, in `spec/system/user_signs_in_spec.rb`: 21 | 22 | ```ruby 23 | describe "Authentication", :js do 24 | it "signs in a user" do 25 | create(:user, email: "me@example.com", password: "sekrit") 26 | 27 | sign_in_as email: "me@example.com", password: "sekrit" 28 | 29 | expect(page).to have_text("Welcome!") 30 | end 31 | end 32 | ``` 33 | 34 | [capybara-webkit]: https://github.com/thoughtbot/capybara-webkit 35 | -------------------------------------------------------------------------------- /_template/README.md: -------------------------------------------------------------------------------- 1 | # Template 2 | 3 | In a sentence or two, describe what this guide is about. A guide can be about a 4 | programming language or framework, a design or development tool, or an entirely 5 | non-technical topic. 6 | 7 | ## Best Practices 8 | 9 | In this section (or as many sections as you need) convey the best practices 10 | around the topic of the guide. 11 | 12 | This typically takes one of three forms: 13 | 14 | 1. A section or sections with lists of specific 15 | [guidelines](/README.md#a-note-on-the-language) 16 | 2. Primarily textual sections 17 | 3. A combination of both 18 | 19 | ## How To Guides 20 | 21 | This section, if applicable, should index a list of "How-to" guides for this 22 | guide's topic. These should be stored relative to this `README.md` file in a 23 | folder named `how-to`. 24 | 25 | Here are some examples: 26 | 27 | - [Do something](./how-to/do-something.md) 28 | - [Do something else](./how-to/do-something-else.md) 29 | -------------------------------------------------------------------------------- /sass/sample.scss: -------------------------------------------------------------------------------- 1 | @import "partial-name"; 2 | 3 | $color-variable: #ffffff; 4 | 5 | /* I'm here to explain what this class does */ 6 | .class-one { 7 | background-color: $color-variable; 8 | border: 0; 9 | line-height: 1.5; 10 | text-size: 0.5rem; 11 | transition: background-color 0.5s ease; 12 | 13 | @media (width >= 1px) { 14 | margin: ($spacing-variable * 2) 1rem; 15 | } 16 | 17 | &:hover { 18 | box-shadow: 0 0 2px 1px rgba($color-variable, 0.2); 19 | } 20 | 21 | &::before { 22 | content: "hello"; 23 | } 24 | } 25 | 26 | $map: ( 27 | "key-1": value-1, 28 | "key-2": value-2, 29 | ); 30 | 31 | .class-two { 32 | @extend %placeholder; 33 | @include mixin; 34 | 35 | align-items: center; 36 | display: flex; 37 | flex: 1 1 auto; 38 | 39 | a { 40 | text-decoration: none; 41 | 42 | &:focus, 43 | &:hover { 44 | text-decoration: underline; 45 | } 46 | } 47 | 48 | &.child { 49 | color: $red; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /open-source/README.md: -------------------------------------------------------------------------------- 1 | # Open Source Protocol 2 | 3 | A guide for releasing and maintaining open source projects. 4 | 5 | ## Accepting a GitHub Pull Request 6 | 7 | Given you have this in your `~/.gitconfig`: 8 | 9 | ```text 10 | [alias] 11 | co-pr = !sh -c 'git fetch origin pull/$1/head:pr/$1 && git checkout pr/$1' - 12 | ``` 13 | 14 | Check out the code by its GitHub pull request number: 15 | 16 | ```console 17 | git co-pr 123 18 | ``` 19 | 20 | Rebase interactively, squash, and potentially improve commit messages: 21 | 22 | ```console 23 | git rebase -i main 24 | ``` 25 | 26 | Look at changes: 27 | 28 | ```console 29 | git diff origin/main 30 | ``` 31 | 32 | Run the code and tests. For example, on a Ruby project: 33 | 34 | ```console 35 | bundle 36 | rake 37 | ``` 38 | 39 | Merge code into main: 40 | 41 | ```console 42 | git checkout main 43 | git merge pr/123 --ff-only 44 | ``` 45 | 46 | Push: 47 | 48 | ```console 49 | git push origin main 50 | ``` 51 | 52 | Clean up: 53 | 54 | ```console 55 | git branch -D pr/123 56 | ``` 57 | -------------------------------------------------------------------------------- /product-review/README.md: -------------------------------------------------------------------------------- 1 | # Product Review 2 | 3 | Cut down cycle time and focus on the user by getting a teammate to review your 4 | changes to the product before you get a code review or deploy to staging. 5 | 6 | For each change, choose one of four techniques: 7 | 8 | - In-person 9 | - Screencast 10 | - SSH tunnel 11 | - Video chat and screen-share 12 | 13 | ## In-person 14 | 15 | If they are sitting next to you, have them review the changes in person. 16 | 17 | ## Screencast 18 | 19 | Use [Licecap] to share a screencast gif in the project's [Basecamp]. 20 | 21 | [licecap]: http://www.cockos.com/licecap/ 22 | [basecamp]: https://basecamp.com/ 23 | 24 | ## SSH tunnel 25 | 26 | Use [ngrok] to set up an SSH tunnel to your work in progress on your laptop: 27 | 28 | ```console 29 | ngrok -subdomain=feature-branch-name 3000 30 | ``` 31 | 32 | Then, share the ngrok URL in the project's Basecamp. 33 | 34 | [ngrok]: https://ngrok.com/ 35 | 36 | ## Video chat and screenshare 37 | 38 | Start a Google Hangout in the project's Basecamp: 39 | 40 | ```text 41 | /hangout 42 | ``` 43 | -------------------------------------------------------------------------------- /testing-rspec/unit_test_spec.rb: -------------------------------------------------------------------------------- 1 | describe SomeClass do 2 | context "when defining a subject" do 3 | # GOOD 4 | # it's okay to define a `subject` here: 5 | subject { "foo" } 6 | 7 | it { should eq "foo" } 8 | end 9 | 10 | context "when using an explicit subject" do 11 | subject { "foo" } 12 | 13 | it "should equal foo" do 14 | # BAD 15 | # although it's valid RSpec code and this test passes, 16 | # it's not okay to use `subject` here: 17 | expect(subject).to eq "foo" 18 | end 19 | end 20 | 21 | describe '.some_class_method' do 22 | it 'does something' do 23 | # ... 24 | end 25 | end 26 | 27 | describe '#some_instance_method' do 28 | it 'does something' do 29 | expect(something).to eq 'something' 30 | end 31 | end 32 | 33 | describe '#another_instance_method' do 34 | context 'when in one case' do 35 | it 'does something' do 36 | # ... 37 | end 38 | end 39 | 40 | context 'when in other case' do 41 | it 'does something else' do 42 | # ... 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /rails/how-to/seed-data.md: -------------------------------------------------------------------------------- 1 | # How to seed development data 2 | 3 | ```ruby 4 | # lib/development/seeder.rb 5 | 6 | require "factory_bot" 7 | 8 | module Development 9 | class Seeder 10 | include FactoryBot::Syntax::Methods 11 | 12 | def self.load_seeds 13 | new.load_seeds 14 | end 15 | 16 | def load_seeds 17 | emails = %w[ralph@example.com ruby@example.com] 18 | 19 | emails.each do |email| 20 | create(:user, email:, password: "password") unless User.exists?(email:) 21 | end 22 | end 23 | end 24 | end 25 | ``` 26 | 27 | ```rb 28 | # lib/tasks/development.rake 29 | 30 | abort "Seeds can only be loaded in local environments" unless Rails.env.local? 31 | 32 | namespace :development do 33 | namespace :db do 34 | desc "Loads seed data into development." 35 | task seed: :environment do 36 | Development::Seeder.load_seeds 37 | end 38 | 39 | namespace :seed do 40 | desc "Truncate tables of each database for development and loads seed data." 41 | task replant: [ "environment", "db:truncate_all", "development:db:seed" ] 42 | end 43 | end 44 | end 45 | ``` 46 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We love contributions from everyone. By participating in this project, you agree 4 | to abide by the [thoughtbot Code Of Conduct]. 5 | 6 | We expect everyone to follow the code of conduct anywhere in thoughtbot's 7 | project codebases, issue trackers, chatrooms, mailing lists, meetups, at other events, and in-person. 8 | Violators will be warned by the core team. 9 | Repeat violations will result in being blocked or banned by the core team at or before the 3rd violation. 10 | 11 | [thoughtbot code of conduct]: https://thoughtbot.com/open-source-code-of-conduct 12 | 13 | ## Getting Feedback 14 | 15 | Since these are our guides, we want everyone at thoughtbot to see them. We have 16 | a lot of people across a lot of timezones, so we leave all PRs open for at 17 | least a week to get feedback from everyone. 18 | 19 | ## Content 20 | 21 | Decisions about which libraries to use should live in template projects such as 22 | [Suspenders]. 23 | 24 | [suspenders]: https://github.com/thoughtbot/suspenders 25 | 26 | ### The Anatomy of a Guide 27 | 28 | Whether you're creating a new guide or adding to an existing one, you can 29 | reference [the template guide](/_template/) if you're unsure where to put 30 | things. 31 | -------------------------------------------------------------------------------- /swift/README.md: -------------------------------------------------------------------------------- 1 | # Swift 2 | 3 | [Sample](sample.swift) 4 | 5 | - Prefer `struct`s over `class`es wherever possible 6 | - Default to marking classes as `final` 7 | - Prefer protocol conformance to class inheritance 8 | - Break long lines after 100 characters 9 | - Use 2 spaces for indentation 10 | - Use `let` whenever possible to make immutable variables 11 | - Name all parameters in functions and enum cases 12 | - Use trailing closures 13 | - Let the compiler infer the type whenever possible 14 | - Group computed properties below stored properties 15 | - Use a blank line above and below computed properties 16 | - Group methods into specific extensions for each level of access control 17 | - When capitalizing acronyms or initialisms, follow the capitalization of the 18 | first letter. 19 | - When using `Void` in function signatures, prefer `()` for arguments and `Void` 20 | for return types. 21 | - Prefer strong IBOutlet references. 22 | - Avoid evaluating a weak reference multiple times in the same scope. Strongify 23 | first, then use the strong reference. 24 | - Prefer to name `IBAction` and target/action methods using a verb describing 25 | the action it will trigger, instead of the user action (e.g., `edit:` instead 26 | of `editTapped:`) 27 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | # EXAMPLE USAGE: 2 | # 3 | # Refer for explanation to following link: 4 | # https://lefthook.dev/configuration/ 5 | # 6 | # pre-push: 7 | # jobs: 8 | # - name: packages audit 9 | # tags: 10 | # - frontend 11 | # - security 12 | # run: yarn audit 13 | # 14 | # - name: gems audit 15 | # tags: 16 | # - backend 17 | # - security 18 | # run: bundle audit 19 | # 20 | # pre-commit: 21 | # parallel: true 22 | # jobs: 23 | # - run: yarn eslint {staged_files} 24 | # glob: "*.{js,ts,jsx,tsx}" 25 | # 26 | # - name: rubocop 27 | # glob: "*.rb" 28 | # exclude: 29 | # - config/application.rb 30 | # - config/routes.rb 31 | # run: bundle exec rubocop --force-exclusion {all_files} 32 | # 33 | # - name: govet 34 | # files: git ls-files -m 35 | # glob: "*.go" 36 | # run: go vet {files} 37 | # 38 | # - script: "hello.js" 39 | # runner: node 40 | # 41 | # - script: "hello.go" 42 | # runner: go run 43 | pre-commit: 44 | parallel: true 45 | jobs: 46 | - name: Lint Markdown 47 | glob: "*.md" 48 | group: 49 | parallel: true 50 | jobs: 51 | - name: Markdownlint 52 | run: npx markdownlint-cli2 {staged_files} 53 | -------------------------------------------------------------------------------- /testing-rspec/avoid_let_spec.rb: -------------------------------------------------------------------------------- 1 | # Not recommended 2 | describe ReportPolicy do 3 | let(:report_id) { 2 } 4 | let(:report_policy) do 5 | ReportPolicy.new( 6 | User.new(report_ids: [1,2]), 7 | Report.new(id: report_id) 8 | ) 9 | end 10 | 11 | describe "#allowed?" do 12 | subject { report_policy.allowed? } 13 | 14 | context "when user has access to report" do 15 | it { should be true } 16 | end 17 | 18 | context "when user does not have access to report" do 19 | let(:report_id) { 3 } 20 | 21 | it { should be false } 22 | end 23 | end 24 | end 25 | 26 | # Recommended 27 | describe ReportPolicy do 28 | describe "#allowed?" do 29 | context "when user has access to report" do 30 | it "returns true" do 31 | policy = build_policy(report_id: 2, allowed_report_ids: [1, 2]) 32 | 33 | expect(policy).to be_allowed 34 | end 35 | end 36 | 37 | context "when user does not have access to report" do 38 | it "returns false" do 39 | policy = build_policy(report_id: 3, allowed_report_ids: [1, 2]) 40 | 41 | expect(policy).not_to be_allowed 42 | end 43 | end 44 | end 45 | 46 | def build_policy(report_id:, allowed_report_ids:) 47 | user = instance_double("User", report_ids: allowed_report_ids) 48 | report = instance_double("Report", id: report_id) 49 | 50 | ReportPolicy.new(user, report) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /web-performance/README.md: -------------------------------------------------------------------------------- 1 | # Web Performance 2 | 3 | Web performance refers to the speed in which web pages are downloaded and 4 | displayed on the user's web browser. Web performance optimization (WPO) or 5 | website optimization is the field of knowledge about increasing web performance. 6 | 7 | ## Resources 8 | 9 | - [Demystifying Speed Tooling (Google I/O ’19)] 10 | - [Fast load times] 11 | - [Your first performance budget with Lighthouse] 12 | - [Performance articles from Harry Roberts of CSS Wizardry] 13 | - [Visualize performance impact between deploys with Calibre] 14 | 15 | ## Tools 16 | 17 | - [Google Lighthouse] - Lighthouse is an open-source, automated tool for 18 | improving the quality of web pages. 19 | - [Calibre] - Automated performance monitoring, with device emulation, metric 20 | budgets, and alerts (powered by Lighthouse) 21 | 22 | [demystifying speed tooling (google i/o ’19)]: https://www.youtube.com/watch?v=mLjxXPHuIJo 23 | [fast load times]: https://web.dev/fast 24 | [your first performance budget with lighthouse]: https://bitsofco.de/your-first-performance-budget-with-lighthouse/ 25 | [performance articles from harry roberts of css wizardry]: https://csswizardry.com/archive/ 26 | [visualize performance impact between deploys with calibre]: https://calibreapp.com/blog/visualise-performance-impact-between-deploys 27 | [google lighthouse]: https://developers.google.com/web/tools/lighthouse/ 28 | [calibre]: https://calibreapp.com/ 29 | -------------------------------------------------------------------------------- /testing-jest/README.md: -------------------------------------------------------------------------------- 1 | # Testing with Jest 2 | 3 | - Use [eslint-plugin-jest] to enforce testing style 4 | - Use [testing-library/jest-dom] and [jest-community/jest-extended] for 5 | supplemental expectation matchers 6 | - Use [React Testing Library] for testing [React](/react/) components 7 | - Use [React Hooks Testing Library] for testing [React Hooks] 8 | - Use [User Event] for simulating DOM events on React components under test 9 | - Use [Fishery] for building factories 10 | - Prefer placing test suite files alongside source files (e.g. `Thing.tsx` / 11 | `Thing.test.tsx`) 12 | - Prefer writing specific unit tests over [Snapshot Testing] 13 | - Prefer `describe` and `it` blocks over `test` blocks 14 | - Prefer [.resolves/.rejects] over awaiting promises in tests 15 | 16 | [eslint-plugin-jest]: https://github.com/jest-community/eslint-plugin-jest 17 | [testing-library/jest-dom]: https://github.com/testing-library/jest-dom 18 | [react testing library]: https://github.com/testing-library/react-testing-library 19 | [react hooks testing library]: https://github.com/testing-library/react-hooks-testing-library 20 | [react hooks]: https://reactjs.org/docs/hooks-overview.html 21 | [user event]: https://github.com/testing-library/user-event 22 | [fishery]: https://github.com/thoughtbot/fishery 23 | [snapshot testing]: https://jestjs.io/docs/en/snapshot-testing 24 | [jest-community/jest-extended]: https://github.com/jest-community/jest-extended 25 | [.resolves/.rejects]: https://jestjs.io/docs/en/asynchronous#resolves--rejects 26 | -------------------------------------------------------------------------------- /ios/README.md: -------------------------------------------------------------------------------- 1 | # iOS Protocol 2 | 3 | A guide for making iPhone and iPad apps with aplomb. 4 | 5 | ## Set Up Laptop 6 | 7 | Install the latest version of Xcode from the App Store. 8 | 9 | ## Create App 10 | 11 | Get Liftoff. 12 | 13 | ```console 14 | brew tap thoughtbot/formulae 15 | brew install liftoff 16 | ``` 17 | 18 | Get CocoaPods 19 | 20 | ```console 21 | [sudo] gem install cocoapods 22 | ``` 23 | 24 | Create the app. 25 | 26 | ```console 27 | liftoff 28 | ``` 29 | 30 | - Be sure to set an appropriate 2 or 3 letter class prefix. 31 | 32 | ## Set Up App 33 | 34 | Get the code. 35 | 36 | ```console 37 | git clone git@github.com:organization/app.git 38 | ``` 39 | 40 | Install the app's dependencies. 41 | 42 | ```console 43 | cd project 44 | pod install 45 | ``` 46 | 47 | ## Git Protocol 48 | 49 | Follow the normal [Git protocol](/git/). 50 | 51 | ## Product Review 52 | 53 | Follow the normal [Product Review protocol](/product-review/). 54 | 55 | ## Code Review 56 | 57 | Follow the normal [Code Review guidelines](/code-review/). When reviewing 58 | others' iOS work, look in particular for: 59 | 60 | - Review that ViewControllers are adhering to the Single Responsibility Principle 61 | - Watch for CoreData thread boundary violations 62 | - Watch for potential retain cycles with blocks 63 | - Ensure that methods that require parameters are using `NSParameterAssert()` 64 | 65 | ## Submit to the App Store 66 | 67 | - Determine if you need to [report your app's use of encryption](https://getonthestore.com/export-compliance/). 68 | -------------------------------------------------------------------------------- /html/README.md: -------------------------------------------------------------------------------- 1 | # HTML 2 | 3 | - Use the [W3C's Markup Validation Service][html-validator] to validate HTML 4 | - Prefer double quotes for attributes. 5 | - Use lowercase text for elements and attributes 6 | - Use double quotes to wrap element attributes 7 | - Use closing tags for all [normal elements] 8 | - Prefer a HTML5 doctype 9 | - Ensure elements are scoped properly 10 | - Elements such as `` and `<meta>` must be placed within the page's 11 | `<head>` element 12 | - Elements such as `<p>`, `<nav>`, `<div>`, etc. should be placed within the 13 | page's `<body>` element 14 | - Ensure `id`s are unique 15 | - Prefer appending attribute values instead of declaring redundant attribute 16 | names 17 | - For example, if adding a class of `c-card--featured`, add it to the existing 18 | class declaration (`class="c-card c-card--featured"`, not `class="c-card" 19 | class="c-card--featured"`) 20 | - Avoid using emoji and other exotic characters as values for attributes such as 21 | `class`, `id`, `data`, and `aria-*`. 22 | - Avoid restricting viewport zooming 23 | - Ensure [parent elements contain no more than 60 child elements] 24 | - Use `<button>` elements instead of `<a>` elements for actions. 25 | - Use `type="button"` for button elements used outside of forms to prevent the 26 | browser from trying to submit form data 27 | - Use a `href` attribute for `<a>` elements with a valid location 28 | - Ensure heading elements are used to section content, and heading levels are 29 | not skipped 30 | 31 | [html-validator]: https://validator.w3.org/ 32 | [normal elements]: https://html.spec.whatwg.org/multipage/syntax.html#normal-elements 33 | [parent elements contain no more than 60 child elements]: https://developers.google.com/web/tools/lighthouse/audits/dom-size 34 | -------------------------------------------------------------------------------- /ruby/Use-an-opinionated-set-of-rules-for-Rubocop.md: -------------------------------------------------------------------------------- 1 | # Use an opinionated set of rules for Rubocop 2 | 3 | Ruby code can be written in many different styles and still be syntactically correct. The presence of divergent style and 4 | format can disrupt communication as well as complicate feature development. Code that exhibits a consistent style is 5 | easier to understand and maintain. The absence of consistent enforced style can result in unnecessary code churn. 6 | 7 | **Therefore:** Use an opinionated set of rules for Rubocop when coding in Ruby. 8 | 9 | - Prefer [standard] for new projects. 10 | - By employing an already decided configuration, you avoid bikeshedding on what the Rubocop configuration should be. 11 | - Code that is consistently formatted and styled will be easier to work with. 12 | 13 | There are, however, a few reasons one might choose not to introduce Standard in a project: 14 | 15 | - Change for the sake of change can be disruptive 16 | - An existing project may already employ it's own opinionated configuration of Rubocop and that set of rules is working. 17 | - A large project, which does not already use Standard, might require a costly amount of time to refactor and retrofit 18 | existing code to conform to a new set of conventions. 19 | - There exists a need or desire to use Cops which are disabled by Standard 20 | - [standard] presents a lightweight but sensible set of style rules to focus on coding 21 | - [standard] prevents you from using cops that it disables 22 | 23 | Overall, the goal is to increase the quality and consistency of code while not getting distracted by disproportionally 24 | minor or trivial details. It's more important for an implementation team to agree to follow shared conventions than it 25 | is to enforce a specific configuration. 26 | 27 | [standard]: https://github.com/testdouble/standard 28 | -------------------------------------------------------------------------------- /general/README.md: -------------------------------------------------------------------------------- 1 | # General Guidelines 2 | 3 | Style and best practices that apply to all languages and frameworks. 4 | 5 | ## Philosophy 6 | 7 | - These are not to be blindly followed; strive to understand these and ask when 8 | in doubt. 9 | - Don't duplicate the functionality of a built-in library. 10 | - Don't swallow exceptions or "fail silently." 11 | - Don't write code that guesses at future functionality. 12 | - Exceptions should be exceptional. 13 | - Keep the code simple. 14 | 15 | ## Code Review 16 | 17 | Use a linter to automatically review your GitHub pull requests for style guide 18 | violations. 19 | 20 | ## Formatting 21 | 22 | - Break long lines after 80 characters. 23 | - Delete trailing spaces. 24 | - Don't misspell. 25 | - Use [Unix-style line endings] (`\n`). 26 | - Use spaces around operators, except for unary operators, such as `!`. 27 | 28 | [unix-style line endings]: http://unix.stackexchange.com/questions/23903/should-i-end-my-text-script-files-with-a-newline 29 | 30 | ## Naming 31 | 32 | - Avoid abbreviations. 33 | - Avoid object types in names (`user_array`, `email_method` `CalculatorClass`, 34 | `ReportModule`). 35 | - Prefer naming classes after domain concepts rather than patterns they 36 | implement (e.g. `Guest` vs `NullUser`, `CachedRequest` vs `RequestDecorator`). 37 | - Name the enumeration parameter the singular of the collection (`users.each { |user| greet(user) }`). 38 | - Name variables, methods, and classes to reveal intent. This includes documentation and 39 | examples (e.g. don't use `foo`, `bar`, `baz` in examples). 40 | - Treat acronyms as words in names (`XmlHttpRequest` not `XMLHTTPRequest`), even 41 | if the acronym is the entire name (`class Html` not `class HTML`). 42 | 43 | ## Organization 44 | 45 | - Order methods so that caller methods are earlier in the file than the methods 46 | they call. 47 | - Order methods so that methods are as close as possible to other methods they 48 | call. 49 | -------------------------------------------------------------------------------- /rails/how-to/deploy_a_rails_app_to_heroku.md: -------------------------------------------------------------------------------- 1 | # How to Deploy a Rails App to Heroku 2 | 3 | View a list of new commits. View changed files. 4 | 5 | ```console 6 | git fetch staging 7 | git log staging/main..main 8 | git diff --stat staging/main 9 | ``` 10 | 11 | If necessary, add new environment variables. 12 | 13 | ```console 14 | heroku config:add NEW_VARIABLE=value --remote staging 15 | ``` 16 | 17 | Deploy to [Heroku] staging. 18 | 19 | ```console 20 | git push staging 21 | ``` 22 | 23 | If necessary, run migrations and restart the dynos. 24 | 25 | ```console 26 | heroku run rake db:migrate --remote staging 27 | heroku restart --remote staging 28 | ``` 29 | 30 | [Introspect] to make sure everything's ok. 31 | 32 | ```console 33 | watch heroku ps --remote staging 34 | ``` 35 | 36 | Test the feature in browser. 37 | 38 | Deploy to production. 39 | 40 | ```console 41 | git fetch production 42 | git log production/main..main 43 | git diff --stat production/main 44 | heroku config:add NEW_VARIABLE=value --remote production 45 | git push production 46 | heroku run rake db:migrate --remote production 47 | heroku restart --remote production 48 | watch heroku ps --remote production 49 | ``` 50 | 51 | Watch logs and metrics dashboards. 52 | 53 | Close pull request and comment `Merged.` 54 | 55 | [heroku]: https://devcenter.heroku.com/articles/quickstart 56 | [introspect]: http://blog.heroku.com/archives/2011/6/24/the_new_heroku_3_visibility_introspection/ 57 | 58 | ## Set Up Production Environment 59 | 60 | - Make sure that your [`Procfile`] is set up to run Unicorn. 61 | - Make sure the PG Backups add-on is enabled. 62 | - Create a read-only [Heroku Follower] for your production database. If a Heroku 63 | database outage occurs, Heroku can use the follower to get your app back up 64 | and running faster. 65 | 66 | [heroku follower]: https://devcenter.heroku.com/articles/improving-heroku-postgres-availability-with-followers 67 | [`procfile`]: https://devcenter.heroku.com/articles/procfile 68 | -------------------------------------------------------------------------------- /ruby/Limit-use-of-conditional-modifiers-to-short-simple-cases.md: -------------------------------------------------------------------------------- 1 | # Limit use of conditional modifiers to short, simple cases 2 | 3 | Conditional modifiers (i.e., `if` or `unless` at the end of a line) can be 4 | surprising when they appear on long or complex lines. The reader might not see 5 | them while scanning the code. 6 | 7 | So, prefer to use them only for short, simple cases. For example: 8 | 9 | ```ruby 10 | do_later if async? 11 | ``` 12 | 13 | The example above can read more naturally than: 14 | 15 | ```rb 16 | if async? 17 | do_later 18 | end 19 | ``` 20 | 21 | ## Complex conditions 22 | 23 | However, if the line is too long (around 80 characters) or complex (e.g., an 24 | `if` with multiple conditions like `if a && b`) prefer the multi-line form: 25 | 26 | ```ruby 27 | # Avoid 28 | block_access! if signed_in? && !current_user.active? 29 | 30 | # Prefer 31 | if signed_in? && !current_user.active? 32 | block_access! 33 | end 34 | ``` 35 | 36 | There might be cases where the conditional modifier work well with multiple 37 | conditions, so use your best judgment. 38 | 39 | ## An opportunity to refactor 40 | 41 | If the conditions are related, consider extracting a method that groups them. 42 | This might allow you to use the conditional modifier form again. 43 | 44 | ```ruby 45 | def inactive_user? 46 | signed_in? && !current_user.active? 47 | end 48 | 49 | block_access! if inactive_user? 50 | ``` 51 | 52 | ## Conditional modifiers feel informal 53 | 54 | The modifier form of conditionals can feel more casual than the multi-line form. 55 | Conversely, the multi-line form _draws attention_ to the conditional and the 56 | code that follows it. Use this to your advantage when you want to emphasize the 57 | conditional and the code that follows it. 58 | 59 | ```rb 60 | # Avoid 61 | def action 62 | return destroy_all if really? 63 | 64 | do_nothing 65 | end 66 | 67 | # Prefer 68 | def action 69 | if really? 70 | destroy_all 71 | else 72 | do_nothing 73 | end 74 | end 75 | ``` 76 | 77 | You can also refactor the code so the less destructive action uses a conditional 78 | modifier, which pairs well with the informal feel of the modifier form: 79 | 80 | ```rb 81 | def action 82 | return do_nothing if chill? 83 | 84 | destroy_all 85 | end 86 | ``` 87 | 88 | ## References 89 | 90 | - You can see further discussion of this guideline here: [#738](https://github.com/thoughtbot/guides/pull/738) 91 | -------------------------------------------------------------------------------- /ruby/sample_1.rb: -------------------------------------------------------------------------------- 1 | class SomeClass 2 | SOME_CONSTANT = "upper case name" 3 | 4 | def initialize(attributes) 5 | @some_attribute = attributes[:some_attribute] 6 | @another_attribute = attributes[:another_attribute] 7 | @user_factory = attributes[:user_factory] 8 | end 9 | 10 | def method_with_arguments(argument_one, argument_two) 11 | a_really_long_line_that_is_broken_up_over_multiple_lines_and 12 | .subsequent_lines_are_indented_and 13 | .each_method_lives_on_its_own_line 14 | end 15 | 16 | def method_with_required_keyword_arguments(one:, two:) 17 | end 18 | 19 | def method_with_multiline_block 20 | some_method_before_block(should_be_followed_by_a_newline) 21 | 22 | items.each do |item| 23 | do_something_with_item 24 | perform_another_action 25 | end 26 | 27 | some_method_after_block(should_follow_after_newline) 28 | end 29 | 30 | def method_with_single_method_block 31 | items.map(&:some_attribute) 32 | end 33 | 34 | def method_with_oneline_combined_methods_block 35 | items.map { |item| "#{item.one} #{item.two}" } 36 | end 37 | 38 | def method_that_returns_an_array 39 | [item_one, item_two] 40 | end 41 | 42 | def method_that_returns_a_hash 43 | {key: "value"} 44 | end 45 | 46 | def method_with_large_hash 47 | { 48 | one: "value", 49 | two: "value" 50 | } 51 | end 52 | 53 | def method_with_large_array 54 | [ 55 | :one, 56 | :two, 57 | :three 58 | ] 59 | end 60 | 61 | def method_which_uses_infix_operators 62 | left + middle - right 63 | end 64 | 65 | def method_which_uses_unary_operator 66 | !signed_in? 67 | end 68 | 69 | def method_without_arguments 70 | if complex_condition? 71 | positive_branch 72 | else 73 | negative_branch 74 | end 75 | 76 | rest_of_body 77 | end 78 | 79 | def method_that_uses_factory 80 | user = user_factory.new 81 | user.ensure_authenticated! 82 | end 83 | 84 | def self.class_method 85 | method_body 86 | end 87 | 88 | def memoized_method 89 | @_memoized_method ||= 1 90 | end 91 | 92 | private 93 | 94 | attr_reader :foo, :user_factory 95 | attr_accessor :bar 96 | attr_writer :baz 97 | 98 | def complex_condition? 99 | part_one? && part_two? 100 | end 101 | end 102 | 103 | module A 104 | class B 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /react-native/README.md: -------------------------------------------------------------------------------- 1 | # React Native 2 | 3 | - Use React following the [React Guide](/react/) 4 | - Use [TypeScript](/javascript-typescript/README.md#typescript) 5 | - Prefer using [core components and apis] over writing bespoke components. 6 | 7 | [core components and apis]: https://reactnative.dev/docs/components-and-apis 8 | 9 | ## Tooling 10 | 11 | - Start new projects with [create-belt-app](https://www.npmjs.com/package/create-belt-app) 12 | - Use [Expo](https://expo.dev) 13 | - Use [Expo EAS](https://expo.dev/eas) for continuous deployment 14 | - Use [Expo Secure Store](https://docs.expo.dev/versions/latest/sdk/securestore/) for storing sensitive data like auth and refresh tokens 15 | - Use [React Navigation](https://reactnavigation.org/) for routing 16 | - Use [TanStack React Query](https://tanstack.com/query/v4/docs/framework/react/overview) as an API client for REST APIs 17 | - Use [Apollo Client](https://www.apollographql.com/docs/react/) as an API client for GraphQL APIs 18 | - Use [Redux Toolkit](https://redux-toolkit.js.org/) for global state 19 | - Avoid storing API data in a global store. Instead, use a dedicated API client. 20 | - Use [React Native Firebase](https://rnfirebase.io/) for push notifications 21 | - Use [Sentry](https://docs.sentry.io/platforms/react-native/) for error reporting 22 | - Prefer [RevenueCat](https://www.revenuecat.com/) for in-app payments 23 | - If RevenueCat pricing is not acceptable since it collects a percentage of revenue, use [react-native-iap](https://react-native-iap.dooboolab.com/docs/get-started/) 24 | 25 | ## Style 26 | 27 | - Prefer using [StyleSheets] 28 | - Prefer [organized and composable styles] 29 | - Avoid designing for only one platform. 30 | - Avoid designing for only one screen size. 31 | - Prefer common mobile patterns and avoid [non-standard patterns]. 32 | 33 | [StyleSheets]: https://reactnative.dev/docs/stylesheet 34 | [organized and composable styles]: https://thoughtbot.com/blog/structure-for-styling-in-react-native 35 | [non-standard patterns]: https://thoughtbot.com/blog/some-tips-for-designing-apps-in-react-native#make-it-feel-native-even-though-it39s-not 36 | 37 | ## Testing 38 | 39 | - Test using React Native [Testing Library](https://callstack.github.io/react-native-testing-library/) and [Jest](https://jestjs.io/) 40 | - Mock API calls in tests using [MSW](https://mswjs.io/). If using Apollo Client, mock using the built-in `MockedProvider` 41 | - Prefer testing on physical devices. 42 | - Use [detox] for integration tests. 43 | 44 | [detox]: https://github.com/wix/Detox 45 | -------------------------------------------------------------------------------- /shell/README.md: -------------------------------------------------------------------------------- 1 | # Shell 2 | 3 | - Break long lines on `|`, `&&`, or `||` and indent the continuations. 4 | - Don't add an extension to executable shell scripts. 5 | - Don't put a line break before `then` or `do`, use `if ...; then` and `while 6 | ...; do`. 7 | - Use `for x; do`, not `for x in "$@"; do`. 8 | - Use `snake_case` for variable names and `ALLCAPS` for environment variables. 9 | - Use single quotes for strings that don't contain escapes or variables. 10 | - Use two-space indentation. 11 | - Don't parse the output of `ls`. Understand [why you shouldn't and available 12 | alternatives]. 13 | - Don't use `cat` to provide a file on `stdin` to a process that accepts file 14 | arguments itself. 15 | - Don't use `echo` with options, escapes, or variables (use `printf` for those 16 | cases). 17 | - Don't use a `/bin/sh` [shebang] unless you plan to test and run your script on 18 | at least: Actual Sh, Dash in POSIX-compatible mode (as it will be run on 19 | Debian), and Bash in POSIX-compatible mode (as it will be run on macOS). 20 | - Don't use any [non-POSIX features] when using a `/bin/sh` [shebang]. 21 | - If calling `cd`, have code to handle a failure to change directories. 22 | - If calling `rm` with a variable, ensure the variable is not empty. 23 | - Prefer "$@" over "$\*" unless you know exactly what you're doing. 24 | - Prefer `awk '/re/ { ... }'` to `grep re | awk '{ ... }'`. 25 | - Prefer `find -exec {} +` to `find -print0 | xargs -0`. 26 | - Prefer `for` loops over `while read` loops. 27 | - Prefer `grep -c` to `grep | wc -l`. 28 | - Prefer `mktemp` over using `$$` to "uniquely" name a temporary file. 29 | - Prefer `sed '/re/!d; s//.../'` to `grep re | sed 's/re/.../'`. 30 | - Prefer `sed 'cmd; cmd'` to `sed -e 'cmd' -e 'cmd'`. 31 | - Prefer checking exit statuses over output in `if` statements (`if grep -q 32 | ...;`, not `if [ -n "$(grep ...)" ];`). 33 | - Prefer reading environment variables over process output (`$TTY` not `$(tty)`, 34 | `$PWD` not `$(pwd)`, etc). 35 | - Use `$( ... )`, not backticks for capturing command output. 36 | - Use `$(( ... ))`, not `expr` for executing arithmetic expressions. 37 | - Use `1` and `0`, not `true` and `false` to represent boolean variables. 38 | - Use `find -print0 | xargs -0`, not `find | xargs`. 39 | - Use quotes around every `"$variable"` and `"$( ... )"` expression unless you 40 | want them to be word-split and/or interpreted as globs. 41 | - Use the `local` keyword with function-scoped variables. 42 | - Identify common problems with [shellcheck]. 43 | 44 | [shebang]: http://en.wikipedia.org/wiki/Shebang_(Unix) 45 | [why you shouldn't and available alternatives]: http://mywiki.wooledge.org/ParsingLs 46 | [non-posix features]: http://mywiki.wooledge.org/Bashism 47 | [shellcheck]: http://www.shellcheck.net/ 48 | -------------------------------------------------------------------------------- /graphql/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL 2 | 3 | A guide for building GraphQL servers and clients. 4 | 5 | ## Learning 6 | 7 | A curated list of resources for learning GraphQL. 8 | 9 | - **[Official GraphQL Learning Site]** 10 | - **[How To GraphQL]** Online tutorial for both server and client GraphQL in 11 | multiple programming languages. 12 | - **[Learning GraphQL]** A clear introduction to GraphQL technology and a walk 13 | through of building a GraphQL server. 14 | - **[GraphQL: A Query Language for your API]** Presentation introducing GraphQL 15 | to thoughtbot. 16 | 17 | [official graphql learning site]: https://graphql.org/learn/ 18 | [how to graphql]: https://www.howtographql.com/ 19 | [learning graphql]: http://shop.oreilly.com/product/0636920137269.do 20 | [graphql: a query language for your api]: https://www.dropbox.com/s/svqe68hpdiixf0g/presentation.pdf?dl=0 21 | 22 | ## Public GraphQL APIs 23 | 24 | Publicly available GraphQL APIs allowing you to explore how GraphQL is and can 25 | be used. 26 | 27 | - **[GitHub GraphQL API Explorer]** 28 | - **[Star Wars GraphQL]** 29 | 30 | [github graphql api explorer]: https://developer.github.com/v4/explorer/ 31 | [star wars graphql]: https://graphql.org/swapi-graphql/ 32 | 33 | ## Tools 34 | 35 | - **[GraphiQL].** An Electron-based "web IDE" for interacting with GraphQL APIs. 36 | GraphiQL can also be served as a page in an application. 37 | - **[GraphQL Playground]** An Electron-based "web IDE" for interacting with 38 | GraphQL APIs. Intends to expand upon GraphiQL. 39 | - **[Insomnia]** An HTTP client with solid GraphQL support. 40 | - **[Apollo Client Dev Tools]** Chrome Extension offering developer tools for 41 | Apollo projects. 42 | 43 | [graphiql]: https://github.com/graphql/graphiql 44 | [graphql playground]: https://github.com/prisma/graphql-playground 45 | [insomnia]: https://insomnia.rest/ 46 | [apollo client dev tools]: https://www.apollographql.com/docs/react/features/developer-tooling 47 | 48 | ## Best Practices 49 | 50 | - Follow the latest version of the [GraphQL specification]. 51 | - When serving over HTTP, respond with a 200 OK status code to all GraphQL 52 | queries. 53 | - If a client or server error occurs, use the `errors` key in the GraphQL 54 | response. 55 | - If a user-facing error occurs (such as invalid user input), use the `data` key 56 | in the GraphQL response. 57 | - If a mutation can fail because of a user error, use a union type to describe 58 | the possible outcomes. 59 | - If there is an authenticated user, provide the user in the context for the 60 | resolver. 61 | - Provide the updated object as a field in mutations. 62 | - Provide the ID of the deleted object as a field in mutations that delete 63 | objects. 64 | - Use JSON as a default transport format. 65 | - Avoid returning null from operations. [#630] 66 | 67 | [graphql specification]: https://graphql.github.io/graphql-spec/ 68 | [#630]: https://github.com/thoughtbot/guides/pull/630 69 | -------------------------------------------------------------------------------- /react/README.md: -------------------------------------------------------------------------------- 1 | # React 2 | 3 | - Use React in [Strict Mode] 4 | - Use React with [TypeScript](/javascript-typescript/README.md#typescript) 5 | - Avoid nested routing if using [React Router] 6 | - Prefer [Function Components] over [Class Components] 7 | - Prefer keeping a single component in each file 8 | - Use `PascalCase` for component names and their file names 9 | - Use [React Hooks] 10 | - Use pre-built hooks when possible (e.g. [streamich/react-use]) 11 | - Use [custom hooks] to encapsulate stateful logic outside a component 12 | - Avoid nesting [Forward Refs] 13 | - Avoid [Higher-Order Components] and [recompose] (see hooks above as an 14 | alternative) 15 | - Prefer the `children` prop over [render props] 16 | - Prefer using [TypeScript prop interfaces] over [PropTypes] 17 | - Prefer the [short syntax] when using [Fragments] 18 | - Prefer [React Contexts] over [Redux] 19 | - Avoid using indexes as the value for [keys] 20 | - Avoid complex conditionals inside component logic 21 | - Prefer [component composition over component inheritance] 22 | 23 | [strict mode]: https://reactjs.org/docs/strict-mode.html 24 | [react hooks]: https://reactjs.org/docs/hooks-overview.html 25 | [custom hooks]: https://reactjs.org/docs/hooks-overview.html#building-your-own-hooks 26 | [streamich/react-use]: https://github.com/streamich/react-use 27 | [function components]: https://reactjs.org/docs/components-and-props.html 28 | [class components]: https://reactjs.org/docs/react-component.html 29 | [forward refs]: https://reactjs.org/docs/forwarding-refs.html 30 | [higher-order components]: https://reactjs.org/docs/higher-order-components.html 31 | [recompose]: https://github.com/acdlite/recompose 32 | [render props]: https://reactjs.org/docs/render-props.html 33 | [typescript prop interfaces]: https://www.typescriptlang.org/docs/handbook/react-&-webpack.html#write-some-code 34 | [proptypes]: https://reactjs.org/docs/typechecking-with-proptypes.html 35 | [short syntax]: https://reactjs.org/docs/fragments.html#short-syntax 36 | [fragments]: https://reactjs.org/docs/fragments.html 37 | [react contexts]: https://reactjs.org/docs/context.html 38 | [redux]: https://react-redux.js.org/ 39 | [keys]: https://reactjs.org/docs/lists-and-keys.html#keys 40 | [component composition over component inheritance]: https://reactjs.org/docs/composition-vs-inheritance.html 41 | [react router]: https://reacttraining.com/react-router/ 42 | 43 | ## General Philosophies 44 | 45 | - For greenfield React projects we like to use [TypeScript]. TypeScript is a 46 | typed superset of JavaScript that compiles to plain JavaScript. For a quick 47 | introduction, check out [TypeScript in 5 minutes]. 48 | - When building React apps with TypeScript and Apollo, we've found working in 49 | [VSCode] to be a mostly-good experience. 50 | 51 | [typescript]: https://www.typescriptlang.org/ 52 | [typescript in 5 minutes]: https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html 53 | [vscode]: https://code.visualstudio.com/ 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Guides 2 | 3 | Guides for working together, getting things done, programming well, and 4 | programming in style. 5 | 6 | ## High level guidelines 7 | 8 | - Be consistent. 9 | - Don't rewrite existing code to follow this guide. 10 | - Don't violate a guideline without a good reason. 11 | - A reason is good when you can convince a teammate. 12 | 13 | ## A note on the language 14 | 15 | - "Avoid" means don't do it unless you have good reason. 16 | - "Don't" means there's never a good reason. 17 | - "Prefer" indicates a better option and its alternative to watch out for. 18 | - "Use" is a positive instruction. 19 | 20 | ## Guides by category 21 | 22 | - [thoughtbot Tech Stack](/tech-stack/) 23 | - [General](/general/) 24 | 25 | ### Collaboration 26 | 27 | - [Code Review](/code-review/) 28 | - [Open Source](/open-source/) 29 | - [Product Review](/product-review/) 30 | 31 | ### Protocols 32 | 33 | - [Accessibility](/accessibility/) 34 | - [Data](/data/) 35 | - [Email](/email/) 36 | - [Object-Oriented Design](/object-oriented-design/) 37 | - [Security](/security/) 38 | - [Web](/web/) 39 | - [Web Performance](/web-performance/) 40 | 41 | ### Languages 42 | 43 | - [Bash](/bash/) 44 | - [CSS](/css/) 45 | - [ERB](/erb/) 46 | - [HTML](/html/) 47 | - [JavaScript & TypeScript](/javascript-typescript/) 48 | - [Python](/python/) 49 | - [Ruby](/ruby/) 50 | - [Sass](/sass/) 51 | - [Shell](/shell/) 52 | - [Swift](/swift/) 53 | 54 | ### Frameworks and platforms 55 | 56 | - [Android](/android/) 57 | - [iOS](/ios/) 58 | - [Rails](/rails/) 59 | - [React](/react/) 60 | - [React Native](/react-native/) 61 | - [Testing with Jest](/testing-jest/) 62 | - [Testing with RSpec](/testing-rspec/) 63 | 64 | ### Tools 65 | 66 | - [Git](/git/) 67 | - [GraphQL](/graphql/) 68 | - [Postgres](/postgres/) 69 | - [Relational Databases](/relational-databases/) 70 | 71 | ## Contributing 72 | 73 | Please read the [contribution guidelines](/CONTRIBUTING.md) before submitting a 74 | pull request. 75 | 76 | In particular: **if you have commit access, please don't merge changes without 77 | waiting a week for everybody to leave feedback**. 78 | 79 | ## Credits 80 | 81 | Thank you, 82 | [contributors](https://github.com/thoughtbot/guides/graphs/contributors)! 83 | 84 | ## License 85 | 86 | Guides is © 2020-2025 thoughtbot, inc. It is distributed under the [Creative 87 | Commons Attribution License](http://creativecommons.org/licenses/by/3.0/). 88 | 89 | <!-- START /templates/footer.md --> 90 | ## About thoughtbot 91 | 92 | ![thoughtbot](https://thoughtbot.com/thoughtbot-logo-for-readmes.svg) 93 | 94 | This repo is maintained and funded by thoughtbot, inc. 95 | The names and logos for thoughtbot are trademarks of thoughtbot, inc. 96 | 97 | We love open source software! 98 | See [our other projects][community]. 99 | We are [available for hire][hire]. 100 | 101 | [community]: https://thoughtbot.com/community?utm_source=github 102 | [hire]: https://thoughtbot.com/hire-us?utm_source=github 103 | 104 | <!-- END /templates/footer.md --> 105 | -------------------------------------------------------------------------------- /swift/sample.swift: -------------------------------------------------------------------------------- 1 | // Don't include generated header comments 2 | 3 | // MARK: Types and naming 4 | 5 | // Types begin with a capital letter 6 | struct User { 7 | let name: String 8 | 9 | // if the first letter of an acronym is lowercase, the entire thing should 10 | // be lowercase 11 | let json: Any 12 | 13 | // if the first letter of an acronym is uppercase, the entire thing should 14 | // be uppercase 15 | static func decode(from json: JSON) -> User { 16 | return User(json: json) 17 | } 18 | } 19 | 20 | // Use () for void arguments and Void for void return types 21 | let f: () -> Void = { } 22 | 23 | // When using classes, default to marking them as final 24 | final class MyViewController: UIViewController { 25 | // Prefer strong IBOutlet references 26 | @IBOutlet var button: UIButton! 27 | } 28 | 29 | // Use typealias when closures are referenced in multiple places 30 | typealias CoolClosure = (Int) -> Bool 31 | 32 | // Use aliased parameter names when function parameters are ambiguous 33 | func yTown(some: Int, withCallback callback: CoolClosure) -> Bool { 34 | return CoolClosure(some) 35 | } 36 | 37 | // It's OK to use $ variable references if the closure is very short and 38 | // readability is maintained 39 | let cool = yTown(5) { $0 == 6 } 40 | 41 | // Use full variable names when closures are more complex 42 | let cool = yTown(5) { foo in 43 | if foo > 5 && foo < 0 { 44 | return true 45 | } else { 46 | return false 47 | } 48 | } 49 | 50 | // Strongify weak references in async closures 51 | APIClient.getAwesomeness { [weak self] result in 52 | guard let `self` = self else { return } 53 | self.stopLoadingSpinner() 54 | self.show(result) 55 | } 56 | 57 | // If the API you are using has implicit unwrapping you should still use if-let 58 | func someUnauditedAPI(thing: String!) { 59 | if let string = thing { 60 | print(string) 61 | } 62 | } 63 | 64 | // When the type is known you can let the compiler infer 65 | let response: Response = .Success(NSData()) 66 | 67 | func doSomeWork() -> Response { 68 | let data = ... 69 | return .Success(data) 70 | } 71 | 72 | switch response { 73 | case let .Success(data): 74 | print("The response returned successfully \(data)") 75 | 76 | case let .Failure(error): 77 | print("An error occured: \(error)") 78 | } 79 | 80 | // MARK: Organization 81 | 82 | // Group methods into specific extensions for each level of access control 83 | private extension MyClass { 84 | func doSomethingPrivate() { } 85 | } 86 | 87 | // MARK: Breaking up long lines 88 | 89 | // One expression to evaluate and short or no return 90 | guard let singleTest = somethingFailable() else { return } 91 | guard statementThatShouldBeTrue else { return } 92 | 93 | // If there is one long expression to guard or multiple expressions 94 | // move else to next line 95 | guard let oneItem = somethingFailable(), 96 | let secondItem = somethingFailable2() 97 | else { return } 98 | 99 | // If the return in else is long, move to next line 100 | guard let something = somethingFailable() else { 101 | return someFunctionThatDoesSomethingInManyWordsOrLines() 102 | } 103 | -------------------------------------------------------------------------------- /security/README.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | A guide for practicing safe web. 4 | 5 | ## Think 6 | 7 | Security is important, and you can't practice these guidelines without 8 | understanding them. Make sure you understand each guideline, why it exists, and 9 | how to follow it. 10 | 11 | Failing to follow these guidelines will likely put you, your team, and your 12 | deployed services at risk of compromise or loss of privacy. 13 | 14 | ## Secure Employee Access and Communication 15 | 16 | The following guidelines apply to how you as an individual secure access to your 17 | systems (laptop, accounts, etc.) and communication (email, etc.). 18 | 19 | ## Protecting Personal or Identifying Information 20 | 21 | See [protecting personal or identifying information][]. 22 | 23 | ### Using Passwords 24 | 25 | - Use a unique password for every account you create. 26 | - Use a tool like [pwgen] or [1password] to generate random passwords. 27 | - Use a tool like GnuPG to encrypt passwords if you need to share them with 28 | somebody. 29 | 30 | [pwgen]: https://github.com/jbernard/pwgen 31 | [1password]: https://1password.com 32 | [protecting personal or identifying information]: ./protecting-personal-or-identifying-information.md 33 | 34 | ### Encryption 35 | 36 | - Ensure [disk encryption] on your laptop. 37 | - Use a PGP signature in an email if you want somebody to trust that you wrote 38 | it. 39 | - Use PGP to check email signatures if you want to know who wrote it. 40 | - Use PGP to encrypt emails if you want to be sure nobody but the recipient is 41 | reading it. 42 | - Use ultimate trust for your own keys. 43 | - Use full trust for keys you have verified in person or via a secure video 44 | chat. 45 | - Don't share your private key with anyone, including services like Keybase. 46 | - Keep at least one backup of your private key and revocation certificate in a 47 | secure location, such as a thumb drive. 48 | 49 | [disk encryption]: https://theintercept.com/2015/04/27/encrypting-laptop-like-mean/ 50 | 51 | ## Physical Security 52 | 53 | The following guidelines apply to how we physically secure our laptops and 54 | mobile devices that may contain customer or user data. 55 | 56 | - Lock your device when you are away from it. 57 | - Don't leave your devices unattended in an unsecured area. 58 | - Install a device tracking and remote data wipe tool such as [Prey]. 59 | 60 | [prey]: https://www.preyproject.com/ 61 | 62 | ## Application Security 63 | 64 | The [application security guidelines](application.md) apply to how we develop 65 | software on behalf of ourselves and clients. 66 | 67 | ## Handling Vulnerabilities 68 | 69 | The following guidelines apply to how we handle security incidents. 70 | 71 | ### Reporting 72 | 73 | When someone finds a possible security issue in our software, we encourage them 74 | to report it to our <security@thoughtbot.com> email address. 75 | 76 | When an email comes in through this channel, reply quickly with confirmation 77 | (and CC <security@thoughtbot.com> so others know that it has been handled) and 78 | the information for the thoughtbot PGP key, which is located at <https://thoughtbot.com/security>. 79 | 80 | ### Reviewing, Logging and Following Up 81 | 82 | When an encrypted message comes in, post the exchange to a new [Hub Message](https://hub.thoughtbot.com/messages/new) in the `security` interest, and keep the thread updated with new messages 83 | as they appear. 84 | 85 | Further discussion of security takes place in the [Security Basecamp]. 86 | 87 | [security basecamp]: https://3.basecamp.com/3091943/projects/15753689 88 | -------------------------------------------------------------------------------- /tech-stack/README.md: -------------------------------------------------------------------------------- 1 | # thoughtbot Stack 2 | 3 | We have a standard technology stack that we use for each new project by default. 4 | This provides a proven base for rapid, quality development and ensures we have a 5 | large team of developers ready to jump on new projects using a known technology. 6 | It helps to avoid decision fatigue at the beginning of each project. 7 | 8 | We deviate from this stack when we find something new that we need to evaluate. 9 | This allows us to practice our value of [Continuous Improvement]. 10 | 11 | We will also swap in alternate technology as necessary when our default stack is 12 | inappropriate for the task at hand. This allows us to practice our value of 13 | [Quality]. 14 | 15 | [continuous improvement]: https://thoughtbot.com/purpose#continuous-improvement 16 | [quality]: https://thoughtbot.com/purpose#quality 17 | 18 | ## Core Stack 19 | 20 | Most developers at thoughtbot learn our Core Stack. The stack is broken up into 21 | layers and each layer depends on another layer. This allows us to swap out an 22 | entire layer when necessary without losing all the decisions made for other 23 | layers. Developers will have varying levels of experience with each layer, but 24 | the gaps between layers are small enough that developers can learn a new layer 25 | while building applications. 26 | 27 | ### UI 28 | 29 | - Use server-rendered HTML when possible as a UI layer. 30 | - Use React when building components with a client-side framework. 31 | - Use TypeScript when writing client-side code. 32 | - Avoid building single-page applications for the web. 33 | - When building a cross-platform mobile app that will be delivered via an app 34 | store, use React Native. 35 | 36 | ### Web 37 | 38 | - Use Ruby on Rails for new applications. 39 | - Use Heroku with git deploys and pipelines for deploying applications. 40 | - Use test-driven development to ensure quality. 41 | - Use GitHub pull-requests to conduct peer code review. 42 | - Use continuous integration to ensure tests continue to pass. 43 | - Use a staging server to ensure new features work as expected before deploying 44 | to production. 45 | 46 | ### Storage 47 | 48 | - Use Postgres to store most data. 49 | 50 | ### Messaging 51 | 52 | - Use Kafka when producing and consuming messages between services. 53 | - Use `ruby-kafka` by default when connecting to Kafka from Ruby applications. 54 | 55 | ### Data 56 | 57 | - Use services in the same Rails application for building data pipelines on top 58 | of Kafka. 59 | 60 | ## Specialized Stacks 61 | 62 | Some applications require functionality that is difficult or impossible to 63 | provide using the Core Stack, or would require an unacceptable compromise on 64 | quality or user experience. In these cases, we utilize alternate, specialized 65 | stacks. Because the gaps between stacks are larger than the gaps between layers 66 | in the Core Stack, most developers won't learn these technologies, and the 67 | specialists who learn these stacks are unlikely to be able to learn many layers 68 | in the Core Stack. 69 | 70 | ### Android (Native) 71 | 72 | - Use Kotlin for writing native app code. 73 | - Use Gradle with Android Studio for building. 74 | - Use MVVM to model views. 75 | - Use Apollo when consuming a GraphQL API. 76 | 77 | ### iOS (Native) 78 | 79 | - Use Swift for writing native app code. 80 | - Use Xcode and `xcodebuild` for building iOS apps. 81 | - Use `xcpretty` to format `xcodebuild` output. 82 | - Use Xcode automatic provisioning when possible. 83 | - Use UIKit with Storyboards and MVVM for creating UIs. 84 | - Use Dispatch and OperationQueue for concurrency. 85 | - Support VoiceOver and VoiceControl for accessibility. 86 | - Use Swift Package Manager for dependencies when possible. 87 | - Manually test on both iPhone and iPad to ensure the app is functional. 88 | - Work closely with designers on UI components. 89 | - Prefer standard UIKit components to custom views. 90 | 91 | We recommend learning at least one of the following: 92 | 93 | - MapKit or Google Maps 94 | - Core Location 95 | - Core Bluetooth 96 | - PhotoKit 97 | - UserNotifications 98 | -------------------------------------------------------------------------------- /css/README.md: -------------------------------------------------------------------------------- 1 | # CSS Best Practices 2 | 3 | - Document the project's CSS architecture (the README, component library or 4 | style guide are good places to do this), including things such as: 5 | - Organization of stylesheet directories and Sass partials 6 | - Selector naming convention 7 | - Code linting tools and configuration 8 | - Browser support 9 | - Use Sass. 10 | - Use [Autoprefixer] to generate vendor prefixes based on the project-specific 11 | browser support that is needed. 12 | - Prefer `overflow: auto` to `overflow: scroll`, because `scroll` will always 13 | display scrollbars outside of macOS, even when content fits in the container. 14 | - [Create breakpoints] when the content "breaks," and is awkward or difficult to 15 | read, 16 | - Avoid creating breakpoints that target specific devices 17 | - Prefer `em` units instead of `px` for breakpoint values 18 | - Start with the smallest viewport size and work upwards using 19 | `min-width`/`min-height` 20 | - Use [double colon syntax] for pseudo-elements 21 | 22 | [autoprefixer]: https://github.com/postcss/autoprefixer 23 | [create breakpoints]: http://bradfrost.com/blog/post/7-habits-of-highly-effective-media-queries/ 24 | [double colon syntax]: https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements#Syntax 25 | 26 | ## Linting 27 | 28 | - Use stylelint to lint CSS & Sass 29 | - We maintain a [sharable stylelint configuration] which enforces our guides 30 | found in this repo 31 | 32 | [sharable stylelint configuration]: https://github.com/thoughtbot/stylelint-config 33 | 34 | ## Selectors 35 | 36 | ### Selector specificity 37 | 38 | - Don't use ID selectors. 39 | - Avoid over-qualified selectors, e.g. `h1.page-title`. 40 | 41 | <details> 42 | 43 | #### Code examples 44 | 45 | `h1.page-title` carries a specificity of 2, but can be reduced to 1 by removing 46 | the `h1` type selector: 47 | 48 | ```diff 49 | -h1.page-title 50 | +.page-title { 51 | // … 52 | } 53 | ``` 54 | 55 | #### Motivation 56 | 57 | Using an ID in a selector increases its specificity, making it more difficult to 58 | work with alongside class selectors. Furthermore, because IDs must be unique 59 | within an HTML document, using them as CSS selectors limits reusability. 60 | 61 | #### Resources 62 | 63 | - Learn about [how specificity is calculated]. 64 | 65 | [how specificity is calculated]: https://www.w3.org/TR/selectors-3/#specificity 66 | 67 | </details> 68 | 69 | ### Selector naming 70 | 71 | - Use lowercase characters and hyphens (sometimes referred to as hyphen-case, 72 | dash-case, or kebab-case) when naming selectors. 73 | - Be consistent about naming conventions. For instance, if a project is using 74 | BEM, continue using it, and if it's not, don't introduce it. 75 | - Don't uses Sass parent selectors (`&`) to concatenate selector names. 76 | 77 | <details> 78 | 79 | #### Code examples 80 | 81 | Use lowercase characters and hyphens in selector names: 82 | 83 | ```scss 84 | .class-name { 85 | // … 86 | } 87 | ``` 88 | 89 | Don't concatenate selector names: 90 | 91 | ```scss 92 | .class { 93 | &__child-class { 94 | // … 95 | } 96 | } 97 | ``` 98 | 99 | #### Motivation 100 | 101 | Concatenating selector names makes it more difficult to search and find 102 | selectors in the codebase. 103 | 104 | </details> 105 | 106 | ## Other style guides 107 | 108 | - [Google HTML/CSS Style Guide](https://google.github.io/styleguide/htmlcssguide.html) 109 | - [Principles of writing consistent, idiomatic CSS](https://github.com/necolas/idiomatic-css) 110 | - [My HTML/CSS coding style](https://csswizardry.com/2012/04/my-html-css-coding-style/) 111 | - [Improving Code Readability With CSS Styleguides](https://www.smashingmagazine.com/2008/05/improving-code-readability-with-css-styleguides/) 112 | - [Code Style Guide: CSS](https://github.com/ThinkUpLLC/ThinkUp/wiki/Code-Style-Guide:-CSS) 113 | - [CSS Coding Standards](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/css/) 114 | - [Scalable and Modular Architecture for CSS](http://smacss.com/) 115 | -------------------------------------------------------------------------------- /git/README.md: -------------------------------------------------------------------------------- 1 | # Git 2 | 3 | A guide for programming within version control. 4 | 5 | ## Best Practices 6 | 7 | - Avoid merge commits by using a [rebase workflow]. 8 | - Squash multiple trivial commits into a single commit. 9 | - Write a [good commit message]. 10 | 11 | [rebase workflow]: https://github.com/thoughtbot/guides/blob/main/git/README.md#merge 12 | [good commit message]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 13 | 14 | ## Maintain a Repo 15 | 16 | - Avoid including files in source control that are specific to your development 17 | machine or process. 18 | - Delete local and remote feature branches after merging. 19 | - Perform work in a feature branch. 20 | - Rebase frequently to incorporate upstream changes. 21 | - Use a [pull request] for code reviews. 22 | 23 | [pull request]: https://help.github.com/articles/using-pull-requests/ 24 | 25 | ## Write a Feature 26 | 27 | Create a local feature branch based off `main`. 28 | 29 | ```console 30 | git checkout main 31 | git pull 32 | git checkout -b <branch-name> 33 | ``` 34 | 35 | Rebase frequently to incorporate upstream changes. 36 | 37 | ```console 38 | git fetch origin 39 | git rebase origin/main 40 | ``` 41 | 42 | Resolve conflicts. When feature is complete and tests pass, stage the changes. 43 | 44 | ```console 45 | git add --all 46 | ``` 47 | 48 | When you've staged the changes, commit them. 49 | 50 | ```console 51 | git status 52 | git commit --verbose 53 | ``` 54 | 55 | Write a [good commit message]. Example format: 56 | 57 | ```text 58 | Present-tense summary under 50 characters 59 | 60 | - More information about commit (under 72 characters). 61 | - More information about commit (under 72 characters). 62 | 63 | http://project.management-system.com/ticket/123 64 | ``` 65 | 66 | If you've created more than one commit, [use `git rebase` interactively] to squash them into cohesive commits with good 67 | messages: 68 | 69 | ```console 70 | git rebase -i origin/main 71 | ``` 72 | 73 | Share your branch. 74 | 75 | ```console 76 | git push origin <branch-name> 77 | ``` 78 | 79 | Submit a [GitHub pull request]. 80 | 81 | Ask for a code review in the project's chat room. 82 | 83 | [use `git rebase` interactively]: https://help.github.com/articles/about-git-rebase/ 84 | [github pull request]: https://help.github.com/articles/using-pull-requests/ 85 | 86 | ## Review Code 87 | 88 | A team member other than the author reviews the pull request. They follow [Code 89 | Review](/code-review/) guidelines to avoid miscommunication. 90 | 91 | They make comments and ask questions directly on lines of code in the GitHub web 92 | interface or in the project's chat room. 93 | 94 | For changes which they can make themselves, they check out the branch. 95 | 96 | ```console 97 | git checkout <branch-name> 98 | ./bin/setup 99 | git diff staging/main..HEAD 100 | ``` 101 | 102 | They make small changes right in the branch, test the feature on their machine, 103 | run tests, commit, and push. 104 | 105 | When satisfied, they comment on the pull request `Ready to merge.` 106 | 107 | ## Merge 108 | 109 | Rebase interactively. Squash commits like "Fix whitespace" into one or a small 110 | number of valuable commit(s). Edit commit messages to reveal intent. Run tests. 111 | 112 | ```console 113 | git fetch origin 114 | git rebase -i origin/main 115 | ``` 116 | 117 | Force push your branch. This allows GitHub to automatically close your pull 118 | request and mark it as merged when your commit(s) are pushed to `main`. It also 119 | makes it possible to [find the pull request] that brought in your changes. 120 | 121 | ```console 122 | git push --force-with-lease origin <branch-name> 123 | ``` 124 | 125 | View a list of new commits. View changed files. Merge branch into `main`. 126 | 127 | ```console 128 | git log origin/main..<branch-name> 129 | git diff --stat origin/main 130 | git checkout main 131 | git merge <branch-name> --ff-only 132 | git push 133 | ``` 134 | 135 | Delete your remote feature branch. 136 | 137 | ```console 138 | git push origin --delete <branch-name> 139 | ``` 140 | 141 | Delete your local feature branch. 142 | 143 | ```console 144 | git branch --delete <branch-name> 145 | ``` 146 | 147 | [find the pull request]: http://stackoverflow.com/a/17819027 148 | -------------------------------------------------------------------------------- /sass/README.md: -------------------------------------------------------------------------------- 1 | # Sass 2 | 3 | - [Sample](sample.scss) 4 | - [Shared stylelint configuration] 5 | 6 | - This configuration aligns with our team-wide guides below. It does _not_, 7 | however, enforce a particular class naming structure, which is a team 8 | decision to be made on a per-project basis. 9 | 10 | - When using [sass-rails], use the provided [asset-helpers] (e.g. `image-url` 11 | and `font-url`), so that Rails' Asset Pipeline will re-write the correct paths 12 | to assets. 13 | - Prefer mixins to `@extend`. 14 | - Use maps and variables to codify and centralize breakpoint values 15 | - Prefer abstract names such as `small`, `medium`, `large`, etc. instead of 16 | specific devices 17 | - Nest breakpoints inside of the relevant selector 18 | - If a component needs a specific breakpoint to work, keep it with the 19 | relevant component partial. If other components need the same value, 20 | integrate it into the centralized breakpoint list 21 | 22 | [shared stylelint configuration]: https://github.com/thoughtbot/stylelint-config 23 | [sass-rails]: https://github.com/rails/sass-rails 24 | [asset-helpers]: https://github.com/rails/sass-rails#asset-helpers 25 | 26 | ## Formatting 27 | 28 | - Use the SCSS syntax. 29 | - Use hyphens when naming mixins, extends, functions & variables: `span-columns` 30 | not `span_columns` or `spanColumns`. 31 | - Avoid using shorthand properties for only one value: `background-color: 32 | #ff0000;`, not `background: #ff0000;` 33 | - Use `//` for comment blocks not `/* */`. 34 | - Avoid in-line operations in shorthand declarations (Ex. `padding: $variable * 35 | 1.5 variable * 2`) 36 | - Use parentheses around individual operations in shorthand declarations: 37 | `padding: ($variable * 1.5) ($variable * 2);` 38 | - Use a `%` unit for the amount/weight when using Sass's color functions: 39 | `darken($color, 20%)`, not `darken($color, 20)` 40 | - Use a trailing comma after each item in a map, including the last item. 41 | 42 | ## Selectors 43 | 44 | - Use meaningful names: `$visual-grid-color` not `$color` or `$vslgrd-clr`. 45 | - Use ID and class names that are as short as possible but as long as necessary. 46 | - Avoid nesting more than 3 selectors deep. 47 | - Avoid using comma delimited selectors. 48 | - Avoid nesting within a media query. 49 | 50 | ## Organization 51 | 52 | - Use a `base` directory for styling element selectors, global variables, global 53 | extends and global mixins. 54 | - Use HTML structure for ordering of selectors. Don't just put styles at the 55 | bottom of the Sass file. 56 | - Avoid having files longer than 100 lines. 57 | 58 | ## General syntax and formatting 59 | 60 | ### Declarations block ordering 61 | 62 | - Order declarations alphabetically. 63 | - Order items within the declaration block in the following order: 64 | 1. Sass at-rules, e.g. `@include` 65 | 1. CSS properties 66 | 1. Media queries 67 | 1. Pseudo-classes 68 | 1. Pseudo-elements 69 | 1. Nested elements 70 | 71 | <details> 72 | 73 | #### Code examples 74 | 75 | Alphabetize declarations: 76 | 77 | ```scss 78 | .class { 79 | display: block; 80 | text-align: center; 81 | width: 100%; 82 | } 83 | ``` 84 | 85 | Alphabetize prefixed properties as if the prefix doesn't exist: 86 | 87 | ```scss 88 | .class { 89 | font-family: system-ui; 90 | -webkit-font-smoothing: antialiased; 91 | font-weight: $weight-variable; 92 | } 93 | ``` 94 | 95 | Comprehensive example of ordering items within a declaration block: 96 | 97 | ```scss 98 | .class { 99 | @include size(10px); 100 | 101 | display: block; 102 | margin: $spacing-variable; 103 | 104 | @media (min-width: $screen-variable) { 105 | padding: $spacing-variable; 106 | } 107 | 108 | &:focus { 109 | border-color: $color-variable; 110 | } 111 | 112 | &::before { 113 | content: ""; 114 | } 115 | 116 | .nested-element { 117 | margin: $spacing-variable; 118 | } 119 | } 120 | ``` 121 | 122 | #### Motivation 123 | 124 | Alphabetizing can be automated and is commonly a feature built into code editors 125 | (see Resources below). 126 | 127 | #### Linting 128 | 129 | Alphabetical declaration ordering can be linted using stylelint with the 130 | [stylelint-order] plugin and its `order/properties-alphabetical-order` rule. 131 | 132 | [stylelint-order]: https://github.com/hudochenkov/stylelint-order 133 | 134 | #### Resources 135 | 136 | - Atom users can use the [Sort Lines package], which provides commands and 137 | keybindings for alphabetical sorting. 138 | - Sublime Text users can use the `Edit > Sort Lines` menu item, or press 139 | <kbd>F5</kbd> to sort lines alphabetically. 140 | 141 | [sort lines package]: https://github.com/atom/sort-lines 142 | 143 | </details> 144 | -------------------------------------------------------------------------------- /testing-rspec/README.md: -------------------------------------------------------------------------------- 1 | # Testing with RSpec 2 | 3 | - Avoid the `private` keyword in specs. 4 | - Avoid checking boolean equality directly. Instead, write predicate methods and 5 | use appropriate matchers. [Example](predicate_tests_spec.rb). 6 | - Prefer `eq` to `==` in RSpec. 7 | - Separate setup, exercise, verification, and teardown phases with newlines. 8 | - Use RSpec's [`expect` syntax]. 9 | - Use RSpec's [`allow` syntax] for method stubs. 10 | - Use `not_to` instead of `to_not` in RSpec expectations. 11 | - Prefer the `have_css` matcher to the `have_selector` matcher in Capybara 12 | assertions. 13 | - Avoid `any_instance` in `rspec-mocks` and `mocha`; Prefer [dependency 14 | injection]. 15 | - Avoid `its`, `specify`, and `before` in RSpec. 16 | - Avoid `let` (or `let!`) in RSpec. Prefer extracting helper methods, but do not 17 | re-implement the functionality of `let`. 18 | [Example](/testing-rspec/avoid_let_spec.rb). 19 | - Avoid using `subject` explicitly _inside of an_ RSpec `it` block. 20 | [Example](/testing-rspec/unit_test_spec.rb). 21 | - Avoid using instance variables in tests. 22 | - Disable real HTTP requests to external services with 23 | `WebMock.disable_net_connect!`. 24 | - Don't test private methods. 25 | - Test background jobs with a [`Delayed::Job` matcher]. 26 | - Use [stubs and spies] \(not mocks\) in isolated tests. 27 | - Use a single level of abstraction within `it` examples. 28 | - Use an `it` example or test method for each execution path through the method. 29 | - Use [assertions about state] for incoming messages. 30 | - Use stubs and spies to assert you sent outgoing messages. 31 | - Use a [Fake] to stub requests to external services. 32 | - Use integration tests to execute the entire app. 33 | - Use non-[SUT] methods in expectations when possible. 34 | 35 | [`expect` syntax]: http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 36 | [`allow` syntax]: https://github.com/rspec/rspec-mocks#method-stubs 37 | [dependency injection]: http://en.wikipedia.org/wiki/Dependency_injection 38 | [`delayed::job` matcher]: https://gist.github.com/3186463 39 | [stubs and spies]: http://thoughtbot.com/blog/spy-vs-spy 40 | [assertions about state]: https://speakerdeck.com/skmetz/magic-tricks-of-testing-railsconf?slide=51 41 | [fake]: http://thoughtbot.com/blog/fake-it 42 | [sut]: http://xunitpatterns.com/SUT.html 43 | 44 | ## Acceptance Tests 45 | 46 | [Sample](acceptance_test_spec.rb) 47 | 48 | - Use the most specific [selectors][] available. 49 | - Don't locate elements with CSS selectors or `[id]` attributes. 50 | - Use [accessible names and descriptions][names_and_descriptions] to locate 51 | elements, to interact with form controls, buttons, and links, or to scope 52 | blocks of actions and assertions. 53 | - Avoid `it` block descriptions that add no information, such as "successfully." 54 | - Avoid `it` block descriptions that repeat the top-level `describe` block 55 | description. 56 | - Place helper methods for system specs directly in a top-level `System` module. 57 | - Use names like `ROLE_ACTION_spec.rb`, such as `user_changes_password_spec.rb`, 58 | for system spec file names. 59 | - Use only one `describe` block per system spec file. 60 | - Use `it` block descriptions that describe the success and failure paths. 61 | - Use spec/system directory to store system specs. 62 | - Use spec/support/system for support code related to system specs. 63 | - Don't assert an element's state with `[class]` or `[data-*]` attributes. 64 | - Use [WAI-ARIA States and Properties][] (i.e. `[role]` or `[aria-*]` 65 | attributes) when asserting an element's state. 66 | - Prefer assertions about implicit semantics and built-in attributes (e.g. an 67 | `<input type="checkbox">` element and `[checked]`, an `<option>` element and 68 | `[selected]`) over WAI-ARIA States and Properties (e.g. a `<button>` element and 69 | `[aria-checked="true"]`, a `<div>` element and `[aria-selected="true"]`). 70 | 71 | > system specs were previously called feature specs and lived in `spec/features` 72 | 73 | [selectors]: https://rubydoc.info/github/teamcapybara/capybara/master/Capybara/Selector 74 | [names_and_descriptions]: https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/ 75 | [WAI-ARIA States and Properties]: https://www.w3.org/TR/wai-aria/#states_and_properties 76 | 77 | ## Factories 78 | 79 | - Order `factories.rb` contents: sequences, traits, factory definitions. 80 | - Order factory attributes: implicit attributes, explicit attributes, child 81 | factory definitions. Each section's attributes are alphabetical. 82 | - Order factory definitions alphabetically by factory name. 83 | 84 | ## Unit Tests 85 | 86 | [Sample](unit_test_spec.rb) 87 | 88 | - Don't prefix `it` block descriptions with `should`. Use [Imperative mood] 89 | instead. 90 | - Use `subject` blocks to define objects for use in one-line specs. 91 | [Example](unit_test_spec.rb#6). 92 | - Put one-liner specs at the beginning of the outer `describe` blocks. 93 | - Use `.method` to describe class methods and `#method` to describe instance 94 | methods. 95 | - Use `context` to describe testing preconditions. 96 | - Use `describe '#method_name'` to group tests by method-under-test 97 | - Use a single, top-level `describe ClassName` block. 98 | - Order validation, association, and method tests in the same order that they 99 | appear in the class. 100 | 101 | [imperative mood]: http://en.wikipedia.org/wiki/Imperative_mood 102 | -------------------------------------------------------------------------------- /.markdownlint-cli2.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | // https://github.com/DavidAnson/markdownlint/blob/v0.32.1/README.md#rules--aliases 3 | // https://github.com/DavidAnson/markdownlint-cli2/blob/main/test/markdownlint-cli2-jsonc-example/.markdownlint-cli2.jsonc 4 | // https://github.com/DavidAnson/markdownlint-cli2/blob/main/schema/markdownlint-config-schema.json 5 | "$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint-cli2/refs/heads/main/schema/markdownlint-cli2-config-schema.json", 6 | "gitignore": true, 7 | "config": { 8 | /* MD001 - Heading levels should only increment by one level at a time */ 9 | "heading-increment": true, 10 | 11 | /* MD003 - Heading style */ 12 | "heading-style": true, 13 | 14 | /* MD004 - Unordered list style */ 15 | "ul-style": true, 16 | 17 | /* MD005 - Inconsistent indentation for list items at the same level */ 18 | "list-indent": true, 19 | 20 | /* MD007 - Unordered list indentation */ 21 | "ul-indent": true, 22 | // { "indent": 2, "start_indented": false }, 23 | 24 | /* MD009 - Trailing spaces */ 25 | "no-trailing-spaces": true, 26 | 27 | /* MD010 - Hard tabs */ 28 | "no-hard-tabs": true, 29 | 30 | /* MD011 - Reversed link syntax */ 31 | "no-reversed-links": true, 32 | 33 | /* MD012 - Multiple consecutive blank lines */ 34 | "no-multiple-blanks": true, 35 | 36 | /* MD013 - Line length */ 37 | // NOTE: Disabled to allow lines of any length; could be enabled in a separate PR after discussion 38 | "line-length": false, 39 | 40 | /* MD014 - Dollar signs used before commands without showing output */ 41 | "commands-show-output": true, 42 | 43 | /* MD018 - No space after hash on atx style heading */ 44 | "no-missing-space-atx": true, 45 | 46 | 47 | /* MD019 - Multiple spaces after hash on atx style heading */ 48 | "no-multiple-space-atx": true, 49 | 50 | /* MD020 - No space inside hashes on closed atx style heading */ 51 | "no-missing-space-closed-atx": true, 52 | 53 | /* MD021 - Multiple spaces inside hashes on closed atx style heading */ 54 | "no-multiple-space-closed-atx": true, 55 | 56 | /* MD022 - Headings should be surrounded by blank lines */ 57 | "blanks-around-headings": true, 58 | 59 | /* MD023 - Headings must start at the beginning of the line */ 60 | "heading-start-left": true, 61 | 62 | /* MD024 - Multiple headings with the same content */ 63 | "no-duplicate-heading": { 64 | "siblings_only": true 65 | }, 66 | 67 | /* MD025 - Multiple top-level headings in the same document */ 68 | "single-title": true, 69 | 70 | /* MD026 - Trailing punctuation in heading */ 71 | "no-trailing-punctuation": true, 72 | 73 | /* MD027 - Multiple spaces after blockquote symbol */ 74 | "no-multiple-space-blockquote": true, 75 | 76 | /* MD028 - Blank line inside blockquote */ 77 | "no-blanks-blockquote": true, 78 | 79 | /* MD029 - Ordered list item prefix */ 80 | "ol-prefix": true, 81 | 82 | /* MD030 - Spaces after list markers */ 83 | "list-marker-space": true, 84 | 85 | /* MD031 - Fenced code blocks should be surrounded by blank lines */ 86 | "blanks-around-fences": true, 87 | 88 | /* MD032 - Lists should be surrounded by blank lines */ 89 | "blanks-around-lists": true, 90 | 91 | /* MD033 - Inline HTML */ 92 | "no-inline-html": { 93 | "allowed_elements": ["dl", "dt", "dd", "kbd", "details"] 94 | }, 95 | 96 | /* MD034 - Bare URL used */ 97 | "no-bare-urls": true, 98 | 99 | /* MD035 - Horizontal rule style */ 100 | "hr-style": true, 101 | 102 | /* MD036 - Emphasis used instead of a heading */ 103 | "no-emphasis-as-heading": true, 104 | 105 | /* MD037 - Spaces inside emphasis markers */ 106 | "no-space-in-emphasis": true, 107 | 108 | /* MD038 - Spaces inside code span elements */ 109 | "no-space-in-code": true, 110 | 111 | /* MD039 - Spaces inside link text */ 112 | "no-space-in-links": true, 113 | 114 | /* MD040 - Fenced code blocks should have a language specified */ 115 | "fenced-code-language": true, 116 | 117 | /* MD041 - First line in a file should be a top-level heading */ 118 | "first-line-heading": true, 119 | 120 | /* MD042 - No empty links */ 121 | "no-empty-links": true, 122 | 123 | /* MD043 - Required heading structure */ 124 | "required-headings": true, 125 | 126 | /* MD044 - Proper names should have the correct capitalization */ 127 | "proper-names": true, 128 | 129 | /* MD045 - Images should have alternate text (alt text) */ 130 | "no-alt-text": true, 131 | 132 | /* MD046 - Code block style */ 133 | "code-block-style": true, 134 | 135 | /* MD047 - Files should end with a single newline character */ 136 | "single-trailing-newline": true, 137 | 138 | /* MD048 - Code fence style */ 139 | "code-fence-style": true, 140 | 141 | /* MD049 - Emphasis style */ 142 | "emphasis-style": true, 143 | 144 | /* MD050 - Strong style */ 145 | "strong-style": true, 146 | 147 | /* MD051 - Link fragments should be valid */ 148 | "link-fragments": true, 149 | 150 | /* MD052 - Reference links and images should use a label that is defined */ 151 | "reference-links-images": { 152 | "shortcut_syntax": false 153 | }, 154 | 155 | /* MD053 - Link and image reference definitions should be needed */ 156 | "link-image-reference-definitions": true, 157 | 158 | /* MD054 - Link and image style */ 159 | "link-image-style": true 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /ruby/README.md: -------------------------------------------------------------------------------- 1 | # Ruby 2 | 3 | [Sample 1](sample_1.rb) [Sample 2](sample_2.rb) 4 | 5 | > [!TIP] 6 | > Click on the linked pull request, commit, or the guideline itself to read more 7 | > detailed explanations with examples and reasoning behind these recommendations. 8 | 9 | - [Use an opinionated set of rules for Rubocop](Use-an-opinionated-set-of-rules-for-Rubocop.md) 10 | - [Limit use of conditional modifiers to short, simple cases](Limit-use-of-conditional-modifiers-to-short-simple-cases.md) 11 | - Avoid multiple assignments per line (`one, two = 1, 2`). [#109] 12 | - Avoid ternary operators (`boolean ? true : false`). Use multi-line `if` 13 | instead to emphasize code branches. [36491dbb9] 14 | - Prefer nested class and module definitions over the shorthand version 15 | [Example](/ruby/sample_1.rb#L103) [#332] 16 | - Prefer `detect` over `find`. [0d819844] 17 | - Prefer `select` over `find_all`. [0d819844] 18 | - Prefer `map` over `collect`. [0d819844] 19 | - Prefer `reduce` over `inject`. [#237] 20 | - Prefer `&:method_name` to `{ |item| item.method_name }` for simple method 21 | calls. [#183] 22 | - Use `%()` for single-line strings containing double-quotes that require 23 | interpolation. [36491dbb9] 24 | - Use heredocs for multi-line strings. [36491dbb9] 25 | - Avoid monkey-patching. 26 | - Generate necessary [Bundler binstubs] for the project, such as `rake` and 27 | `rspec`, and add them to version control. 28 | - Prefer classes to modules when designing functionality that is shared by 29 | multiple models. 30 | - Avoid organizational comments (`# Validations`). [#63] 31 | - Use empty lines around multi-line blocks. 32 | 33 | --- 34 | 35 | - Avoid bang (!) method names. Prefer descriptive names. [#122] 36 | - Use `?` suffix for predicate methods. [0d819844] 37 | - Use `def self.method`, not `class << self`. [40090e22] 38 | - Use `def` with parentheses when there are arguments. [36491dbb9] 39 | - Avoid optional parameters. Does the method do too much? 40 | - Order class methods above instance methods. [#320] 41 | - Prefer `private` when indicating scope. Use `protected` only with comparison 42 | methods like `def ==(other)`, `def <(other)`, and `def >(other)`. 43 | 44 | --- 45 | 46 | - Prefix unused variables or parameters with underscore (`_`). [#335] 47 | - Name variables created by a factory after the factory (`user_factory` creates 48 | `user`). 49 | - Suffix variables holding a factory with `_factory` (`user_factory`). 50 | - Use a leading underscore when defining instance variables for memoization. 51 | [#373] 52 | - Prefer method invocation over instance variables. [#331] 53 | 54 | [#63]: https://github.com/thoughtbot/guides/pull/63 55 | [#109]: https://github.com/thoughtbot/guides/pull/109 56 | [#122]: https://github.com/thoughtbot/guides/pull/122 57 | [#183]: https://github.com/thoughtbot/guides/pull/183 58 | [#237]: https://github.com/thoughtbot/guides/pull/237 59 | [#320]: https://github.com/thoughtbot/guides/pull/320 60 | [#331]: https://github.com/thoughtbot/guides/pull/331 61 | [#332]: https://github.com/thoughtbot/guides/pull/332 62 | [#335]: https://github.com/thoughtbot/guides/pull/335 63 | [#373]: https://github.com/thoughtbot/guides/pull/373 64 | [0d819844]: https://github.com/thoughtbot/guides/commit/0d819844 65 | [36491dbb9]: https://github.com/thoughtbot/guides/commit/36491dbb9 66 | [40090e22]: https://github.com/thoughtbot/guides/commit/40090e22 67 | [bundler binstubs]: https://github.com/sstephenson/rbenv/wiki/Understanding-binstubs 68 | 69 | ## Bundler 70 | 71 | - Specify the [Ruby version] to be used on the project in the `Gemfile`. 72 | - Use a [pessimistic version] in the `Gemfile` for gems that follow semantic 73 | versioning, such as `rspec`, `factory_bot`, and `capybara`. 74 | - Use a [versionless] `Gemfile` declarations for gems that are safe to update 75 | often, such as pg, thin, and debugger. 76 | - Use an [exact version] in the `Gemfile` for fragile gems, such as Rails. 77 | 78 | [ruby version]: http://bundler.io/v1.3/gemfile_ruby.html 79 | [exact version]: http://thoughtbot.com/blog/a-healthy-bundle 80 | [pessimistic version]: http://thoughtbot.com/blog/a-healthy-bundle 81 | [versionless]: http://thoughtbot.com/blog/a-healthy-bundle 82 | 83 | ## Ruby Gems 84 | 85 | - Declare dependencies in the `<PROJECT_NAME>.gemspec` file. 86 | - Reference the `gemspec` in the `Gemfile`. 87 | - Use [Appraisal] to test the gem against multiple versions of gem dependencies 88 | (such as Rails in a Rails engine). 89 | - Use [Bundler] to manage the gem's dependencies. 90 | - Use continuous integration (CI) to show build status within the code review 91 | process and to test against multiple Ruby versions. 92 | 93 | [appraisal]: https://github.com/thoughtbot/appraisal 94 | [bundler]: http://bundler.io 95 | 96 | ## Ruby JSON APIs 97 | 98 | - Review the recommended practices outlined in Heroku's [HTTP API Design Guide] 99 | before designing a new API. 100 | - Write integration tests for your API endpoints. When the primary consumer of 101 | the API is a JavaScript client maintained within the same code base as the 102 | provider of the API, write [system specs]. Otherwise write [request specs]. 103 | 104 | [http api design guide]: https://github.com/interagent/http-api-design 105 | [system specs]: https://web.archive.org/web/20230131005307/https://relishapp.com/rspec/rspec-rails/docs/system-specs/system-spec 106 | [request specs]: https://web.archive.org/web/20221207001104/https://www.relishapp.com/rspec/rspec-rails/docs/request-specs/request-spec 107 | 108 | ## How To Guides 109 | 110 | - [Release a Ruby gem](./how-to/release_a_ruby_gem.md) 111 | -------------------------------------------------------------------------------- /javascript-typescript/README.md: -------------------------------------------------------------------------------- 1 | # JavaScript & TypeScript 2 | 3 | ## JavaScript 4 | 5 | [Sample](sample.js) 6 | 7 | - Use [TypeScript](#typescript) 8 | - Use the latest stable JavaScript syntax with a transpiler, such as [babel]. 9 | - Use [ESLint] and [Prettier] for auto-formatting and auto-fixing 10 | - Use [Jest] for unit testing 11 | - Prefer ES6 classes over prototypes. 12 | - Use strict equality checks (`===` and `!==`) except when comparing against 13 | (`null` or `undefined`). 14 | - Prefer [arrow functions] `=>`, over the `function` keyword except when 15 | defining classes or methods. 16 | - Prefer ES6 [destructuring] over object literal notation. 17 | - Use ES6 [spread] and [rest] operator wherever possible for a cleaner code. 18 | - Use `PascalCase` for classes, `lowerCamelCase` for variables and functions, 19 | `SCREAMING_SNAKE_CASE` for constants, `_singleLeadingUnderscore` for private 20 | variables and functions. 21 | - Prefer [template strings] over string concatenation. 22 | - Prefer promises over callbacks. 23 | - Prefer array functions like `forEach`, `map`, `filter` and `reduce` over `for/while` loops. 24 | - Use `const` for declaring variables that will never be re-assigned, and `let` 25 | otherwise. 26 | - Avoid `var` to declare variables. 27 | - Prefer [async/await] over traditional promise syntax 28 | - Use the [Nullish coalescing operator] `??` 29 | 30 | ## TypeScript 31 | 32 | - Use TypeScript in [strict mode] 33 | - Prefer [Functions] over [Classes] 34 | - Use `PascalCase` for [Interfaces] and [Type Aliases] 35 | - Use [readonly] properties where applicable 36 | - Use [const Assertions] where applicable to avoid type widening 37 | - Avoid [Mixins] 38 | - Avoid [Decorators] 39 | - Avoid [Overloading Functions] 40 | - Prefer [Optional Properties] in an interface rather than declaring the 41 | property type as `T | undefined` 42 | - Prefer explicitly defining interfaces over [Extending Interfaces] 43 | - Avoid the use of the [any] type 44 | - Avoid the [Non-null assertion operator] 45 | - Avoid [Type Assertions] 46 | - Prefer the `as`-syntax for [Type Assertions] over the angle-bracket syntax 47 | - Prefer [Type Guards] over [Type Assertions] 48 | - Prefer [Union Types], [Lookup Types], [Mapped Types] and [const Assertions] 49 | over [Enums] 50 | - Prefer [arrow functions] `=>`, over the `function` keyword except when using 51 | [Generics] 52 | 53 | ## Formatting 54 | 55 | - Use [Prettier defaults](https://prettier.io/docs/en/options.html) with the following additional configuration (.prettierrc): 56 | 57 | ```json 58 | { 59 | "singleQuote": true 60 | } 61 | ``` 62 | 63 | This configuration includes: 64 | - Use semicolons at the end of each statement ([sample](/javascript/sample.js#L5)) 65 | - Prefer single quotes ([sample](/javascript/sample.js#L11)) 66 | - Use a trailing comma after each item in a multi-line array or object literal, including the last item. ([sample](/javascript/sample.js#L11)) 67 | 68 | If ESLint is used along with Prettier, the ESLInt plugin [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) should also be used to turn off all ESLint style rules that are already handled by Prettier. 69 | 70 | [babel]: https://babeljs.io/ 71 | [eslint]: https://eslint.org/ 72 | [prettier]: https://prettier.io/ 73 | [jest]: /testing-jest/ 74 | [template strings]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/template_strings 75 | [arrow functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions 76 | [destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment 77 | [spread]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax 78 | [rest]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters 79 | [functions]: https://www.typescriptlang.org/docs/handbook/2/functions.html 80 | [classes]: https://www.typescriptlang.org/docs/handbook/2/classes.html 81 | [readonly]: https://www.typescriptlang.org/docs/handbook/2/objects.html#readonly-properties 82 | [const Assertions]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions 83 | [overloading functions]: https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads 84 | [async/await]: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await 85 | [optional properties]: https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties 86 | [extending interfaces]: https://www.typescriptlang.org/docs/handbook/2/objects.html#extending-types 87 | [any]: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#any 88 | [non-null assertion operator]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator 89 | [type assertions]: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions 90 | [Nullish coalescing operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing 91 | [Type Guards]: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#typeof-type-guards 92 | [generics]: https://www.typescriptlang.org/docs/handbook/2/generics.html 93 | [strict mode]: https://www.typescriptlang.org/tsconfig/#strict 94 | [mixins]: https://www.typescriptlang.org/docs/handbook/mixins.html 95 | [decorators]: https://www.typescriptlang.org/docs/handbook/decorators.html 96 | [union types]: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types 97 | [lookup types]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types 98 | [mapped types]: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html 99 | [enums]: https://www.typescriptlang.org/docs/handbook/enums.html 100 | [interfaces]: https://www.typescriptlang.org/docs/handbook/2/objects.html 101 | [type aliases]: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-aliases 102 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Data 2 | 3 | A guide for managing a series of tubes. 4 | 5 | ## Best Practices 6 | 7 | - Avoid automatic retries for non-idempotent operations. 8 | - Use at least once delivery with idempotent operations. 9 | - Use at most once delivery with non-idempotent operations. 10 | - Use stream processing for data which is not guaranteed to fit into memory. 11 | 12 | ### Streaming 13 | 14 | - Avoid storing messages with different schemas in the same topic or queue. 15 | - Keep messages as small as possible. 16 | - Use as few distinct resources (CPU, Postgres, S3, HTTP requests) as possible 17 | in each consumer. 18 | - Use constant memory to process streams when feasible. 19 | - When one message would perform more than one operation, instead have that 20 | message append a new message for each operation. 21 | - When reading from the same queue with more than one consumer, ensure that it's 22 | acceptable to process messages from that queue out of order. 23 | - When partitioning messages in a single topic, ensure that it's acceptable for 24 | messages from different partitions to be processed out of order. 25 | 26 | ## Glossary 27 | 28 | <dl> 29 | <dt>Data Engineering</dt> 30 | <dd> 31 | The task of building infrastructure and process for 32 | ingesting, processing, and aggregating data so that it can be displayed to users 33 | or made available to data scientists. 34 | </dd> 35 | 36 | <dt>Data Science</dt> 37 | <dd> 38 | The practice of using statistics, machine learning, and other 39 | tools to analyze data to discover trends and truths that can be used to provide 40 | business intelligence. 41 | </dd> 42 | 43 | <dt>Batch Processing</dt> 44 | <dd> 45 | Processing large amounts of data at once. This is acceptable 46 | for smaller amounts of data and can be simpler in terms of development and 47 | deployment. Some batch processes can also be useful for "recomputing the world" 48 | when you want to analyze existing data in a new way. 49 | </dd> 50 | 51 | <dt>Data Streaming</dt> 52 | <dd> 53 | Processing data in small chunks, one at a time, rather than 54 | processing all data at once. Streaming is necessary for processing infinite 55 | event streams. It's also useful for processing large amounts of data, because it 56 | prevents memory overflows during processing and makes it easier to process data 57 | in a distributed or real-time manner. 58 | </dd> 59 | 60 | <dt>Real-time</dt> 61 | <dd> 62 | Analyzing data and delivering results simultaneously so that stream 63 | output is always visible. For example, real-time analytics will mean that the 64 | system is constantly processing events (clicks, purchases, etc) and displaying 65 | the latest results in a user interface. 66 | </dd> 67 | 68 | <dt>Parallel Processing</dt> 69 | <dd> 70 | Performing multiple tasks at the same time, for example 71 | on different cores or processors. Parallel processing is necessary in order to 72 | perform more than one computation at once; common uses are parsing or 73 | aggregation. 74 | </dd> 75 | 76 | <dt>Concurrent Processing</dt> 77 | <dd> 78 | Managing multiple ongoing tasks at once without 79 | necessarily processing more than one task in the same exact moment. Concurrent 80 | processing is required in order to perform more than one effect at once, such as 81 | waiting for multiple network requests to complete. 82 | </dd> 83 | 84 | <dt>Constant Memory</dt> 85 | <dd> 86 | Processing a stream where the amount of memory required does 87 | not increase with the size of the stream. 88 | </dd> 89 | 90 | <dt>At Least Once Delivery</dt> 91 | <dd> 92 | A guarantee that a given message will be delivered at 93 | least once, but may be delivered more than once. This is achieved in Kafka by 94 | committing an offset after it's been fully processed and in RabbitMQ by 95 | acknowledging a message after fully processing it. 96 | </dd> 97 | 98 | <dt>At Most Once Delivery</dt> 99 | <dd> 100 | A guarantee that a message will never be delivered more 101 | than once, but may not be delivered at all. This is achieved in Kafka by 102 | committing an offset before fully processing a message and in RabbitMQ by 103 | acknowledging a message before fully processing it. 104 | </dd> 105 | 106 | <dt>Distributed Data Processing</dt> 107 | <dd> 108 | Breaking up data into partitions so that large 109 | amounts of data can be processed by many machines simultaneously. 110 | </dd> 111 | 112 | <dt>Cluster</dt> 113 | <dd> 114 | Several computers (or virtual machines) grouped together to perform a 115 | single task. 116 | </dd> 117 | 118 | <dt>Scala</dt> 119 | <dd> 120 | A programming language (like Ruby, Python, or JavaScript) which is fast 121 | and has become popular for data-focused tasks. Scala runs on the Java Virtual 122 | Machine, which is a high-performance engine for running languages like Scala 123 | that compile into bytecode. 124 | </dd> 125 | 126 | <dt>Type Safety</dt> 127 | <dd> 128 | Languages that provide type safety (such as Scala) check the 129 | program for possible errors as part of the compilation process, which allows 130 | developers to prevent many types of bugs before being deployed. 131 | </dd> 132 | 133 | <dt>Spark</dt> 134 | <dd> 135 | A distributed computing engine for big data and data streams. Spark is 136 | a Scala-focused framework for data engineering and data science. 137 | </dd> 138 | 139 | <dt>Kafka</dt> 140 | <dd> 141 | A distributed commit log for data streams. Many of the large data 142 | systems deployed today use Kafka. 143 | </dd> 144 | 145 | <dt>Record Stream</dt> 146 | <dd> 147 | A stream where each message is an independent, unique record 148 | which does not replace a previous record in the stream. 149 | </dd> 150 | 151 | <dt>Changelog Stream</dt> 152 | <dd> 153 | A stream where each message represents the latest state for 154 | a particular entity. 155 | </dd> 156 | 157 | <dt>Topic</dt> 158 | <dd> 159 | In Kafka, a partitioned, append-only log of messages which can be 160 | consumed in order by partition. 161 | </dd> 162 | 163 | <dt>Partition</dt> 164 | <dd> 165 | In Kafka, a way of breaking the messages of a topic into groups 166 | which can be consumed in parallel by one or more workers. 167 | </dd> 168 | 169 | <dt>Queue</dt> 170 | <dd> 171 | In RabbitMQ, messages sent to an exchange are placed on a queue. 172 | Messages on a queue can be consumed in parallel by one or more workers. 173 | </dd> 174 | 175 | <dt>Consumer</dt> 176 | <dd>An application or process that reads from a data stream.</dd> 177 | 178 | <dt>Producer</dt> 179 | <dd>An application or process that writes to a data stream.</dd> 180 | </dl> 181 | -------------------------------------------------------------------------------- /code-review/README.md: -------------------------------------------------------------------------------- 1 | # Code Review 2 | 3 | A guide for reviewing code and having your code reviewed. 4 | 5 | Watch a presentation that covers this material from [Derek Prior at RailsConf 2015](https://www.youtube.com/watch?v=PJjmw9TRB7s). 6 | 7 | ## Everyone 8 | 9 | - **Accept that many programming decisions are opinions** 10 | - Discuss tradeoffs, which you prefer, and reach a resolution quickly. 11 | 12 | - **Ask good questions; don't make demands** 13 | - "What do you think about naming this `:user_id`?" 14 | 15 | - **Good questions avoid judgment and avoid assumptions about the author's 16 | perspective** 17 | - **Ask for clarification** 18 | - "I didn't understand. Can you clarify?" 19 | 20 | - **Avoid selective ownership of code** 21 | - "Mine", "not mine", "yours" 22 | 23 | - **Avoid using terms that could be seen as referring to personal traits** 24 | - "Dumb", "stupid". 25 | - Assume everyone is intelligent and well-meaning. 26 | 27 | - **Avoid diminishing words** 28 | - "simply", "simple", "just" 29 | 30 | - **Be explicit** 31 | - Remember people don't always understand your intentions online. 32 | 33 | - **When disagreeing, provide alternative solutions** 34 | - Don't [simply reject an idea][dont-mcblock-me]. [Explain your reasoning](https://thoughtbot.com/blog/don-t-review-prs-like-a-space-wizard) and [suggest alternative approaches](https://github.com/thoughtbot/guides/pull/762#discussion_r2135772338). 35 | 36 | - **Be humble** 37 | - "I'm not sure - let's look it up." 38 | 39 | - **Don't use hyperbole** 40 | - "Always", "never", "endlessly", "nothing" 41 | 42 | - **Don't use sarcasm** 43 | - **Keep it real** 44 | - If emoji, animated gifs, or humor aren't you, don't force them. 45 | - If they are, use them with aplomb. 46 | 47 | - **Talk synchronously if there are too many "I didn't understand" or "Alternative solution:" comments** 48 | - Chat, screen-sharing, in person 49 | - Post a follow-up comment summarizing the discussion. 50 | 51 | - **If you learned something new, share your appreciation** 52 | - "I did not know about this. Thank you for sharing it." 53 | 54 | - **Avoid the "since you're at it" attitude** 55 | - If you would like to recommend a code change unrelated to the current 56 | pull request, suggest it in the appropriate place or open a ticket for it 57 | (on Trello, JIRA, GitHub project...) 58 | 59 | ## Having Your Code Reviewed 60 | 61 | - **Be grateful for the reviewer's suggestions** 62 | - "Good call. I'll make that change." 63 | 64 | - **Be aware that it can be [challenging to convey emotion and intention online]** 65 | - You may want to consider [using labels] to convey intention and tone. 66 | 67 | - **Explain why the code exists** 68 | - "It's like that because of these reasons. Would it be more clear if I rename this class/file/method/variable?" 69 | 70 | - **Extract some changes and refactoring into future tickets/stories** 71 | - **When making visual changes, include screenshots or screencasts to show the effect of the changes** 72 | - You may want to consider before/after screenshots or screencasts whenever applicable. 73 | 74 | - **Link to the code review from the ticket/story** 75 | - "Ready for review: `https://github.com/organization/project/pull/1` 76 | 77 | - **Push commits based on earlier rounds of feedback as isolated commits to the branch** 78 | - Do not squash until the branch is ready to merge. 79 | - Reviewers should be able to read individual updates based on their earlier feedback. 80 | 81 | - **Seek to understand the reviewer's perspective** 82 | - **Try to respond to every comment** 83 | - **Wait to merge the branch until continuous integration tells you the test suite is green in the branch** 84 | - TDDium, Travis CI, CircleCI, GitHub Actions, etc. 85 | 86 | - **Merge once you feel confident in the code and its impact on the project** 87 | - **Final editorial control rests with the pull request author** 88 | 89 | - **Recognize the work of your teammates when you are pairing** 90 | - Use `Co-Authored-By: <name> <email>` at the end of your commit message. 91 | 92 | ## Reviewing Code 93 | 94 | Understand why the change is necessary (fixes a bug, improves the user experience, refactors the existing code). 95 | 96 | Then: 97 | 98 | - **Communicate which ideas you feel strongly about and those you don't** 99 | - **Identify ways to simplify the code while still solving the problem** 100 | - **If discussions turn too philosophical or academic, move the discussion offline to a regular Friday afternoon technique discussion** 101 | - In the meantime, let the author make the final decision on alternative implementations. 102 | 103 | - **Offer alternative implementations** 104 | - But assume the author already considered them. 105 | - "What do you think about using a custom validator here?" 106 | 107 | - **Seek to understand the author's perspective** 108 | - **Approve the pull request** 109 | - **Remember that you are here to provide feedback, not to be a gatekeeper** 110 | - When suggesting changes using the "Add a suggestion" feature: 111 | - **Communicate clearly which lines you suggest adding/removing** 112 | - **Test the suggested changes to validate it works whenever possible** 113 | - **When not possible, let the pull request author know that you did not test the suggestion** 114 | - This applies to code and information in general. 115 | - Be cautious with information you got from an unofficial source _like an LLM or blog post_. 116 | - **Provide some context to let the author know why you're suggesting the change** 117 | 118 | ## Style Comments 119 | 120 | Reviewers should comment on missed style guidelines. Example comment: 121 | 122 | > Order resourceful routes alphabetically by name. 123 | 124 | An example response to style comments: 125 | 126 | Whoops. Good catch, thanks. Fixed in a4994ec. 127 | 128 | If you disagree with a guideline, open an issue on the guides repo rather than debating it within the code review. In the meantime, apply the guideline. It's often helpful to set up a linter like [standard] to format code automatically. 129 | 130 | This helps us have more meaningful conversations on PRs rather than debating personal style preferences. 131 | 132 | - **Leave one comment only, for multiple stylistic offenses of the same kind** 133 | - If there are a few occurrences of the same change needed, do not 134 | leave multiple comments for the same change, rather suggest running the linter, 135 | and/or leave one comment only, mentioning the line and elsewhere, 136 | as long as the other files are being edited in the pull request. 137 | 138 | [challenging to convey emotion and intention online]: https://thoughtbot.com/blog/empathy-online 139 | [using labels]: https://conventionalcomments.org 140 | [standard]: https://github.com/testdouble/standard 141 | [dont-mcblock-me]: https://www.schneems.com/2025/06/03/dont-mcblock-me 142 | -------------------------------------------------------------------------------- /rails/README.md: -------------------------------------------------------------------------------- 1 | # Rails 2 | 3 | ## Application 4 | 5 | - Name initializers for their gem name. 6 | - Use `lib` for code that is not app-specific and could later be extracted into a gem. 7 | - Use `app/jobs` for code that doesn't need to return anything and can be run asynchronously. 8 | - Generate necessary [Spring binstubs] for the project, such as `rake` and 9 | `rspec`, and add them to version control. 10 | - Use the [`.ruby-version`] file convention to specify the Ruby version and patch level for a project. 11 | - Prefer `cookies.signed` over `cookies` to [prevent tampering]. 12 | - Use `ENV.fetch` for environment variables instead of `ENV[]`so that unset 13 | environment variables are detected on deploy. 14 | 15 | [spring binstubs]: https://github.com/sstephenson/rbenv/wiki/Understanding-binstubs 16 | [`.ruby-version`]: https://gist.github.com/fnichol/1912050 17 | [prevent tampering]: https://www.bigbinary.com/blog/cookies-on-rails 18 | 19 | ## Routes 20 | 21 | - Avoid the `:except` option in routes. 22 | - Avoid `member` and `collection` routes. 23 | - Order resourceful routes alphabetically by name. 24 | - Use the `:only` option to explicitly state exposed routes. 25 | - Prefer [resource routing] over [generating routes] individually 26 | - Use `_url` suffixes for named routes in mailer views and [redirects]. Use `_path` suffixes for named routes everywhere else. 27 | 28 | [resource routing]: https://guides.rubyonrails.org/routing.html#resource-routing-the-rails-default 29 | [generating routes]: https://guides.rubyonrails.org/routing.html#generating-paths-and-urls-from-code 30 | [redirects]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30 31 | 32 | ## Views and UI 33 | 34 | - Put application-wide partials in the [`app/views/application`] directory. 35 | - Use the default `render 'partial'` syntax over `render partial: 'partial'`. 36 | - Use `link_to` for GET requests, and `button_to` for other HTTP verbs. 37 | - Don't reference a model class directly from a view. 38 | - Don't use instance variables in partials. Pass local variables to partials from view templates. 39 | - Use only one instance variable in each view. 40 | 41 | [`app/views/application`]: http://railscasts.com/episodes/269-template-inheritance 42 | 43 | ## Controllers 44 | 45 | - Use private instead of protected when defining controller methods. 46 | - Order controller contents: filters, public methods, private methods. 47 | - Avoid instantiating more than one object in controllers. 48 | 49 | ## Models 50 | 51 | Guidance on ActiveRecord, ActiveModel, and other model objects. 52 | 53 | - Order ActiveRecord associations alphabetically by association type, then 54 | attribute name. [Example](/rails/sample.rb#L2-L4). 55 | - Order ActiveRecord validations alphabetically by attribute name. 56 | - Order ActiveRecord associations above ActiveRecord validations. 57 | - Order model contents: constants, macros, public methods, private methods. 58 | - Use `def self.method`, not the `scope :method` DSL. [#643](https://github.com/thoughtbot/guides/pull/643) 59 | - Use new-style `validates :name, presence: true` validations, and put all 60 | validations for a given column together. [Example](/rails/sample.rb#L6). 61 | - Avoid bypassing validations with methods like `save(validate: false)`, 62 | `update_attribute`, and `toggle`. 63 | - Avoid naming methods after database columns in the same class. 64 | - Don't return false from `ActiveModel` callbacks, but instead raise an exception. 65 | - Don't use SQL or SQL fragments (`where('inviter_id IS NOT NULL')`) outside of models. 66 | - Validate the associated `belongs_to` object (`user`), not the database column (`user_id`). 67 | - Use `touch: true` when declaring `belongs_to` relationships. 68 | - Use [Pundit][] when you need to restrict access to models and data. 69 | 70 | [Pundit]: https://github.com/varvet/pundit 71 | 72 | ## Database and Persistence 73 | 74 | - Name date columns with `_on` suffixes. 75 | - Name datetime columns with `_at` suffixes. 76 | - Back boolean concepts like deleted? or published? with timestamps columns in the database `deleted_at`, `published_at`. 77 | This can be valuable when you need to know **when** something took place. [Time for A Boolean](https://github.com/calebhearth/time_for_a_boolean) provides a nice interface for this. 78 | - Name time columns (referring to a time of day with no date) with `_time` 79 | suffixes. 80 | - Keep `db/schema.rb` or `db/development_structure.sql` under version control. 81 | - Use `db/seeds.rb` for data that is required in all environments. 82 | - Use `development:db:seed` rake task for development environment seed data. [Example](/rails/how-to/seed-data.md). 83 | 84 | ## Security 85 | 86 | - Set [config.sandbox_by_default][sandbox] to `true` in production-like environments to avoid accidental writing to the production database. 87 | 88 | [sandbox]: https://guides.rubyonrails.org/configuring.html#config-sandbox-by-default 89 | 90 | ## Migrations 91 | 92 | [Sample](migration.rb) 93 | 94 | - Set an empty string as the default constraint for **non-required** string and text 95 | fields. [Example](migration.rb#L6). [#159](https://github.com/thoughtbot/guides/pull/159) 96 | - Set an explicit [`on_delete` behavior for foreign keys]. 97 | - Don't change a migration after it has been merged into `main` if the desired 98 | change can be solved with another migration. 99 | - If there are default values, set them in migrations. 100 | - Use SQL, not `ActiveRecord` models, in migrations. 101 | - [Add foreign key constraints] in migrations. 102 | 103 | [`on_delete` behavior for foreign keys]: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key 104 | [add foreign key constraints]: http://thoughtbot.com/blog/referential-integrity-with-foreign-keys 105 | 106 | ## Factories 107 | 108 | - [Use blocks](/ruby/sample_2.rb#L10) when declaring date and time attributes in FactoryBot factories. 109 | 110 | ## Temporal 111 | 112 | - Prefer `Time.current` over `Time.now` 113 | - Prefer `Date.current` over `Date.today` 114 | - Prefer `Time.zone.parse("2014-07-04 16:05:37")` over `Time.parse("2014-07-04 115 | 16:05:37")` 116 | 117 | ## Translations 118 | 119 | - Ensure that the application is setup to support multiple locales. 120 | - Ensure that the application raises an error when a translation is missing for a 121 | given locale in development and tests. 122 | - Order i18n translations alphabetically by key name. 123 | 124 | ## Email 125 | 126 | - Use the user's name in the `From` header and email in the `Reply-To` when 127 | [delivering email on behalf of the app's users]. 128 | 129 | [delivering email on behalf of the app's users]: http://thoughtbot.com/blog/delivering-email-on-behalf-of-users 130 | 131 | ## Code Review 132 | 133 | Follow the normal [Code Review guidelines](/code-review/). When reviewing 134 | others' Rails work, look in particular for: 135 | 136 | - Review data integrity closely, such as migrations that make irreversible 137 | changes to the data, and whether there is a related todo to make a database 138 | backup during the staging and production deploys. 139 | - Review SQL queries for potential SQL injection. 140 | - Review whether dependency upgrades include a reason in the commit message, 141 | such as a link to the dependency's `ChangeLog` or `NEWS` file. 142 | - Review whether new database indexes are necessary if new columns or SQL 143 | queries were added. 144 | - Review whether new scheduler (`cron`) tasks have been added and whether there 145 | is a related todo in the project management system to add it during the 146 | staging and production deploys. 147 | 148 | ## Asset Management 149 | 150 | - Use [ActiveStorage] to manage file uploads that live on ActiveRecord objects. 151 | - Don't use live storage backends like S3 or Azure in tests. 152 | 153 | [ActiveStorage]: https://guides.rubyonrails.org/active_storage_overview.html 154 | 155 | ## Testing 156 | 157 | - Prefer [webmock][] over [VCR][]. 158 | 159 | [webmock]: https://github.com/webmock/webmock 160 | [VCR]: https://github.com/vcr/vcr 161 | 162 | ## How To Guides 163 | 164 | - [Start a New Rails App](./how-to/start_a_new_rails_app.md) 165 | - [Deploy a Rails App to Heroku](./how-to/deploy_a_rails_app_to_heroku.md) 166 | - [Feature-test JavaScript in a Rails App](./how-to/feature_test_javascript_in_a_rails_app.md) 167 | -------------------------------------------------------------------------------- /accessibility/README.md: -------------------------------------------------------------------------------- 1 | # Accessibility 2 | 3 | A guide for auditing and maintaining accessible web sites and apps. 4 | 5 | ## Basics 6 | 7 | thoughtbot strives for AA level [Web Content Accessibility Guideline (WCAG)] 8 | compliance. Perform one or more of these checks to ensure your work is 9 | accessible. 10 | 11 | ### Automation 12 | 13 | Automated checks can catch a lot of common issues before they reach production. 14 | 15 | - Test the application in a browser (like Capybara-driven [Acceptance 16 | Tests](../testing-rspec/README.md#acceptance-tests)) 17 | - When using Capybara, use [CapybaraAccessibilityAudit] 18 | - Use tools such as [WAVE] or [axe's browser extensions] to run audits on your 19 | local build 20 | - Use a CI/CD solution such as [AccessLint] or [axe]. axe has integrations with popular test frameworks like RSpec and Jest 21 | 22 | [CapybaraAccessibilityAudit]: https://github.com/thoughtbot/capybara_accessibility_audit 23 | 24 | ### Usability 25 | 26 | [Manual usability testing] ensures things work as intended. 27 | 28 | - Test your local build using a screen reader such as [VoiceOver] or [NVDA] 29 | - Use auditing tools to catch issues that cannot be 30 | found using automated checks 31 | - [Accessibility Insights] accessibility auditing browser extension 32 | - [Readability Analyzer][simple and direct] for auditing text 33 | - [axe DevTools] accessibility testing browser extension 34 | - [WAVE Evaluation Tool] accessibility testing browser extension 35 | - [ARIA DevTools] browser extension for checking ARIA roles 36 | - [tab11y] browser extension for checking tab order 37 | - [WCAG Color contrast checker] browser extension 38 | - Validate your HTML with a tool like [W3C's Markup Validation Service][w3c-markup-validator] 39 | - Hire assistive technology users to user test your product 40 | 41 | [axe DevTools]: https://chromewebstore.google.com/detail/axe-devtools-web-accessib/lhdoppojpmngadmnindnejefpokejbdd 42 | [WAVE Evaluation Tool]: https://chromewebstore.google.com/detail/wave-evaluation-tool/jbbplnpkjmmeebjpijfedlgcdilocofh 43 | [ARIA DevTools]: https://chromewebstore.google.com/detail/aria-devtools/dneemiigcbbgbdjlcdjjnianlikimpck 44 | [tab11y]: https://chromewebstore.google.com/detail/taba11y-tab-order-accessi/aocppmckdocdjkphmofnklcjhdidgmga 45 | [WCAG Color contrast checker]: https://chromewebstore.google.com/detail/wcag-color-contrast-check/plnahcmalebffmaghcpcmpaciebdhgdf 46 | [w3c-markup-validator]: https://validator.w3.org/ 47 | 48 | ## Quick checks 49 | 50 | ### Design 51 | 52 | - Ensure all content's foreground color values meet the [minimum contrast ratio] 53 | for the background color they are placed over ([WCAG 1.4.3][wcag-1-4-3]) 54 | - Ensure all interactive content has distinct hover and focus states to help 55 | indicate interactivity ([WCAG 2.4.7][wcag-2-4-7]) 56 | - Ensure interactive elements [have a visible text label][rule-2] 57 | - Ensure color is not the only way to determine meaning ([WCAG 1.4.1][wcag-1-4-1]) 58 | - Ensure interactive components use common UI affordances where applicable, to 59 | help users understand how they can be operated 60 | - Prefer icons and glyphs that don't rely on specialized knowledge to understand 61 | their meaning, unless being used in a domain-specific context 62 | - Prefer language that is [simple and direct] ([WCAG 3.1][wcag-3-1]) 63 | - Ensure form inputs have [labels that are visible in every state][placeholder-labels] 64 | - Ensure link and button text is descriptive and distinct ([WCAG 2.4.4][wcag-2-4-4]) 65 | - Prefer content that is broken into logical sections, with headings that 66 | explain the content that follows ([WCAG 2.4.10][wcag-2-4-10]) 67 | - Prefer text sizing that is set to 16px or larger 68 | - Ensure animation does not auto-play, can be paused, and avoids [vestibular and 69 | seizure triggers] ([WCAG 2.2.2][wcag-2-2-2]) 70 | - Ensure video content has captions ([WCAG 1.2.2][wcag-1-2-2]) 71 | - Prefer larger interactive target sizes, with some space between grouped 72 | interactive controls ([WCAG 2.5.8][wcag-2-5-8]) 73 | 74 | ### Development 75 | 76 | - Ensure every focusable or interactive element has an [accessible name][] ([WCAG 4.1.2][wcag-4-1-2]) 77 | - Follow the [Cardinal Rules of Naming][]: 78 | 1. [Heed Warnings and Test Thoroughly][rule-1] 79 | 2. [Prefer Visible Text][rule-2] 80 | 3. [Prefer Native Techniques][rule-3] 81 | 4. [Avoid Browser Fallback][rule-4] 82 | 5. [Compose Brief, Useful Names][rule-5] 83 | - Ensure [semantic markup][semantic-markup] is used to describe content 84 | - Ensure content does not disappear off the screen when zoomed ([WCAG 1.4.10][wcag-1-4-10]) 85 | - Ensure that interactive content can be tabbed to and activated using the 86 | keyboard, and that the tab order matches reading order ([WCAG 2.1.1][wcag-2-1-1], [WCAG 2.4.3][wcag-2-4-3]) 87 | - Ensure that heading elements are used, and that heading levels are placed in a 88 | logical order ([WCAG 2.4.10][wcag-2-4-10]) 89 | - Ensure that [landmarks][landmark-regions] are used to describe the overall layout of the page or 90 | view 91 | - Ensure that alternative descriptions for image content are concise, 92 | descriptive, and use punctuation (`alt` attributes for images, `title` 93 | elements for SVGs) 94 | - Ensure [labels are programmatically associated][labels-associated-inputs] with their inputs 95 | - Prefer implementing a method to allow users to skip sections of repeated 96 | content ([WCAG 2.4.1][wcag-2-4-1]) 97 | - Ensure each page or view has a unique title that describes the content it 98 | contains ([WCAG 2.4.2][wcag-2-4-2]) 99 | - The [`title` attribute is only used to describe `iframe` element contents][title-iframe] 100 | - Ensure that [links are used to navigate to other locations and buttons are used 101 | to trigger actions][links-vs-buttons] 102 | - Ensure that [focus is trapped inside of modal interactions][focus-traps] 103 | - Ensure `fieldset` and `legend` elements are used to [group related inputs and 104 | label them][fieldsets-legends] 105 | - Ensure form feedback messaging is programmatically associated with the 106 | relevant inputs ([WCAG 3.3.1][wcag-3-3-1]) 107 | - Ensure that dynamic changes to a web page are announced ([WCAG 4.1.3][wcag-4-1-3]) 108 | - Prefer using role selectors in automated acceptance tests 109 | - [capybara_accessible_selectors] 110 | - [Testing Library's `getByRole()`][testing-library-getbyrole] 111 | - [Playwright's `getByRole()`][playwright-getbyrole] 112 | 113 | [accessible name]: https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/ 114 | [Cardinal Rules of Naming]: https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/#cardinalrulesofnaming 115 | [rule-1]: https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/#naming_rule_heed_warnings 116 | [rule-2]: https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/#naming_rule_visible_text 117 | [rule-3]: https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/#naming_rule_native_techniques 118 | [rule-4]: https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/#naming_rule_avoid_fallback 119 | [rule-5]: https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/#naming_rule_brief_names 120 | [capybara_accessible_selectors]: https://github.com/citizensadvice/capybara_accessible_selectors 121 | [testing-library-getbyrole]: https://testing-library.com/docs/queries/byrole 122 | [playwright-getbyrole]: https://playwright.dev/docs/locators#locate-by-role 123 | [landmark-regions]: https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/ 124 | [labels-associated-inputs]: https://www.w3.org/WAI/WCAG22/Techniques/html/H44 125 | [title-iframe]: https://www.w3.org/WAI/WCAG22/Techniques/html/H64 126 | [links-vs-buttons]: https://www.nngroup.com/videos/buttons-vs-links/ 127 | [focus-traps]: https://okenlabs.com/blog/accessibility-implementing-focus-traps/ 128 | [fieldsets-legends]: https://www.w3.org/WAI/WCAG22/Techniques/html/H71 129 | [placeholder-labels]: https://www.deque.com/blog/accessible-forms-the-problem-with-placeholders/#:~:text=A%20Placeholder%20Is%20Not%20a%20Replacement%20for%20Visible%20Labels 130 | [semantic-markup]: https://www.w3.org/WAI/WCAG22/Techniques/html/H101 131 | 132 | [wcag-1-4-3]: https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html 133 | [wcag-1-4-1]: https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html 134 | [wcag-3-1]: https://www.w3.org/WAI/WCAG22/Understanding/readable.html 135 | [wcag-2-4-4]: https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-in-context.html 136 | [wcag-2-4-10]: https://www.w3.org/WAI/WCAG22/Understanding/section-headings.html 137 | [wcag-2-2-2]: https://www.w3.org/WAI/WCAG22/Understanding/pause-stop-hide.html 138 | [wcag-1-2-2]: https://www.w3.org/WAI/WCAG22/Understanding/captions-prerecorded.html 139 | [wcag-2-5-8]: https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html 140 | [wcag-4-1-2]: https://www.w3.org/WAI/WCAG22/Understanding/name-role-value.html 141 | [wcag-4-1-3]: https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html 142 | [wcag-1-4-10]: https://www.w3.org/WAI/WCAG22/Understanding/reflow.html 143 | [wcag-2-1-1]: https://www.w3.org/WAI/WCAG22/Understanding/keyboard.html 144 | [wcag-2-4-3]: https://www.w3.org/WAI/WCAG22/Understanding/focus-order.html 145 | [wcag-2-4-1]: https://www.w3.org/WAI/WCAG22/Understanding/bypass-blocks.html 146 | [wcag-2-4-2]: https://www.w3.org/WAI/WCAG22/Understanding/page-titled.html 147 | [wcag-3-3-1]: https://www.w3.org/WAI/WCAG22/Understanding/error-identification.html 148 | [wcag-2-4-7]: https://www.w3.org/WAI/WCAG22/Understanding/focus-visible.html 149 | 150 | ## Full audit 151 | 152 | When at all possible, use the guidelines in the basics and quick check sections 153 | to attempt to address accessibility in a proactive way. 154 | 155 | If a thorough analysis needs to be performed, use the following workflow to 156 | perform a comprehensive accessibility audit that checks against most WCAG 157 | criterion: 158 | 159 | 1. Create a copy of the [Accessibility Audit Template] spreadsheet in Google 160 | Drive 161 | 1. Break apart the site or app to be audited into discrete user flow sections, 162 | ordered by importance 163 | 1. Add yourself as the section lead on the audit template, document the relevant 164 | URL and date, and set a status 165 | 1. For each user flow, identify each component that enables the user flow to 166 | function 167 | 1. For each component, check against the test criteria for each row, and then 168 | assign it one of four ratings: 169 | - **N/A**: This test does not apply to this component 170 | - **Pass**: This component meets this test's criteria 171 | - **Moderate**: This component does not meet this test's criteria, but can 172 | worked around 173 | - **Critical**: This component does not meet this test's criteria, and cannot 174 | be worked around 175 | 1. Once a component is completed, update its status 176 | 1. Continue until all user flows have been audited 177 | 178 | Use the Notes sheet to leave per-cell comments when necessary, referencing them 179 | with a link. The next steps for an audit are handled on a per-project basis. 180 | 181 | [accessibility audit template]: https://www.fsb.org.uk/resources/article/accessibility-audit-template-MCTMWUV4Z27FEXRANM566TOZXNOE 182 | [accesslint]: https://github.com/marketplace/accesslint 183 | [axe]: https://www.deque.com/axe/axe-for-web/integrations/ 184 | [axe's browser extensions]: https://www.deque.com/axe/axe-for-web/ 185 | [minimum contrast ratio]: https://webaim.org/resources/linkcontrastchecker/ 186 | [manual usability testing]: https://www.smashingmagazine.com/2018/09/importance-manual-accessibility-testing/ 187 | [nvda]: https://a11yproject.com/posts/getting-started-with-nvda/ 188 | [accessibility insights]: https://accessibilityinsights.io 189 | [simple and direct]: https://datayze.com/readability-analyzer.php 190 | [vestibular and seizure triggers]: https://alistapart.com/article/designing-safer-web-animation-for-motion-sensitivity/ 191 | [voiceover]: https://a11yproject.com/posts/getting-started-with-voiceover/ 192 | [wave]: https://wave.webaim.org/extension/ 193 | [web content accessibility guideline (wcag)]: https://www.w3.org/WAI/standards-guidelines/wcag/ 194 | -------------------------------------------------------------------------------- /ruby/.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - db/schema.rb 4 | 5 | require: 6 | - rubocop-rails 7 | - rubocop-performance 8 | 9 | Naming/AccessorMethodName: 10 | Description: Check the naming of accessor methods for get_/set_. 11 | Enabled: false 12 | 13 | Style/Alias: 14 | Description: 'Use alias_method instead of alias.' 15 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' 16 | Enabled: false 17 | 18 | Style/ArrayJoin: 19 | Description: 'Use Array#join instead of Array#*.' 20 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join' 21 | Enabled: false 22 | 23 | Style/AsciiComments: 24 | Description: 'Use only ascii symbols in comments.' 25 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' 26 | Enabled: false 27 | 28 | Naming/AsciiIdentifiers: 29 | Description: 'Use only ascii symbols in identifiers.' 30 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' 31 | Enabled: false 32 | 33 | Style/Attr: 34 | Description: 'Checks for uses of Module#attr.' 35 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' 36 | Enabled: false 37 | 38 | Metrics/BlockNesting: 39 | Description: 'Avoid excessive block nesting' 40 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' 41 | Enabled: false 42 | 43 | Style/CaseEquality: 44 | Description: 'Avoid explicit use of the case equality operator(===).' 45 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' 46 | Enabled: false 47 | 48 | Style/CharacterLiteral: 49 | Description: 'Checks for uses of character literals.' 50 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals' 51 | Enabled: false 52 | 53 | Style/ClassAndModuleChildren: 54 | Description: 'Checks style of children classes and modules.' 55 | Enabled: true 56 | EnforcedStyle: nested 57 | 58 | Metrics/ClassLength: 59 | Description: 'Avoid classes longer than 100 lines of code.' 60 | Enabled: false 61 | 62 | Metrics/ModuleLength: 63 | Description: 'Avoid modules longer than 100 lines of code.' 64 | Enabled: false 65 | 66 | Style/ClassVars: 67 | Description: 'Avoid the use of class variables.' 68 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars' 69 | Enabled: false 70 | 71 | Style/CollectionMethods: 72 | Enabled: true 73 | PreferredMethods: 74 | find: detect 75 | inject: reduce 76 | collect: map 77 | find_all: select 78 | 79 | Style/ColonMethodCall: 80 | Description: 'Do not use :: for method call.' 81 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' 82 | Enabled: false 83 | 84 | Style/CommentAnnotation: 85 | Description: >- 86 | Checks formatting of special comments 87 | (TODO, FIXME, OPTIMIZE, HACK, REVIEW). 88 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' 89 | Enabled: false 90 | 91 | Metrics/AbcSize: 92 | Description: >- 93 | A calculated magnitude based on number of assignments, 94 | branches, and conditions. 95 | Enabled: false 96 | 97 | Metrics/BlockLength: 98 | CountComments: true # count full line comments? 99 | Max: 25 100 | Exclude: 101 | - "spec/**/*" 102 | 103 | Metrics/CyclomaticComplexity: 104 | Description: >- 105 | A complexity metric that is strongly correlated to the number 106 | of test cases needed to validate a method. 107 | Enabled: false 108 | 109 | Rails/Delegate: 110 | Description: 'Prefer delegate method for delegations.' 111 | Enabled: false 112 | 113 | Style/PreferredHashMethods: 114 | Description: 'Checks use of `has_key?` and `has_value?` Hash methods.' 115 | StyleGuide: '#hash-key' 116 | Enabled: false 117 | 118 | Style/Documentation: 119 | Description: 'Document classes and non-namespace modules.' 120 | Enabled: false 121 | 122 | Style/DoubleNegation: 123 | Description: 'Checks for uses of double negation (!!).' 124 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang' 125 | Enabled: false 126 | 127 | Style/EachWithObject: 128 | Description: 'Prefer `each_with_object` over `inject` or `reduce`.' 129 | Enabled: false 130 | 131 | Style/EmptyLiteral: 132 | Description: 'Prefer literals to Array.new/Hash.new/String.new.' 133 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' 134 | Enabled: false 135 | 136 | # Checks whether the source file has a utf-8 encoding comment or not 137 | # AutoCorrectEncodingComment must match the regex 138 | # /#.*coding\s?[:=]\s?(?:UTF|utf)-8/ 139 | Style/Encoding: 140 | Enabled: false 141 | 142 | Style/EvenOdd: 143 | Description: 'Favor the use of Fixnum#even? && Fixnum#odd?' 144 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' 145 | Enabled: false 146 | 147 | Naming/FileName: 148 | Description: 'Use snake_case for source file names.' 149 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' 150 | Enabled: false 151 | 152 | Style/FrozenStringLiteralComment: 153 | Description: >- 154 | Add the frozen_string_literal comment to the top of files 155 | to help transition from Ruby 2.3.0 to Ruby 3.0. 156 | Enabled: false 157 | 158 | Lint/FlipFlop: 159 | Description: 'Checks for flip flops' 160 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' 161 | Enabled: false 162 | 163 | Style/FormatString: 164 | Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' 165 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' 166 | Enabled: false 167 | 168 | Style/GlobalVars: 169 | Description: 'Do not introduce global variables.' 170 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars' 171 | Reference: 'http://www.zenspider.com/Languages/Ruby/QuickRef.html' 172 | Enabled: false 173 | 174 | Style/GuardClause: 175 | Description: 'Check for conditionals that can be replaced with guard clauses' 176 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' 177 | Enabled: false 178 | 179 | Style/IfUnlessModifier: 180 | Description: >- 181 | Favor modifier if/unless usage when you have a 182 | single-line body. 183 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier' 184 | Enabled: false 185 | 186 | Style/IfWithSemicolon: 187 | Description: 'Do not use if x; .... Use the ternary operator instead.' 188 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs' 189 | Enabled: false 190 | 191 | Style/InlineComment: 192 | Description: 'Avoid inline comments.' 193 | Enabled: false 194 | 195 | Style/Lambda: 196 | Description: 'Use the new lambda literal syntax for single-line blocks.' 197 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line' 198 | Enabled: false 199 | 200 | Style/LambdaCall: 201 | Description: 'Use lambda.call(...) instead of lambda.(...).' 202 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call' 203 | Enabled: false 204 | 205 | Style/LineEndConcatenation: 206 | Description: >- 207 | Use \ instead of + or << to concatenate two string literals at 208 | line end. 209 | Enabled: false 210 | 211 | Metrics/MethodLength: 212 | Description: 'Avoid methods longer than 10 lines of code.' 213 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' 214 | Enabled: false 215 | 216 | Style/ModuleFunction: 217 | Description: 'Checks for usage of `extend self` in modules.' 218 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function' 219 | Enabled: false 220 | 221 | Style/MultilineBlockChain: 222 | Description: 'Avoid multi-line chains of blocks.' 223 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' 224 | Enabled: false 225 | 226 | Style/NegatedIf: 227 | Description: >- 228 | Favor unless over if for negative conditions 229 | (or control flow or). 230 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives' 231 | Enabled: false 232 | 233 | Style/NegatedWhile: 234 | Description: 'Favor until over while for negative conditions.' 235 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives' 236 | Enabled: false 237 | 238 | Style/Next: 239 | Description: 'Use `next` to skip iteration instead of a condition at the end.' 240 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' 241 | Enabled: false 242 | 243 | Style/NilComparison: 244 | Description: 'Prefer x.nil? to x == nil.' 245 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' 246 | Enabled: false 247 | 248 | Style/Not: 249 | Description: 'Use ! instead of not.' 250 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not' 251 | Enabled: false 252 | 253 | Style/NumericLiterals: 254 | Description: >- 255 | Add underscores to large numeric literals to improve their 256 | readability. 257 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' 258 | Enabled: false 259 | 260 | Style/OneLineConditional: 261 | Description: >- 262 | Favor the ternary operator(?:) over 263 | if/then/else/end constructs. 264 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' 265 | Enabled: false 266 | 267 | Naming/BinaryOperatorParameterName: 268 | Description: 'When defining binary operators, name the argument other.' 269 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' 270 | Enabled: false 271 | 272 | Metrics/ParameterLists: 273 | Description: 'Avoid parameter lists longer than three or four parameters.' 274 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' 275 | Enabled: false 276 | 277 | Style/PercentLiteralDelimiters: 278 | Description: 'Use `%`-literal delimiters consistently' 279 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' 280 | Enabled: false 281 | 282 | Style/PerlBackrefs: 283 | Description: 'Avoid Perl-style regex back references.' 284 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' 285 | Enabled: false 286 | 287 | Naming/PredicateName: 288 | Description: 'Check the names of predicate methods.' 289 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' 290 | ForbiddenPrefixes: 291 | - is_ 292 | Exclude: 293 | - spec/**/* 294 | 295 | Style/Proc: 296 | Description: 'Use proc instead of Proc.new.' 297 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc' 298 | Enabled: false 299 | 300 | Style/RaiseArgs: 301 | Description: 'Checks the arguments passed to raise/fail.' 302 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages' 303 | Enabled: false 304 | 305 | Style/RegexpLiteral: 306 | Description: 'Use / or %r around regular expressions.' 307 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' 308 | Enabled: false 309 | 310 | Style/Sample: 311 | Description: >- 312 | Use `sample` instead of `shuffle.first`, 313 | `shuffle.last`, and `shuffle[Fixnum]`. 314 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#arrayshufflefirst-vs-arraysample-code' 315 | Enabled: false 316 | 317 | Style/SelfAssignment: 318 | Description: >- 319 | Checks for places where self-assignment shorthand should have 320 | been used. 321 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment' 322 | Enabled: false 323 | 324 | Style/SingleLineBlockParams: 325 | Description: 'Enforces the names of some block params.' 326 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks' 327 | Enabled: false 328 | 329 | Style/SingleLineMethods: 330 | Description: 'Avoid single-line methods.' 331 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods' 332 | Enabled: false 333 | 334 | Style/SignalException: 335 | Description: 'Checks for proper usage of fail and raise.' 336 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method' 337 | Enabled: false 338 | 339 | Style/SpecialGlobalVars: 340 | Description: 'Avoid Perl-style global variables.' 341 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms' 342 | Enabled: false 343 | 344 | Style/StringLiterals: 345 | Description: 'Checks if uses of quotes match the configured preference.' 346 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals' 347 | EnforcedStyle: double_quotes 348 | Enabled: true 349 | 350 | Style/TrailingCommaInArguments: 351 | Description: 'Checks for trailing comma in argument lists.' 352 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' 353 | EnforcedStyleForMultiline: comma 354 | SupportedStylesForMultiline: 355 | - comma 356 | - consistent_comma 357 | - no_comma 358 | Enabled: true 359 | 360 | Style/TrailingCommaInArrayLiteral: 361 | Description: 'Checks for trailing comma in array literals.' 362 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' 363 | EnforcedStyleForMultiline: comma 364 | SupportedStylesForMultiline: 365 | - comma 366 | - consistent_comma 367 | - no_comma 368 | Enabled: true 369 | 370 | Style/TrailingCommaInHashLiteral: 371 | Description: 'Checks for trailing comma in hash literals.' 372 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' 373 | EnforcedStyleForMultiline: comma 374 | SupportedStylesForMultiline: 375 | - comma 376 | - consistent_comma 377 | - no_comma 378 | Enabled: true 379 | 380 | Style/TrivialAccessors: 381 | Description: 'Prefer attr_* methods to trivial readers/writers.' 382 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family' 383 | Enabled: false 384 | 385 | Style/VariableInterpolation: 386 | Description: >- 387 | Don't interpolate global, instance and class variables 388 | directly in strings. 389 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate' 390 | Enabled: false 391 | 392 | Style/WhenThen: 393 | Description: 'Use when x then ... for one-line cases.' 394 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases' 395 | Enabled: false 396 | 397 | Style/WhileUntilModifier: 398 | Description: >- 399 | Favor modifier while/until usage when you have a 400 | single-line body. 401 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' 402 | Enabled: false 403 | 404 | Style/WordArray: 405 | Description: 'Use %w or %W for arrays of words.' 406 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' 407 | Enabled: false 408 | 409 | # Layout 410 | 411 | Layout/ParameterAlignment: 412 | Description: 'Here we check if the parameters on a multi-line method call or definition are aligned.' 413 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' 414 | Enabled: false 415 | 416 | Layout/ConditionPosition: 417 | Description: >- 418 | Checks for condition placed in a confusing position relative to 419 | the keyword. 420 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' 421 | Enabled: false 422 | 423 | Layout/DotPosition: 424 | Description: 'Checks the position of the dot in multi-line method calls.' 425 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' 426 | EnforcedStyle: trailing 427 | 428 | Layout/ExtraSpacing: 429 | Description: 'Do not use unnecessary spacing.' 430 | Enabled: true 431 | 432 | Layout/MultilineOperationIndentation: 433 | Description: >- 434 | Checks indentation of binary operations that span more than 435 | one line. 436 | Enabled: true 437 | EnforcedStyle: indented 438 | 439 | Layout/MultilineMethodCallIndentation: 440 | Description: >- 441 | Checks indentation of method calls with the dot operator 442 | that span more than one line. 443 | Enabled: true 444 | EnforcedStyle: indented 445 | 446 | Layout/InitialIndentation: 447 | Description: >- 448 | Checks the indentation of the first non-blank non-comment line in a file. 449 | Enabled: false 450 | 451 | Layout/LineLength: 452 | Description: 'Limit lines to 80 characters.' 453 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' 454 | Max: 80 455 | 456 | # Lint 457 | 458 | Lint/AmbiguousOperator: 459 | Description: >- 460 | Checks for ambiguous operators in the first argument of a 461 | method invocation without parentheses. 462 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args' 463 | Enabled: false 464 | 465 | Lint/AmbiguousRegexpLiteral: 466 | Description: >- 467 | Checks for ambiguous regexp literals in the first argument of 468 | a method invocation without parenthesis. 469 | Enabled: false 470 | 471 | Lint/AssignmentInCondition: 472 | Description: "Don't use assignment in conditions." 473 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition' 474 | Enabled: false 475 | 476 | Lint/CircularArgumentReference: 477 | Description: "Don't refer to the keyword argument in the default value." 478 | Enabled: false 479 | 480 | Lint/DeprecatedClassMethods: 481 | Description: 'Check for deprecated class method calls.' 482 | Enabled: false 483 | 484 | Lint/DuplicateHashKey: 485 | Description: 'Check for duplicate keys in hash literals.' 486 | Enabled: false 487 | 488 | Lint/EachWithObjectArgument: 489 | Description: 'Check for immutable argument given to each_with_object.' 490 | Enabled: false 491 | 492 | Lint/ElseLayout: 493 | Description: 'Check for odd code arrangement in an else block.' 494 | Enabled: false 495 | 496 | Lint/FormatParameterMismatch: 497 | Description: 'The number of parameters to format/sprint must match the fields.' 498 | Enabled: false 499 | 500 | Lint/SuppressedException: 501 | Description: "Don't suppress exception." 502 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' 503 | Enabled: false 504 | 505 | Lint/LiteralAsCondition: 506 | Description: 'Checks of literals used in conditions.' 507 | Enabled: false 508 | 509 | Lint/LiteralInInterpolation: 510 | Description: 'Checks for literals used in interpolation.' 511 | Enabled: false 512 | 513 | Lint/Loop: 514 | Description: >- 515 | Use Kernel#loop with break rather than begin/end/until or 516 | begin/end/while for post-loop tests. 517 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break' 518 | Enabled: false 519 | 520 | Lint/NestedMethodDefinition: 521 | Description: 'Do not use nested method definitions.' 522 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-methods' 523 | Enabled: false 524 | 525 | Lint/NonLocalExitFromIterator: 526 | Description: 'Do not use return in iterator to cause non-local exit.' 527 | Enabled: false 528 | 529 | Lint/ParenthesesAsGroupedExpression: 530 | Description: >- 531 | Checks for method calls with a space before the opening 532 | parenthesis. 533 | StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' 534 | Enabled: false 535 | 536 | Lint/RequireParentheses: 537 | Description: >- 538 | Use parentheses in the method call to avoid confusion 539 | about precedence. 540 | Enabled: false 541 | 542 | Lint/UnderscorePrefixedVariableName: 543 | Description: 'Do not use prefix `_` for a variable that is used.' 544 | Enabled: false 545 | 546 | Lint/RedundantCopDisableDirective: 547 | Description: >- 548 | Checks for rubocop:disable comments that can be removed. 549 | Note: this cop is not disabled when disabling all cops. 550 | It must be explicitly disabled. 551 | Enabled: false 552 | 553 | Lint/Void: 554 | Description: 'Possible use of operator/literal/variable in void context.' 555 | Enabled: false 556 | 557 | # Performance 558 | 559 | Performance/CaseWhenSplat: 560 | Description: >- 561 | Place `when` conditions that use splat at the end 562 | of the list of `when` branches. 563 | Enabled: false 564 | 565 | Performance/Count: 566 | Description: >- 567 | Use `count` instead of `select...size`, `reject...size`, 568 | `select...count`, `reject...count`, `select...length`, 569 | and `reject...length`. 570 | Enabled: false 571 | 572 | Performance/Detect: 573 | Description: >- 574 | Use `detect` instead of `select.first`, `find_all.first`, 575 | `select.last`, and `find_all.last`. 576 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerabledetect-vs-enumerableselectfirst-code' 577 | Enabled: false 578 | 579 | Performance/FlatMap: 580 | Description: >- 581 | Use `Enumerable#flat_map` 582 | instead of `Enumerable#map...Array#flatten(1)` 583 | or `Enumberable#collect..Array#flatten(1)` 584 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablemaparrayflatten-vs-enumerableflat_map-code' 585 | Enabled: false 586 | 587 | Performance/ReverseEach: 588 | Description: 'Use `reverse_each` instead of `reverse.each`.' 589 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#enumerablereverseeach-vs-enumerablereverse_each-code' 590 | Enabled: false 591 | 592 | Performance/Size: 593 | Description: >- 594 | Use `size` instead of `count` for counting 595 | the number of elements in `Array` and `Hash`. 596 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#arraycount-vs-arraysize-code' 597 | Enabled: false 598 | 599 | Performance/StringReplacement: 600 | Description: >- 601 | Use `tr` instead of `gsub` when you are replacing the same 602 | number of characters. Use `delete` instead of `gsub` when 603 | you are deleting characters. 604 | Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringgsub-vs-stringtr-code' 605 | Enabled: false 606 | 607 | # Rails 608 | 609 | Rails/ActionFilter: 610 | Description: 'Enforces consistent use of action filter methods.' 611 | Enabled: false 612 | 613 | Rails/Date: 614 | Description: >- 615 | Checks the correct usage of date aware methods, 616 | such as Date.today, Date.current etc. 617 | Enabled: false 618 | 619 | Rails/FindBy: 620 | Description: 'Prefer find_by over where.first.' 621 | Enabled: false 622 | 623 | Rails/FindEach: 624 | Description: 'Prefer all.find_each over all.find.' 625 | Enabled: false 626 | 627 | Rails/HasAndBelongsToMany: 628 | Description: 'Prefer has_many :through to has_and_belongs_to_many.' 629 | Enabled: false 630 | 631 | Rails/Output: 632 | Description: 'Checks for calls to puts, print, etc.' 633 | Enabled: false 634 | 635 | Rails/ReadWriteAttribute: 636 | Description: >- 637 | Checks for read_attribute(:attr) and 638 | write_attribute(:attr, val). 639 | Enabled: false 640 | 641 | Rails/ScopeArgs: 642 | Description: 'Checks the arguments of ActiveRecord scopes.' 643 | Enabled: false 644 | 645 | Rails/TimeZone: 646 | Description: 'Checks the correct usage of time zone aware methods.' 647 | StyleGuide: 'https://github.com/bbatsov/rails-style-guide#time' 648 | Reference: 'http://danilenko.org/2012/7/6/rails_timezones' 649 | Enabled: false 650 | 651 | Rails/Validation: 652 | Description: 'Use validates :attribute, hash of validations.' 653 | Enabled: false 654 | -------------------------------------------------------------------------------- /security/application.md: -------------------------------------------------------------------------------- 1 | # The thoughtbot Guide to Application Security 2 | 3 | ## Threat modeling 4 | 5 | The task of identifying concrete attacks and understanding their relationship 6 | with the code is the core task of threat modeling. We can understand this from 7 | two perspectives: 8 | 9 | - identify what can go wrong, and 10 | - don't account for things that cannot go wrong. 11 | 12 | Identifying what can go wrong is what is most often [written about when 13 | discussing threat] modeling. There are [many threat modeling techniques], but 14 | the summary is: 15 | 16 | 1. Create a list of what an attacker can do on your app. For a Web app, they 17 | might be able to spoof HTTP headers, submit malicious data, or embed a Web page 18 | in an `iframe`. 19 | 2. Add to the list the weak points of the app. These will likely be places where 20 | you are doing something non-standard, which the frameworks don't know how to 21 | protect. 22 | 3. Prioritize this list. Take into account factors such as difficulty of attack, 23 | likelihood of attack, ease of mitigating the attack, and severity of attack. 24 | 25 | Anything not in the list are things you cannot use as a reason to do something. 26 | Since the list is prioritized, you can use it to help prioritize tickets or 27 | split tickets. 28 | 29 | [written about when discussing threat]: https://www.owasp.org/index.php/Application_Threat_Modeling 30 | [many threat modeling techniques]: https://insights.sei.cmu.edu/sei_blog/2018/12/threat-modeling-12-available-methods.html 31 | 32 | ## Library updates 33 | 34 | The easiest line of defense you have as a developer is [applying security fixes 35 | for our dependencies] as they are released. 36 | 37 | On the flip perspective, when releasing a security fix for one of our projects, 38 | make it trivial to upgrade: don't include new features or unrelated bug fixes. 39 | 40 | There are a few ways to keep up with security fixes: 41 | 42 | - Any platform-specific tool, such as [bundler-audit]. 43 | - [Any official CVE feed]. 44 | 45 | If you have access, the thoughtbot [Security Basecamp] does our best to keep up 46 | with security issues that we think will affect us or our clients. 47 | 48 | [applying security fixes for our dependencies]: https://snyk.io/blog/top-ten-most-popular-docker-images-each-contain-at-least-30-vulnerabilities/ 49 | [bundler-audit]: https://github.com/rubysec/bundler-audit#readme 50 | [any official cve feed]: https://cve.mitre.org/cve/data_updates.html 51 | [security basecamp]: https://3.basecamp.com/3091943/projects/15753689 52 | 53 | ## Secure programming 54 | 55 | In an ideal world, access to the program's source code will not give an attacker 56 | an advantage. This is not always possible, but programming with a mindset of 57 | preventing an all-knowing attacker can be healthy. 58 | 59 | [Some tips] along the way: 60 | 61 | - Always check return values. If the procedure can raise, make sure to handle 62 | that (to prevent DoS attacks). If the procedure can signal failure, make sure 63 | to handle that (to prevent read-after-free-style attacks). 64 | - Fail fast. If the data seems odd, don't recover: fail. 65 | - Leave the most security-sensitive code as an [omega mess], once it works. Too 66 | many bugs -- more than zero -- come out of refactoring to be worth a change in 67 | the name of code beauty. 68 | 69 | [some tips]: https://twitter.com/SarahJamieLewis/status/1097300029016989696 70 | [omega mess]: https://speakerdeck.com/skmetz/go-ahead-make-a-mess 71 | 72 | ## User data 73 | 74 | Any data from the user is malicious until proven innocent. Examples of user 75 | input are data from forms, HTTP headers, text the user enters into your mobile 76 | app, IP address, MAC address, email headers, file paths, GraphQL queries, 77 | uploaded files, and stdin. And more. 78 | 79 | When possible, rely on a framework to parse user data. Don't parse HTTP headers 80 | by hand, use the Rails validations, pass JSON data through a schema validator, 81 | send addresses straight to the shipping or map API, etc. 82 | 83 | If you can't rely on a library, handle user data in two stages: verify, then 84 | work with it. For example, if someone uploads a file with a filename ending in 85 | `.jpg`, use `libmagic` to confirm that it is a JPEG, and then consider it less 86 | tainted and ready for use. 87 | 88 | ### SQL injection 89 | 90 | We know about this one, so let's make sure it does not happen. 91 | 92 | Whenever you run a SQL query, don't insert user input into it. If you must 93 | insert user data into it, use a [bind variable]. (The details of how bind 94 | variables work depends on your object-relational mapping library.) 95 | 96 | [bind variable]: https://www.ibm.com/developerworks/library/se-bindvariables/index.html 97 | 98 | ### YAML 99 | 100 | [YAML is too vulnerable to attacks] to consider for new projects. 101 | 102 | [yaml is too vulnerable to attacks]: https://trailofbits.github.io/rubysec/yaml/index.html 103 | 104 | ### Client-side validation 105 | 106 | All client-side validation, such as a React component that tells the user that 107 | their email address is not in a valid format, is for presentation. These checks, 108 | and more, must be duplicated on the backend. Any attacker can use curl to bypass 109 | your client-side validations. 110 | 111 | ### Cookies 112 | 113 | Cookies are user-controlled input and, therefore, should be treated with 114 | suspicion. If possible, don't rely on a cookie. 115 | 116 | Cookies can be copied between browsers. Just because a request sends a cookie 117 | does not mean that the cookie was sent by the user's original browser. It might 118 | come from curl. 119 | 120 | One way to retain control over the cookie data is to sign it using a secret key 121 | only known by the server. Rails does this for you. 122 | 123 | ## Logging 124 | 125 | Logging is a compromise between having enough data to be able to debug a problem 126 | and having too much personally-identifying information about a user. 127 | 128 | Make sure not to log passwords, credit card numbers, or any other information 129 | that you do not strictly need. Err on the side of not logging any strings, if 130 | possible. 131 | 132 | In Rails, use the `filter_parameters` configuration setting to remove known 133 | attributes from the logs. 134 | 135 | In addition, if you are logging to a service over a network connection, make 136 | sure the connection itself is secured using TLS. 137 | 138 | ## Personally-Identifying Information (PII) 139 | 140 | As much as you can, do not touch any information you don't need. Some tricks for 141 | this: 142 | 143 | - Send any credit card data directly to the payment processor from the client. 144 | They'll give back a token, which you can store safely. 145 | - You probably don't need the user's sex, gender, date of birth, middle name, 146 | and so on. You might, but ask yourself first: do you? 147 | 148 | When you must store PII: 149 | 150 | - Use [password best practices] for any account with access to PII, 151 | including developer accounts which have access to production. 152 | - Avoid using shared logins with access to PII, even if such logins are managed 153 | by a service like 1Password. 154 | - If you must own a shared login, such as AWS account root credentials, store a 155 | password and OTP secret separately so that two people are required when 156 | accessing the account. 157 | - Use multi-factor authentication for all accounts with access to PII, 158 | including developer accounts with access to production. 159 | - Use an audit log documented when PII was accessed and why. 160 | - Use a documented procedure for onboarding and offboarding users with access to 161 | PII, including developers. 162 | - Avoid granting access to PII until necessary; only users that require access 163 | should be granted access. 164 | - Use [application-level encryption] to encrypt all PII. 165 | - Use in-transit and at-rest encryption for any database containing PII. 166 | - Use network isolation, such as an [AWS VPC], for databases containing PII. 167 | - Use a unique encryption key, such as an [AWS Customer Managed Key] for each database containing PII. 168 | - Use automatic rotation for any passwords with access to PII, such as Postgres 169 | credentials. 170 | 171 | [password best practices]: ./README.md#using-passwords 172 | [application-level encryption]: https://edgeguides.rubyonrails.org/active_record_encryption.html 173 | [AWS VPC]: https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html 174 | [AWS Customer Managed Key]: https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html#customer-cmk 175 | 176 | ## Randomization 177 | 178 | Most modern cryptography is dependent on really big prime numbers and access to 179 | solid randomization. If you find yourself in a place where you need a random 180 | number, here are some things to keep in mind. 181 | 182 | - Don't do this yourself. If you can use Ruby's `SecureRandom` or functions like 183 | `arc4random_buf(3)` and `arc4random_uniform(3)`, do that instead. 184 | - [Do not restrict the randomized space] with a modulo or floating-point 185 | multiplication bias. Instead, try generating a random number in a loop, 186 | returning when the value is within the desired range. 187 | - Use an unpredictable seed. Do not use the current time, or the seconds since 188 | boot, or `0`, or your age, or the result from calling rand seeded on a 189 | predictable seed. If possible, use a random number generator that you do not 190 | seed yourself, such as `arc4random(4)` or `/dev/random`. 191 | - Use a non-blocking random number generator. If an attacker discovers that the 192 | random number generator blocks, such as Linux's `/dev/urandom`, that is a 193 | potential denial of service attack vector. 194 | 195 | [do not restrict the randomized space]: http://www.pcg-random.org/posts/bounded-rands.html 196 | 197 | ## Hashing 198 | 199 | A hashing function provides a one-way encoding of an object. Use this any time 200 | you don't actually care what the value _is_, but instead you care that you have 201 | it at all. The only operation you'll want to perform against a hashed object is 202 | an equality check. 203 | 204 | (As a side note, when people refer to dictionary data structures as "hashes", 205 | they're referring to the fact that a hashing function is used to turn the key 206 | into a unique number.) 207 | 208 | (As a second side note, when people refer to blockchains as "cryptocurrency", 209 | they're making reference to the fact that they used a hashing function. Twice.) 210 | 211 | Hashing algorithms are as strong as their ability to generate a unique, 212 | one-direction hash. When someone finds a way to generate the same hash for two 213 | different inputs, the hashing function is considered insecure. The American 214 | National Institute of Standards and Technology (NIST) maintains [a list of 215 | approved hash algorithms]; as of this writing SHA-2 and SHA-3 are approved. 216 | 217 | Note that base64 encoding is not a hashing function, since it intentionally can 218 | be decoded. 219 | 220 | Use an approved secure hashing algorithm to verify that something has not been 221 | tampered with. Some examples of that are tarballs (both ones you download and 222 | also ones you provide to other devs -- always send a hash of the file so the 223 | downloader can confirm the file before opening it) and API request bodies. 224 | 225 | A fun example is to make a "precommit" statement among friends: create a 226 | sentence predicting an outcome, then share the hash of the sentence. When the 227 | outcome comes true, share the original text. 228 | 229 | [a list of approved hash algorithms]: https://csrc.nist.gov/Projects/Hash-Functions 230 | 231 | ### Hash-based Message Authentication Code (HMAC) 232 | 233 | If using a hash to verify a JSON API body, you and the client might have a 234 | shared secret that you concatenate onto the body so you can be sure that it is 235 | untampered with. 236 | 237 | The way most secure hashing algorithms work is based on blocks of bytes of a 238 | specific length. The input is split and padded to fit into the correct length. 239 | This leaves them open to a length-extension attack, where a knowledgeable 240 | attacker can add on to the input and compute a valid new hash by 241 | reverse-engineering the internal state of the hashing function without knowing 242 | the secret. 243 | 244 | A Hash-based Message Authentication Code (HMAC) is designed to work around that. 245 | Instead of hashing the secret concatenated with the message, it hashes the 246 | secret concatenated with the hash of the secret concatenated with the message. 247 | 248 | It's possible that you will not directly interact with HMACs but they do show up 249 | in TLS, JWT, and one-time passwords. 250 | 251 | ### Passwords 252 | 253 | Note that for passwords, the attacker does not need to know the user's password 254 | _per se_; the attacker needs to know a string which will generate the desired 255 | hash. This is known as a collision attack. 256 | 257 | A rainbow table attack is done with a rainbow table: a giant list of every 258 | possible string and its resulting hash. Using such a list, the attacker can 259 | quickly look up the password given a hash. 260 | 261 | A similar attack is to, given one hash, run through every possible string, 262 | hashing each one, until you find a match. 263 | 264 | Rainbow-table-style attacks have been on the rise since the early 1990s, making 265 | typical secure hashing functions inappropriate for passwords. 266 | 267 | The first solution is to use a salt: generate a random number, add that to the 268 | user's password, and hash _that_ string. Store the salt alongside the hashed 269 | password; each user gets their own salt. 270 | 271 | Salts destroy rainbow tables and cause headaches for hashing each string one at 272 | a time. But not enough of a headache: GPUs are at a point now where they can run 273 | secure hashing functions quickly. Too quickly. 274 | 275 | The solution is to use a key derivation algorithm. These are much like normal 276 | hashing algorithms (they're actually quite different, but that difference is 277 | negligible), except they are intentionally slow. 278 | 279 | The most common password hashing algorithms are bcrypt, scrypt, and PBKDF2. Each 280 | of these require a salt, but handle it themselves: the output of these functions 281 | is a string that contains the salt plus the hashed value. Store that entire 282 | string as the hashed password. 283 | 284 | ## Encryption 285 | 286 | An encryption algorithm is one where a string can be made illegible and then 287 | returned back to the original string again, and where decrypting requires an 288 | out-of-band secret. 289 | 290 | Less abstractly: a string can be encrypted, and then to decrypt you must know 291 | the password. 292 | 293 | There are two kinds of encryption algorithms: symmetric and asymmetric. A 294 | symmetric algorithm is one where the same secret is used to encrypt it and 295 | decrypt it. An asymmetric algorithm is one where the string can be encrypted and 296 | decrypted using different secrets -- where the person encrypting cannot 297 | necessarily decrypt it. 298 | 299 | The most popular symmetric algorithms you'll encounter are AES and Twofish. 300 | These might be useful for encrypting a file to share with a group of people or 301 | for encrypting your filesystem. 1Password uses AES to encrypt an entire vault; 302 | it is encrypted at rest, and only decrypted when you enter the passphrase. 303 | 304 | Asymmetric encryption algorithms, also known as public/private key pair 305 | encryption, are more well-known -- in large part for how tricky they are to get 306 | right. Some famous ones are SSH, TLS (previously SSL), and PGP. These start by 307 | generating a pair of encryption secrets known as the public and private keys. 308 | Anyone with the public key can encrypt a string, but only the holder of the 309 | private key can decrypt it. 310 | 311 | ([The math around asymmetric encryption] is cool. I won't go into it.) 312 | 313 | [the math around asymmetric encryption]: http://pi.math.cornell.edu/~mec/2003-2004/cryptography/diffiehellman/diffiehellman.html 314 | 315 | Asymmetric keys and messages encrypted using an asymmetric algorithm are larger 316 | than messages encrypted using a symmetric algorithm. It is common to use an 317 | asymmetric algorithm -- where fewer people need to know the secret of how to 318 | decrypt -- to exchange the secrets for a symmetric algorithm, then use a 319 | symmetric algorithm for the rest of the exchange. Such a protocol will save 320 | bytes and computational power. 321 | 322 | In order for any of this to work, you need to get your hands on a confirmed 323 | public key. Each public key has a fingerprint -- an abbreviated and 324 | easily-confirmable portion of the entire secret. How this works in practice 325 | depends on the protocol. 326 | 327 | ### Signing 328 | 329 | An asymmetric encryption algorithm can be run in reverse to provide for signing. 330 | In this, a private key is used to sign a string, producing a signature string. 331 | The public key can be used to verify that the private key was used to generate 332 | the signature, proving that the string was in the control of the owner of the 333 | private key. 334 | 335 | This is useful for certificate authorities, as used by TLS, but also useful for 336 | sharing files. You can provide the tarball and the signature, and anyone with 337 | your public key can verify that the tarball was created by you (or, at least, 338 | anyone with your private key). The Debian package system is built around this. 339 | 340 | ### SSH 341 | 342 | SSH defaults to a trust-on-first-use (TOFU) policy: the first time you connect 343 | to a server you are asked to confirm the server's public key fingerprint: 344 | 345 | ```text 346 | The authenticity of host heroku.com can't be established. 347 | RSA key fingerprint is 8tF0wX2WquK45aGKs/Bh1dKmBXH08vxUe0VCJJWOA/o. 348 | Are you sure you want to continue connecting (yes/no)? 349 | ``` 350 | 351 | The server admin will need to tell you out of band whether that is the correct 352 | fingerprint ([Heroku publishes their fingerprint online]). 353 | 354 | [heroku publishes their fingerprint online]: https://devcenter.heroku.com/articles/git-repository-ssh-fingerprints 355 | 356 | ### PGP 357 | 358 | OpenPGP is a way for users to trust each other; therefore, fingerprint 359 | verification happens in person, often in a [key signing party]. People will 360 | exchange the fingerprint of their PGP key face-to-face, often written on paper, 361 | and then later will confirm that the key they have for the person matches the 362 | fingerprint on the paper. 363 | 364 | This mechanism is called a web of trust. 365 | 366 | [key signing party]: http://mdcc.cx/gnupg/ksp_intro.en.html 367 | 368 | ### TLS 369 | 370 | Transport Layer Security is a way for a browser to trust a server. The browser 371 | ships with a list of trusted public keys. Each Web site serves up its own public 372 | key, plus a signature from another key. If the signature is by one of the 373 | trusted public keys, the browser accepts the Web site's key; otherwise, it's a 374 | failure. 375 | 376 | For example, Firefox trusts GlobalSign. thoughtbot.com has a TLS certificate 377 | that was signed by GlobalSign. When you visit thoughtbot.com, it sends its 378 | public key plus the signature from GlobalSign. Firefox trusts GlobalSign, so it 379 | trusts thoughtbot.com's key. 380 | 381 | This mechanism is called a certificate authority. 382 | 383 | ## Encrypting and hashing 384 | 385 | Encryption (PGP, AES) is different from hashing (SHA-256, bcrypt, etc.), because 386 | it can be reversed, and this is different from encoding (base64, base58, etc.) 387 | because reversing it requires a password. 388 | 389 | It can be handy to combine these technologies: 390 | 391 | - Encrypt and hash. The recipient can confirm that they have the right string by 392 | checking the hash before even attempting to decrypt it. This might save them 393 | from attempting to decrypt a malicious string. 394 | - Hash and encode. This can be handy for when you need to triple-check that a 395 | JSON payload made it through safely: hash the JSON, then base64 the hash, 396 | which encodes it into ASCII, making it safer to send across HTTP. 397 | 398 | ## TLS 399 | 400 | [Transport Layer Security (TLS) is a general-purpose mechanism] for confirming 401 | the integrity, confidentiality, and authenticity of data sent over TCP. It 402 | combines symmetric encryption, asymmetric encryption, and hashing functions to 403 | transmit data securely and efficiently. 404 | 405 | It does _not_ guarantee that the domain owner is trustworthy. TLS does not 406 | relate to trust. It can only guarantee that the information is sent, untampered 407 | and privately, only to the recipient you are sending it to. It does not 408 | guarantee that you are sending it to the right recipient. 409 | 410 | There are two kinds of certificates signed by certificate authorities: domain 411 | verification and extended verification. Domain verification does what it says on 412 | the tin: it confirms that the holder of the private key is also in control of 413 | the domain name. Extended verification goes further and cannot be automated: it 414 | confirms that the holder of the private key controls the domain name and is the 415 | person or company they claim to be. 416 | 417 | Extended verification does not guarantee that the holder of the private key is 418 | trustworthy. 419 | 420 | We typically interact with TLS via HTTPS, but it can be used for any TCP 421 | connection, such as email. 422 | 423 | The first version of TLS was named Secure Sockets Layer (SSL); at the end of the 424 | last century, SSL was found to be trivially vulnerable and has not been 425 | intentionally used since. 426 | 427 | [transport layer security (tls) is a general-purpose mechanism]: http://rebecca.meritz.com/ggm15/ 428 | 429 | ### HSTS 430 | 431 | The most common attack vector for a secure protocol is a downgrade attack. Many 432 | protocols have a backward-compatibility mechanism that allows the client and 433 | server to negotiate which version of a protocol they both understand. Convincing 434 | the server to downgrade to a version of a protocol with a known bug is a 435 | downgrade attack. 436 | 437 | The worst case is when you can convince the server to drop the security 438 | entirely. This is possible with HTTPS with a standard man-in-the-middle attack 439 | of the _unencrypted_ HTTP connection. 440 | 441 | It works like this: 442 | 443 | 1. User visits `twitter.com`. 444 | 2. Web browser turns this into `http://twitter.com/`. 445 | 3. An eavesdropper intercepts the connection and redirects to their own server, 446 | secured using TLS, but entirely under their control. 447 | 448 | The problem is step (2). The current solution is HTTP Strict Transport Security 449 | (HSTS). Under HSTS, the browser knows about domain names that should always be 450 | HTTPS. It literally has a list. If you enter `twitter.com` into the URL bar, it 451 | will check its list, find `twitter.com` in there, and complete the full URL as 452 | `https://twitter.com/`. 453 | 454 | Web sites can add themselves to the user's local list by sending the 455 | `Strict-Transport-Security` header. The value for this header is 456 | `max-age=31536000; includeSubDomains; preload`. 457 | 458 | - `max-age` determines how long this domain should remain in the browser's list. 459 | `31536000` is one year. Feel free to go longer. 460 | - `includeSubDomains` specifies that subdomains should also be considered part 461 | of the HSTS list. 462 | - `preload` tells the browser maintainer that you are comfortable with this 463 | domain being part of [the list shipped with the browser]. 464 | 465 | Unrelated to HSTS but sort of a corollary of how the attack works: specify the 466 | protocol (`https://`) in all your links. 467 | 468 | [the list shipped with the browser]: https://hstspreload.org/ 469 | 470 | ## Passwords 471 | 472 | As has been mentioned, use bcrypt for storing your passwords in a database. 473 | 474 | Design the user experience to encourage your users to use a password manager: 475 | 476 | - Allow paste. Do the minimum to the password field -- and be sure to annotate 477 | that it is a standard password field (`type="password"` in HTML, 478 | `android:inputType="password"` in Android, 479 | `passwordTextField.isSecureTextEntry=true` in iOS) -- so that the user's 480 | password manager can work with it. 481 | - Never expire passwords. [To quote NIST]: 482 | > Users tend to choose weaker memorized secrets when they know that they will 483 | > have to change them in the near future. When those changes do occur, they 484 | > often select a secret that is similar to their old memorized secret by 485 | > applying a set of common transformations such as increasing a number in the 486 | > password. This practice provides a false sense of security if any of the 487 | > previous secrets has been compromised since attackers can apply these same 488 | > common transformations. 489 | - Allow passwords to be more complex. Whether you want to enforce light password 490 | complexity rules or not ([NIST discourages password complexity rules], allow 491 | for passwords longer or otherwise more complex than you expect. When possible, 492 | treat passwords as bytes that are immediately hashed and stored. 493 | 494 | A good password has a few properties: 495 | 496 | - Complex enough to be hard to crack through a boring enumeration attack. 497 | - Able to be changed when compromised. 498 | - Unique to the account. 499 | - Can be stored securely by the user (in their own head, in a password manager, 500 | in a locked notebook, etc.). 501 | - Can be kept as a secret. 502 | - Leaking the password only threatens the security of that password. 503 | 504 | Biometrics (iris scan, face recognition, thumbprint reader, etc.) violate most 505 | of those qualities. Biometrics are useful for identity but incorrect to use as 506 | an authentication secret. 507 | 508 | [to quote nist]: https://pages.nist.gov/800-63-FAQ/#q-b5 509 | [nist discourages password complexity rules]: https://pages.nist.gov/800-63-FAQ/#q-b6 510 | 511 | ### Timing attacks 512 | 513 | An attacker can learn a lot from _how long_ it takes to be denied access. If 514 | it's instant, that means the input didn't even pass validation; if it's kinda 515 | long, that means that the input got past validation and computed a hash but one 516 | of the first few characters of the hash were incorrect; a longer delay means 517 | that most of the hash was right. Knowing how much of the hash was right allows 518 | the attacker to narrow the attack space. 519 | 520 | The solution: use a constant-time equality check for comparing the hashed 521 | values. Bcrypt libraries ship with a function that does everything for you. 522 | ActiveSupport ships with [`secure_compare`] for constant-time comparisons. 523 | 524 | Worst case: pad the string to a fixed length then make sure your loop goes 525 | through every character even after you know the answer. 526 | 527 | [`secure_compare`]: https://api.rubyonrails.org/classes/ActiveSupport/SecurityUtils.html#method-c-secure_compare 528 | 529 | ## Multi-factor authentication (2FA) 530 | 531 | Given an email and password, you can authenticate as a user any time you wish. 532 | If someone were to mistakenly use the same password for multiple services, it is 533 | as strong as the least secure of those services: if the password were leaked, 534 | the password for all of those accounts would be leaked along with it. 535 | 536 | We can mitigate these kinds of attacks by requiring a second security factor -- 537 | for example, a second password. We can go further by defining different 538 | categories of authentication: 539 | 540 | - Something you know, such as a string of letters. 541 | - Something you are, such as biometrics. 542 | - Something you have, such as a phone. 543 | 544 | We can use a HOTP or TOTP algorithm to send and verify short codes out of band 545 | to something the user has, such as via email, SMS, an external program, or a 546 | hardware key. [Email and SMS have known security issues], as we'll discuss 547 | later, so lean on an external program or hardware when possible. 548 | 549 | [email and sms have known security issues]: https://www.makeuseof.com/tag/two-factor-authentication-sms-apps/ 550 | 551 | ### OTP 552 | 553 | The HMAC-based One-Time Password (HOTP) algorithm, [RFC 4226], is a somewhat 554 | straightforward function. The details can be found in the RFC but in summary it 555 | works like this: the client and server communicate a shared secret (typically 556 | via QR code). Whenever you need a one-time password, the shared secret is 557 | combined with an incrementing number, hashed, and then six digits are pulled 558 | out. Those six digits are the one-time password. 559 | 560 | Where can one get an incrementing number that both the client and server know 561 | about? We can use the number of minutes since the epoch. This gives us the 562 | Time-based One-Time Password (TOTP) algorithm, [RFC 6238]. 563 | 564 | Most languages have a library for handling OTP. Ruby's is called `rotp`. As 565 | always, use the library instead of implementing it yourself. 566 | 567 | In practice it goes like this: 568 | 569 | 1. Generate a secret. Store this for the user. 570 | 2. Present the secret to the user as a QR code and optionally as a string. The 571 | user will use an app to scan the QR code into the OTP app. Some example apps 572 | are Google Authenticator and Duo, but the algorithm is simple enough that any 573 | app will do. 574 | 3. Prompt the user for an OTP. If they confirm correctly, enable 2FA for them 575 | through this method. 576 | 4. Next time they sign in, prompt them for an OTP generated by their app. 577 | Confirm it by comparing against the OTP calculated on the server. 578 | 579 | Note that the app can run entirely offline: it works by adding a secret key 580 | locally and computing an OTP from a combination of hashing functions. However, 581 | the counter value (e.g. minutes since epoch) must remain in sync between the 582 | server and client. Typically this means using NTP. If debugging, check the time 583 | first. 584 | 585 | Also note that TOTP is using the minute as the counter. If the client computes 586 | the OTP at 12:30:59 and the server computes the OTP at 12:31:02, it will compute 587 | a different value. The RFC recommends that the server accept OTP values for any 588 | time over the past 30 seconds or the future 30 seconds, to account for latency 589 | and drift. 590 | 591 | [rfc 4226]: https://tools.ietf.org/html/rfc4226 592 | [rfc 6238]: https://tools.ietf.org/html/rfc6238 593 | 594 | ### Communicating an OTP 595 | 596 | An external app (Google Authenticator, 1Password, or a command-line tool) is the 597 | safest easy option for the client: the only point of attack is when the secret 598 | is initially communicated, and otherwise using it is offline and out of band. 599 | 600 | Sending an OTP from the server is less secure since it provides a window of 601 | attack each time the user authenticates. If you can send it securely, such as 602 | via an encrypted email or over Signal, that will reduce the attack. 603 | 604 | Plain text emails are open to the public and can be read or spoofed by anyone, 605 | making them effectively useless for communicating a one-time password. 606 | 607 | Using SMS is [categorised as "RESTRICTED" by NIST][nist], since they are open to 608 | exploitation from a variety of methods (e.g device swap, SIM swap, number 609 | porting, etc). 610 | 611 | Sending a one-time password via SMS is more secure than only a single form of 612 | authentication. 613 | 614 | [nist]: https://pages.nist.gov/800-63-3/sp800-63b.html#pstnOOB 615 | 616 | ## Rate-limiting 617 | 618 | An attacker could try many passwords, OTP codes, emails, or any kind of input 619 | to compromise your system. 620 | 621 | An attacker could also request slow endpoints of your application to make it 622 | use its limited-resources. 623 | 624 | Tools like [rack-attack](https://github.com/rack/rack-attack) can help you 625 | minimize this attack surface. 626 | --------------------------------------------------------------------------------