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/). ## About thoughtbot ![thoughtbot](https://thoughtbot.com/thoughtbot-logo-for-readmes.svg) 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 ================================================ 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 ================================================ ================================================ 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: ` 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`.
#### 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
### 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.
#### 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.
## 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
Data Engineering
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.
Data Science
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.
Batch Processing
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.
Data Streaming
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.
Real-time
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.
Parallel Processing
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.
Concurrent Processing
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.
Constant Memory
Processing a stream where the amount of memory required does not increase with the size of the stream.
At Least Once Delivery
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.
At Most Once Delivery
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.
Distributed Data Processing
Breaking up data into partitions so that large amounts of data can be processed by many machines simultaneously.
Cluster
Several computers (or virtual machines) grouped together to perform a single task.
Scala
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.
Type Safety
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.
Spark
A distributed computing engine for big data and data streams. Spark is a Scala-focused framework for data engineering and data science.
Kafka
A distributed commit log for data streams. Many of the large data systems deployed today use Kafka.
Record Stream
A stream where each message is an independent, unique record which does not replace a previous record in the stream.
Changelog Stream
A stream where each message represents the latest state for a particular entity.
Topic
In Kafka, a partitioned, append-only log of messages which can be consumed in order by partition.
Partition
In Kafka, a way of breaking the messages of a topic into groups which can be consumed in parallel by one or more workers.
Queue
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.
Consumer
An application or process that reads from a data stream.
Producer
An application or process that writes to a data stream.
================================================ 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 ``` 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 ``` 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 ./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 ``` View a list of new commits. View changed files. Merge branch into `main`. ```console git log origin/main.. git diff --stat origin/main git checkout main git merge --ff-only git push ``` Delete your remote feature branch. ```console git push origin --delete ``` Delete your local feature branch. ```console git branch --delete ``` [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 `` 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": "[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](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/