Repository: thoughtbot/guides
Branch: main
Commit: 11e9e5b2122d
Files: 80
Total size: 172.7 KB
Directory structure:
gitextract_ro1iws4k/
├── .github/
│ └── workflows/
│ ├── linting.yml
│ └── main.yml
├── .gitignore
├── .hound.yml
├── .markdownlint-cli2.jsonc
├── .tool-versions
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── README.md
├── _template/
│ ├── README.md
│ └── how-to/
│ ├── do-something-else.md
│ └── do-something.md
├── accessibility/
│ └── README.md
├── android/
│ ├── README.md
│ └── android_layout.xml
├── bash/
│ └── README.md
├── code-review/
│ └── README.md
├── css/
│ └── README.md
├── data/
│ └── README.md
├── email/
│ └── README.md
├── erb/
│ ├── README.md
│ └── sample.html.erb
├── general/
│ └── README.md
├── git/
│ └── README.md
├── graphql/
│ └── README.md
├── html/
│ └── README.md
├── ios/
│ └── README.md
├── javascript-typescript/
│ ├── .eslintrc.json
│ ├── README.md
│ └── sample.js
├── lefthook.yml
├── mise.toml
├── object-oriented-design/
│ └── README.md
├── open-source/
│ └── README.md
├── package.json
├── postgres/
│ └── README.md
├── product-review/
│ └── README.md
├── python/
│ └── README.md
├── rails/
│ ├── README.md
│ ├── ai-rules/
│ │ ├── CLAUDE.md
│ │ └── rules/
│ │ ├── controllers.md
│ │ ├── database.md
│ │ ├── models.md
│ │ ├── security.md
│ │ ├── testing.md
│ │ └── views.md
│ ├── how-to/
│ │ ├── deploy_a_rails_app_to_heroku.md
│ │ ├── feature_test_javascript_in_a_rails_app.md
│ │ ├── seed-data.md
│ │ └── start_a_new_rails_app.md
│ ├── migration.rb
│ └── sample.rb
├── react/
│ └── README.md
├── react-native/
│ └── README.md
├── relational-databases/
│ └── README.md
├── ruby/
│ ├── .rubocop.yml
│ ├── Limit-use-of-conditional-modifiers-to-short-simple-cases.md
│ ├── README.md
│ ├── Use-an-opinionated-set-of-rules-for-Rubocop.md
│ ├── how-to/
│ │ └── release_a_ruby_gem.md
│ ├── sample_1.rb
│ └── sample_2.rb
├── sass/
│ ├── .stylelintrc.json
│ ├── README.md
│ └── sample.scss
├── security/
│ ├── README.md
│ ├── application.md
│ └── protecting-personal-or-identifying-information.md
├── shell/
│ └── README.md
├── swift/
│ ├── README.md
│ └── sample.swift
├── tech-stack/
│ └── README.md
├── testing-jest/
│ └── README.md
├── testing-rspec/
│ ├── README.md
│ ├── acceptance_test_spec.rb
│ ├── avoid_let_spec.rb
│ ├── predicate_tests_spec.rb
│ └── unit_test_spec.rb
├── web/
│ └── README.md
└── web-performance/
└── README.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/linting.yml
================================================
on: [pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DavidAnson/markdownlint-cli2-action@v19
with:
globs: |
./**/*.md
================================================
FILE: .github/workflows/main.yml
================================================
name: update-templates
on:
push:
branches:
- main
workflow_dispatch:
jobs:
update-templates:
permissions:
contents: write
pull-requests: write
pages: write
uses: thoughtbot/templates/.github/workflows/dynamic-readme.yaml@main
secrets:
token: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
.obsidian
node_modules
================================================
FILE: .hound.yml
================================================
coffeescript:
enabled: false
eslint:
enabled: true
config_file: javascript-typescript/.eslintrc.json
version: 6.3.0
haml:
enabled: true
config_file: haml/haml.yml
javascript:
enabled: false
rubocop:
enabled: true
config_file: ruby/.rubocop.yml
version: 0.72.0
scss:
enabled: false
stylelint:
enabled: true
config_file: sass/.stylelintrc.json
================================================
FILE: .markdownlint-cli2.jsonc
================================================
{
// https://github.com/DavidAnson/markdownlint/blob/v0.32.1/README.md#rules--aliases
// https://github.com/DavidAnson/markdownlint-cli2/blob/main/test/markdownlint-cli2-jsonc-example/.markdownlint-cli2.jsonc
// https://github.com/DavidAnson/markdownlint-cli2/blob/main/schema/markdownlint-config-schema.json
"$schema": "https://raw.githubusercontent.com/DavidAnson/markdownlint-cli2/refs/heads/main/schema/markdownlint-cli2-config-schema.json",
"gitignore": true,
"config": {
/* MD001 - Heading levels should only increment by one level at a time */
"heading-increment": true,
/* MD003 - Heading style */
"heading-style": true,
/* MD004 - Unordered list style */
"ul-style": true,
/* MD005 - Inconsistent indentation for list items at the same level */
"list-indent": true,
/* MD007 - Unordered list indentation */
"ul-indent": true,
// { "indent": 2, "start_indented": false },
/* MD009 - Trailing spaces */
"no-trailing-spaces": true,
/* MD010 - Hard tabs */
"no-hard-tabs": true,
/* MD011 - Reversed link syntax */
"no-reversed-links": true,
/* MD012 - Multiple consecutive blank lines */
"no-multiple-blanks": true,
/* MD013 - Line length */
// NOTE: Disabled to allow lines of any length; could be enabled in a separate PR after discussion
"line-length": false,
/* MD014 - Dollar signs used before commands without showing output */
"commands-show-output": true,
/* MD018 - No space after hash on atx style heading */
"no-missing-space-atx": true,
/* MD019 - Multiple spaces after hash on atx style heading */
"no-multiple-space-atx": true,
/* MD020 - No space inside hashes on closed atx style heading */
"no-missing-space-closed-atx": true,
/* MD021 - Multiple spaces inside hashes on closed atx style heading */
"no-multiple-space-closed-atx": true,
/* MD022 - Headings should be surrounded by blank lines */
"blanks-around-headings": true,
/* MD023 - Headings must start at the beginning of the line */
"heading-start-left": true,
/* MD024 - Multiple headings with the same content */
"no-duplicate-heading": {
"siblings_only": true
},
/* MD025 - Multiple top-level headings in the same document */
"single-title": true,
/* MD026 - Trailing punctuation in heading */
"no-trailing-punctuation": true,
/* MD027 - Multiple spaces after blockquote symbol */
"no-multiple-space-blockquote": true,
/* MD028 - Blank line inside blockquote */
"no-blanks-blockquote": true,
/* MD029 - Ordered list item prefix */
"ol-prefix": true,
/* MD030 - Spaces after list markers */
"list-marker-space": true,
/* MD031 - Fenced code blocks should be surrounded by blank lines */
"blanks-around-fences": true,
/* MD032 - Lists should be surrounded by blank lines */
"blanks-around-lists": true,
/* MD033 - Inline HTML */
"no-inline-html": {
"allowed_elements": ["dl", "dt", "dd", "kbd", "details"]
},
/* MD034 - Bare URL used */
"no-bare-urls": true,
/* MD035 - Horizontal rule style */
"hr-style": true,
/* MD036 - Emphasis used instead of a heading */
"no-emphasis-as-heading": true,
/* MD037 - Spaces inside emphasis markers */
"no-space-in-emphasis": true,
/* MD038 - Spaces inside code span elements */
"no-space-in-code": true,
/* MD039 - Spaces inside link text */
"no-space-in-links": true,
/* MD040 - Fenced code blocks should have a language specified */
"fenced-code-language": true,
/* MD041 - First line in a file should be a top-level heading */
"first-line-heading": true,
/* MD042 - No empty links */
"no-empty-links": true,
/* MD043 - Required heading structure */
"required-headings": true,
/* MD044 - Proper names should have the correct capitalization */
"proper-names": true,
/* MD045 - Images should have alternate text (alt text) */
"no-alt-text": true,
/* MD046 - Code block style */
"code-block-style": true,
/* MD047 - Files should end with a single newline character */
"single-trailing-newline": true,
/* MD048 - Code fence style */
"code-fence-style": true,
/* MD049 - Emphasis style */
"emphasis-style": true,
/* MD050 - Strong style */
"strong-style": true,
/* MD051 - Link fragments should be valid */
"link-fragments": true,
/* MD052 - Reference links and images should use a label that is defined */
"reference-links-images": {
"shortcut_syntax": false
},
/* MD053 - Link and image reference definitions should be needed */
"link-image-reference-definitions": true,
/* MD054 - Link and image style */
"link-image-style": true
}
}
================================================
FILE: .tool-versions
================================================
node latest
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Code of Conduct
By participating in this project, you agree to abide by the [thoughtbot code of
conduct].
[thoughtbot code of conduct]: https://thoughtbot.com/open-source-code-of-conduct
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
We love contributions from everyone. By participating in this project, you agree
to abide by the [thoughtbot Code Of Conduct].
We expect everyone to follow the code of conduct anywhere in thoughtbot's
project codebases, issue trackers, chatrooms, mailing lists, meetups, at other events, and in-person.
Violators will be warned by the core team.
Repeat violations will result in being blocked or banned by the core team at or before the 3rd violation.
[thoughtbot code of conduct]: https://thoughtbot.com/open-source-code-of-conduct
## Getting Feedback
Since these are our guides, we want everyone at thoughtbot to see them. We have
a lot of people across a lot of timezones, so we leave all PRs open for at
least a week to get feedback from everyone.
## Content
Decisions about which libraries to use should live in template projects such as
[Suspenders].
[suspenders]: https://github.com/thoughtbot/suspenders
### The Anatomy of a Guide
Whether you're creating a new guide or adding to an existing one, you can
reference [the template guide](/_template/) if you're unsure where to put
things.
================================================
FILE: README.md
================================================
# Guides
Guides for working together, getting things done, programming well, and
programming in style.
## High level guidelines
- Be consistent.
- Don't rewrite existing code to follow this guide.
- Don't violate a guideline without a good reason.
- A reason is good when you can convince a teammate.
## A note on the language
- "Avoid" means don't do it unless you have good reason.
- "Don't" means there's never a good reason.
- "Prefer" indicates a better option and its alternative to watch out for.
- "Use" is a positive instruction.
## Guides by category
- [thoughtbot Tech Stack](/tech-stack/)
- [General](/general/)
### Collaboration
- [Code Review](/code-review/)
- [Open Source](/open-source/)
- [Product Review](/product-review/)
### Protocols
- [Accessibility](/accessibility/)
- [Data](/data/)
- [Email](/email/)
- [Object-Oriented Design](/object-oriented-design/)
- [Security](/security/)
- [Web](/web/)
- [Web Performance](/web-performance/)
### Languages
- [Bash](/bash/)
- [CSS](/css/)
- [ERB](/erb/)
- [HTML](/html/)
- [JavaScript & TypeScript](/javascript-typescript/)
- [Python](/python/)
- [Ruby](/ruby/)
- [Sass](/sass/)
- [Shell](/shell/)
- [Swift](/swift/)
### Frameworks and platforms
- [Android](/android/)
- [iOS](/ios/)
- [Rails](/rails/)
- [React](/react/)
- [React Native](/react-native/)
- [Testing with Jest](/testing-jest/)
- [Testing with RSpec](/testing-rspec/)
### Tools
- [Git](/git/)
- [GraphQL](/graphql/)
- [Postgres](/postgres/)
- [Relational Databases](/relational-databases/)
## Contributing
Please read the [contribution guidelines](/CONTRIBUTING.md) before submitting a
pull request.
In particular: **if you have commit access, please don't merge changes without
waiting a week for everybody to leave feedback**.
## Credits
Thank you,
[contributors](https://github.com/thoughtbot/guides/graphs/contributors)!
## License
Guides is © 2020-2025 thoughtbot, inc. It is distributed under the [Creative
Commons Attribution License](http://creativecommons.org/licenses/by/3.0/).
<!-- START /templates/footer.md -->
## About thoughtbot

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