├── .gitignore
├── javascript
├── EDGE-CASES.md
└── README.md
├── git
├── demo-branch.jpg
├── hotfix-branch.png
├── GitWorkflowDiagram.png
├── commits.md
├── README.md
└── README-es.md
├── python
├── images
│ ├── drf-spectacular-1.png
│ └── drf-spectacular-2.png
└── cookiecutter-django.md
├── .github
├── pull_request_template.md
└── ISSUE_TEMPLATE
│ └── new-discussion.md
├── node
├── FAQ.md
└── README.md
├── code-review
├── pr-template.md
└── README.md
├── ruby
├── rspec
│ ├── README.md
│ ├── tutorial
│ │ └── let-vs-let.md
│ ├── tutorial.md
│ └── style_guide.md
└── rails.md
├── css.md
├── pair_programming.md
├── open-source
├── OSS_README_example.md
├── README.md
├── rootstrap_contributing_guide.md
└── developing_gems.md
├── README.md
├── pull-request
└── README.md
├── infrastructure
└── Heroku.md
├── flutter
└── README.md
└── kotlin
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/javascript/EDGE-CASES.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/git/demo-branch.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/tech-guides/HEAD/git/demo-branch.jpg
--------------------------------------------------------------------------------
/git/hotfix-branch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/tech-guides/HEAD/git/hotfix-branch.png
--------------------------------------------------------------------------------
/git/GitWorkflowDiagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/tech-guides/HEAD/git/GitWorkflowDiagram.png
--------------------------------------------------------------------------------
/python/images/drf-spectacular-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/tech-guides/HEAD/python/images/drf-spectacular-1.png
--------------------------------------------------------------------------------
/python/images/drf-spectacular-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rootstrap/tech-guides/HEAD/python/images/drf-spectacular-2.png
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | [Discussion Link](https://github.com/rootstrap/tech-guides/issues/NUMBER)
2 |
3 | **Description:**
4 |
--------------------------------------------------------------------------------
/node/FAQ.md:
--------------------------------------------------------------------------------
1 | # Node.JS FAQ
2 |
3 | ## Table of Contents
4 |
5 | 1. [When should I use node?](#1-node-usage)
6 | 2. [What version of Node should I pick? Why?](#2-node-versions)
7 | 3. [Typed vs Not typed](#3-node-typing)
8 | 4. [Node's Event Loop](#4-event-loop)
9 |
10 |
11 |
--------------------------------------------------------------------------------
/code-review/pr-template.md:
--------------------------------------------------------------------------------
1 | ## Name Of the PR
2 |
3 | #### Trello board reference:
4 |
5 | * [Trello Card #]()
6 |
7 | ---
8 |
9 | #### Description:
10 |
11 | *
12 |
13 | ---
14 |
15 | #### Reviewers:
16 |
17 | *
18 |
19 | ---
20 |
21 | #### Notes:
22 |
23 | *
24 |
25 | ---
26 |
27 | #### Tasks:
28 |
29 |
30 | ---
31 |
32 | #### Risk:
33 |
34 | *
35 |
36 | ---
37 |
38 | #### Preview:
39 |
40 | * N/A
41 |
--------------------------------------------------------------------------------
/ruby/rspec/README.md:
--------------------------------------------------------------------------------
1 | # RSpec guides
2 |
3 | RSpec is the most used testing tool in Ruby that we use here at Rootstrap.
4 | You can check our guides:
5 |
6 | * Do you want to know how to write specs? Check [RSpec Tutorial/Guide](tutorial.md)
7 |
8 | * Maintaining specs could be hard when the amount of tests and developers that work/worked in the project increases, so we have to stick to a standard, check our [RSpec Style Guide](style_guide.md)
9 |
--------------------------------------------------------------------------------
/javascript/README.md:
--------------------------------------------------------------------------------
1 | # Js Style Guide
2 |
3 | - Check well known guide [Javascript Airbnb](https://github.com/airbnb/javascript)
4 |
5 | ## Table of Contents
6 |
7 | 1. [General Concepts](#1-general-concepts-best-practices)
8 | 2. [Scopes](#2-scopes-practices)
9 | 3. [Functions](#3-functions-best-practices)
10 | 4. [Objects & Prototypes](#4-objects-prototypes-best-practices)
11 | 5. [Arrays & Dictionaries](#5-arrays-dictionaries-best-practices)
12 | 6. [Concurrency](#6-concurrency-best-practices)
13 | 7. [Advanced](#7-advanced-best-practices)
14 | 8. [Code Quality and Complexity Management](#8-quality-complexity)
15 |
16 |
17 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/new-discussion.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: New discussion
3 | about: Start a new discussion
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Affected Guide:**
11 | A link to the guide/s that will receive changes.
12 |
13 | **Describe your motive to discuss:**
14 | A clear and concise description of what is motivating you to start this discussion. Add examples if possible.
15 |
16 | **Describe the solution you propose**
17 | A clear and concise description of what you propose.
18 |
19 | **Describe alternatives you've considered**
20 | A clear and concise description of any alternative solutions you've considered.
21 |
22 | **Additional context**
23 | Add any other context or screenshots here.
24 |
--------------------------------------------------------------------------------
/git/commits.md:
--------------------------------------------------------------------------------
1 | Rules of a great Git commit message
2 | ===========
3 |
4 | - Give a useful message (self-descriptive), avoid messages like: "Fix PR or bug"
5 | - Limit the subject line to 50 characters
6 | - Capitalize the subject line
7 | - Do not end the subject line with a period
8 | - Use the imperative mood in the subject line, that is as if you were commanding someone. Start the line with "Fix", "Add", "Change" instead of "Fixed", "Added", "Changed".
9 | - Use the body to explain what and why vs. how
10 |
11 | Good commit messages serve at least three important purposes:
12 |
13 | - To help the future maintainers, say five years into the future, to find out why a particular change was made to the code or why a specific feature was added.
14 | - To help us write a good release note.
15 | - To speed up the reviewing process.
16 |
--------------------------------------------------------------------------------
/css.md:
--------------------------------------------------------------------------------
1 | # The CSS Style Guide
2 |
3 | We follow https://cssguidelin.es. In the css naming convention, we are aligned with the Hyphen Delimited convention instead of BEM-like one.
4 | For SASS, we follow https://sass-guidelin.es/
5 |
6 | ## CSS Units
7 |
8 | Refer to this table to decide which unit to use in each case.
9 |
10 | | Unit | Use for |
11 | |:-----------|:-----------------------------------------------------------------|
12 | | rem | font-size, dimensions (margin, padding, height, width) |
13 | | % | width (responsive containers) |
14 | | px | small details (borders, corners, shadows, images), media queries |
15 | | *unitless* | line-height |
16 |
17 | More info:
18 |
19 | - https://gist.github.com/janogarcia/8c140ff9294a285830a0
20 | - https://gist.github.com/basham/2175a16ab7c60ce8e001
--------------------------------------------------------------------------------
/pair_programming.md:
--------------------------------------------------------------------------------
1 | # Pair Programming
2 |
3 | **Before:**
4 |
5 | - Agree Scope (Pairing for two hours? Until the ticket is complete? Just get past the bug?)
6 | - Agree Physical/Virtual Location ("Will we both be comfortable here?")
7 | - Agree Working Environment (Two keyboards? Text editor and other tools)
8 | - Agree Pairing Style (Time-based? Ping pong?)
9 |
10 | **During:**
11 |
12 | - Keep the chat going (Think aloud. Encourage/support)
13 | - Keep switching (Follow the pairing style)
14 | - Keep both involved ("Could we do this another way?")
15 | - Keep breaking ([Have a break, have a…](https://www.youtube.com/watch?v=fejBO1HZXVQ))
16 | - Keep checking in ("Could we search for a guide separately?")
17 |
18 | **After:**
19 |
20 | - Ask for Feedback (What did we do well? What could we have done better? It’ll feel weird, do it anyway)
21 |
22 | [Guide](https://github.com/thoughtbot/guides/tree/master/working-together) by thoughtbot, inc. / [CC BY](https://creativecommons.org/licenses/by/3.0/)
23 |
--------------------------------------------------------------------------------
/open-source/OSS_README_example.md:
--------------------------------------------------------------------------------
1 | # [lib_name]
2 |
3 |
4 | 
5 | [](https://codeclimate.com/github/rootstrap/[lib_name]/maintainability)
6 | [](https://codeclimate.com/github/rootstrap/[lib_name]/test_coverage)
7 |
8 | Description
9 |
10 | ## Prerequisites
11 | - Install something
12 |
13 | ## Installation
14 | - Run some command
15 |
16 | ## Usage
17 | - Do something
18 |
19 | ## Contributing
20 | Bug reports (please use Issues) and pull requests are welcome on GitHub at https://github.com/rootstrap/[lib_name]. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
21 |
22 | ## License
23 | The library is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
24 |
25 | ## Credits
26 | **[lib_name]** is maintained by [Rootstrap](http://www.rootstrap.com) with the help of our [contributors](https://github.com/rootstrap/[lib_name]/contributors).
27 |
28 | [
](http://www.rootstrap.com)
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rootstrap Guides
2 |
3 | This repository contains several guidelines that document processes and standards followed by our entire organization. We hope they may serve as a recommendation to anyone interested in following our quality standards.
4 | Guidelines are based on our own experiences and other companies' best practices.
5 | The guidelines exploit Git's advantages with regards to collaborative work, encouraging all of our developers to get actively involved with them.
6 |
7 | ### General
8 |
9 | - [Code review](./code-review)
10 | - [Git Workflow](./git)
11 | - [Git commits](./git/commits.md)
12 | - [Open Source](./open-source/README.md)
13 | - [Pair Programming](./pair_programming.md)
14 | - [Opening a Pull Request](./pull-request/README.md)
15 |
16 | ### Languages
17 |
18 | - [Ruby](./ruby)
19 | - [Swift](https://rootstrap.github.io/swift/)
20 | - [Javascript](./javascript)
21 | - [CSS](./css.md)
22 | - [Python](./python)
23 | - [Kotlin](./kotlin)
24 |
25 | ### Frameworks/Libraries
26 |
27 | - [Ruby on Rails](./ruby/rails.md)
28 | - [Angular 1](https://github.com/johnpapa/angular-styleguide/blob/master/a1)
29 | - [Angular 2+](https://angular.io/guide/styleguide)
30 | - [React](https://www.notion.so/rootstrap/Rootstrap-React-Guidelines-3a7f9ada653e44a185a729b437970a7b?pvs=4)
31 | - [RSpec](./ruby/rspec/README.md)
32 | - [Django](./python/cookiecutter-django.md)
33 | - [Node.Js](./node/README.md)
34 | - [Flutter](./flutter/README.md)
35 |
36 | ### Hosting Platforms
37 |
38 | - [Heroku](./infrastructure/Heroku.md)
39 |
40 | If you disagree with a guideline, open an issue on the guides repo rather than
41 | debating it within the code review. In the meantime, apply the guideline.
42 |
--------------------------------------------------------------------------------
/ruby/rspec/tutorial/let-vs-let.md:
--------------------------------------------------------------------------------
1 | # let vs let!
2 |
3 | Understanding the difference between `let` and `let!` is fundamental when starting to learn RSpec.
4 |
5 | Use `let` to define a memoized helper method. The value will be cached
6 | across multiple calls in the same example but not across examples.
7 |
8 | Note that `let` is lazy-evaluated. That means that the expression is not evaluated until the first time it is invoked from somewhere else. You can use `let!` to force the expression's invocation before each example.
9 |
10 | ### When should `let!` be used?
11 | - When there's a need to trigger an insertion into the database before running an example block.
12 | - An alternative to `let!` is invoking the variable from a `before` or `subject` block, or even from inside an example. This practice is discouraged.
13 |
14 | ### Example
15 |
16 | #### Bad
17 | ```
18 | let!(:user) { create(:user) }
19 |
20 | let!(:membership) { create(:membership, user: user) }
21 | ```
22 |
23 | #### Good
24 | ```
25 | let(:user) { create(:user) }
26 |
27 | let!(:membership) { create(:membership, user: user) }
28 | ```
29 | ---
30 | #### Bad
31 | ```
32 | let!(:params) do
33 | {
34 | email: 'user@example.com',
35 | password: 'password'
36 | }
37 | end
38 | ```
39 |
40 | #### Good
41 | ```
42 | let(:params) do
43 | {
44 | email: 'user@example.com',
45 | password: 'password'
46 | }
47 | end
48 | ```
49 | Take a look at `params`. The expression is defining in-memory attributes, so there's no need to use `!`.
50 |
51 | ---
52 |
53 | In conclusion, prefer the use of `let` unless there is a compelling reason to choose `let!`.
54 | ### Resources:
55 | - [Let and let!](https://relishapp.com/rspec/rspec-core/v/2-5/docs/helper-methods/let-and-let)
56 |
--------------------------------------------------------------------------------
/open-source/README.md:
--------------------------------------------------------------------------------
1 | # Open Source Recommendations
2 |
3 | Rootstrap enjoys being a part of the open source community and considers that open source is good for everyone. By embracing open source, it enables collaboration for solving specific problems and helps in reducing bugs.
4 |
5 | This is a guide for publishing and maintaining open source projects, check the Github guidelines for Open Source:
6 | - [Getting started](https://opensource.guide/starting-a-project/)
7 | - [How To contribute](https://opensource.guide/how-to-contribute/)
8 | - [Best practices for maintainers](https://opensource.guide/best-practices/)
9 | - [Code of Conduct](https://opensource.guide/code-of-conduct/)
10 |
11 | **Make sure to have:**
12 | - License (default: [MIT](https://opensource.org/licenses/MIT))
13 | - Code of Conduct (default: [Contributor Covenant](https://www.contributor-covenant.org/))
14 | - Contributing: We follow the [Rootstrap Contributing Guide](./rootstrap_contributing_guide.md)
15 | - Continuous Integration (default: [Github Actions](https://github.com/features/actions))
16 | - Code Quality tools (default: [CodeClimate](https://codeclimate.com))
17 | - Readme with: Description, Prerequisites/Installation, Usage, Company credits and links to items above (template: [README example](./OSS_README_example.md))
18 | - Follow [semver](https://semver.org/) for good versioning. Start with version `0.1` and once it has been successfully used in at least one project upgrade to `1.0`. Generate a new git tag when releasing a new version.
19 | - For libs use the `master` branch not the `develop` branch as we'd normally do with apps.
20 | - Add https://rootstrap.com as the repo website and remove unused tabs and sections
21 | -
22 |
23 |
24 | **The items listed below are nice to have for the repository but aren't mandatory:**
25 | - Add Changelog
26 | - Blocked main branch for force push
27 |
28 | For ruby specific details check [developing Gems](./developing_gems.md)
29 |
--------------------------------------------------------------------------------
/open-source/rootstrap_contributing_guide.md:
--------------------------------------------------------------------------------
1 | # Rootstrap Contributing Guide
2 |
3 | You can contribute to our open source projects if you have an issue, found a bug or think there's some functionality required that would add value to the gem. To do so, please check if there's not already an [issue](https://github.com/rootstrap/{repository}/issues) for that, if you find there's not, create a new one with as much detail as possible.
4 |
5 | If you want to contribute with code as well, please follow the next steps:
6 |
7 | 1. Read, understand and agree to our [code of conduct](https://github.com/rootstrap/{repository}/blob/master/CODE_OF_CONDUCT.md)
8 | 2. [Fork the repo](https://help.github.com/articles/about-forks/)
9 | 3. Clone the project into your machine:
10 | `$ git clone git@github.com:rootstrap/{repository}.git`
11 | 4. Access the repo:
12 | `$ cd {repository}`
13 | 5. Create your feature/bugfix branch:
14 | `$ git checkout -b your_new_feature`
15 | or
16 | `$ git checkout -b fix/your_fix` in case of a bug fix
17 | (if your PR is to address an existing issue, it would be good to name the branch after the issue, for example: if you are trying to solve issue 182, then a good idea for the branch name would be `182_your_new_feature`)
18 | 6. Write tests for your changes (feature/bug)
19 | 7. Code your (feature/bugfix)
20 | 8. Run the code analysis tool by doing:
21 | `$ rake code_analysis`
22 | 9. Run the tests:
23 | `$ bundle exec rspec`
24 | All tests must pass. If all tests (both code analysis and rspec) do pass, then you are ready to go to the next step:
25 | 10. Commit your changes:
26 | `$ git commit -m 'Your feature or bugfix title'`
27 | 11. Push to the branch `$ git push origin your_new_feature`
28 | 12. Create a new [pull request](https://help.github.com/articles/creating-a-pull-request/)
29 |
30 | Some helpful guides that will help you know how we work:
31 | 1. [Code review](https://github.com/rootstrap/tech-guides/tree/master/code-review)
32 | 2. [GIT workflow](https://github.com/rootstrap/tech-guides/tree/master/git)
33 | 3. [Ruby style guide](https://github.com/rootstrap/tech-guides/tree/master/ruby)
34 | 4. [Rails style guide](https://github.com/rootstrap/tech-guides/blob/master/ruby/rails.md)
35 | 5. [RSpec style guide](https://github.com/rootstrap/tech-guides/blob/master/ruby/rspec/README.md)
36 |
37 | For more information or guides like the ones mentioned above, please check our [tech guides](https://github.com/rootstrap/tech-guides). Keep in mind that the more you know about these guides, the easier it will be for your code to get approved and merged.
38 |
39 | Note: You can push as many commits as you want when working on a pull request, we just ask that they are descriptive and tell a story. Do try to open a pull request with just one commit but if you think you need to divide what you did into more commits to convey what you are trying to do, go for it.
40 |
41 |
42 | Thank you very much for your time and for considering helping in our projects.
43 |
--------------------------------------------------------------------------------
/pull-request/README.md:
--------------------------------------------------------------------------------
1 | # Opening a Pull Request(PR)
2 |
3 | Think in your PR as something you are selling to your fellow developers, you the author are the seller, and the reviewers are the customers. You want to be as effective as possible in demostrating the value of your code, and why it is needed. This guide will try to help you accomplish that.
4 |
5 | 1. Define scope (What are you tackling on this PR?):
6 |
7 | **User Stories/Features:**
8 |
9 | The maximum scope of a PR should be a complete User Story.
10 | However, sometimes a single User Story may result in too many changes in the code base making your PR too
11 | difficult to review and once reviewed, too difficult to address all the comments you received. So if your PR is going to be bigger than 400 lines (for most techs), or whatever is the maximum allowed in the tech you're working,
12 | a better approach is to break the User Story even further.
13 |
14 | For example, we get this User Story: "As a user, I want to be able log in to the app."
15 | This User Story can be implemented in different PRs to facilitate review, as follows:
16 |
17 | - "As a user, I want to be able to enter my email and password into a log in form and hit the submit button"
18 | - "As a user I should see errors if the email or password I entered are incorrect"
19 | - "As a user I should be logged in to the system if I entered correct email and password"
20 |
21 |
22 | **Bug fixes/Minor tasks:**
23 |
24 | It may sound like a good idea to mix together a lot of small bug fixes in a single PR, yet this will result on reviewers having a hard time identifying the topic of your PR or even, understanding what is it about.
25 | A good practice would be to either have one PR per bug fix or -in some cases- have a few bug fixes that are related with each other in a single PR.
26 |
27 |
28 | 2. Implement the needed code changes:
29 | If tackling a big/complex feature is considered good practice to discuss the approach with teammates and tech colleagues that will be reviewing your code later.
30 | 3. Run/test your code manually to make sure it works and all the scoped functionality works as expected without breaking pre-existent functionality.
31 | 4. Make sure your Unit Tests, Automated Tests, etc document all the changes.
32 | 5. Make sure that pre-existent tests still run correctly.
33 | 6. Use descriptive messages in your commits.
34 | 7. Add any tags you consider relevant to your PR.
35 | 8. Choose a PR title that describes the main intent of it.
36 | 9. Add a description enumerating: Issues fixed, user stories addressed and any relevant information for the reviewer. This might include screenshots and/or gifs showcasing the intended feature.
37 | 10. Update status of User Story and link it to the PR.
38 | 11. Request a review for at least two developers familiar to the technology or code-base.
39 | If there are reviewers assigned to your project make sure to request a review from them.
40 |
--------------------------------------------------------------------------------
/open-source/developing_gems.md:
--------------------------------------------------------------------------------
1 | # Gems
2 |
3 | ## Creating Gems/Rails Engines
4 |
5 | When creating a plain Ruby Gem use the [Rootstrap's gem generator]([https://github.com/rootstrap/rsgem](https://github.com/rootstrap/rsgem)):
6 |
7 | ```sh
8 |
9 | $ gem install rsgem
10 | $ rsgem new [name]
11 |
12 | ```
13 |
14 | Or use the Bundler command:
15 |
16 | ```sh
17 |
18 | $ bundle gem [name]
19 |
20 | ```
21 |
22 | When creating a Rails Engine use the Rails command:
23 |
24 | ```sh
25 |
26 | $ rails plugin new [name] --mountable
27 |
28 | ```
29 |
30 |
31 |
32 | ## Gem version
33 |
34 | Keep gem version in a constant and use it like this:
35 |
36 | ```ruby
37 |
38 | spec.version = [name]::VERSION
39 |
40 | ```
41 |
42 |
43 |
44 | ## Dependencies versions
45 |
46 | Take into account to restrict not only the lower required version of dependencies but also the upper supported version so to not break apps if any of your dependencies changes their API. Normally you should fix just to the major version.
47 |
48 |
49 |
50 | ## Code quality tools
51 |
52 | Make sure to add:
53 |
54 | - `Rubocop`
55 |
56 | - `Reek`
57 |
58 |
59 |
60 | ## Gemfile/Gemfile.lock/gemspec
61 |
62 | - `Gemfile` should only have the `gemspec` directive. An exception is when you need to develop against a gem that hasn't yet been released, in this case you can declare that dependency in the Gemfile:
63 |
64 | ```ruby
65 |
66 | gem 'rack', github: 'rack/rack'
67 |
68 | ```
69 |
70 | - `Gemfile.lock` should be gitignored when developing gems.
71 |
72 | - `gemspec` is the place to declare dependencies.
73 |
74 |
75 |
76 | ## Testing
77 |
78 | Make the default rake task to run every intended check (tests, code analysis).
79 |
80 | ```sh
81 |
82 | bundle exec rake
83 |
84 | ```
85 |
86 |
87 |
88 | For testing against different versions of `gem` dependencies you should add a `gemfiles` folder and inside it declare each separate `Gemfile` which can be then used by the CI.
89 |
90 | ## Release a new Gem version
91 |
92 | To be able to publish or update your gem in RubyGems, you first need to create a release and tag for your version.
93 | You can follow this steps:
94 | 1. Create a new branch for release purpose.
95 | 1. Update your gem `VERSION`.
96 | 1. Make sure your gemspec `spec.version` uses this new version.
97 | 1. You can also open a PR if your changes include more than just the version number.
98 | 1. Once your branch is merged, create a tag.
99 | ```sh
100 | $ git tag v[x.x.x] # Create a tag for version
101 | $ git push origin v[x.x.x] # Push the tag to Github
102 | ```
103 |
104 | ## Publishing in RubyGems
105 |
106 | First create an account on [RubyGems](https://rubygems.org/sign_up).
107 | Then follow the next steps:
108 |
109 | ```sh
110 |
111 | $ gem build [gem-name].gemspec # Build the gem
112 | $ gem push [gem-build].gem # Push the gem to RubyGems
113 | $ gem owner --add rootstrap [gem-name] # Add Rootstrap as an Owner
114 |
115 | ```
116 |
--------------------------------------------------------------------------------
/node/README.md:
--------------------------------------------------------------------------------
1 | # Node.JS Style Guide
2 |
3 | ## Table of Contents
4 |
5 | 1. [Project Scaffolding & Structure](#1-project-structure-practices)
6 | 2. [Error Handling](#2-error-handling-practices)
7 | 3. [Code Style Practices](#3-code-style-practices)
8 | 4. [Security Practices](#4-security-best-practices)
9 | 5. [Performance Practices](#5-performance-best-practices)
10 | 6. [Express Specifics](#6-express-specifics-best-practices)
11 | 7. [Code Quality and Complexity Management](#8-quality-complexity)
12 |
13 |
14 |
15 | # `4. Security Best Practices`
16 |
17 | ## 4.1. Use Helmet
18 |
19 | It protects your app from some well-known web vulnerabilities by setting HTTP headers appropriately.
20 | Helmet is actually just a collection of smaller middleware functions that set security-related HTTP response headers, like:
21 |
22 | - CSP (Content-Security-Policy) header to prevent cross-site scripting attacks.
23 | - hidePoweredBy removes the X-Powered-By header.
24 | - ieNoOpen sets X-Download-Options for IE8+.
25 |
26 | **Otherwise:** Your app will be vulnerable to some well known attacks.
27 |
28 | 🔗 **READ MORE:** https://expressjs.com/en/advanced/best-practice-security.html#use-helmet
29 |
30 |
31 |
32 | ## 4.2. How to decide which Node.js version?
33 |
34 | Don’t use deprecated or vulnerable versions of the framework you are using.
35 | A good way to go is to always use the last stable version.
36 |
37 | **Otherwise:** You could have security issues and no support working on them
38 |
39 | 🔗 **READ MORE (Express.js specific):** https://expressjs.com/en/advanced/best-practice-security.html#dont-use-deprecated-or-vulnerable-versions-of-express
40 |
41 |
42 |
43 | # `5. Performance Best Practices`
44 |
45 | ## 5.1. Use gzip compression
46 |
47 | Gzip compressing can greatly decrease the size of the response body and hence increase the speed of a web app.
48 |
49 | **Otherwise:** You could face long waiting times in large paylods/high-traffic web apps.
50 |
51 | **GTK:** For a high-traffic website in production, the best way to put compression in place is to implement it at a reverse proxy level (see Use a reverse proxy). In that case, you do not need to use compression middleware.
52 |
53 |
54 |
55 | ## 5.2. Do not use console.log
56 |
57 | Not for the obvious reasons, but because console.log is synchronous when the destination is a terminal or a file, so it is not suitable for production unless you pipe the output to another program.
58 |
59 | **GTK:** Use a more mature logger like Winston or Bunyan
60 |
61 | 🔗 **READ MORE:** https://strongloop.com/strongblog/compare-node-js-logging-winston-bunyan/
62 |
63 |
64 |
65 | ## 5.3. Set NODE_ENV='production' for Production Environment (Express.js specific)
66 |
67 | Setting NODE_ENV to “production” makes Express:
68 |
69 | - Cache view templates.
70 | - Cache CSS files generated from CSS extensions.
71 | - Generate less verbose error messages.
72 |
73 | 🔗 **READ MORE:** https://www.dynatrace.com/news/blog/the-drastic-effects-of-omitting-node-env-in-your-express-js-applications/
74 |
75 |
76 |
--------------------------------------------------------------------------------
/code-review/README.md:
--------------------------------------------------------------------------------
1 | Code Review
2 | ===========
3 |
4 | A guide for reviewing code and having your code reviewed.
5 |
6 | Everyone
7 | ----
8 |
9 | - Accept that many programming decisions are opinions. Discuss tradeoffs, which you prefer, and reach a resolution quickly.
10 | - Ask questions; don't make demands. ("What do you think about naming this `:user_id`?")
11 | - Ask for clarification. ("I didn't understand. Can you clarify?")
12 | - Avoid selective ownership of code. ("mine", "not mine", "yours")
13 | - Avoid using terms that could be seen as referring to personal traits. ("dumb",
14 | "stupid"). Assume everyone is attractive, intelligent, and well-meaning.
15 | - Be explicit. Remember people don't always understand your intentions online.
16 | - Be humble. ("I'm not sure - let's look it up.")
17 | - Don't use hyperbole. ("always", "never", "endlessly", "nothing")
18 | - Don't use sarcasm.
19 | - Keep it real. If emoji, animated gifs, or humor aren't you, don't force them. If they are, use them with aplomb.
20 | - Talk in person if there are too many "I didn't understand" or "Alternative solution:" comments. Post a follow-up comment summarizing offline discussion.
21 |
22 | Having Your Code Reviewed
23 | ----
24 |
25 | - Add 2 reviewers to the project (in some explicit cases could be more), the reviewers should have the expertise needed, ideally one of them has business knowledge of the project.
26 | - Add a good description to the PR, it helps the reviewers to understand the code and the context of it. You can follow [Pull Request Format](pr-template.md)
27 | - Add screenshots with the PR description if there are visual changes, gifs or before/after images are also appreciated but not mandatory.
28 | - Use labels to identify the PR status (ask to an repo admin if there are a missing label), some of them are: On hold and Awaiting info.
29 | - Be grateful for the reviewer's suggestions. ("Good call. I'll make that change.")
30 | - Don't take it personally. The review is of the code, not you.
31 | - 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?")
32 | - Link to the code review from the ticket/story. ("Ready for review: https://github.com/organization/project/pull/1")
33 | - 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.
34 | - Seek to understand the reviewer's perspective.
35 | - One of the reviewers or you can merge once:
36 | - Continuous Integration (CircleCi, Github Actions, etc) tells you the test suite is green in the branch.
37 | - Two reviewers approved the PR
38 |
39 | Reviewing Code
40 | ----
41 |
42 | Understand why the change is necessary (fixes a bug, improves the user
43 | experience, refactors the existing code). Then:
44 |
45 | - Communicate which ideas you feel strongly about and those you don't.
46 | - Identify ways to simplify the code while still solving the problem.
47 | - 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.
48 | - Offer alternative implementations, but assume the author already considered them. ("What do you think about using a custom validator here?")
49 | - Seek to understand the author's perspective.
50 | - Sign off on the pull request with the approving tool of Github/Bitbucket .
51 | - Use and reference the recommended style guide of the technology.
52 |
--------------------------------------------------------------------------------
/ruby/rspec/tutorial.md:
--------------------------------------------------------------------------------
1 | # RSpec Tutorial/Guide
2 |
3 | RSpec is a testing tool for Ruby, it is the most frequently used testing library in Ruby applications, and it was the source of inspiration for testing tools in other languages like jasmine and mocha. This tutorial will aim to steer you into the path of testing and show you the different ways of testing.
4 |
5 | There are some key parts in an RSpec test:
6 | * `describe`: it is an example group, usually groups the tests of a method or class.
7 | * `it`: the atom of the tests, it should contain one test/expectation.
8 | * `before`: hook that executes before a group of tests, depending where is located, if it’s located inside a describe or context, it will be executed before the tests inside the describe or context. By default runs before each test, but you can pass the option `:all` to run before all of them only. It’s usually used for testing setup
9 | * `after`: same as before, but after the tests, it’s usually used for teardown setup.
10 | * `context`: same behavior as `describe` but it’s used for a semantic purpose: each block of tests inside the context behaves in a certain way or under certain conditions. As an example, you can check it in model specs.
11 | * `let`: it's a helper method for variables. The value will be cached across multiple calls in the same example but not across examples. By default `let` variables are lazy. [Read more - let vs let!](./tutorial/let-vs-let.md).
12 |
13 | ## Model specs
14 | They are straightforward unit tests which tests small parts of the system, they shouldn’t test private methods, just model’s methods and callbacks. These unit tests follows the same rules as services, jobs, mailers and any other ruby classes, except controllers that needs some setup.
15 |
16 | ```
17 | describe User do
18 | describe '#full_name' do
19 | let(:user) { create(:user) }
20 |
21 | it 'returns the correct name' do
22 | expect(user.full_name).to eq("#{user.first_name} #{user.last_name}")
23 | end
24 | end
25 |
26 | describe '.search' do
27 | let(:user) { create(:user) }
28 |
29 | context 'with the right term' do
30 | it 'returns a user' do
31 | expect(User.search(user.name)).to include user
32 | end
33 | end
34 |
35 | context 'with another term' do
36 | it 'returns 0 users' do
37 | expect(User.search('whatever')).to be_empty
38 | end
39 | end
40 | end
41 | end
42 | ```
43 |
44 | As you can see, instance methods are described with `# `and class methods with `.` .
45 |
46 | ## Feature specs
47 | Feature specs are integration tests that aim to cover all the application flow, it starts when a user does something in the UI (like clicking a button) and finishes when the user sees the response (like an alert). Thus they are written from the user viewpoint, but in some cases you can check the Model/DB if there aren’t a visible response or to make sure the app is working as expected.
48 |
49 | We use Capybara for interacting with the browser and PhantomJS (through the ruby driver poltergeist) or webkit to test pages with js. You can take a look at the capybara’s GitHub page [about drivers](https://github.com/teamcapybara/capybara#drivers). If your test doesn't need Javascript in order to verify the behavior, don't enable it. An example of feature test is:
50 |
51 | ```
52 | describe 'User creates a task' do
53 | before do
54 | visit new_task_path
55 | end
56 |
57 | it 'can create a task' do
58 | fill_in 'Title', with: 'My new task'
59 | fill_in 'Description', with: 'I need to do the laundry'
60 | click_button 'Create Task'
61 |
62 | expect(page).to have_content 'The task was created'
63 | end
64 | end
65 | ```
66 |
67 | Also, Capybara comes with a built-in DSL for creating descriptive acceptance tests, where the previous code can be rewritten as:
68 |
69 | ```
70 | feature 'User creates a task' do
71 | background do
72 | visit new_task_path
73 | end
74 |
75 | scenario 'can create a task' do
76 | fill_in 'Title', with: 'My new task'
77 | fill_in 'Description', with: 'I need to do the laundry'
78 | click_button 'Create Task'
79 |
80 | expect(page).to have_content 'The task was created'
81 | end
82 | end
83 | ```
84 |
85 | Both ways are accepted but the first one is the most used in the company because the other types of testings use the same structure. We recommend checking [this guide](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara) that brings some tips at the time of doing feature testing.
86 |
87 |
88 | ## Controller specs
89 | Although feature specs are great for acceptance tests, they are slower to run. So leave the edge case tests to your model or controller. However, don't use them for API test, you should be using requests specs for that. An example of it is the following:
90 |
91 | ```
92 | describe RegistrationsController do
93 | describe 'POST create' do
94 | let(:email) { 'email@example.com' }
95 | let(:password) { 'somepassword' }
96 |
97 | it 'redirects to the dashboard' do
98 | post :create, { email: email, password: password }
99 | expect(response).to redirect_to '/dashboard'
100 | end
101 |
102 | it 'creates an user' do
103 | expect do
104 | post :create, { email: email, password: password }
105 | end.to change(User, :count).by(1)
106 | end
107 |
108 | it 'shows a successfully flash' do
109 | post :create, { email: email, password: password }
110 | expect(flash[:notice]).to match(/You can start using the app!/)
111 | end
112 | end
113 | end
114 | ```
115 |
116 | ## Request specs
117 | Requests specs are the integration tests for APIs (but way faster), they are designed to drive behavior through the full stack. We recommend doing requests specs over controller specs for APIs because the routes and real responses are covered. An example of them are:
118 |
119 | ```
120 | describe 'POST api/v1/users/sign_in', type: :request do
121 | let(:user) { create(:user, password: password) }
122 | let(:params) do {
123 | user:
124 | {
125 | email: user.email,
126 | password: password
127 | }
128 | }
129 | end
130 |
131 | context 'with correct params' do
132 | before do
133 | post new_user_session_path, params: params, as: :json
134 | end
135 |
136 | it 'returns success' do
137 | expect(response).to be_success
138 | end
139 |
140 | it 'returns the user' do
141 | expect(json[:user][:id]).to eq(user.id)
142 | expect(json[:user][:email]).to eq(user.email)
143 | end
144 | end
145 | end
146 | ```
147 |
148 | ## Testing tools
149 | We use some tools that help and improve the testing experience, they are:
150 |
151 | * [FactoryBot](https://github.com/thoughtbot/factory_bot): You can use `Product.create` in your test, but the setup of it could be tedious to do everywhere, so we prefer moving the data setup to one place: the factories. You can achieve this with Rails fixtures, but you don’t have too much control over them.
152 | * [Database Cleaner](https://github.com/DatabaseCleaner/database_cleaner): The idea behind it is to maintain a clean state between tests, so you don't have to worry about any data left by a previous test.
153 | * [Faker](https://github.com/stympy/faker): It’s a library for generating fake data such as names, addresses, and phone numbers.
154 | * [Webmock](https://github.com/bblimke/webmock)/[vcr](https://github.com/vcr/vcr): It’s a library for stubbing HTTP request, you shouldn’t interact with real APIs during tests because you can reach API’s quota or make unexpected changes to real data.
155 |
--------------------------------------------------------------------------------
/infrastructure/Heroku.md:
--------------------------------------------------------------------------------
1 | # Heroku Hosting Guideline
2 |
3 | ## Intro
4 |
5 | Heroku is the PaaS platform of choice for Rootstrap, even as, in order to support larger projects and offer greater value by reducing our customer's infrastructure costs we are driven to adopt larger cloud providers, primarily AWS in its multiple offerings: plain IaaS (EC2), PaaS (Elastic Beanstalk), or container-based platforms (ECS/EKS).
6 | This guide establishes a criteria for choosing Heroku as hosting platform for new projects and overall best practices around it.
7 |
8 | ### Table of Contents
9 |
10 | - [Intro](#intro)
11 | - [Pros](#pros)
12 | - [Cons](#cons)
13 | - [Heroku vs AWS](#heroku-vs-aws)
14 | - [When to use Heroku](#when-to-use-heroku)
15 | - [When to move to AWS](#when-to-move-to-aws)
16 | - [Comparison Tools](#estimation-and-comparison-tools)
17 | - [Best Practices](#best-practices)
18 |
19 |
20 | ### Pros
21 | * Very easy to get started
22 | * Free tier extends including most AddOns supports cheap PoC and MVP environments
23 | * Quick fixes based on customer feedback are deployed immediately
24 | * Setup can be implemented End2End without Linux Admin or DevOps expertise
25 |
26 | ### Cons
27 | * Cost rise quickly after basic usage tiers
28 | * No automated scalability out of the box (Addon solutions are also expensive)
29 | * Not really feasible for very compute-intensive projects
30 | * Deployments can be slow for larger apps
31 | * Not all tech stacks supported
32 |
33 |
34 | ## Heroku vs AWS
35 |
36 | ### When to use Heroku
37 |
38 | In order to deploy a new project into Heroku, most of these conditions should be met:
39 |
40 | * Initial scope of the project is just an MVP
41 | * Project will have low resource requirements (RAM, CPU) even in Production short/medium-term
42 | * The project is built on a well-known and fully supported stack, eg.
43 | * Language:
44 | * Python: Django/Flask
45 | * Ruby: Rails 4+
46 | * Node.js 12+
47 | * Database:
48 | * PostgreSQL 11+
49 | * In-memory data store:
50 | * Redis 5
51 | * Email:
52 | * Sendgrid
53 | * Mailgun
54 | * Logging:
55 | * Papertrail
56 | * There are no DevOps hours allocated to the project
57 | * There is no available [Reference Architecture](https://www.notion.so/rootstrap/AWS-Architecture-5e8083e3968a45de9e240885a31921be) for AWS that fits the needs of the project
58 |
59 | ### When to move to AWS
60 |
61 | As a rule of thumb, when total ownership costs for the project in Heroku exceeds or are expected to exceed $350/month, it is worthwhile to invest in migrating to AWS.
62 | This is the estimated cost for an application including:
63 | * A Production environment with:
64 | * 4 (2 web, 2 workers) `Standard 2X` instances (1GB RAM, 2x CPU share)
65 | * 1 `Standard 0` PostgreSQL instance (4GB RAM, 64 GB storage, 120-connection limit)
66 | * 1 `Premium 1` Redis instance (100MB memory, 80-connection limit)
67 | * 1 `Tough Tiger` RabbitMQ instance (10 million messages/month)
68 | * 1 `Fixa` Papertrail instance (65MB/day, 7-day indexing, 365-day archiving)
69 | * 1 `Bronze` Sendgrid instance (40k emails/momth)
70 | * Small dev team (up to 5 collaborators)
71 | * Standard Heroku Support plan
72 | * A Staging environment with Hobby-level instances ($7/month) and free-tier data addons, with occasional upscaling (demo sessions, workshops, load testing)
73 |
74 | This budget is just above a comparable setup in AWS, using dedicated (prod) and shared (staging) EC2 and RDS instances with on-demand purchasing + SES for email, including network traffic costs, EBS-backed storage, CloudWatch basic monitoring, with Developer-tier AWS support plan.
75 |
76 | In addition, implementing recommended Reference Architectures in AWS should allow to reduce these costs, eg. by sharing larger instances and by combining reserved purchasing + spot purchasing for burst or async workloads
77 |
78 | Scaling to further capacity, high-availability setups or greater storage needs quickly expands the cost difference between platforms.
79 |
80 | ### Estimation and comparison tools
81 | Basic instance pricing: https://www.heroku.com/pricing
82 |
83 | Add-ons: https://elements.heroku.com/addons
84 |
85 | Amazon cost calculator tool: https://calculator.s3.amazonaws.com/index.html
86 |
87 | Amazon EC2/RDS instance comparison chart: https://www.ec2instances.info/
88 |
89 | ## Best Practices
90 |
91 | Most configuration principles that apply to any cloud platform are valid here and should be elaborated further on the DevOps Hub. Some items which are particularly relevant for Heroku are worth mentioning here.
92 |
93 | #### Concurrent web servers
94 | Web applications that process concurrent requests make more efficient use of dyno resources than those that only process one request at a time.
95 |
96 | Official recommendations from Heroku are:
97 | * Ruby: [Puma](https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server)
98 | * Python: [Gunicorn](https://devcenter.heroku.com/articles/python-gunicorn)
99 |
100 | #### Officially supported buildpacks and tools
101 | Heroku has official buildpacks, among other languages, for:
102 | * [Ruby](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-ruby)
103 | * Rails 4.x and higher
104 | * Use Bundler for dependency management
105 | * [Python](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-python)
106 | * supported runtimes: 3.6, 3.7, 2.7(not recommended)
107 | * Django or Flask framwork
108 | * Celery for async tasks
109 | * Any other pip-installable packages are supported, except some with C dependencies
110 | * [Node](https://elements.heroku.com/buildpacks/heroku/heroku-buildpack-nodejs)
111 | * supported runtimes: 13.x, 12.x, 10.x(not recommended)
112 | * dependencies installed from package.json using Yarn or npm
113 |
114 | #### Separate environments
115 | When maintaining more than one environment on Heroku (eg. Development, Test, Demo, Staging, Production), each should be configured as a separate app.
116 |
117 | If there are multiple repos that belong to the same project, each component should be deployed as an independent app with their own environment-specific deploys.
118 |
119 | [Pipelines](https://devcenter.heroku.com/articles/pipelines) can be used to promote the same build between environments, **except** if the application has a stateful build, ie compiles configuration variables into the deployed artifact (which regardled of the platform or deployment method must be avoided if at all possible).
120 |
121 | #### Continuous deployment from GitHub
122 | Configure GitHub integration for Heroku apps to enable automatic deploy upon specified branches: https://devcenter.heroku.com/articles/github-integration
123 |
124 | Integrations should be configured in a manner consistent with the [Git Workflow guide](../git/README.md), eg.
125 | * Every PR merged into `develop` should automatically be deployed to Development env
126 | * Deploy demo branches to specific environment for this purpose (Staging or Demo)
127 | * Deploy Releases (from either `develop` or `master`) automatically to Staging environment
128 | * Trigger deploys to Production manually and from release tags in `master` branch or by promoting the latest release on Staging using a pipeline.
129 |
130 | #### S3 for static files
131 |
132 | Application dynos have ephemeral filesystems, meaning a solution is required for hosting static durable files (eg React frontend apps).
133 |
134 | Amazon S3 is the preferred platform for serving static files due to its very low price, high availability and durability, and caching capabilities (using CloudFront).
135 |
136 | All supported languages in Heroku can use the S3 API for handling file uploads: https://devcenter.heroku.com/articles/s3
137 |
--------------------------------------------------------------------------------
/flutter/README.md:
--------------------------------------------------------------------------------
1 | # Flutter Style Guide
2 |
3 | This Flutter style guide recommends best practices so that real-world Flutter
4 | programmers can write code that can be maintained by other real-world Flutter
5 | programmers. A style guide that reflects real-world usage gets used, while a
6 | style guide that holds to an ideal that has been rejected by the people it is
7 | supposed to help risks not getting used at all—no matter how good it is.
8 |
9 | ## Table of Contents
10 |
11 | * [Identifiers](#identifiers)
12 | * [Formatting](#formatting)
13 | * [Performance](#performance)
14 | * [Tests](#tests)
15 |
16 | ### Identifiers
17 | Identifiers come in three flavors in Dart.
18 | UpperCamelCase names capitalize the first letter of each word, including the first.
19 | lowerCamelCase names capitalize the first letter of each word, except the first which is always lowercase, even if it’s an acronym.
20 | lowercase_with_underscores names use only lowercase letters, even for acronyms, and separate words with _.
21 |
22 | - DO name types using UpperCamelCase.
23 | Linter rule: camel_case_types
24 | Classes, enum types, typedefs, and type parameters should capitalize the first letter of each word (including the first word), and use no separators.
25 | ```
26 | class SliderMenu { ... }
27 | class HttpRequest { ... }
28 | typedef Predicate = bool Function(T value);
29 | ```
30 |
31 | This even includes classes intended to be used in metadata annotations.
32 | ```
33 | class Foo {
34 | const Foo([Object? arg]);
35 | }
36 |
37 | @Foo(anArg)
38 | class A { ... }
39 |
40 | @Foo()
41 | class B { ... }
42 | ```
43 |
44 | If the annotation class’s constructor takes no parameters, you might want to create a separate lowerCamelCase constant for it.
45 | ```
46 | const foo = Foo();
47 | @foo
48 | class C { ... }
49 | ```
50 |
51 | - DO name extensions using UpperCamelCase.
52 | Linter rule: camel_case_extensions
53 | Like types, extensions should capitalize the first letter of each word (including the first word), and use no separators.
54 | ```
55 | extension MyFancyList on List { ... }
56 | extension SmartIterable on Iterable { ... }
57 | ```
58 |
59 | - DO name import prefixes using lowercase_with_underscores.
60 | Linter rule: library_prefixes
61 | Good:
62 | ```
63 | import 'dart:math' as math;
64 | import 'package:angular_components/angular_components.dart' as angular_components;
65 | import 'package:js/js.dart' as js;
66 | ```
67 |
68 | Bad:
69 | ```
70 | import 'dart:math' as Math;
71 | import 'package:angular_components/angular_components.dart' as angularComponents;
72 | import 'package:js/js.dart' as JS;
73 | ```
74 |
75 | - DO name other identifiers using lowerCamelCase.
76 | Linter rule: non_constant_identifier_namesClass members, top-level definitions, variables, parameters, and named parameters should capitalize the first letter of each word except the first word, and use no separators.
77 | ```
78 | var count = 3;
79 | HttpRequest httpRequest;
80 | void align(bool clearItems) {
81 | // ...
82 | }
83 | ```
84 |
85 | - PREFER using lowerCamelCase for constant names.
86 | Linter rule: constant_identifier_names
87 | In new code, use lowerCamelCase for constant variables, including enum values.
88 | ```
89 | const pi = 3.14;
90 | const defaultTimeout = 1000;
91 | final urlScheme = RegExp('^([a-z]+):');
92 | class Dice {
93 | static final numberGenerator = Random();
94 | }
95 | ```
96 |
97 | - DO capitalize acronyms and abbreviations longer than two letters like words.
98 | Capitalized acronyms can be hard to read, and multiple adjacent acronyms can lead to ambiguous names. For example, given a name that starts with HTTPSFTP, there’s no way to tell if it’s referring to HTTPS FTP or HTTP SFTP.
99 | To avoid this, acronyms and abbreviations are capitalized like regular words.
100 |
101 | ```
102 | class HttpConnection {}
103 | class DBIOPort {}
104 | class TVVcr {}
105 | class MrRogers {}
106 | var httpRequest = ...
107 | var uiHandler = ...
108 | var userId = ...
109 | Id id;
110 | ```
111 |
112 | - PREFER using _, __, etc. for unused callback parameters.
113 | Sometimes the type signature of a callback function requires a parameter, but the callback implementation doesn’t use the parameter. In this case, it’s idiomatic to name the unused parameter _. If the function has multiple unused parameters, use additional underscores to avoid name collisions: __, ___, etc.
114 |
115 | ```
116 | futureOfVoid.then((_) {
117 | //...
118 | });
119 | ```
120 |
121 | - DON’T use prefix letters.
122 | Hungarian notation and other schemes arose in the time of BCPL, when the compiler didn’t do much to help you understand your code. Because Dart can tell you the type, scope, mutability, and other properties of your declarations, there’s no reason to encode those properties in identifier names.
123 | ```
124 | Good: defaultTimeout
125 | Bad: kDefaultTimeout
126 | ```
127 |
128 | ### Formatting
129 | Like many languages, Dart ignores whitespace. However, humans don’t. Having a consistent whitespace style helps ensure that human readers see code the same way the compiler does.
130 |
131 | - AVOID lines longer than 80 characters:
132 | Linter rule: lines_longer_than_80_chars
133 | Readability studies show that long lines of text are harder to read because your eye has to travel farther when moving to the beginning of the next line. This is why newspapers and magazines use multiple columns of text.
134 |
135 | If you really find yourself wanting lines longer than 80 characters, our experience is that your code is likely too verbose and could be a little more compact. The main offender is usually VeryLongCamelCaseClassNames. Ask yourself, “Does each word in that type name tell me something critical or prevent a name collision?” If not, consider omitting it.
136 |
137 | Note that dart format does 99% of this for you, but the last 1% is you. It does not split long string literals to fit in 80 columns, so you have to do that manually.
138 |
139 | Exception: When a URI or file path occurs in a comment or string (usually in an import or export), it may remain whole even if it causes the line to go over 80 characters. This makes it easier to search source files for a path.
140 |
141 | Exception: Multi-line strings can contain lines longer than 80 characters because newlines are significant inside the string and splitting the lines into shorter ones can alter the program.
142 |
143 | - DO use curly braces for all flow control statements:
144 | Linter rule: curly_braces_in_flow_control_structures
145 | Doing so avoids the dangling else problem.
146 | ```
147 | if (isWeekDay) {
148 | print('Bike to work!');
149 | } else {
150 | print('Go dancing or read a book!');
151 | }
152 | ```
153 |
154 | Exception: When you have an if statement with no else clause and the whole if statement fits on one line, you can omit the braces if you prefer:
155 |
156 | ```
157 | if (arg == null) return defaultValue;
158 | ```
159 |
160 | If the body wraps to the next line, though, use braces:
161 |
162 | ```
163 | if (overflowChars != other.overflowChars) {
164 | return overflowChars < other.overflowChars;
165 | }
166 | ```
167 |
168 | Use raw string
169 | A raw string can be used to not come across escaping only backslashes and dollars.
170 | ```
171 | //Do
172 | var s = r'This is demo string \ and $';
173 |
174 | //Do not
175 | var s = 'This is demo string \\ and \$';
176 | ```
177 |
178 | - Use ternary operator for single-line cases.
179 | ```
180 | String welcomeMessage = !isNewUser ? 'Welcome!' : 'Please sign up!';
181 | ```
182 |
183 | - Always highlight the type of member when its value type is known. Do not use var when it is not required. As var is a dynamic type takes more space and time to resolve.
184 | ```
185 | //Do
186 | int item = 10;
187 | final Car bar = Car();
188 | String name = 'john';
189 | const int timeOut = 20;
190 |
191 | //Do not
192 | var item = 10;
193 | final car = Car();
194 | const timeOut = 2000;
195 | ```
196 |
197 | ### Performance
198 | * Minimize expensive operations:
199 | Some operations are more expensive than others, meaning that they consume more resources. Obviously, you want to only use these operations when necessary. How you design and implement your app’s UI can have a big impact on how efficiently it runs.
200 |
201 | Here are some things to keep in mind when designing your UI:
202 | - Avoid repetitive and costly work in build() methods since build() can be invoked frequently when ancestor widgets rebuild.
203 | - Avoid overly large single widgets with a large build() function. Split them into different widgets based on encapsulation but also on how they change.
204 | - When setState() is called on a State object, all descendent widgets rebuild. Therefore, localize the setState() call to the part of the subtree whose UI actually needs to change. Avoid calling setState() high up in the tree if the change is contained to a small part of the tree.
205 | - The traversal to rebuild all descendents stops when the same instance of the child widget as the previous frame is re-encountered. This technique is heavily used inside the framework for optimizing animations where the animation doesn’t affect the child subtree. See the TransitionBuilder pattern and the source code for SlideTransition, which uses this principle to avoid rebuilding its descendents when animating.
206 | - Always try to use const widgets. The widget will not change when setState call we should define it as constant. It will impede the widget from being rebuilt so it revamps performance.
207 | - To create reusable pieces of UIs, prefer using a StatelessWidget rather than a function.
208 | - Use MediaQuery/LayoutBuilder only when needed.
209 | - Use streams only when needed
210 |
211 | * Using SizedBox instead of Container in Flutter:
212 | There are multiple use cases where you will require to use a placeholder. Here is the ideal example below:
213 | ```
214 | return _isNotLoaded ? Container() : YourAppropriateWidget();
215 | ```
216 |
217 | The main issue here is that Container doesn't have a const constructor and load a lot of business logic on it, on the other hand SizedBox is a const constructor and builds a fixed-size box. The width and height parameters can be null to specify that the size of the box should not be constrained in the corresponding dimension.
218 |
219 | Thus, when we have to implement the placeholder, SizedBox should be used rather than using a container.
220 | ```
221 | return _isNotLoaded ? SizedBox() : YourAppropriateWidget();
222 | ```
223 |
224 | * Minimize use of opacity and clipping
225 | Opacity is another expensive operation, as is clipping. Here are some tips you might find to be useful:
226 | - Use the Opacity widget only when necessary. See the Transparent image section in the Opacity API page for an example of applying opacity directly to an image, which is faster than using the Opacity widget.
227 | - Instead of wrapping simple shapes or text in an Opacity widget, it’s usually faster to just draw them with a semitransparent color. (Though this only works if there are no overlapping bits in the to-be-drawn shape.)
228 | - To implement fading in an image, consider using the FadeInImage widget, which applies a gradual opacity using the GPU’s fragment shader.
229 | - Clipping doesn’t call saveLayer() (unless explicitly requested with Clip.antiAliasWithSaveLayer), so these operations aren’t as expensive as Opacity, but clipping is still costly, so use with caution. By default, clipping is disabled (Clip.none), so you must explicitly enable it when needed.
230 | - To create a rectangle with rounded corners, instead of applying a clipping rectangle, consider using the borderRadius property offered by many of the widget classes.
231 |
232 |
233 | * Implement grids and lists thoughtfully:
234 | How your grids and lists are implemented might be causing performance problems for your app. This section describes an important best practice when creating grids and lists, and how to determine whether your app uses excessive layout passes.
235 | When building a large grid or list, use the lazy builder methods, with callbacks. That ensures that only the visible portion of the screen is built at startup time.
236 |
237 | * Select libraries carefully:
238 | - Before using a third-party library take into account:
239 | Do you relly needed it? It makes more sense to write the code or copy the code than depend on a library.
240 | - When was the package updated? We should always avoid using stale packages.
241 | - How popular is the package? If the package has considerable popularity, it is much easier to find community support.
242 | - How frequently does the package get updated? This is really important if we want to take advantage of the latest Dart features.
243 |
244 | ### Tests
245 | * Write tests for critical functionality
246 | The contingencies of relying on manual testing will always be there, having an automated set of tests can help you save a notable amount of time and effort. As Flutter mainly targets multiple platforms, testing each and every functionality after every change would be time-consuming and call for a lot of repeated effort.
247 |
248 | Let’s face the facts, having 100% code coverage for testing will always be the best option, however, it might not always be possible on the basis of available time and budget. Nonetheless, it’s still essential to have at least tests to cover the critical functionality of the app.
249 |
250 | Unit and widget tests are the topmost options to go with from the very beginning and it’s not at all tedious as compared to integration tests.
251 |
--------------------------------------------------------------------------------
/git/README.md:
--------------------------------------------------------------------------------
1 | # Git Workflow
2 |
3 | We use style guides and standards on a daily basis to help us keep a unified criterion regarding each type of technology. The main aim of this document is to create a git workflow standard for all the different projects in the company.
4 |
5 | There are many advantages that come with the use of the previously mentioned criteria. One of them is that it allows us to have the same nomenclature to make it easy to incorporate new team members as well as keeping a better project organization. Furthermore, it tackles common problems by providing effective solutions. Last but not least, the workflow focuses on keeping a tidy and reliable history that truly reflects the project’s current state.
6 |
7 | Below is listed the document content. Some of the instructions are mandatory and need to be followed throughout the development process of any project. The mandatory specifications are highlighted with a **[Required]** label. Even though some of the directions are not mandatory, they are highly recommended.
8 |
9 |
10 | [Spanish version](./README-es.md)
11 |
12 |
13 | ## Table of Contents
14 |
15 | - [Branches](#branches)
16 | - [Before releasing to production](#before-releasing-to-production)
17 | - [Develop branch (develop)](#develop-branch-develop-required)
18 | - [Staging branch (staging)](#staging-branch-staging-required)
19 | - [QA branch (qa)](#qa-branch-qa-optional)
20 | - [Feature branch (feature/x)](#feature-branch-featurex)
21 | - [Fix branch (fix/x)](#fix-branch-fixx)
22 | - [Enhancement branch (enhancement/x)](#enhancement-branch-enhancementx)
23 | - [Feedback branch (feedback/x)](#feedback-branch-feedbackx)
24 | - [Demo branch (demo)](#demo-branch-demo)
25 | - [Post production release](#post-production-release-)
26 | - [Master branch (master)](#master-branch-master-required)
27 | - [Release branch (release_branch_x)](#release-branch-release_branch_x-required)
28 | - [Integration of a Release branch to master and develop](#integration-of-a-release-branch-to-master-and-develop)
29 | - [Feedback branch (feedback/x)](#feedback-branch-feedbackx-1)
30 | - [Hotfix branch (hotfix/sign-up)](#hotfix-hotfixsign-up-required)
31 | - [Release management](#release-management)
32 | - [Before releasing to production](#before-releasing-to-production-1)
33 | - [After releasing to production](#after-releasing-to-production-required)
34 | - [First release to production](#first-release-to-production)
35 | - [Other releases to production](#other-releases-to-production)
36 | - [Major changes (MAJOR)](#major-changes-major)
37 | - [Minor changes (MINOR)](#minor-changes-minor)
38 | - [Bug fixes (PATCH)](#bug-fixes-patch)
39 |
40 |
41 | # Branches
42 |
43 | ## Before releasing to production
44 |
45 | This section contains aspects related to branch management before releasing to production.
46 |
47 | ### Develop branch (develop) **[Required]**
48 |
49 | This branch is the main one because it reflects the work done by the whole team during the development phase in a project. It is used as the base branch until the app is released to production.
50 |
51 |
52 | * **Origin:** *master* (in the initial project set up this branch is created from master)
53 | * **Destination:** -
54 | * **Use case:** Integration of the work done by the development team.
55 |
56 | ### Staging branch (staging) **[Required]**
57 |
58 | This branch has all the features ready to be tested by the Client.
59 |
60 | If the project has CD configured, every time something is merged into this branch the code will be automatically deployed to the Staging environment for the Client to test.
61 |
62 | * **Origin:** *master* (in the initial project set up this branch is created from master)
63 | * **Destination:** -
64 | * **Use case:** Integration of the work done by the development team which is already tested and approved by the QA team and ready for the Client to review.
65 |
66 | ### QA branch (qa) **[Optional]**
67 |
68 | This branch has all the features ready to be tested by the QA team.
69 |
70 | If the project has CD configured, every time something is merged into this branch the code will be automatically deployed to the QA environment.
71 |
72 | * **Origin:** *master* (in the initial project set up this branch is created from master)
73 | * **Destination:** *staging*
74 | * **Use case:** Integration of the work done by the development team ready for QA team to test.
75 |
76 | **_Note_**: *If a feature merged into the QA branch is not ready to be promoted to the Staging branch, then all needed changes (like: fixes, removing the feature, disabling access to it, etc) should be made in the Develop branch, followed by the promotion of those changes to the QA branch to continue the flow.*
77 |
78 | ### Feature branch (feature/x)
79 |
80 | This branch’s purpose is to develop a new feature. It has all the progress until the functionality being developed is completed. Pull requests are created from these type of branches and once they have passed the code review process, they have to be merged to develop.
81 |
82 | _Example: feature/sign-up_
83 |
84 | * **Origin:** *develop*
85 | * **Destination:** *develop*
86 | * **Use case:** Develop a new feature during a sprint.
87 |
88 |
89 |
90 |
91 |
92 | ### Fix branch (fix/x)
93 |
94 | This branch is created in order to fix a bug during the development phase.
95 |
96 | _Example: fix/sign-up-error-messages_
97 |
98 | * **Origin:** *develop*
99 | * **Destination:** *develop*
100 | * **Use case:** Fix a bug during development
101 |
102 | ### Enhancement branch (enhancement/x)
103 |
104 | This branch is created to add an enhancement during the development phase on something that was already merged to the development branch.
105 |
106 | _Example: enhancement/users-retrievement-query_
107 |
108 | * **Origin:** *develop*
109 | * **Destination:** *develop*
110 | * **Use case:** Add an enhancement during development.
111 |
112 | ### Feedback branch (feedback/x)
113 |
114 | It is used to work on feedback provided by any team member or the PO about a previously developed feature.
115 |
116 | _Example: feedback/sign-up-change-inputs-order_
117 |
118 | * **Origin:** *develop*
119 | * **Destination:** *develop*
120 | * **Use case:** Implement feedback given by the PO or any other team member about a specific feature previously developed
121 |
122 | ### Demo branch (demo)
123 |
124 | This branch has two main aims:
125 |
126 | 1. Avoid interference with code review process
127 | 2. Show the PO all the progress done by the team in a review session
128 |
129 | It is a temporal branch created from develop where all branches that are not ready to be integrated to develop are merged. The only purpose of this branch is to present things in the demo, so it should be immediately discarded after performing it.
130 |
131 | Changes in this branch will be reflected in develop after reviewed and approved in a code review process.
132 |
133 | * **Origin:** *develop*
134 | * **Destination:** - (delete branch)
135 | * **Use case:** Integration of all the features (completed or not) needed in a review session. A PR approved by code review is a requirement to merge a new feature to **_develop_**, sometimes, due to different reasons (incomplete or unreviewed code), new features are not ready on time to be presented to the PO in a demo. In order to solve this inconvenience, a temporal branch is created to be deployed or to create a build from it (depending on technology); there, all the branches to be shown in a review can be merged automatically without the need of a code review.
136 |
137 | If there is a change of any kind in the feature branches in a commit (*git amend*) or in the history (*git rebase*), a new *demo* branch can be created to reflect the change or it can just be merged in the existing branch.
138 |
139 | **_Note_**: *Despite the fact that this branch's main purpose is showing the progress in a review, it is not advised to send a version from that branch. In these cases you should talk to project or company referents.*
140 |
141 | *This branch can be deployed in an environment previous to staging such as development. See: Environment management base on the git workflow (work in progress).*
142 |
143 |
144 |
145 |
146 |
147 | ## Post production release
148 |
149 | This section’s purpose is explaining branch management after releasing to production.
150 |
151 | All the previously mentioned branches must be used in the same way they were used before release, except for those branches which are explained again under this section.
152 |
153 | The main purpose is to keep commits history as clean as possible. As a consequence, classical approaches based on *cherry-pick* and *merge* to keep branches up to date are replaced for the use of *rebase*.
154 |
155 | ### Master branch (master) **[Required]**
156 |
157 | It reflects the current code deployed in production, therefore, it must be stable. All the new features to be developed are going to have as origin *develop* and they are going to be merged to master once they are working as expected and approved by PO in the Staging environment.
158 |
159 | * **Origin:** -
160 | * **Destination:** -
161 | * **Use case:** Reflect production’s current state.
162 |
163 | ### Release branch (release_branch_x) **[Required]**
164 |
165 | Its main objective is to separate new features ready to be released to production from those that either the team or the client do not consider to be ready for release. As a consequence, this branch only contains features that are production ready.
166 |
167 | It should be deployed in a testing environment (usually staging) accessible to the client and all the feedback given by him/her must be included in this branch.
168 |
169 | The timing in which this branch should be created depends on the project and the team. In projects with big teams is convenient to create it when the development of the first feature in the version starts. However, when teams are small it is better to create it once all the features to be released are completed. This is the most common scenario in the company.
170 |
171 | * **Origin:** *develop*
172 | * **Destination:** *master and develop*
173 | * **Use case:** Merge feedback or fixes to code that were released to a testing environment. If there are bugs to fix or feedback to include after the branch has been released to a testing environment, they should be merged to this branch and not to *develop.* In this way, we avoid releasing future features that belong to a different release and were merged to *develop* by another developer in the team. Furthermore, it allows us to keep track of features we will release next.
174 |
175 | #### **Integration of a Release branch to master and develop**
176 |
177 | Once the release branch is ready to go to production it should be included in master and develop as well.
178 |
179 | **Master**
180 |
181 | It is directly included in **master** through a Pull Request created from master. Ideally, every commit in that PR passed through a code review process, consequently, another code review is unnecessary to merge it, the developer should just check everything is correct.
182 |
183 | **Develop**
184 |
185 | It is directly included in **develop** through a Pull Request created from the release branch. Everything said for the master branch applies here too.
186 |
187 | ### Feedback branch (feedback/x)
188 |
189 | This branch is created to work on feedback related to a specific feature.
190 |
191 | _Example: feedback/sign-up-change-inputs-order_
192 |
193 | * **Origin:** *release branch*
194 | * **Destination:** *release branch*
195 | * **Use case:** Feedback from the client or any team member about a feature after its release branch has been deployed to a testing environment.
196 |
197 | ### Hotfix (hotfix/sign-up) **[Required]**
198 |
199 | This branch is created to solve bugs or critical changes that should not be mixed with the current development —*develop*— branch.
200 |
201 | Development bugs should not be confused with these bugs, development bugs should be merged to *develop or a release branch.* Not every bug deserves a hotfix, a hotfix aims to solve a critical bug that directly affects essential system functionality.
202 |
203 | It is the **developer's responsibility** to reflect that hotfix in develop by also submitting a Pull Request from this branch.
204 |
205 | * **Origin:** *master*
206 | * **Destination:** *master and develop*
207 | * **Use case:** Critical changes that affect the expected behaviour of the application in production.
208 |
209 |
210 |
211 |
212 |
213 |
214 | # Release management
215 |
216 | In this section, we explain how releases before and after production should be handled.
217 |
218 | **_What is a release?_**
219 |
220 | It is a link to a new version with notes describing all the changes implemented since the last version.
221 |
222 | These versions are handled with tags which are automatically created with every new release. Tags are immutable pictures of a branch.
223 |
224 | **_Advantages_**
225 |
226 | Version management sprint to sprint or post production is a way of keeping track of all the versions available in staging and specially in production, so we can better organize our work.
227 |
228 | ## Before releasing to production
229 |
230 | Once the sprint is finished and feedback from the client was included, a release is created. See [https://help.github.com/articles/creating-releases/](https://help.github.com/articles/creating-releases/)
231 |
232 | * **Origin:** develop
233 | * **Versión:** v0.x, x being the current sprint number.
234 | * **Title:** Release sprint x, x being the current sprint number.
235 | * **Description:** should contain a list with all the features developed during the sprint.
236 |
237 | _Note: version management before releasing to production is highly recommended but not mandatory._
238 |
239 | ## After releasing to production **[Required]**
240 |
241 | Releases after production are the most important ones because they allow us to have an immutable image of the code in production. In this case, releases are always created from *master*. For example, after merging a hotfix or release branch into master.
242 |
243 | ### First release to production
244 |
245 | The first time the app is released to production, there must be a merge of all the content in *develop* to *master.* Then, the release is created.
246 |
247 | * **Origin:** master
248 | * **Version**: v1.0
249 | * **Title:** Release v1.0
250 | * **Description:** high level details of the features developed
251 |
252 |
253 | ### Other releases to production
254 |
255 | After the first release to production, it is recommendable to use the standard MAJOR.MINOR.PATCH.
256 |
257 | #### Major changes (MAJOR)
258 |
259 | Version in which incompatible changes with the previous version or a big number of features that represent a change at the business level are introduced.
260 |
261 | * **Origin:** master
262 | * **Version:** (vMAJOR + 1).0.0
263 | * **Title:** Release (vMAJOR + 1).0.0
264 | * **Description:** new features and minor bug fixes
265 |
266 | #### Minor changes (MINOR)
267 |
268 | When new functionalities and non-critical bug fixes are released we increase the MINOR value to generate a new version.
269 |
270 | * **Origin:** master
271 | * **Version:** vMAJOR.(MINOR + 1).0
272 | * **Title:** Release vMAJOR.(MINOR + 1).0
273 | * **Description:** new features and minor bug fixes
274 |
275 |
276 | #### Bug fixes (PATCH)
277 |
278 | It contains urgent and non-urgent bug fixes. If bugs do not require immediate action, we wait until some bugs are fixed to release a new version. When a bug is urgent a new release must be created.
279 |
280 | * **Origin:** master
281 | * **Version:** vMAJOR.MINOR.(PATCH + 1)
282 | * **Title:** Release vMAJOR.MINOR.(PATCH + 1)
283 | * **Description:** fixed bugs that are important
284 |
--------------------------------------------------------------------------------
/ruby/rspec/style_guide.md:
--------------------------------------------------------------------------------
1 | # RSpec style guide
2 |
3 | This RSpec style guide outlines our recommended best practices so that our developers can write code that can be maintained by other (future) developers.
4 | This is meant to be a style guide that reflects real-world usage, holding to an ideal that has been agreed upon by many of the people it was intended to be used by.
5 |
6 |
7 | ## Style Guide Rules
8 |
9 | ### Line Returns after `feature`, `context`, or `describe`
10 |
11 | Do not leave line returns after `feature`, `context` or `describe`
12 | descriptions. It makes the code more difficult to read and lowers the
13 | value of logical chunks.
14 |
15 | #### Bad Example
16 |
17 | ```ruby
18 | describe Article do
19 |
20 | describe '#summary' do
21 |
22 | context 'when there is a summary' do
23 |
24 | it 'returns the summary' do
25 | # ...
26 | end
27 | end
28 | end
29 | end
30 | ```
31 |
32 | #### Good Example
33 |
34 | ```ruby
35 | describe Article do
36 | describe '#summary' do
37 | context 'when there is a summary' do
38 | it 'returns the summary' do
39 | # ...
40 | end
41 | end
42 | end
43 | end
44 | ```
45 |
46 | ### `let`, `subject`, and `before`/`after` group line returns
47 |
48 | Leave one line return after `let`, `subject`, and `before`/`after` blocks.
49 |
50 | #### Bad Example
51 |
52 | ```ruby
53 | describe Article do
54 | subject { create(:some_article) }
55 | describe '#summary' do
56 | # ...
57 | end
58 | end
59 | ```
60 |
61 | #### Good Example
62 |
63 | ```ruby
64 | describe Article do
65 | subject { create(:some_article) }
66 |
67 | describe '#summary' do
68 | # ...
69 | end
70 | end
71 | ```
72 |
73 | ### `let`, `subject`, `before`/`after` grouping
74 |
75 | Only group `let`, `subject` blocks and separate them from `before`/`after`
76 | blocks. It makes the code much more readable.
77 |
78 | #### Bad Example
79 |
80 | ```ruby
81 | describe Article do
82 | subject { create(:some_article) }
83 | let(:user) { create(:user) }
84 | before do
85 | # ...
86 | end
87 | after do
88 | # ...
89 | end
90 | describe '#summary' do
91 | # ...
92 | end
93 | end
94 | ```
95 |
96 | #### Good Example
97 |
98 | ```ruby
99 | describe Article do
100 | subject { create(:some_article) }
101 | let(:user) { create(:user) }
102 |
103 | before do
104 | # ...
105 | end
106 |
107 | after do
108 | # ...
109 | end
110 |
111 | describe '#summary' do
112 | # ...
113 | end
114 | end
115 | ```
116 |
117 | ### `it` block line returns
118 |
119 | Leave one line return around `it` blocks. This helps to separate the
120 | expectations from their conditional logic (contexts for instance).
121 |
122 | #### Bad Example
123 |
124 | ```ruby
125 | describe '#summary' do
126 | let(:item) { double('something') }
127 |
128 | it 'returns the summary' do
129 | # ...
130 | end
131 | it 'does something else' do
132 | # ...
133 | end
134 | it 'does another thing' do
135 | # ...
136 | end
137 | end
138 | ```
139 |
140 | #### Good Example
141 |
142 | ```ruby
143 | describe '#summary' do
144 | let(:item) { double('something') }
145 |
146 | it 'returns the summary' do
147 | # ...
148 | end
149 |
150 | it 'does something else' do
151 | # ...
152 | end
153 |
154 | it 'does another thing' do
155 | # ...
156 | end
157 | end
158 | ```
159 |
160 | ### `before(:each)`or `before`
161 |
162 | There is no need to specify `(:each)` for `before`/`after` blocks, as it is
163 | the default functionality. The expression `before(:all)` should be rarely used,
164 | but if you find a case where it is necessary, just write it out as `before(:all)`
165 |
166 | #### Bad Example
167 |
168 | ```ruby
169 | describe '#summary' do
170 | before(:each) do
171 | subject.summary = 'something'
172 | end
173 | end
174 | ```
175 |
176 | #### Good Example
177 |
178 | ```ruby
179 | describe '#summary' do
180 | before do
181 | subject.summary = 'something'
182 | end
183 | end
184 | ```
185 |
186 | ### 'should' it or 'should not' in `it` statements
187 |
188 | Do not write 'should' or 'should not' at the beginning of your `it` blocks.
189 | The descriptions represent actual functionality - not what might be happening.
190 |
191 | #### Bad Example
192 |
193 | ```ruby
194 | it 'should return the summary' do
195 | # ...
196 | end
197 | ```
198 |
199 | #### Good Example
200 |
201 | ```ruby
202 | it 'returns the summary' do
203 | # ...
204 | end
205 | ```
206 |
207 | ### The One Expectation
208 |
209 | Use only one expectation per example. There are very few scenarios where two
210 | or more expectations in a single `it` block should be used. So, general rule
211 | of thumb is one expectation per `it` block.
212 |
213 | #### Bad Example
214 |
215 | ```ruby
216 | describe ArticlesController do
217 | #...
218 |
219 | describe 'GET new' do
220 | it 'assigns new article and renders the new article template' do
221 | get :new
222 | expect(assigns[:article]).to be_a(Article)
223 | expect(response).to render_template :new
224 | end
225 | end
226 |
227 | # ...
228 | end
229 | ```
230 |
231 | #### Good Example
232 |
233 | ```ruby
234 | describe ArticlesController do
235 | #...
236 |
237 | describe 'GET new' do
238 | it 'assigns a new article' do
239 | get :new
240 | expect(assigns[:article]).to be_a(Article)
241 | end
242 |
243 | it 'renders the new article template' do
244 | get :new
245 | expect(response).to render_template :new
246 | end
247 | end
248 | end
249 | ```
250 |
251 | ### Context Cases
252 |
253 | `context` blocks should pretty much always have an opposite negative case. It
254 | should actually be a strong code smell if there is a single context (without a
255 | matching negative case) that needs refactoring, or may have no purpose.
256 |
257 | #### Bad Example
258 |
259 | ```ruby
260 | # This is a case where refactoring is the correct choice
261 | describe '#attributes' do
262 | context 'the returned hash' do
263 | it 'includes the display name' do
264 | # ...
265 | end
266 |
267 | it 'includes the creation time' do
268 | # ...
269 | end
270 | end
271 | end
272 |
273 | # This is a case where the negative case needs to be tested, but wasn't
274 | describe '#attributes' do
275 | context 'when display name is present' do
276 | before do
277 | subject.display_name = 'something'
278 | end
279 |
280 | it 'includes the display name' do
281 | # ...
282 | end
283 | end
284 | end
285 | ```
286 |
287 | #### Good Example
288 |
289 | ```ruby
290 | # Refactored
291 | describe '#attributes' do
292 | subject { create(:article) }
293 |
294 | expect(subject.attributes).to include subject.display_name
295 | expect(subject.attributes).to include subject.created_at
296 | end
297 |
298 | # Negative case added
299 | describe '#attributes' do
300 | context 'when display name is present' do
301 | before do
302 | subject.display_name = 'something'
303 | end
304 |
305 | it 'includes the display name' do
306 | # ...
307 | end
308 | end
309 |
310 | context 'when display name is not present' do
311 | before do
312 | subject.display_name = nil
313 | end
314 |
315 | it 'does not include the display name' do
316 | # ...
317 | end
318 | end
319 | end
320 | ```
321 |
322 | ### `context` descriptions
323 |
324 | `context` block descriptions should always start with 'when' or ‘with’, and be in the
325 | form of a sentence with proper grammar.
326 |
327 | #### Bad Example
328 |
329 | ```ruby
330 | context 'the display name not present' do
331 | # ...
332 | end
333 | ```
334 |
335 | #### Good Example
336 |
337 | ```ruby
338 | context 'when the display name is not present' do
339 | # ...
340 | end
341 | ```
342 |
343 | ### `it` descriptions
344 |
345 | `it` block descriptions should never end with a conditional. This is a code
346 | smell that advices the `it` most likely needs to be wrapped in a `context`. This also happens when the description is too long.
347 |
348 | #### Bad Example
349 |
350 | ```ruby
351 | it 'returns the display name if it is present' do
352 | # ...
353 | end
354 | ```
355 |
356 | #### Good Example
357 |
358 | ```ruby
359 | context 'when display name is present' do
360 | it 'returns the display name'
361 | end
362 |
363 | # This encourages the addition of negative test cases that might have
364 | # been overlooked
365 | context 'when display name is not present' do
366 | it 'returns nil'
367 | end
368 | ```
369 |
370 | ### `describe` block naming
371 |
372 | - use hash `#method` for instance methods
373 | - use dot `.method` for class methods
374 |
375 | Given the following exists
376 |
377 | ```ruby
378 | class Article
379 | def summary
380 | #...
381 | end
382 |
383 | def self.latest
384 | #...
385 | end
386 | end
387 | ```
388 |
389 | #### Bad Example
390 |
391 | ```ruby
392 | describe Article do
393 | describe 'summary' do
394 | #...
395 | end
396 |
397 | describe 'latest' do
398 | #...
399 | end
400 | end
401 | ```
402 |
403 | #### Good Example
404 |
405 | ```ruby
406 | describe Article do
407 | describe '#summary' do
408 | #...
409 | end
410 |
411 | describe '.latest' do
412 | #...
413 | end
414 | end
415 | ```
416 |
417 | ### `it` in iterators
418 |
419 | Do not write iterators to generate tests. When another developer adds a
420 | feature to one of the items in the iteration, he must then break it out into a
421 | separate test - he is forced to edit code that has nothing to do with his pull
422 | request. Use shared examples instead.
423 |
424 | #### Bad Example
425 |
426 | ```ruby
427 | [:new, :show, :index].each do |action|
428 | it 'returns 200' do
429 | get action
430 | expect(response).to be_ok
431 | end
432 | end
433 | ```
434 |
435 | #### Good Example
436 |
437 | more verbose for the time being, but better for the future development
438 |
439 | ```ruby
440 |
441 | shared_examples 'responds successfully' do
442 | it 'returns 200' do
443 | get method
444 | expect(response).to be_ok
445 | end
446 | end
447 |
448 | describe 'GET new' do
449 | let(:method) { 'new' }
450 |
451 | it_behaves_like 'responds successfully'
452 | end
453 |
454 | describe 'GET show' do
455 | let(:method) { 'show' }
456 |
457 | it_behaves_like 'responds successfully'
458 | end
459 |
460 | describe 'GET index' do
461 | let(:method) { 'index' }
462 |
463 | it_behaves_like 'responds successfully'
464 | end
465 | ```
466 |
467 | ### Factories/Fixtures
468 |
469 | Use [FactoryBot](https://github.com/thoughtbot/factory_bot) to create test
470 | objects in integration tests. You should very rarely have to use
471 | `ModelName.create` within an integration spec. Do **not** use fixtures as they
472 | are not nearly as maintainable as factories.
473 |
474 | ```ruby
475 | subject { create(:some_article) }
476 | ```
477 |
478 | ### Mocks/Stubs/Doubles
479 |
480 | Use mocks and stubs with caution. While they help to improve the performance
481 | of the test suite, you can mock/stub yourself into a false-positive state very
482 | easily. When resorting to mocking and stubbing, only mock against a small,
483 | stable, obvious (or documented) API, so stubs are likely to represent reality
484 | after future refactoring.
485 |
486 | This generally means you should use them with more isolated/behavioral
487 | tests rather than with integration tests.
488 |
489 | ```ruby
490 | # double an object
491 | article = double('article')
492 |
493 | # stubbing a method
494 | allow(Article).to receive(:find).with(5).and_return(article)
495 | ```
496 |
497 | *NOTE*: if you stub a method that could give a false-positive test result, you
498 | have gone too far. See below:
499 |
500 | #### Bad Example
501 |
502 | ```ruby
503 | subject { double('article') }
504 |
505 | describe '#summary' do
506 | context 'when summary is not present' do
507 | # This stubbing of the #nil? method, makes the test pass, but
508 | # you are no longer testing the functionality of the code,
509 | # you are testing the functionality of the test suite.
510 | # This test would pass if there was not a single line of code
511 | # written for the Article class.
512 | it 'returns nil' do
513 | summary = double('summary')
514 | allow(subject).to receive(:summary).and_return(summary)
515 | allow(summary).to receive(:nil?).and_return(true)
516 | expect(subject.summary).to be_nil
517 | end
518 | end
519 | end
520 | ```
521 |
522 | #### Good Example
523 |
524 | ```ruby
525 | subject { double('article') }
526 |
527 | describe '#summary' do
528 | context 'when summary is not present' do
529 | # This is no longer stubbing all of the functionality, and will
530 | # actually test the objects handling of the methods return value.
531 | it 'returns nil' do
532 | allow(subject).to receive(:summary).and_return(nil)
533 | expect(subject.summary).to be_nil
534 | end
535 | end
536 | end
537 | ```
538 |
539 | ### Dealing with Time
540 |
541 | Always use [Timecop](https://github.com/travisjeffery/timecop) instead of
542 | stubbing anything on Time or Date.
543 |
544 | #### Bad Example
545 |
546 | ```ruby
547 | it 'offsets the time 2 days into the future' do
548 | current_time = Time.now
549 | allow(Time).to receive(:now).and_return(current_time)
550 | expect(subject.get_offset_time).to be_the_same_time_as (current_time + 2.days)
551 | end
552 | ```
553 |
554 | #### Good Example
555 |
556 | ```ruby
557 | it 'offsets the time 2 days into the future' do
558 | Timecop.freeze(Time.now) do
559 | expect(subject.get_offset_time).to eq 2.days.from_now
560 | end
561 | end
562 | ```
563 |
564 |
565 | ### `let` blocks
566 |
567 | Use `let` blocks instead of `before(:each)` blocks to create data for the spec
568 | examples. `let` blocks get lazily evaluated. It also removes the instance
569 | variables from the test suite (which don't look as nice as local variables).
570 |
571 | These should primarily be used when you have duplication among a number of
572 | `it` blocks within a `context` but not all of them. Be careful with overuse of
573 | `let` as it makes the test suite much more difficult to read.
574 |
575 | ```ruby
576 | # use this:
577 | let(:article) { create(:article) }
578 |
579 | # ... instead of this:
580 | before { @article = create(:article) }
581 | ```
582 |
583 | ### `subject`
584 |
585 | Use `subject` when possible
586 |
587 | ```ruby
588 | describe Article do
589 | subject { create(:article) }
590 |
591 | it 'is not published on creation' do
592 | expect(subject).not_to be_published
593 | end
594 | end
595 | ```
596 |
597 | ### Magic Matchers
598 |
599 | Use RSpec's 'magical matcher' methods when possible. For instance, a class
600 | with the method `published?` should be tested with the following:
601 |
602 | ```ruby
603 | it 'is published' do
604 | # actually tests subject.published? == true
605 | expect(subject).to be_published
606 | end
607 | ```
608 |
609 | ### Incidental State
610 |
611 | Avoid incidental state as much as possible.
612 |
613 | #### Bad Example
614 |
615 | ```ruby
616 | it 'publishes the article' do
617 | article.publish
618 |
619 | # Creating another shared Article test object above would cause this
620 | # test to break
621 | expect(Article.count).to eq(2)
622 | end
623 | ```
624 |
625 | #### Good Example
626 |
627 | ```ruby
628 | it 'publishes the article' do
629 | expect { article.publish }.to change(Article, :count).by(1)
630 | end
631 | ```
632 |
633 | ### DRY
634 |
635 | Be careful not to focus on being 'DRY' by moving repeated expectations into a shared environment too early, as this can lead to brittle tests that rely too much on one another.
636 |
637 | Generally, is it best to start doing everything directly in your `it`
638 | blocks even if it is duplication and then refactor your tests once they are working, to be a little more DRY.
639 | However, keep in mind that duplication in test suites is NOT frowned upon,
640 | in fact it is preferred if it provides easier understanding and reading of a test.
641 |
642 |
643 | # Credit
644 |
645 | We based this guide on [reachlocal/rspec-style-guide](https://github.com/reachlocal/rspec-style-guide)
646 |
--------------------------------------------------------------------------------
/git/README-es.md:
--------------------------------------------------------------------------------
1 | # Git Workflow
2 |
3 | En nuestro trabajo diario aplicamos diferentes guías de estilos y estandarizaciones que nos ayudan a tener un criterio unificado en relacion a diferentes tecnologías. El objetivo de este documento es crear un estándar a nivel empresa para el workflow de git en los diferentes proyectos.
4 |
5 | Los lineamientos descriptos en este documento tienen variadas ventajas. Entre ellas, nos permite manejar la misma nomenclatura facilitando la incorporación de otros integrantes al equipo y promueve la mejor organización de los proyectos. Asimismo, ayuda a definir soluciones para algunos problemas habituales. Finalmente, el workflow planteado esta enfocado a mantener un historial confiable que refleje el estado del proyecto.
6 |
7 | A continuación, listaremos el contenido de este documento. Algunos de los lineamientos son de carácter obligatorio y deben ser seguidos en el desarrollo de cualquier proyecto. Los contenidos de carácter obligatorio serán resaltados con la etiqueta **[Required]**. Si bien el resto de los lineamientos no son obligatorios, son altamente recomendables.
8 |
9 | * [Versión en Inglés](./README.md)
10 |
11 |
12 | ## Contenido
13 |
14 | - [Manejo de branches](#manejo-de-branches)
15 | - [Previo salir a producción](#previo-salir-a-producci%C3%B3n)
16 | - [Develop branch (develop)](#develop-branch-develop--requerida)
17 | - [Staging branch (staging)](#staging-branch-staging-requerida)
18 | - [QA branch (qa)](#qa-branch-qa-opcional)
19 | - [Feature branch (feature/x)](#feature-branch-featurex)
20 | - [Fix branch (fix/x)](#fix-branch-fixx)
21 | - [Enhancement branch (enhancement/x)](#enhancement-branch-enhancementx)
22 | - [Feedback branch (feedback/x)](#feedback-branch-feedbackx)
23 | - [Demo branch (demo)](#demo-branch-demo)
24 | - [Post salida a producción](#post-salida-a-producción)
25 | - [Master branch (master)](#master-branch-master-required)
26 | - [Release branch (release_branch_x)](#release-branch-release_branch_x-required)
27 | - [Integración de Release branch a master y develop](#integraci%C3%B3n-de-release-branch-a-master-y-develop)
28 | - [Feedback branch (feedback/x)](#feedback-feedbackx)
29 | - [Hotfix branch (hotfix/sign-up)](#hotfix-hotfixsign-up-requerida)
30 | - [Manejo de Releases](#manejo-de-releases)
31 | - [Previo salir a producción](#previo-salir-a-producci%C3%B3n-1)
32 | - [Post salida a producción](#post-salida-a-producción-required)
33 | - [Primera salida a producción](#primera-salida-a-producci%C3%B3n)
34 | - [Posteriores salidas a producción](#posteriores-salidas-a-producci%C3%B3n)
35 | - [Nueva versión (Major)](#nueva-versión-major)
36 | - [Arreglo de bugs (PATCH)](#arreglo-de-bugs-patch)
37 | - [Cambios menores (MINOR)](#cambios-menores-minor)
38 |
39 |
40 | # Manejo de branches
41 |
42 |
43 | ## Previo salir a producción
44 |
45 | En esta sección se explica el manejo de cada una de las branches involucradas en el proceso de desarrollo previo a salir a producción.
46 |
47 |
48 | ### Develop branch (develop) **[Requerida]**
49 |
50 | Es la branch principal, refleja la integración de todo el trabajo del equipo durante la etapa de desarrollo del proyecto.
51 | Es la branch base hasta salir a producción por primera vez.
52 |
53 | * **Origen:** *master* (cuando se inicia el proyecto, es creada desde master)
54 | * **Destino:** -
55 | * **Casos de uso:** Integración del trabajo del equipo.
56 |
57 |
58 | ### Staging branch (staging) **[Requerida]**
59 |
60 | Esta branch contiene todas las funcionalidades listas para ser testeadas por el Cliente.
61 |
62 | Si el proyecto tiene CD configurado, cada vez que algo sea mergeado en esta branch el código será automáticamente deployado al ambiente de Staging para que el Cliente pueda probarlo.
63 |
64 | * **Origen:** *master* (cuando se inicia el proyecto, es creada desde master)
65 | * **Destino:** -
66 | * **Casos de uso:** Integración del trabajo hecho por el equipo de desarrollo ya testeado y aprobado por el equipo de QA, listo para ser revisado por el Cliente.
67 |
68 | ### QA branch (qa) **[Opcional]**
69 |
70 | Esta branch contiene todas las funcionalidades listas para ser testeadas por el equipo de QA.
71 |
72 | Si el proyecto tiene CD configurado, cada vez que algo sea mergeado en esta branch el código será automáticamente deployado al ambiente de QA.
73 |
74 | * **Origen:** *master* (cuando se inicia el proyecto, es creada desde master)
75 | * **Destino:** *staging*
76 | * **Casos de uso:** Integración del trabajo hecho por el equipo de desarrollo listo para ser testeado por el equipo de QA.
77 |
78 | **_Nota_**: *Si una de las funcionalidades mergeadas en QA no está lista para ser promovida a la branch Staging, los ajustes necesarios (eliminar la funcionalidad/deshabilitar el acceso a la misma/etc) deben ser realizados en la branch Develop, para luego ser promovidos a la branch QA y así continuar con el flujo.*
79 |
80 |
81 |
82 |
83 |
84 | ### Feature branch (feature/x)
85 |
86 | Esta branch contiene una funcionalidad o parte de una en la que se está trabajando actualmente. Una vez completado el trabajo, se crea un Pull Request a develop y luego de finalizar el proceso de review es mergeada a develop.
87 |
88 | _Ejemplo: feature/sign-up_
89 |
90 | * **Origen:** *develop*
91 | * **Destino:** *develop*
92 | * **Casos de uso:** Funcionalidad durante desarrollo.
93 |
94 |
95 | ### Fix branch (fix/x)
96 |
97 | Esta branch es creada para el arreglo de un bug durante la etapa de desarrollo.
98 |
99 | _Ejemplo: fix/sign-up-error-messages_
100 |
101 | * **Origen:** *develop*
102 | * **Destino:** *develop*
103 | * **Casos de uso:** Bug durante desarrollo.
104 |
105 |
106 | ### Enhancement branch (enhancement/x)
107 |
108 | Esta branch es creada para agregar una mejora durante la etapa de desarrollo sobre alguna funcionalidad que ha sido mergeada previamente a la branch development.
109 |
110 | _Ejemplo: enhancement/users-retrievement-query_
111 |
112 | * **Origen:** *develop*
113 | * **Destino:** *develop*
114 | * **Casos de uso:** Agregar mejora durante desarrollo.
115 |
116 |
117 | ### Feedback branch (feedback/x)
118 |
119 | Esta branch es creada para trabajar en feedback de una determinada funcionalidad.
120 |
121 | _Ejemplo: feedback/sign-up-change-inputs-order_
122 |
123 | * **Origen:** *develop*
124 | * **Destino:** *develop*
125 | * **Casos de uso:** Feedback del cliente o cualquier integrante del equipo de una determinada funcionalidad durante desarrollo.
126 |
127 |
128 | ### Demo branch (demo)
129 |
130 | Esta branch tiene dos objetivos:
131 |
132 | 1. No afectar el proceso de code review.
133 | 2. Mostrar el avance del equipo en la review con el cliente.
134 |
135 | Branch temporal creada a partir de develop en la cual se mergea el contenido de otras branches que aún no estan prontas para ser integradas a develop. El único propósito de esta branch es enteramente dedicado a la demo, por lo que debe ser descartada inmediatamente despues de la realización de la misma.
136 |
137 | * **Origen:** *develop*
138 | * **Destino:** - (borrar branch)
139 | * **Casos de uso:** Mostrar todas las funcionalidades desarrolladas hasta el momento en la instancia de review, no importa si ya estan completadas o no.
140 | Para poder mergear una branch a develop, es obligatorio que la misma pase por un adecuado proceso de code review, sin embargo, es frecuente que al momento de la review, la branch aun no este completa o el PR aun no haya sido aprobado. Para esto, todas las branches que van a ser mostradas en la review, pueden ser mergeadas automáticamente en la branch temporal demo, sin necesidad de code review.
141 |
142 | Si se realizan cambios en las branches, se puede mergear de nuevo ese cambio en la branch *demo* o crear de nuevo la branch temporal si hay un cambio en un commit (*git amend*) o en la historia (*git rebase*).
143 |
144 | *Nota: a pesar de ser una branch que tiene como finalidad mostrar el avance en una review, no es recomendable enviar una versión a partir de esta branch. En estos casos, hablar con el referente del proyecto o algunos de los referentes de la empresa.*
145 |
146 | *Es recomendable hacer el deploy de esta branch a un ambiente previo a staging, como puede ser development. Ver: manejo de environments en base a git workflow (documento en construcción).*
147 |
148 |
149 |
150 |
151 |
152 |
153 | ## Post salida a producción
154 |
155 | En esta sección se explica el manejo de cada una de las branches involucradas en el proceso de desarrollo post salida a producción.
156 |
157 | A no ser que la branch sea nuevamente explicada en esta sección, las anteriores branches detalladas en la sección *Previo salir a producción*, deben seguir siendo utilizadas de la misma manera.
158 |
159 | Con el manejo de branches detallado a continuación, se busca mantener el historial de commits, siempre y cuando sea posible.
160 | Por esta razón, para poder mantener las branches actualizadas entre si, no se utilizan enfoques clásicos basados en el uso de *cherry-pick* y *merge*. De lo contrario, se prioriza el uso de *rebase*.
161 |
162 |
163 | ### Master branch (master) **[Required]**
164 |
165 | Esta branch refleja el estado de producción, siempre debe estar en un estado estable.
166 |
167 | Por esta razón, las nuevas funcionalidades continúan siendo desarrolladas a partir de *develop* y son integradas a master una vez que se encuentran funcionando y son aprobadas por el cliente en un ambiente de prueba.
168 |
169 | * **Origen:** -
170 | * **Destino:** -
171 | * **Casos de uso:** Reflejar producción
172 |
173 |
174 | ### Release branch (release_branch_x) **[Required]**
175 |
176 | Esta branch es creada conteniendo las nuevas funcionalidades que quieren ser agregadas al ambiente de producción.
177 |
178 | Su función es separar el desarrollo de nuevas funcionalidades que quieren ser agregadas a producción de otras que aún no están prontas o simplemente aún no quieren ser liberadas por el equipo o el cliente.
179 |
180 | Debe ser liberada por primera vez a un ambiente de prueba utilizado por el cliente y el feedback proveniente del mismo será agregado a esta branch.
181 |
182 | El momento en que debe ser creada depende del proyecto y el equipo.
183 | En equipos con varios integrantes es recomendando crearla al momento que se comienza a desarrollar la primer feature de la versión.
184 | Por otra parte, en equipos con pocos integrantes es recomendable crearla una vez completadas las funcionalidades que quieren ser liberadas. Este es el caso más común en la empresa.
185 |
186 | * **Origen:** *develop*
187 | * **Destino:** *master*
188 | * **Casos de uso:** Luego de liberada la branch a un ambiente de prueba, puede existir feedback del cliente o encontrarse bugs. Estos arreglos deben ir a la release branch y no a develop.
189 | De esta manera, si en simultáneo otro dev comenzó a trabajar en funcionalidades futuras y lo integró a *develop*, se evita liberar funcionalidades que no pertenecían a la release actual. Además, nos permite mantener un orden en el tiempo en relación a que vamos a liberar próximamente.
190 |
191 |
192 | #### Integración de Release branch a master y develop
193 |
194 | Luego de que la release branch está pronta para ser liberada a producción, la misma debe ser integrada a master y también a develop.
195 |
196 | ***Master***
197 |
198 | Directamente a través de un Pull Request que es creado desde la release a **master**. Idealmente, cada uno de los commit de este Pull Request ya pasaron por un proceso de code review, por lo cual, no es necesario repetir el review para mergear, simplemente asegurarse que todo se encuentra de acuerdo a lo planeado y funcionando correctamente.
199 |
200 | ***Develop***
201 |
202 | La release branch, puede además contener commits que no se encuentran en develop, comúnmente, aquellos que surgieron luego de feedback del cliente y fueron integrados a la release y no a develop.
203 |
204 | Para mantener **develop** actualizado, se debe hacer un rebase con *origin master* y luego un push a develop desde develop local.
205 |
206 | A continuación se detallan los comandos adecuados:
207 |
208 | 1. `git checkout develop`
209 | 2. `git pull origin develop:develop` para así actualizar *develop* local con *origin/develop*.
210 | 3. `git rebase -i origin/master` para actualizar el historial de develop con respecto a origin/master.
211 | 4. `git push origin develop:develop` para actualizar origin/develop.
212 |
213 |
214 | ### Feedback (feedback/x)
215 |
216 | Esta branch es creada para trabajar en el feedback de una funcionalidad.
217 |
218 | _Ejemplo: feedback/sign-up-change-inputs-order_
219 |
220 | * **Origen:** *release branch*
221 | * **Destino:** *release branch*
222 | * **Casos de uso:** Feedback del cliente o interno de una determinada funcionalidad luego de liberada una release branch a un ambiente de prueba.
223 |
224 |
225 | ### Hotfix (hotfix/sign-up) **[Requerida]**
226 |
227 | Estas branches son no planeadas, son creadas para solucionar bugs o cambios críticos, que no deben ser mezclados con el actual desarrollo (*develop* branch).
228 |
229 | No confundir con bugs de desarrollo, estos últimos van contra *develop* o una release branch. Además, no todos los bugs son hotfixes, solo aquellos que se priorizan como críticos y afectan directamente una funcionalidad importante del sistema.
230 |
231 | El cambio también debe estar presente en develop. Por lo cual, es **responsabilidad del desarrollador** **que creó el commit** que una vez que el Pull Request es mergeado en master, **inmediatamente** el commit también se encuentra en la branch *develop.*
232 |
233 | Para esto, se deben seguir los mismos pasos detallados en la subsección **integración de release branch a master y develop** para la branch develop.
234 |
235 | * **Origen:** *master*
236 | * **Destino:** *master*
237 | * **Casos de uso:** Bugs o cambios críticos de producción.
238 |
239 |
240 |
241 |
242 |
243 |
244 | # Manejo de Releases
245 |
246 | En esta sección se explica cómo manejar releases previo y post salida a producción. Comenzando con una breve explicación del porqué y las ventajas que nos brinda el manejo de las mismas.
247 |
248 | **_¿Que es?_**
249 |
250 | Una Release es un link a una versión con un conjunto de notas que describen los cambios con respecto a la última versión.
251 |
252 | Las versiones se manejan utilizando tags, las cuales se crean automáticamente al crear una release. Las tags son fotos inmutables de una branch.
253 |
254 | **_Ventajas_**
255 |
256 | Manejar versiones sprint a sprint o post salida a producción nos permite manejar el trabajo de forma ordenada y tener un trackeo confiable de que versiones se encuentran en staging y principalmente en producción.
257 |
258 |
259 | ## Previo salir a producción
260 |
261 | Una vez finalizado el sprint y solucionado el feedback proveniente del cliente se crea una release.
262 | Ver [https://help.github.com/articles/creating-releases/](https://help.github.com/articles/creating-releases/)
263 |
264 | * **Origen:** develop
265 | * **Versión:** v0.x, donde x es el número del sprint en el cual se está trabajando.
266 | * **Título:** Release sprint x, donde x es el número del sprint.
267 | * **Descripción:** detalle de las funcionalidades desarrolladas durante desarrollo.
268 |
269 | _Nota: el manejo de releases previo salir a producción es altamente recomendado pero no obligatorio._
270 |
271 |
272 | ## Post salida a producción **[Required]**
273 |
274 | El manejo de Releases luego de salir a producción es por demás importante dado que nos permite tener una captura inmutable de lo que hay en producción.
275 | En este caso, la tag se crea desde *master*. Por ejemplo, luego de mergear una branch hotfix o release a master.
276 |
277 |
278 | ### Primera salida a producción
279 |
280 | La primera vez que que se libera a producción se debe mergear el contenido de la branch *develop* en *master*. Luego, la release es creada.
281 |
282 | * **Origen:** master
283 | * **Versión:** v1.0
284 | * **Título:** Release v1.0
285 | * **Descripción:** detalle de las funcionalidades desarrolladas durante desarrollo.
286 |
287 |
288 | ### Posteriores salidas a producción
289 |
290 | En posteriores releases a producción se recomienda el estándar MAJOR.MINOR.PATCH.
291 |
292 |
293 | #### Nueva versión (Major)
294 |
295 | Se liberan un conjunto de funcionalidades que no son compatibles con la versión anterior o un conjunto muy grande de funcionalidades que pueden marcar un gran cambio desde el punto de vista del negocio.
296 |
297 | * **Origen:** master
298 | * **Versión** : (vMAJOR + 1).0.0
299 | * **Título:** Release (vMAJOR + 1).0.0
300 | * **Descripción:** descripción general de la nueva versión y detalle de las nuevas funcionalidades desarrolladas
301 |
302 |
303 | #### Cambios menores (MINOR)
304 |
305 | Versión que introduce cambios compatibles con la versión anterior.
306 | En este caso, dado que se liberan un conjunto de nuevas funcionalidades y arreglo de bugs no críticos, se incrementa en uno el valor MINOR para generar la nueva versión.
307 |
308 | * **Origen:** master
309 | * **Versión** : vMAJOR.(MINOR + 1).0
310 | * **Título:** Release vMAJOR.(MINOR + 1).0
311 | * **Descripción:** detalle de las nuevas funcionalidades desarrolladas y bugs que fueron solucionados.
312 |
313 |
314 | #### Arreglo de bugs (PATCH)
315 |
316 | Contiene uno o más arreglos de bugs. Independientemente de si los mismos son urgentes o no.
317 | Comúnmente, si los bugs no son urgentes, se espera a solucionar un conjunto de bugs para liberar una nueva versión.
318 | En caso de que el bug sea urgente, es necesario liberar una versión cuanto antes.
319 |
320 | * **Versión** : vMAJOR.MINOR.(PATCH + 1)
321 | * **Título:** Release vMAJOR.MINOR.(PATCH + 1)
322 | * **Descripción:** detalle de los bugs arreglados que son considerados importantes.
323 |
--------------------------------------------------------------------------------
/kotlin/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Kotlin Style Guide
3 |
4 | This Kotlin style guide recommends best practices so that real-world Kotlin
5 | programmers can write code that can be maintained by other real-world Kotlin
6 | programmers. A style guide that reflects real-world usage gets used, while a
7 | style guide that holds to an ideal that has been rejected by the people it is
8 | supposed to help risks not getting used at all—no matter how good it is.
9 |
10 |
11 | ## Table of Contents
12 |
13 | * [Source file names](#source-file-names)
14 | * [Function names](#function-names)
15 | * [Property names](#property-names)
16 | * [Formatting](#formatting)
17 | * [Horizontal whitespace](#horizontal-whitespace)
18 | * [Colon](#colon)
19 | * [Class header](#class-header-formatting)
20 | * [Annotation](#annotation-formatting)
21 | * [Functions](#function-formatting)
22 | * [Expression body](#expression-body-formatting)
23 | * [Properties](#property-formatting)
24 | * [Control flow statements](#formatting-control-flow-statements)
25 | * [Method calls](#method-call-formatting)
26 | * [Lambdas](#lambda-formatting)
27 | * [Documentation/comments](#documentation-comments)
28 | * [Type aliases](#type-aliases)
29 | * [Conditional statements](#using-conditional-statements)
30 | * [Extension functions](#using-extension-functions)
31 | * [Scope functions](#scope-functions)
32 | * [run](#run)
33 | * [let](#let)
34 | * [apply](#apply)
35 | * [also](#also)
36 | * [with](#with)
37 | * [Combining scope functions](#combining-scope-functions)
38 |
39 | ---------------------
40 |
41 | ### Source file names:
42 | If a Kotlin file contains a single class (potentially with related top-level
43 | declarations), its name should be the same as the name of the class, with the
44 | .kt extension appended. If a file contains multiple classes or only top-level
45 | declarations, choose a name describing what the file contains, and name the
46 | file accordingly. Use the camel case with an uppercase first letter (for example,
47 | ProcessDeclarations.kt )
48 |
49 | ### Function names:
50 | Names of functions, properties, and local variables start with a lower case
51 | letter and use the camel case and no underscores:
52 |
53 | ```
54 | fun processDeclarations() { /*...*/ }
55 | var declarationCount = 1
56 | ```
57 |
58 | Exception: factory functions used to create instances of classes can have the
59 | same name as the class being created:
60 |
61 | ```
62 | abstract class Foo { /*...*/ }
63 | class FooImpl : Foo { /*...*/ }
64 | fun FooImpl(): Foo { return FooImpl() }
65 | ```
66 |
67 | In tests (and only in tests), it's acceptable to use method names with spaces
68 | enclosed in backticks.(Note that such method names are currently not supported
69 | by the Android runtime.) Underscores in method names are also allowed in test
70 | code.
71 |
72 | ```
73 | class MyTestCase {
74 | @Test fun `ensure everything works`() { /*...*/ }
75 | @Test fun ensureEverythingWorks_onAndroid() { /*...*/ }
76 | }
77 | ```
78 |
79 | ### Property names:
80 | Names of constants (properties marked with const, top-level or object val
81 | properties with no custom get) should use uppercase snake case:
82 |
83 | ```
84 | const val MAX_COUNT = 8
85 | val USER_NAME_FIELD = "UserName"
86 | ```
87 |
88 | Names of top-level or object properties which hold objects with behavior or
89 | mutable data should use camel-case names:
90 |
91 | ```
92 | val mutableCollection: MutableSet = HashSet()
93 | ```
94 |
95 | Names of properties holding references to singleton objects can use the same
96 | naming style as object declarations:
97 |
98 | ```
99 | val PersonComparator: Comparator = /*...*/
100 | ```
101 |
102 | For enum constants, it's OK to use either uppercase snake case
103 | ( enum class Color { RED, GREEN } ) or regular camel-case names starting with
104 | an uppercase first letter, depending on the usage.
105 |
106 | ### Formatting:
107 | Use 4 spaces for indentation. Do not use tabs. For curly braces, put the opening
108 | brace in the end of the line where the construct begins, and the closing brace on
109 | a separate line aligned horizontally with the opening construct.
110 |
111 | ```
112 | if (elements != null) {
113 | for (element in elements) {
114 | // ...
115 | }
116 | }
117 | ```
118 |
119 | ### Horizontal whitespace:
120 | Put spaces around binary operators ( a + b ).
121 | Exception: don't put spaces around the "range to" operator ( 0..i ).
122 | Do not put spaces around unary operators ( a++ )
123 | Put spaces between control keywords ( if , when , for and while ) and the
124 | corresponding opening parenthesis. Do not put a space before an opening parenthesis
125 | in a primary constructor declaration, method declaration or method call.
126 |
127 | ```
128 | class A(val x: Int)
129 | fun foo(x: Int) { ... }
130 | fun bar() { foo(1) }
131 | ```
132 |
133 | - Never put a space after '(', ',', '[' , or before ']' , ')'
134 | - Never put a space around '.' or '?.' :
135 | foo.bar().filter { it > 2 }.joinToString() , foo?.bar()
136 | - Put a space after '//' :
137 | // This is a comment
138 | - Do not put spaces around angle brackets used to specify type parameters:
139 | class Map { ... }
140 | - Do not put spaces around '::' :
141 | Foo::class , String::length
142 | - Do not put a space before ? used to mark a nullable type:
143 | String?
144 |
145 | ### Colon
146 | Put a space before ':' in the following cases:
147 | - when it's used to separate a type and a supertype.
148 | - when delegating to a superclass constructor or a different constructor of the
149 | same class.
150 | - After the object keyword.
151 | - Don't put a space before ':' when it separates a declaration and its type.
152 | - Always put a space after ':' .
153 |
154 | ```
155 | abstract class Foo : IFoo {
156 | abstract fun foo(a: Int): T
157 | }
158 |
159 | class FooImpl : Foo() {
160 | constructor(x: String) : this(x) { /*...*/ }
161 | val x = object : IFoo { /*...*/ }
162 | }
163 | ```
164 |
165 | ### Class header formatting:
166 | Classes with a few primary constructor parameters can be written in a single line:
167 |
168 | ```
169 | class Person(id: Int, name: String)
170 | ```
171 |
172 | Classes with longer headers should be formatted so that each primary constructor
173 | parameter is in a separate line with indentation. Also, the closing parenthesis
174 | should be on a new line. If we use inheritance, then the superclass constructor
175 | call or list of implemented interfaces should be located on the same line as the
176 | parenthesis:
177 |
178 | ```
179 | class Person(
180 | id: Int,
181 | name: String,
182 | surname: String
183 | ) : Human(id, name) { /*...*/ }
184 | ```
185 |
186 | For multiple interfaces, the superclass constructor call should be located first and
187 | then each interface should be located in a different line:
188 |
189 | ```
190 | class Person(
191 | id: Int,
192 | name: String,
193 | surname: String
194 | ) : Human(id, name),
195 | KotlinMaker { /*...*/ }
196 | ```
197 |
198 | For classes with a long supertype list, put a line break after the colon and
199 | align all supertype names horizontally:
200 |
201 | ```
202 | class MyFavouriteVeryLongClassHolder :
203 | MyLongHolder(),
204 | SomeOtherInterface,
205 | AndAnotherOne {
206 |
207 | fun foo() { /*...*/ }
208 | }
209 | ```
210 |
211 | To clearly separate the class header and body when the class header is long, either put
212 | a blank line following the class header (as in the example above), or put the opening
213 | curly brace on a separate line:
214 |
215 | ```
216 | class MyFavouriteVeryLongClassHolder :
217 | MyLongHolder(),
218 | SomeOtherInterface,
219 | AndAnotherOne
220 | {
221 | fun foo() { /*...*/ }
222 | }
223 | ```
224 |
225 | Use regular indent (4 spaces) for constructor parameters.
226 |
227 | ### Annotation formatting:
228 | Annotations are typically placed on separate lines, before the declaration to
229 | which they are attached, and with the same indentation:
230 |
231 | ```
232 | @Target(AnnotationTarget.PROPERTY)
233 | annotation class JsonExclude
234 | ```
235 |
236 | Annotations without arguments may be placed on the same line:
237 |
238 | ```
239 | @JsonExclude @JvmField
240 | var x: String
241 | ```
242 |
243 | A single annotation without arguments may be placed on the same line as the
244 | corresponding declaration:
245 |
246 | ```
247 | @Test fun foo() { /*...*/ }
248 | ```
249 |
250 | ### Function formatting:
251 | If the function signature doesn't fit on a single line, use the following syntax:
252 |
253 | ```
254 | fun longMethodName(
255 | argument: ArgumentType = defaultValue,
256 | argument2: AnotherArgumentType
257 | ): ReturnType {
258 | // body
259 | }
260 | ```
261 |
262 | Use regular indent (4 spaces) for function parameters.
263 |
264 | Prefer using an expression body for functions with the body consisting of a single
265 | expression.
266 |
267 | ```
268 | // bad
269 | fun foo(): Int {
270 | return 1
271 | }
272 |
273 | // good
274 | fun foo() = 1
275 | ```
276 |
277 | ### Expression body formatting:
278 | If the function has an expression body that doesn't fit in the same line as the
279 | declaration, put the = sign on the first line. Indent the expression body by 4
280 | spaces.
281 |
282 | ```
283 | fun f(x: String) =
284 | x.length
285 | ```
286 |
287 | ### Property formatting:
288 | For very simple read-only properties, consider one-line formatting:
289 |
290 | ```
291 | val isEmpty: Boolean get() = size == 0
292 | ```
293 |
294 | For more complex properties, always put get and set keywords on separate lines:
295 |
296 | ```
297 | val foo: String
298 | get() { /*...*/ }
299 | ```
300 |
301 | For properties with an initializer, if the initializer is long, add a line break
302 | after the equals sign and indent the initializer by four spaces:
303 |
304 | ```
305 | private val defaultCharset: Charset? =
306 | EncodingRegistry.getInstance().getDefaultCharsetForPropertiesFiles(file)
307 | ```
308 |
309 | ### Formatting control flow statements:
310 | If the condition of an if or when the statement is multiline, always use curly
311 | braces around the body of the statement. Indent each subsequent line of the
312 | condition by 4 spaces relative to statement begin. Put the closing parentheses
313 | of the condition together with the opening curly brace on a separate line:
314 |
315 | ```
316 | if (!component.isSyncing &&
317 | !hasAnyKotlinRuntimeInScope(module)
318 | ) {
319 | return createKotlinNotConfiguredPanel(module)
320 | }
321 | ```
322 |
323 | Put the else , catch , finally keywords, as well as the while keyword of a do/while
324 | loop, on the same line as the preceding curly brace:
325 |
326 | ```
327 | if (condition) {
328 | // body
329 | } else {
330 | // else part
331 | }
332 |
333 |
334 | try {
335 | // body
336 | } finally {
337 | // cleanup
338 | }
339 | ```
340 |
341 | In a when statement, if a branch is more than a single line, consider separating it
342 | from adjacent case blocks with a blank line:
343 |
344 | ```
345 | private fun parsePropertyValue(propName: String, token: Token) {
346 | when (token) {
347 | is Token.ValueToken ->
348 | callback.visitValue(propName, token.value)
349 | Token.LBRACE -> {
350 | // …
351 | }
352 | }
353 | }
354 | ```
355 |
356 | Put short branches on the same line as the condition, without braces.
357 |
358 | ```
359 | when (foo) {
360 | true -> bar() // good
361 | false -> { baz() } // bad
362 | }
363 | ```
364 |
365 | ### Method call formatting:
366 | In long argument lists, put a line break after the opening parenthesis. Indent
367 | arguments by 4 spaces. Group multiple closely related arguments on the same line.
368 |
369 | ```
370 | drawSquare(
371 | x = 10, y = 10,
372 | width = 100, height = 100,
373 | fill = true )
374 | ```
375 |
376 | Put spaces around the = sign separating the argument name and value.
377 |
378 | ### Lambda formatting:
379 | In lambda expressions, spaces should be used around the curly braces, as well as
380 | around the arrow which separates the parameters from the body.
381 | If a call takes a single lambda, it should be passed outside of parentheses whenever
382 | possible.
383 |
384 | ```
385 | list.filter { it > 10 }
386 | ```
387 |
388 | If assigning a label for a lambda, do not put a space between the label and the opening
389 | curly brace:
390 |
391 | ```
392 | fun foo() {
393 | ints.forEach list@{
394 | // ...
395 | }
396 | }
397 | ```
398 |
399 | When declaring parameter names in a multiline lambda, put the names on the first line,
400 | followed by the arrow and the newline:
401 |
402 | ```
403 | appendCommaSeparated(properties) {
404 | prop -> val propertyValue = prop.get(obj) // ...
405 | }
406 | ```
407 |
408 | If the parameter list is too long to fit on a line, put the arrow on a separate line:
409 |
410 | ```
411 | foo {
412 | context: Context,
413 | environment: Env
414 | ->
415 | context.configureEnv(environment)
416 | }
417 | ```
418 |
419 | ### Documentation comments:
420 | For longer documentation comments, place the opening /** on a separate line and begin
421 | each subsequent line with an asterisk:
422 |
423 | ```
424 | /**
425 | * This is a documentation comment
426 | * on multiple lines.
427 | */
428 | ```
429 |
430 | Short comments can be placed on a single line:
431 |
432 | ```
433 | /** This is a short documentation comment. */
434 | ```
435 |
436 | String templates:
437 | Don't use curly braces when inserting a simple variable into a string template.
438 | Use curly braces only for longer expressions.
439 |
440 | ```
441 | println("$name has ${children.size} children")
442 | ```
443 |
444 | ### Type aliases:
445 | If you have a functional type or a type with type parameters which is used multiple times
446 | in a codebase, prefer deDning a type alias for it:
447 |
448 | ```
449 | typealias PersonIndex = Map
450 | ```
451 |
452 | ### Using conditional statements:
453 | Prefer using the expression form of try, if and when. Examples:
454 |
455 | ```
456 | return if (x) foo() else bar()
457 |
458 | return when(x) {
459 | 0 -> "zero"
460 | else -> "nonzero"
461 | }
462 | ```
463 |
464 | The above is preferable to:
465 |
466 | ```
467 | if (x)
468 | return foo()
469 | else
470 | return bar()
471 |
472 | when(x) {
473 | 0 -> return "zero"
474 | else -> return "nonzero"
475 | }
476 | ```
477 |
478 | ### Loops on ranges:
479 | Use the until function to loop over an open range:
480 |
481 | ```
482 | for (i in 0..n - 1) { /*...*/ } // bad
483 | for (i in 0 until n) { /*...*/ } // good
484 | ```
485 |
486 | ### Using extension functions:
487 | Use extension functions liberally. Every time you have a function that works primarily
488 | on an object, consider making it an extension function accepting that object as a receiver.
489 | To minimize API pollution, restrict the visibility of extension functions as much as it
490 | makes sense. As necessary, use local extension functions, member extension functions,
491 | or top-level extension functions with private visibility.
492 |
493 | ### Scope functions:
494 | Kotlin provides a variety of functions to execute a block of code in the context of a
495 | given object: let, run, with, apply, and also. There is not a specific rule for each one,
496 | you use them to have clean and easy to maintain code, and since Kotlin is a compiled
497 | language, with the use of those functions, you can get better performance of your apps.
498 |
499 | #### run
500 | Receive 'this' as an instance of the current context and return a final value, should be
501 | used to execute actions over the same context.
502 |
503 | ```
504 | val dog = Dog()
505 | val result = dog.run { //this as instance of the object dog
506 | jump() // same as dog.jump() also you can use this.jump()
507 | sit()
508 | eat()
509 | sleep() // the final return value
510 | }
511 | ```
512 |
513 | Without "run" the code looks like:
514 |
515 | ```
516 | dog.jump()
517 | dog.sit()
518 | dog.eat()
519 | val result = dog.sleep()
520 | ```
521 |
522 | #### let
523 | Receive 'it' as an instance of the current object and return a final value.
524 | It should be used to use the object context several times.
525 |
526 | ```
527 | dog.let {
528 | renameDog(it, "Canelo")
529 | paintDog(it, red)
530 | }
531 | ```
532 |
533 | #### apply
534 | Receives 'this' as an instance of the current context and return the object context.
535 | It should be used to change context data:
536 |
537 | ```
538 | dog.apply {
539 | name = "Canelo"
540 | color = "Red"
541 | size = "Big"
542 | }
543 | ```
544 |
545 | #### also
546 | Receive 'it' as an instance of the object and return the same context as result.
547 | It should be used to execute a code after a creation of the object or parameters
548 | assignations:
549 |
550 | ```
551 | val dog = Dog().also {
552 | selectedDog = it
553 | }
554 | ```
555 |
556 | - also can be combined with apply:
557 |
558 | ```
559 | dog.apply {
560 | name = "Canelo"
561 | color = "Red"
562 | size = "Big"
563 | }.also {
564 | selectedDog = it
565 | }
566 | ```
567 |
568 | #### with
569 | Similar to let, but using 'this' as context. It should be used if you know the
570 | object is not null:
571 |
572 | ```
573 | val dog = Dog()
574 | with(dog) {
575 | renameDog(this, "Canelo")
576 | eat()
577 | }
578 | ```
579 |
580 | But if you need to check the object nullability you should use 'let':
581 |
582 | ```
583 | dog?.let {
584 | ...
585 | }
586 | ```
587 |
588 | #### Combining scope functions
589 | Imagine you have two objects and you need to use a scope function inside another,
590 | you need to specify the name of the context to avoid context overlapping.
591 |
592 | ```
593 | dog1.let { // it as instance of dog1
594 | dog2.let { d2 -> // you have to rename the context for dog2
595 | it.isBrotherOf(d2) //it stell an instance of dog1
596 | doSomething(arrayListOf(it,d2))
597 | }
598 | // d2 doesn't exist in this context
599 | it.jump()
600 | }
601 | ```
602 |
--------------------------------------------------------------------------------
/python/cookiecutter-django.md:
--------------------------------------------------------------------------------
1 | # How to start a new Django project with Cookiecutter
2 | The aim of this guide is to help you to initialize your Django project from one of the most recognized open-source utilities for this area.
3 |
4 | ## Install Cookiecutter
5 | [*Cookiecutter*](https://github.com/cookiecutter/cookiecutter) is a command-line tool that helps us to create projects from different templates, in our case, we will create a Django project.
6 |
7 | Firstly, we have to globally install that tool running `$ pip install cookiecutter`
8 |
9 | ## Create a new project
10 | Since we want to create a Django project, we need to use the [Cookiecutter Django](https://github.com/pydanny/cookiecutter-django) template.
11 |
12 | Go to your development directory (the location where you want to create the root folder of the project) and run the following command to start the creation:
13 | `$ cookiecutter gh:pydanny/cookiecutter-django`
14 |
15 | ## Fill generator options
16 | *Cookiecutter* gives us a set of pre-defined configurations to include at our project. Each configuration depends on each project that you will be involved in. You can take a look at [the full list of generation options](https://cookiecutter-django.readthedocs.io/en/latest/project-generation-options.html).
17 |
18 | Despite the previously mentioned, we provide you a list of the principal configuration that you have to set in a new *Cookiecutter* project.
19 |
20 | NOTE:
21 | - Each option suggests a default value if you agree with it, just press enter.
22 |
23 | Some interesting options:
24 | - **project_name**: Write the name of the new project using an upper case at the beginning. For ex.: Cookiecutter Starter.
25 | - **project_slug**: Write the name of the new project using downcases and underscores. At Rootstrap we use the same name of the code repository. For ex.: cookiecutter_starter.
26 | - **description**: Write a short description of the new project. Remember to add it to the readme file. For ex.: This project is an example of how to configure a Django project from the beginning with Cookiecutter Django
27 | - **author_name**: Write the company name. For ex.: Rootstrap, Inc.
28 | - **domain_name**: Write the customer domain name. For ex.: example.com
29 | - **email**: Write the customer email contact. For ex.: info@example.com
30 | - **open_source_license**: Choose the License according to the final purpose of the project. If it's an open-source project, you should select the option `1` (MIT). But if it's a client project, you should select the option `5` (Not open source).
31 | - **windows**: This option asks us if we want to be able to develop at Windows environment. At Rootstrap we use Linux ones, so, you should enter the option: `n`.
32 | - **use_pycharm**: This option asks us if we want to add some configuration files to run the project at PyCharm, the Jetbrains' Python IDE. It's personal, choose `n` or `y` according to your preferences.
33 | - **cloud_provider**: Choose the cloud provider for files resources that will be attached from the models. At Rootstrap we use AWS, so, you should select option `1`.
34 | - **mail_service**: Choose the email service for send emails from the application. At Rootstrap we use Sendgrid, so, you should select option `6`.
35 | - **use_drf**: This option asks us if we want to add [Django REST framework](https://www.django-rest-framework.org/) to our application. At Rootstrap we use it as the base of our API, so, you should enter the option: `y`.
36 | - **use_mailhog**: This option asks us if we want to add [Mailhog](https://github.com/mailhog/MailHog) as an email viewer during the development process. We recommend that option, so, you should enter: `y`.
37 | - **use_heroku**: This option asks us if we want to add some configuration files to deploy the project to *Heroku*. At Rootstrap we usually deploy to it, so, you should enter: `y`.
38 | - **keep_local_envs_in_vcs**: This option asks us if we want to add the environment configurations to the Version Control System. Also, it's useful to set Heroku or docker variables. At Rootstrap we manage that information differently, so, you should enter: `n`.
39 |
40 |
41 | View full bash output. (Click ▶ to display the file)
42 |
43 | ```bash
44 | project_name [My Awesome Project]: Cookiecutter Starter
45 | project_slug [cookiecutter_starter]:
46 | description [Behold My Awesome Project!]: This proyect is an example of how configure a Django proyect since beginning
47 | author_name [Daniel Roy Greenfeld]: Rootstrap, Inc.
48 | domain_name [example.com]: example.com
49 | email [rootstrap@example.com]: info@example.com
50 | version [0.1.0]:
51 | Select open_source_license:
52 | 1 - MIT
53 | 2 - BSD
54 | 3 - GPLv3
55 | 4 - Apache Software License 2.0
56 | 5 - Not open source
57 | Choose from 1, 2, 3, 4, 5 [1]: 1
58 | timezone [UTC]:
59 | windows [n]:
60 | use_pycharm [n]:
61 | use_docker [n]:
62 | Select postgresql_version:
63 | 1 - 12.3
64 | 2 - 11.8
65 | 3 - 10.8
66 | 4 - 9.6
67 | 5 - 9.5
68 | Choose from 1, 2, 3, 4, 5 [1]: 1
69 | Select js_task_runner:
70 | 1 - None
71 | 2 - Gulp
72 | Choose from 1, 2 [1]:
73 | Select cloud_provider:
74 | 1 - AWS
75 | 2 - GCP
76 | 3 - None
77 | Choose from 1, 2, 3 [1]: 1
78 | Select mail_service:
79 | 1 - Mailgun
80 | 2 - Amazon SES
81 | 3 - Mailjet
82 | 4 - Mandrill
83 | 5 - Postmark
84 | 6 - Sendgrid
85 | 7 - SendinBlue
86 | 8 - SparkPost
87 | 9 - Other SMTP
88 | Choose from 1, 2, 3, 4, 5, 6, 7, 8, 9 [1]: 6
89 | use_async [n]:
90 | use_drf [n]: y
91 | custom_bootstrap_compilation [n]: n
92 | use_compressor [n]:
93 | use_celery [n]:
94 | use_mailhog [n]: y
95 | use_sentry [n]:
96 | use_whitenoise [n]:
97 | use_heroku [n]: y
98 | Select ci_tool:
99 | 1 - None
100 | 2 - Travis
101 | 3 - Gitlab
102 | Choose from 1, 2, 3 [1]:
103 | keep_local_envs_in_vcs [y]: n
104 | debug [n]:
105 | ```
106 |
107 |
108 |
109 |
110 | Note: By default the root folder of the project generated will be equal to the entered project_slug, that is, the name of the main Django app. After this process, you can change the root folder project name if you want.
111 |
112 | ## Install pipenv
113 | The recommendation from Rootstrap is that you use [`pipenv`](https://github.com/pypa/pipenv) to manage the development environment dependencies.
114 |
115 | To install the package globally, just run `$ pip3 install pipenv`
116 |
117 | ## Install dependencies
118 | In order to use pipenv and avoid confusions we are going to generate a Pipfile with the existing requirements and then remove all "requirements.txt" type of files.
119 |
120 | 1. Install the main dependencies: `$ pipenv install -r requirements/production.txt`
121 | 2. Open `requirements/local.txt` and delete the first line: `-r base.txt`. This will prevent the duplication of all base dependencies under `[dev-packages]` in the Pipfile.
122 | 3. Install dev dependencies: `$ pipenv install -r requirements/local.txt --dev`
123 | 4. Delete the following files and folder:
124 | - `requirements.txt` from the root folder of the project.
125 | - `requirements/base.txt`, `requirements/local.txt`, `requirements/production.txt` and the `requirements` folder.
126 |
127 | NOTES:
128 | - Remember, when you install a new dependency, check if it will be needed at production or not. In the case that it won't be needed, add the flag `--dev` at the end of the installation command.
129 | - In the Pipfile, try to avoid the use of `"*"` for the library versions. For each library set the used version in the project. This is to avoid future compatibility errors.
130 | - At `pipenv` to run a command, you have two options:
131 | 1. You can run `$ pipenv shell`, which creates a shell with the virtual environment activated, and in there, you can run any command without prefixes. For example, the command that runs the server should be just `$ python manage.py runserver`
132 | 2. If you haven't entered to the shell, you can write `pipenv run` before any command, for example, the command that runs the server should be `$ pipenv run python manage.py runserver`
133 |
134 | ## Create psql database
135 | `$ createdb `
136 |
137 | For example `$ createdb cookiecutter_starter-db-dev`
138 |
139 | ## Load .env file for local configurations
140 | 1. At `config/settings/base.py` set `DJANGO_READ_DOT_ENV_FILE` to load the configurations from `.env` file.
141 | ```python
142 | # config/settings/base.py
143 |
144 | # Before
145 | READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False)
146 |
147 | # After
148 | READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=True)
149 | ```
150 |
151 | 2. Create `.env` file at root folder and `.env.example` to track the local configuration needed.
152 | ```yml
153 | # .env.example
154 |
155 | DJANGO_DEBUG=on
156 | DATABASE_URL=postgres://:@localhost:5432/
157 | ```
158 |
159 | For example, let's say you have:
160 | - A created db named `cookiecutter_starter-db-dev`, and a user `postgres` with password `postgres`
161 | - Then the value for `DATABASE_URL` would be: `DATABASE_URL=postgres://postgres:postgres@localhost:5432/cookiecutter_starter-db-dev`
162 |
163 | IMPORTANT SUGGESTION:
164 | - For critical or sensitive information/credentials use an environment variable in `.env` file and add it also to `.env.example` for consistency. Also, set the definition of the env var in the config file without the `default` parameter.
165 |
166 | ### Important Note
167 | For the next commands, we will assume that you are in the root folder, and you have already entered to the sell with `$ pipenv shell`
168 |
169 | ## Migrate
170 | `$ python manage.py migrate`
171 |
172 | ## Run test
173 | `$ pytest`
174 |
175 | ## Run test and get coverage percentage
176 | `$ coverage run -m pytest && pipenv run coverage report -m`
177 |
178 | NOTE:
179 | - The command must run without exceptions because it's mandatory for future configurations.
180 | - Don't matter if you write tests following the [unittest](https://docs.python.org/3/library/unittest.html) or [pytest](https://docs.pytest.org/) approach since `$ pytest` command will be able to run both of them.
181 |
182 | ## Install Mailhog
183 | `$ brew install mailhog`
184 |
185 | NOTE: If you prefer other options to install *Mailhog*, you can take a look at the [Mailhog installation guideline](https://github.com/mailhog/MailHog#installation).
186 |
187 | Start the server running `$ mailhog`
188 |
189 | Finally, if you go to `http://localhost:8025/`, your mail server is running.
190 |
191 | ## Code Style
192 | ### [flake8](https://flake8.pycqa.org/) and [flake8-isort](https://github.com/gforcada/flake8-isort)
193 | *NOTE: By default this package is already installed.*
194 |
195 | 1. `$ pipenv install flake8 flake8-isort --dev`
196 | 2. Add custom configuration at `setup.cfg` file according to the Rootstrap's standards.
197 | - **extend-ignore**: exclude flake8 warnings to avoid black conflicts. [See more.](https://black.readthedocs.io/en/stable/compatible_configs.html#id2)
198 | ```yml
199 | # setup.cfg
200 |
201 | [flake8]
202 | max-line-length = 120
203 | exclude = .tox,.git,*/migrations/*,*/static/CACHE/*,docs,node_modules,venv/*
204 | extend-ignore = E203, W503
205 | ```
206 | 3. Check running `$ flake8 .`
207 | - This command list all the issues found.
208 | - A useful command could be `$ isort .` which resolves all the issues related to import the styles. Also, you can add the flag `--interactive` at the end of the command to resolve or ignore one issue by one.
209 |
210 | ### [black](https://black.readthedocs.io/)
211 | *NOTE: By default this package is already installed.*
212 |
213 | 1. `$ pipenv install black --dev`
214 | 2. Add custom configuration creating a `pyproject.toml` file in the root folder according to the Rootstrap's standards and adding the next configuration.
215 | - *black* does not support configurations at `setup.cfg` file and there isn't a plan to do it in the future. [See more.](https://github.com/psf/black/issues/683)
216 | ```yml
217 | # pyproject.toml
218 |
219 | [tool.black]
220 | skip_string_normalization = false
221 | line-length = 120
222 | exclude = '.*\/(migrations|settings)\/.*'
223 | ```
224 | 3. Check running `$ black . --check`
225 | - This command list all the issues found.
226 | - A useful command could be `$ black .` which resolves all the issues. Also, you can run `$ black . --diff` to watch the proposed changes.
227 |
228 | ### If you want to have this in a new Github repository
229 | If you want to have this project in a new Github repository under the rootstrap organization, then follow these steps:
230 | 1. Go to Github.
231 | 2. Click on create new repository.
232 | 3. Under Owner, select rootstrap.
233 | 4. Write the name for the respository equal to the name of the root folder of the project created with DjangoCookieCutter.
234 | 5. Copy the `git clone with ssh value` from the project.
235 | 6. Go to the root folder project in your terminal.
236 | 7. Run `$ git init`
237 | 8. Run `$ git remote add origin `
238 | 9. If your current branch hasn't the `main` branch, then run `$ git checkout -b main`
239 | 10. Run `$ git add .`
240 | 11. Run `$ git commit -m "First commit"`. You can change the `"First commit"` message with whatever you think is correct.
241 | 12. Run `$ git push origin main`
242 | 13. If you had to run `$ git checkout -b main` then now run `$ git branch -M main`
243 |
244 | ### [pre-commit](https://pre-commit.com/)
245 | *NOTE: By default this package is already installed.*
246 |
247 | This is optional and is a local configuration. A sample `pre-commit-config` hook already exists in the generated project as default.
248 | 1. `$ pipenv install pre-commit --dev`
249 | 2. `$ pre-commit install` # A git repo is required for pre-commit to install
250 |
251 | This will run flake8, black and isort locally before creating a commit. This can avoid having to wait for the CI to fail to remember that we missed some styling issues. You can ignore the pre commit hook by adding `-n` when creating a commit (e.g. `git commit -m 'ignore pre-commit' -n`).
252 |
253 | You can check the `.pre-commit-config.yaml` file for more details.
254 |
255 | Note: the first commit after running step 2 will take a while but after that it will be pretty fast.
256 |
257 | ### [mypy](https://mypy.readthedocs.io/en/stable/)
258 | *NOTE: By default, this package is already installed but it is NOT a stage added to pre-commit or Github Actions.*
259 | First, take a look at [typing hints](https://github.com/rootstrap/tech-guides/tree/master/python#typing-hints) document in the Python section of the tech guides.
260 | Cookiecutter already configures Mypy in `setup.cfg` file, if you need to change any of these configurations, please check the `[mypy]` section in this file.
261 | Adding this typing hint check library to pre-commit or GitHub Workflow Actions is optional, it depends on your project scope.
262 | If you add Mypy to pre-commit it will check the typing in the files you change on your commit, to add Mypy to pre-commit you will need to add something like the following:
263 |
264 | .pre-commit-config.yaml (Click ▶ to display the file)
265 |
266 | ```yaml
267 | # .pre-commit-config.yaml
268 | ...
269 |
270 | - repo: https://github.com/pre-commit/mirrors-mypy
271 | rev: v0.942
272 | hooks:
273 | - id: mypy
274 | additional_dependencies: [django-stubs]
275 |
276 | ...
277 | ```
278 | You may need more configuration in `additional_dependencies` depending on your project libraries because these pre-commit stages are running in an independent environment.
279 |
280 |
281 |
282 | If you want to check the Github Wokflow configuration please take a look at this [section](#github-workflow)
283 |
284 | A useful command could be `$ mypy .`, which shows you all the typing issues you have in all your Python files.
285 |
286 | ### Single-quotes
287 | > *Here at Rootstrap we prefer to use single-quoted strings over double quotes. For docstrings we use double quotes since the chance of writing a ' is higher in the documentation string of a class or a method.*
288 | >
289 | > [Rootstrap Guides/Python/String Quotes](https://github.com/rootstrap/tech-guides/tree/master/python#string-quotes)
290 |
291 | To convert the existing double quotes to single ones, follow these steps:
292 | 1. In your IDE, search by the regex `/(?
314 | .codeclimate.yml. (Click ▶ to display the file)
315 |
316 | ```yml
317 | # .codeclimate.yml
318 | version: "2"
319 |
320 | checks:
321 | argument-count:
322 | config:
323 | threshold: 5 # Allow at most 5 arguments in a function
324 | method-count:
325 | config:
326 | threshold: 25 # Allow at most 25 functions in a class
327 |
328 | exclude_patterns:
329 | - "**/migrations/**"
330 | - "**/settings/**"
331 | - "docs/"
332 | - "**/*.env"
333 | - "build/"
334 | - "dist/"
335 | - "**__pycache__**"
336 |
337 | plugins:
338 | pep8:
339 | enabled: true
340 | duplication:
341 | enabled: true
342 | exclude_patterns:
343 | - "**/tests/**" # Don't check duplication on tests
344 | ```
345 |
346 |
347 | ### GitHub Workflow
348 |
349 | 1. Create the path `.github/workflows` and inner it the file `python-app.yml` (or any file name you want as long it is in this folder) with the following content.
350 | 2. Create a project secret to use it at the config file. [guide](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository).
351 | - Name: `CC_TEST_REPORTER_ID`
352 | - Value: `Test Coverage ID`
353 | 3. Add badge to readme file. [guide](https://docs.github.com/en/actions/managing-workflow-runs/adding-a-workflow-status-badge#using-the-event-parameter)
354 | - The workflow name is the value of the first configuration `name`, in the example above is `Python application`.
355 |
356 | ```markdown
357 | # Template:
358 | .. image:: https://github.com///workflows//badge.svg
359 |
360 | # Example:
361 | .. image:: https://github.com/afmicc/cookiecutter_starter/workflows/Python%20application/badge.svg
362 | :alt: Tests status
363 | ```
364 |
365 |
366 | .github/workflows/python-app.yml. (Click ▶ to display the file)
367 |
368 | ```yml
369 | # .github/workflows/python-app.yml
370 |
371 | name: Python application
372 |
373 | on: [push, pull_request]
374 |
375 | jobs:
376 | build:
377 | runs-on: ubuntu-latest
378 | services:
379 | db:
380 | image: postgres:11.6-alpine
381 | env:
382 | POSTGRES_PASSWORD: postgres
383 | POSTGRES_USER: postgres
384 | ports:
385 | - "5432:5432"
386 | env:
387 | DATABASE_URL: postgresql://postgres:postgres@localhost:5432/postgres
388 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
389 |
390 | steps:
391 | - uses: actions/checkout@v2
392 |
393 | - name: Set up Python 3.8
394 | uses: actions/setup-python@v2
395 | with:
396 | python-version: 3.8
397 |
398 | - name: Installing resoruces
399 | run: |
400 | pip install --upgrade pip==20.0.2
401 | pip install pipenv
402 |
403 | - name: Cache pipenv
404 | uses: actions/cache@v2
405 | id: pipenv-cache
406 | with:
407 | path: ~/.local/share/virtualenvs
408 | key: ${{ runner.os }}-pipenv-cache-${{ hashFiles('Pipfile.lock') }}
409 | restore-keys: |
410 | ${{ runner.os }}-pipenv-cache
411 |
412 | - name: Installing requirements
413 | if: steps.pipenv-cache.outputs.cache-hit != 'true'
414 | run: |
415 | pipenv install --dev
416 |
417 | - name: Checking PEP8 code style
418 | run: |
419 | pipenv run flake8 --count
420 |
421 | - name: Checking Black code formatter
422 | run: |
423 | pipenv run black . --check
424 |
425 | - name: Check typing
426 | run: |
427 | pipenv run mypy .
428 |
429 | - name: Running tests
430 | run: |
431 | pipenv run coverage run -m pytest --ds=config.settings.test
432 |
433 | - name: Checking coverage
434 | run: |
435 | pipenv run coverage report --fail-under=90 -m
436 | pipenv run coverage xml
437 |
438 | - name: Setup Code Climate test-reporter
439 | run: |
440 | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
441 | chmod +x ./cc-test-reporter
442 | ./cc-test-reporter before-build
443 | ./cc-test-reporter after-build --coverage-input-type coverage.py --exit-code $?
444 | ```
445 |
446 |
447 |
448 | ## Adding a new app
449 |
450 | ### Important Note
451 | For the next commands, we will also assume that you are in the root folder, and you have already entered to the sell with `$ pipenv shell`
452 |
453 | 1. To create a new app into the project, we have to run the command `$ python manage.py startapp `
454 | - **new_app_name**: is the name of the new app and it has to be written in plural. For example: targets
455 | 2. Since that command creates the new app at the root directory, you have to move it to `` directory, where there are other existing apps like `users`.
456 | - **project_name**: is the name that you define in the first step of the project creation. For example: cookiecutter_starter
457 | 3. Other change is needed, you have to edit the `apps.py` file of the new app and replace the current `name` field of the config class from only `` to `.`
458 | ```python
459 | # cookiecutter_starter/targets/apps.py
460 |
461 | # Before
462 |
463 | class TargetsConfig(AppConfig):
464 | name = 'targets'
465 |
466 | # After
467 |
468 | class TargetsConfig(AppConfig):
469 | name = 'cookiecutter_starter.targets'
470 | ```
471 | 4. Finally, you need to add the created app to the `INSTALLED_APPS` configuration. We need to go to the file `config/settings/base.py` and find the `LOCAL_APPS` variable where we should add our recently created app following this pattern: `..apps.`. As we can see in the following example, the `LOCAL_APPS` is concatenated to other required apps to create the recognized `INSTALLED_APPS` variable, this is just a splitting that applies Cookiecutter to improve the organization of the app in the config files.
472 | ```python
473 | # config/settings/base.py
474 | ...
475 |
476 | LOCAL_APPS = [
477 | 'cookiecutter_starter.users.apps.UsersConfig',
478 | # Your stuff: custom apps go here
479 | 'cookiecutter_starter.targets.apps.TargetsConfig',
480 | ]
481 |
482 | INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
483 |
484 | ...
485 | ```
486 |
487 | ### Solution Structure
488 |
489 | The internal organization of the structure of an app could depend on the different requirements and circumstances that you have to solve. At Rootstrap, we provide a recommended structure which you could follow whenever you can.
490 |
491 |
492 | Structure. (Click ▶ to display the file)
493 |
494 | ```
495 |
496 | ├── config
497 | │ ├── settings
498 | │ │ ├── ...
499 | │ │ ├── base.py - Here is the base.py file that we mentioned before
500 | │ │ ├── local.py
501 | │ │ ├── production.py
502 | │ │ └── test.py
503 | │ ├── urls.py
504 | │ └── ...
505 | ├── - Where you place new apps
506 | │ ├── templates
507 | │ │ ├──
508 | │ │ │ └── _form.html
509 | │ │ ├── users
510 | │ │ │ └── ...
511 | │ │ ├── ...
512 | │ │ ├── 403.html
513 | │ │ ├── 404.html
514 | │ │ ├── 500.html
515 | │ │ └── base.html
516 | │ ├──
517 | │ │ ├── api
518 | │ │ │ ├── serializers.py
519 | │ │ │ └── views.py
520 | │ │ ├── migrations
521 | │ │ │ └── ...
522 | │ │ ├── tests
523 | │ │ │ ├── __init__.py
524 | │ │ │ ├── factories.py
525 | │ │ │ ├── test_forms.py
526 | │ │ │ ├── test_models.py
527 | │ │ │ ├── test_urls.py
528 | │ │ │ └── test_views.py
529 | │ │ ├── utils
530 | │ │ │ └── ...
531 | │ │ ├── __init__.py
532 | │ │ ├── admin.py
533 | │ │ ├── apps.py - Here is the apps.py file that we mentioned before
534 | │ │ ├── forms.py
535 | │ │ ├── models.py
536 | │ │ ├── urls.py
537 | │ │ └── views.py
538 | │ ├── users
539 | │ │ └── ...
540 | │ └── ...
541 | ├── ...
542 | └── manage.py
543 | ```
544 |
545 |
546 |
547 | ## Drf-spectacular
548 |
549 | This [library](https://drf-spectacular.readthedocs.io/en/latest/) is a sane and flexible [OpenAPI 3.0](https://spec.openapis.org/oas/v3.0.3) schema generation for Django REST framework.
550 | It is already installed and configured by default in the creation of a Cookiecutter project in Django.
551 | The project has 3 important goals:
552 | * Extract as much schema information from DRF as possible.
553 | * Provide flexibility to make the schema usable in the real world (not only toy examples). It is relevant to mention here that a commonly used tool at RootStrap is [Apiary](https://apiary.io/). This tool supports [OpenAPI schemas](https://help.apiary.io/swagger/).
554 | * Generate a schema that works well with the most popular client generators.
555 |
556 | You will see something like this in your app urls.py:
557 |
558 | ```python
559 | # config/urls.py
560 | from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
561 |
562 | ...
563 |
564 | urlpatterns = [
565 | ...
566 | # YOUR PATTERNS
567 | path('api/schema/', SpectacularAPIView.as_view(), name='api-schema'),
568 | # This will download a .yaml file with your api schema
569 | # Optional UI:
570 | path('api/docs/', SpectacularSwaggerView.as_view(url_name='api-schema'), name='api-docs'),
571 | # This will enable an UI to show your api schema in HTML.
572 | ]
573 | ```
574 |
575 | The UI views will be like the following images:
576 |
577 | Images (Click ▶ to display the images)
578 | Generally it will be like this:
579 |
580 | 
581 |
582 | In every endpoint, you will see a description of the response and the parameters allowed. Even you can use the endpoint from this web interface.
583 |
584 | 
585 |
586 |
587 |
588 | This library is also extra customizable. You could use the `@extend_schema` decorator to customize APIView, Viewsets, function-based views, and @action. This decorator will allow you to add additional parameters or customize the existing ones (description, types), override request/response serializers, and more!
589 |
590 |
591 | @extend_schema decorator example (Click ▶ to display example)
592 |
593 | Example extracted from drf-spectacular documentation.
594 |
595 | ```python
596 | from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiExample
597 | from drf_spectacular.types import OpenApiTypes
598 |
599 | class AlbumViewset(viewset.ModelViewset):
600 | serializer_class = AlbumSerializer
601 |
602 | @extend_schema(
603 | request=AlbumCreationSerializer,
604 | responses={201: AlbumSerializer},
605 | )
606 | def create(self, request):
607 | # your non-standard behaviour
608 | return super().create(request)
609 |
610 | @extend_schema(
611 | # extra parameters added to the schema
612 | parameters=[
613 | OpenApiParameter(name='artist', description='Filter by artist', required=False, type=str),
614 | OpenApiParameter(
615 | name='release',
616 | type=OpenApiTypes.DATE,
617 | location=OpenApiParameter.QUERY,
618 | description='Filter by release date',
619 | examples=[
620 | OpenApiExample(
621 | 'Example 1',
622 | summary='short optional summary',
623 | description='longer description',
624 | value='1993-08-23'
625 | ),
626 | ...
627 | ],
628 | ),
629 | ],
630 | # override default docstring extraction
631 | description='More descriptive text',
632 | # provide Authentication class that deviates from the views default
633 | auth=None,
634 | # change the auto-generated operation name
635 | operation_id=None,
636 | # or even completely override what AutoSchema would generate. Provide raw Open API spec as Dict.
637 | operation=None,
638 | # attach request/response examples to the operation.
639 | examples=[
640 | OpenApiExample(
641 | 'Example 1',
642 | description='longer description',
643 | value=...
644 | ),
645 | ...
646 | ],
647 | )
648 | def list(self, request):
649 | # your non-standard behaviour
650 | return super().list(request)
651 | ```
652 |
653 |
654 |
655 | Please check the [drf-spectacular](https://drf-spectacular.readthedocs.io/en/latest/) documentation to find out more about it.
656 |
657 | ## Final
658 |
659 | Congratulations! Your project is fully configured 💪
660 |
661 | Here the example project: [afmicc/cookiecutter_starter](https://github.com/afmicc/cookiecutter_starter)
662 |
--------------------------------------------------------------------------------
/ruby/rails.md:
--------------------------------------------------------------------------------
1 | # Prelude
2 |
3 | > Role models are important.
4 | > -- Officer Alex J. Murphy / RoboCop
5 |
6 | The goal of this guide is to present a set of best practices and style
7 | prescriptions for Ruby on Rails 4/5 development.
8 | Some of the advice here is applicable only to Rails 4.0+.
9 |
10 | # The Rails Style Guide
11 |
12 | This Rails style guide recommends best practices so that real-world Rails
13 | programmers can write code that can be maintained by other real-world Rails
14 | programmers. A style guide that reflects real-world usage gets used, and a
15 | style guide that holds to an ideal that has been rejected by the people it
16 | is supposed to help risks not getting used at all – no matter how good
17 | it is.
18 |
19 |
20 | ## Table of Contents
21 |
22 | * [Configuration](#configuration)
23 | * [Routing](#routing)
24 | * [Controllers](#controllers)
25 | * [Rendering](#rendering)
26 | * [Models](#models)
27 | * [ActiveRecord](#activerecord)
28 | * [ActiveRecord Queries](#activerecord-queries)
29 | * [Migrations](#migrations)
30 | * [Views](#views)
31 | * [Internationalization](#internationalization)
32 | * [Assets](#assets)
33 | * [Mailers](#mailers)
34 | * [Active Support Core Extensions](#active-support-core-extensions)
35 | * [Time](#time)
36 | * [Bundler](#bundler)
37 | * [Managing processes](#managing-processes)
38 | * [Logging](#logging)
39 |
40 | ## Configuration
41 |
42 | *
43 | Put custom initialization code in `config/initializers`. The code in
44 | initializers executes on application startup.
45 | [[link](#config-initializers)]
46 |
47 | *
48 | Keep initialization code for each gem in a separate file with the same name
49 | as the gem, for example `carrierwave.rb`, `active_admin.rb`, etc.
50 | [[link](#gem-initializers)]
51 |
52 | *
53 | Adjust accordingly the settings for development, test and production
54 | environment (in the corresponding files under `config/environments/`)
55 | [[link](#dev-test-prod-configs)]
56 |
57 | * Mark additional assets for precompilation (if any):
58 |
59 | ```Ruby
60 | # config/environments/production.rb
61 | # Precompile additional assets (application.js, application.css,
62 | #and all non-JS/CSS are already added)
63 | config.assets.precompile += %w( rails_admin/rails_admin.css rails_admin/rails_admin.js )
64 | ```
65 |
66 | *
67 | Keep configuration that's applicable to all environments in the
68 | `config/application.rb` file.
69 | [[link](#app-config)]
70 |
71 | *
72 | Create an additional `staging` environment that closely resembles the
73 | `production` one.
74 | [[link](#staging-like-prod)]
75 |
76 | *
77 | Keep any additional configuration in YAML files under the `config/` directory.
78 | [[link](#yaml-config)]
79 |
80 | Since Rails 4.2 YAML configuration files can be easily loaded with the new `config_for` method:
81 |
82 | ```Ruby
83 | Rails::Application.config_for(:yaml_file)
84 | ```
85 |
86 | ## Routing
87 | *
88 | Prefer `resources` over custom routes.
89 | [[link](#resource-routing)]
90 |
91 | ```Ruby
92 | # bad
93 | get 'topics/:id', to: 'topics#show'
94 |
95 | # good
96 | resources :topics, only: :show
97 | ```
98 |
99 | *
100 | When you need to add more actions to a RESTful resource (do you really need
101 | them at all?) use `member` and `collection` routes.
102 | [[link](#member-collection-routes)]
103 |
104 | ```Ruby
105 | # bad
106 | get 'subscriptions/:id/unsubscribe'
107 | resources :subscriptions
108 |
109 | # good
110 | resources :subscriptions do
111 | get 'unsubscribe', on: :member
112 | end
113 |
114 | # bad
115 | get 'photos/search'
116 | resources :photos
117 |
118 | # good
119 | resources :photos do
120 | get 'search', on: :collection
121 | end
122 | ```
123 |
124 | *
125 | If you need to define multiple `member/collection` routes use the
126 | alternative block syntax.
127 | [[link](#many-member-collection-routes)]
128 |
129 | ```Ruby
130 | resources :subscriptions do
131 | member do
132 | get 'unsubscribe'
133 | # more routes
134 | end
135 | end
136 |
137 | resources :photos do
138 | collection do
139 | get 'search'
140 | # more routes
141 | end
142 | end
143 | ```
144 |
145 | *
146 | Use nested routes to express better the relationship between ActiveRecord
147 | models.
148 | [[link](#nested-routes)]
149 |
150 | ```Ruby
151 | class Post < ActiveRecord::Base
152 | has_many :comments
153 | end
154 |
155 | class Comments < ActiveRecord::Base
156 | belongs_to :post
157 | end
158 |
159 | # routes.rb
160 | resources :posts do
161 | resources :comments
162 | end
163 | ```
164 |
165 | *
166 | If you need to nest routes more than 1 level deep then use the `shallow: true` option. This will save user from long urls `posts/1/comments/5/versions/7/edit` and you from long url helpers `edit_post_comment_version`.
167 |
168 | ```Ruby
169 | resources :posts, shallow: true do
170 | resources :comments do
171 | resources :versions
172 | end
173 | end
174 | ```
175 |
176 | *
177 | Use namespaced routes to group related actions.
178 | [[link](#namespaced-routes)]
179 |
180 | ```Ruby
181 | namespace :admin do
182 | # Directs /admin/products/* to Admin::ProductsController
183 | # (app/controllers/admin/products_controller.rb)
184 | resources :products
185 | end
186 | ```
187 |
188 | *
189 | Never use the legacy wild controller route. This route will make all actions
190 | in every controller accessible via GET requests.
191 | [[link](#no-wild-routes)]
192 |
193 | ```Ruby
194 | # very bad
195 | match ':controller(/:action(/:id(.:format)))'
196 | ```
197 |
198 | *
199 | Don't use `match` to define any routes unless there is need to map multiple request types among `[:get, :post, :patch, :put, :delete]` to a single action using `:via` option.
200 | [[link](#no-match-routes)]
201 |
202 | *
203 | Don't create routes that are not being used. Use `:only` and `:except` to generate only the routes you actually need.
204 | [[link](#non-existing-routes)]
205 |
206 | ## Controllers
207 |
208 | *
209 | Keep the controllers skinny - they should only retrieve data for the view
210 | layer and shouldn't contain any business logic (all the business logic
211 | should naturally reside in the model).
212 | [[link](#skinny-controllers)]
213 |
214 | *
215 | Each controller action should (ideally) invoke only one method other than an
216 | initial find or new.
217 | [[link](#one-method)]
218 |
219 | *
220 | Share no more than two instance variables between a controller and a view.
221 | [[link](#shared-instance-variables)]
222 |
223 |
224 | ### Rendering
225 |
226 | *
227 | Prefer using a template over inline rendering.
228 | [[link](#inline-rendering)]
229 |
230 | ```Ruby
231 | # very bad
232 | class ProductsController < ApplicationController
233 | def index
234 | render inline: "<% products.each do |p| %><%= p.name %>
<% end %>", type: :erb
235 | end
236 | end
237 |
238 | # good
239 | ## app/views/products/index.html.erb
240 | <%= render partial: 'product', collection: products %>
241 |
242 | ## app/views/products/_product.html.erb
243 | <%= product.name %>
244 | <%= product.price %>
245 |
246 | ## app/controllers/foo_controller.rb
247 | class ProductsController < ApplicationController
248 | def index
249 | render :index
250 | end
251 | end
252 | ```
253 |
254 | *
255 | Prefer `render plain:` over `render text:`.
256 | [[link](#plain-text-rendering)]
257 |
258 | ```Ruby
259 | # bad - sets MIME type to `text/html`
260 | ...
261 | render text: 'Ruby!'
262 | ...
263 |
264 | # bad - requires explicit MIME type declaration
265 | ...
266 | render text: 'Ruby!', content_type: 'text/plain'
267 | ...
268 |
269 | # good - short and precise
270 | ...
271 | render plain: 'Ruby!'
272 | ...
273 | ```
274 |
275 | *
276 | Prefer [corresponding symbols](https://gist.github.com/mlanett/a31c340b132ddefa9cca) to numeric HTTP status codes. They are meaningful and do not look like "magic" numbers for less known HTTP status codes.
277 | [[link](#http-status-code-symbols)]
278 |
279 | ```Ruby
280 | # bad
281 | ...
282 | render status: 500
283 | ...
284 |
285 | # good
286 | ...
287 | render status: :forbidden
288 | ...
289 | ```
290 |
291 | ## Models
292 |
293 | *
294 | Introduce non-ActiveRecord model classes freely.
295 | [[link](#model-classes)]
296 |
297 | *
298 | Name the models with meaningful (but short) names without abbreviations.
299 | [[link](#meaningful-model-names)]
300 |
301 | *
302 | If you need model objects that support ActiveRecord behavior (like validation)
303 | without the ActiveRecord database functionality use the
304 | [ActiveAttr](https://github.com/cgriego/active_attr) gem.
305 | [[link](#activeattr-gem)]
306 |
307 | ```Ruby
308 | class Message
309 | include ActiveAttr::Model
310 |
311 | attribute :name
312 | attribute :email
313 | attribute :content
314 | attribute :priority
315 |
316 | attr_accessible :name, :email, :content
317 |
318 | validates :name, presence: true
319 | validates :email, format: { with: /\A[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}\z/i }
320 | validates :content, length: { maximum: 500 }
321 | end
322 | ```
323 |
324 | For a more complete example refer to the
325 | [RailsCast on the subject](http://railscasts.com/episodes/326-activeattr).
326 |
327 | *
328 | Unless they have some meaning in the business domain, don't put methods in
329 | your model that just format your data (like code generating HTML). These
330 | methods are most likely going to be called from the view layer only, so their
331 | place is in decorators. Keep your models for business logic and data-persistence
332 | only.
333 | [[link](#model-business-logic)]
334 |
335 | ### ActiveRecord
336 |
337 | *
338 | Avoid altering ActiveRecord defaults (table names, primary key, etc) unless
339 | you have a very good reason (like a database that's not under your control).
340 | [[link](#keep-ar-defaults)]
341 |
342 | ```Ruby
343 | # bad - don't do this if you can modify the schema
344 | class Transaction < ActiveRecord::Base
345 | self.table_name = 'order'
346 | ...
347 | end
348 | ```
349 |
350 | *
351 | Group macro-style methods (`has_many`, `validates`, etc) in the beginning of
352 | the class definition.
353 | [[link](#macro-style-methods)]
354 |
355 | ```Ruby
356 | class User < ActiveRecord::Base
357 | # keep the default scope first (if any)
358 | default_scope { where(active: true) }
359 |
360 | # constants come up next
361 | COLORS = %w(red green blue)
362 |
363 | # afterwards we put attr related macros
364 | attr_accessor :formatted_date_of_birth
365 |
366 | attr_accessible :login, :first_name, :last_name, :email, :password
367 |
368 | # Rails4+ enums after attr macros, prefer the hash syntax
369 | enum gender: { female: 0, male: 1 }
370 |
371 | # followed by association macros
372 | belongs_to :country
373 |
374 | has_many :authentications, dependent: :destroy
375 |
376 | # and validation macros
377 | validates :email, presence: true
378 | validates :username, presence: true
379 | validates :username, uniqueness: { case_sensitive: false }
380 | validates :username, format: { with: /\A[A-Za-z][A-Za-z0-9._-]{2,19}\z/ }
381 | validates :password, format: { with: /\A\S{8,128}\z/, allow_nil: true }
382 |
383 | # next we have callbacks
384 | before_save :cook
385 | before_save :update_username_lower
386 |
387 | # other macros (like devise's) should be placed after the callbacks
388 |
389 | ...
390 | end
391 | ```
392 |
393 | *
394 | Prefer `has_many :through` to `has_and_belongs_to_many`. Using `has_many
395 | :through` allows additional attributes and validations on the join model.
396 | [[link](#has-many-through)]
397 |
398 | ```Ruby
399 | # not so good - using has_and_belongs_to_many
400 | class User < ActiveRecord::Base
401 | has_and_belongs_to_many :groups
402 | end
403 |
404 | class Group < ActiveRecord::Base
405 | has_and_belongs_to_many :users
406 | end
407 |
408 | # preferred way - using has_many :through
409 | class User < ActiveRecord::Base
410 | has_many :memberships
411 | has_many :groups, through: :memberships
412 | end
413 |
414 | class Membership < ActiveRecord::Base
415 | belongs_to :user
416 | belongs_to :group
417 | end
418 |
419 | class Group < ActiveRecord::Base
420 | has_many :memberships
421 | has_many :users, through: :memberships
422 | end
423 | ```
424 |
425 | *
426 | Prefer `self[:attribute]` over `read_attribute(:attribute)`.
427 | [[link](#read-attribute)]
428 |
429 | ```Ruby
430 | # bad
431 | def amount
432 | read_attribute(:amount) * 100
433 | end
434 |
435 | # good
436 | def amount
437 | self[:amount] * 100
438 | end
439 | ```
440 |
441 | *
442 | Prefer `self[:attribute] = value` over `write_attribute(:attribute, value)`.
443 | [[link](#write-attribute)]
444 |
445 | ```Ruby
446 | # bad
447 | def amount
448 | write_attribute(:amount, 100)
449 | end
450 |
451 | # good
452 | def amount
453 | self[:amount] = 100
454 | end
455 | ```
456 |
457 | *
458 | Always use the new ["sexy"
459 | validations](http://thelucid.com/2010/01/08/sexy-validation-in-edge-rails-rails-3/).
460 | [[link](#sexy-validations)]
461 |
462 | ```Ruby
463 | # bad
464 | validates_presence_of :email
465 | validates_length_of :email, maximum: 100
466 |
467 | # good
468 | validates :email, presence: true, length: { maximum: 100 }
469 | ```
470 |
471 | *
472 | When a custom validation is used more than once or the validation is some
473 | regular expression mapping, create a custom validator file.
474 | [[link](#custom-validator-file)]
475 |
476 | ```Ruby
477 | # bad
478 | class Person
479 | validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
480 | end
481 |
482 | # good
483 | class EmailValidator < ActiveModel::EachValidator
484 | def validate_each(record, attribute, value)
485 | record.errors[attribute] << (options[:message] || 'is not a valid email') unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
486 | end
487 | end
488 |
489 | class Person
490 | validates :email, email: true
491 | end
492 | ```
493 |
494 | *
495 | Keep custom validators under `app/validators`.
496 | [[link](#app-validators)]
497 |
498 | *
499 | Consider extracting custom validators to a shared gem if you're maintaining
500 | several related apps or the validators are generic enough.
501 | [[link](#custom-validators-gem)]
502 |
503 | *
504 | Use named scopes freely.
505 | [[link](#named-scopes)]
506 |
507 | ```Ruby
508 | class User < ActiveRecord::Base
509 | scope :active, -> { where(active: true) }
510 | scope :inactive, -> { where(active: false) }
511 |
512 | scope :with_orders, -> { joins(:orders).select('distinct(users.id)') }
513 | end
514 | ```
515 |
516 | *
517 | When a named scope defined with a lambda and parameters becomes too
518 | complicated, it is preferable to make a class method instead which serves the
519 | same purpose of the named scope and returns an `ActiveRecord::Relation`
520 | object. Arguably you can define even simpler scopes like this.
521 | [[link](#named-scope-class)]
522 |
523 | ```Ruby
524 | class User < ActiveRecord::Base
525 | def self.with_orders
526 | joins(:orders).select('distinct(users.id)')
527 | end
528 | end
529 | ```
530 |
531 | *
532 | Beware of the behavior of the
533 | [following](http://guides.rubyonrails.org/active_record_validations.html#skipping-validations)
534 | methods. They do not run the model validations and
535 | could easily corrupt the model state.
536 | [[link](#beware-skip-model-validations)]
537 |
538 | ```Ruby
539 | # bad
540 | Article.first.decrement!(:view_count)
541 | DiscussionBoard.decrement_counter(:post_count, 5)
542 | Article.first.increment!(:view_count)
543 | DiscussionBoard.increment_counter(:post_count, 5)
544 | person.toggle :active
545 | product.touch
546 | Billing.update_all("category = 'authorized', author = 'David'")
547 | user.update_attribute(:website, 'example.com')
548 | user.update_columns(last_request_at: Time.current)
549 | Post.update_counters 5, comment_count: -1, action_count: 1
550 |
551 | # good
552 | user.update_attributes(website: 'example.com')
553 | ```
554 |
555 | *
556 | Use user-friendly URLs. Show some descriptive attribute of the model in the URL
557 | rather than its `id`. There is more than one way to achieve this:
558 | [[link](#user-friendly-urls)]
559 |
560 | * Override the `to_param` method of the model. This method is used by Rails
561 | for constructing a URL to the object. The default implementation returns
562 | the `id` of the record as a String. It could be overridden to include another
563 | human-readable attribute.
564 |
565 | ```Ruby
566 | class Person
567 | def to_param
568 | "#{id} #{name}".parameterize
569 | end
570 | end
571 | ```
572 |
573 | In order to convert this to a URL-friendly value, `parameterize` should be
574 | called on the string. The `id` of the object needs to be at the beginning so
575 | that it can be found by the `find` method of ActiveRecord.
576 |
577 | * Use the `friendly_id` gem. It allows creation of human-readable URLs by
578 | using some descriptive attribute of the model instead of its `id`.
579 |
580 | ```Ruby
581 | class Person
582 | extend FriendlyId
583 | friendly_id :name, use: :slugged
584 | end
585 | ```
586 |
587 | Check the [gem documentation](https://github.com/norman/friendly_id) for more
588 | information about its usage.
589 |
590 | *
591 | Use `find_each` to iterate over a collection of AR objects. Looping through a
592 | collection of records from the database (using the `all` method, for example)
593 | is very inefficient since it will try to instantiate all the objects at once.
594 | In that case, batch processing methods allow you to work with the records in
595 | batches, thereby greatly reducing memory consumption.
596 | [[link](#find-each)]
597 |
598 |
599 | ```Ruby
600 | # bad
601 | Person.all.each do |person|
602 | person.do_awesome_stuff
603 | end
604 |
605 | Person.where('age > 21').each do |person|
606 | person.party_all_night!
607 | end
608 |
609 | # good
610 | Person.find_each do |person|
611 | person.do_awesome_stuff
612 | end
613 |
614 | Person.where('age > 21').find_each do |person|
615 | person.party_all_night!
616 | end
617 | ```
618 |
619 | *
620 | Since [Rails creates callbacks for dependent
621 | associations](https://github.com/rails/rails/issues/3458), always call
622 | `before_destroy` callbacks that perform validation with `prepend: true`.
623 | [[link](#before_destroy)]
624 |
625 | ```Ruby
626 | # bad (roles will be deleted automatically even if super_admin? is true)
627 | has_many :roles, dependent: :destroy
628 |
629 | before_destroy :ensure_deletable
630 |
631 | def ensure_deletable
632 | fail "Cannot delete super admin." if super_admin?
633 | end
634 |
635 | # good
636 | has_many :roles, dependent: :destroy
637 |
638 | before_destroy :ensure_deletable, prepend: true
639 |
640 | def ensure_deletable
641 | fail "Cannot delete super admin." if super_admin?
642 | end
643 | ```
644 |
645 | *
646 | Define the `dependent` option to the `has_many` and `has_one` associations.
647 | [[link](#has_many-has_one-dependent-option)]
648 |
649 | ```Ruby
650 | # bad
651 | class Post < ActiveRecord::Base
652 | has_many :comments
653 | end
654 |
655 | # good
656 | class Post < ActiveRecord::Base
657 | has_many :comments, dependent: :destroy
658 | end
659 | ```
660 |
661 | *
662 | When persisting AR objects always use the exception raising bang! method or handle the method return value.
663 | This applies to `create`, `save`, `update`, `destroy`, `first_or_create` and `find_or_create_by`.
664 | [[link](#save-bang)]
665 |
666 | ```Ruby
667 | # bad
668 | user.create(name: 'Bruce')
669 |
670 | # bad
671 | user.save
672 |
673 | # good
674 | user.create!(name: 'Bruce')
675 | # or
676 | bruce = user.create(name: 'Bruce')
677 | if bruce.persisted?
678 | ...
679 | else
680 | ...
681 | end
682 |
683 | # good
684 | user.save!
685 | # or
686 | if user.save
687 | ...
688 | else
689 | ...
690 | end
691 | ```
692 |
693 | ### ActiveRecord Queries
694 |
695 | *
696 | Avoid string interpolation in
697 | queries, as it will make your code susceptible to SQL injection
698 | attacks.
699 | [[link](#avoid-interpolation)]
700 |
701 | ```Ruby
702 | # bad - param will be interpolated unescaped
703 | Client.where("orders_count = #{params[:orders]}")
704 |
705 | # good - param will be properly escaped
706 | Client.where('orders_count = ?', params[:orders])
707 | ```
708 |
709 | *
710 | Consider using named placeholders instead of positional placeholders
711 | when you have more than 1 placeholder in your query.
712 | [[link](#named-placeholder)]
713 |
714 | ```Ruby
715 | # okish
716 | Client.where(
717 | 'created_at >= ? AND created_at <= ?',
718 | params[:start_date], params[:end_date]
719 | )
720 |
721 | # good
722 | Client.where(
723 | 'created_at >= :start_date AND created_at <= :end_date',
724 | start_date: params[:start_date], end_date: params[:end_date]
725 | )
726 | ```
727 |
728 | *
729 | Favor the use of `find` over `where`
730 | when you need to retrieve a single record by id.
731 | [[link](#find)]
732 |
733 | ```Ruby
734 | # bad
735 | User.where(id: id).take
736 |
737 | # good
738 | User.find(id)
739 | ```
740 |
741 | *
742 | Favor the use of `find_by` over `where` and `find_by_attribute`
743 | when you need to retrieve a single record by some attributes.
744 | [[link](#find_by)]
745 |
746 | ```Ruby
747 | # bad
748 | User.where(first_name: 'Bruce', last_name: 'Wayne').first
749 |
750 | # bad
751 | User.find_by_first_name_and_last_name('Bruce', 'Wayne')
752 |
753 | # good
754 | User.find_by(first_name: 'Bruce', last_name: 'Wayne')
755 | ```
756 |
757 | *
758 | Favor the use of `where.not` over SQL.
759 | [[link](#where-not)]
760 |
761 | ```Ruby
762 | # bad
763 | User.where("id != ?", id)
764 |
765 | # good
766 | User.where.not(id: id)
767 | ```
768 | *
769 | When specifying an explicit query in a method such as `find_by_sql`, use
770 | heredocs with `squish`. This allows you to legibly format the SQL with
771 | line breaks and indentations, while supporting syntax highlighting in many
772 | tools (including GitHub, Atom, and RubyMine).
773 | [[link](#squished-heredocs)]
774 |
775 | ```Ruby
776 | User.find_by_sql(<<-SQL.squish)
777 | SELECT
778 | users.id, accounts.plan
779 | FROM
780 | users
781 | INNER JOIN
782 | accounts
783 | ON
784 | accounts.user_id = users.id
785 | # further complexities...
786 | SQL
787 | ```
788 |
789 | [`String#squish`](http://apidock.com/rails/String/squish) removes the indentation and newline characters so that your server
790 | log shows a fluid string of SQL rather than something like this:
791 |
792 | ```
793 | SELECT\n users.id, accounts.plan\n FROM\n users\n INNER JOIN\n acounts\n ON\n accounts.user_id = users.id
794 | ```
795 |
796 | ## Migrations
797 |
798 | *
799 | Keep the `schema.rb` (or `structure.sql`) under version control.
800 | [[link](#schema-version)]
801 |
802 | *
803 | Use `rake db:schema:load` instead of `rake db:migrate` to initialize an empty
804 | database.
805 | [[link](#db-schema-load)]
806 |
807 | *
808 | Enforce default values in the migrations themselves instead of in the
809 | application layer.
810 | [[link](#default-migration-values)]
811 |
812 | ```Ruby
813 | # bad - application enforced default value
814 | class Product < ActiveRecord::Base
815 | def amount
816 | self[:amount] || 0
817 | end
818 | end
819 |
820 | # good - database enforced
821 | class AddDefaultAmountToProducts < ActiveRecord::Migration
822 | def change
823 | change_column_default :products, :amount, 0
824 | end
825 | end
826 | ```
827 |
828 | While enforcing table defaults only in Rails is suggested by many
829 | Rails developers, it's an extremely brittle approach that
830 | leaves your data vulnerable to many application bugs. And you'll
831 | have to consider the fact that most non-trivial apps share a
832 | database with other applications, so imposing data integrity from
833 | the Rails app is impossible.
834 |
835 | *
836 | Enforce foreign-key constraints. As of Rails 4.2, ActiveRecord
837 | supports foreign key constraints natively.
838 | [[link](#foreign-key-constraints)]
839 |
840 | *
841 | When writing constructive migrations (adding tables or columns),
842 | use the `change` method instead of `up` and `down` methods.
843 | [[link](#change-vs-up-down)]
844 |
845 | ```Ruby
846 | # the old way
847 | class AddNameToPeople < ActiveRecord::Migration
848 | def up
849 | add_column :people, :name, :string
850 | end
851 |
852 | def down
853 | remove_column :people, :name
854 | end
855 | end
856 |
857 | # the new preferred way
858 | class AddNameToPeople < ActiveRecord::Migration
859 | def change
860 | add_column :people, :name, :string
861 | end
862 | end
863 | ```
864 |
865 | *
866 | If you have to use models in migrations, make sure you define them
867 | so that you don't end up with broken migrations in the future
868 | [[link](#define-model-class-migrations)]
869 |
870 | ```Ruby
871 | # db/migrate/.rb
872 | # frozen_string_literal: true
873 |
874 | # bad
875 | class ModifyDefaultStatusForProducts < ActiveRecord::Migration
876 | def change
877 | old_status = 'pending_manual_approval'
878 | new_status = 'pending_approval'
879 |
880 | reversible do |dir|
881 | dir.up do
882 | Product.where(status: old_status).update_all(status: new_status)
883 | change_column :products, :status, :string, default: new_status
884 | end
885 |
886 | dir.down do
887 | Product.where(status: new_status).update_all(status: old_status)
888 | change_column :products, :status, :string, default: old_status
889 | end
890 | end
891 | end
892 | end
893 |
894 | # good
895 | # Define `table_name` in a custom named class to make sure that
896 | # you run on the same table you had during the creation of the migration.
897 | # In future if you override the `Product` class
898 | # and change the `table_name`, it won't break
899 | # the migration or cause serious data corruption.
900 | class MigrationProduct < ActiveRecord::Base
901 | self.table_name = :products
902 | end
903 |
904 | class ModifyDefaultStatusForProducts < ActiveRecord::Migration
905 | def change
906 | old_status = 'pending_manual_approval'
907 | new_status = 'pending_approval'
908 |
909 | reversible do |dir|
910 | dir.up do
911 | MigrationProduct.where(status: old_status).update_all(status: new_status)
912 | change_column :products, :status, :string, default: new_status
913 | end
914 |
915 | dir.down do
916 | MigrationProduct.where(status: new_status).update_all(status: old_status)
917 | change_column :products, :status, :string, default: old_status
918 | end
919 | end
920 | end
921 | end
922 | ```
923 |
924 | *
925 | Name your foreign keys explicitly instead of relying on Rails auto-generated
926 | FK names. (http://guides.rubyonrails.org/active_record_migrations.html#foreign-keys)
927 | [[link](#meaningful-foreign-key-naming)]
928 |
929 | ```Ruby
930 | # bad
931 | class AddFkArticlesToAuthors < ActiveRecord::Migration
932 | def change
933 | add_foreign_key :articles, :authors
934 | end
935 | end
936 |
937 | # good
938 | class AddFkArticlesToAuthors < ActiveRecord::Migration
939 | def change
940 | add_foreign_key :articles, :authors, name: :articles_author_id_fk
941 | end
942 | end
943 | ```
944 |
945 | *
946 | Don't use non-reversible migration commands in the `change` method.
947 | Reversible migration commands are listed below.
948 | [ActiveRecord::Migration::CommandRecorder](http://api.rubyonrails.org/classes/ActiveRecord/Migration/CommandRecorder.html)
949 | [[link](#reversible-migration)]
950 |
951 | ```ruby
952 | # bad
953 | class DropUsers < ActiveRecord::Migration
954 | def change
955 | drop_table :users
956 | end
957 | end
958 |
959 | # good
960 | class DropUsers < ActiveRecord::Migration
961 | def up
962 | drop_table :users
963 | end
964 |
965 | def down
966 | create_table :users do |t|
967 | t.string :name
968 | end
969 | end
970 | end
971 |
972 | # good
973 | # In this case, block will be used by create_table in rollback
974 | # http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters.html#method-i-drop_table
975 | class DropUsers < ActiveRecord::Migration
976 | def change
977 | drop_table :users do |t|
978 | t.string :name
979 | end
980 | end
981 | end
982 | ```
983 |
984 | *
985 | Avoid `FLOAT` to store rational numbers. `FLOAT` does not store precise values. Either convert the rational to a base unit (eg: store money in cents as an integer), or use the `DECIMAL` type, which does store precise values.
986 | [[link](#avoid-float)]
987 |
988 | ## Views
989 |
990 | *
991 | Never call the model layer directly from a view.
992 | [[link](#no-direct-model-view)]
993 |
994 | *
995 | Never make complex formatting in the views, export the formatting to a method
996 | in the decorator.
997 | [[link](#no-complex-view-formatting)]
998 |
999 | *
1000 | Mitigate code duplication by using partial templates and layouts.
1001 | [[link](#partials)]
1002 |
1003 | ## Internationalization
1004 |
1005 | *
1006 | No strings or other locale specific settings should be used in the views,
1007 | models and controllers. These texts should be moved to the locale files in the
1008 | `config/locales` directory.
1009 | [[link](#locale-texts)]
1010 |
1011 | *
1012 | When the labels of an ActiveRecord model need to be translated, use the
1013 | `activerecord` scope:
1014 | [[link](#translated-labels)]
1015 |
1016 | ```
1017 | en:
1018 | activerecord:
1019 | models:
1020 | user: Member
1021 | attributes:
1022 | user:
1023 | name: 'Full name'
1024 | ```
1025 |
1026 | Then `User.model_name.human` will return "Member" and
1027 | `User.human_attribute_name("name")` will return "Full name". These
1028 | translations of the attributes will be used as labels in the views.
1029 |
1030 |
1031 | *
1032 | Separate the texts used in the views from translations of ActiveRecord
1033 | attributes. Place the locale files for the models in a folder `locales/models` and the
1034 | texts used in the views in folder `locales/views`.
1035 | [[link](#organize-locale-files)]
1036 |
1037 | * When organization of the locale files is done with additional directories,
1038 | these directories must be described in the `application.rb` file in order
1039 | to be loaded.
1040 |
1041 | ```Ruby
1042 | # config/application.rb
1043 | config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
1044 | ```
1045 |
1046 | *
1047 | Place the shared localization options, such as date or currency formats, in
1048 | files under the root of the `locales` directory.
1049 | [[link](#shared-localization)]
1050 |
1051 | *
1052 | Use the short form of the I18n methods: `I18n.t` instead of `I18n.translate`
1053 | and `I18n.l` instead of `I18n.localize`.
1054 | [[link](#short-i18n)]
1055 |
1056 | *
1057 | Use "lazy" lookup for the texts used in views. Let's say we have the following
1058 | structure:
1059 | [[link](#lazy-lookup)]
1060 |
1061 | ```
1062 | en:
1063 | users:
1064 | show:
1065 | title: 'User details page'
1066 | ```
1067 |
1068 | The value for `users.show.title` can be looked up in the template
1069 | `app/views/users/show.html.haml` like this:
1070 |
1071 | ```Ruby
1072 | = t '.title'
1073 | ```
1074 |
1075 | *
1076 | Use the dot-separated keys in the controllers and models instead of specifying
1077 | the `:scope` option. The dot-separated call is easier to read and trace the
1078 | hierarchy.
1079 | [[link](#dot-separated-keys)]
1080 |
1081 | ```Ruby
1082 | # bad
1083 | I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]
1084 |
1085 | # good
1086 | I18n.t 'activerecord.errors.messages.record_invalid'
1087 | ```
1088 |
1089 | *
1090 | More detailed information about the Rails I18n can be found in the [Rails
1091 | Guides](http://guides.rubyonrails.org/i18n.html)
1092 | [[link](#i18n-guides)]
1093 |
1094 | ## Assets
1095 |
1096 | Use the [assets pipeline](http://guides.rubyonrails.org/asset_pipeline.html) to leverage organization within
1097 | your application.
1098 |
1099 | *
1100 | Reserve `app/assets` for custom stylesheets, javascripts, or images.
1101 | [[link](#reserve-app-assets)]
1102 |
1103 | *
1104 | Use `lib/assets` for your own libraries that don’t really fit into the
1105 | scope of the application.
1106 | [[link](#lib-assets)]
1107 |
1108 | *
1109 | Third party code such as [jQuery](http://jquery.com/) or
1110 | [bootstrap](http://twitter.github.com/bootstrap/) should be placed in
1111 | `vendor/assets`.
1112 | [[link](#vendor-assets)]
1113 |
1114 | *
1115 | When possible, use gemified versions of assets (e.g.
1116 | [jquery-rails](https://github.com/rails/jquery-rails),
1117 | [jquery-ui-rails](https://github.com/joliss/jquery-ui-rails),
1118 | [bootstrap-sass](https://github.com/thomas-mcdonald/bootstrap-sass),
1119 | [zurb-foundation](https://github.com/zurb/foundation)).
1120 | [[link](#gem-assets)]
1121 |
1122 | ## Mailers
1123 |
1124 | *
1125 | Name the mailers `SomethingMailer`. Without the Mailer suffix it isn't
1126 | immediately apparent what's a mailer and which views are related to the
1127 | mailer.
1128 | [[link](#mailer-name)]
1129 |
1130 | *
1131 | Provide both HTML and plain-text view templates.
1132 | [[link](#html-plain-email)]
1133 |
1134 | *
1135 | Enable errors raised on failed mail delivery in your development environment.
1136 | The errors are disabled by default.
1137 | [[link](#enable-delivery-errors)]
1138 |
1139 | ```Ruby
1140 | # config/environments/development.rb
1141 |
1142 | config.action_mailer.raise_delivery_errors = true
1143 | ```
1144 |
1145 | *
1146 | Use a local SMTP server like
1147 | [Mailcatcher](https://github.com/sj26/mailcatcher) in the development
1148 | environment. Or [Letter Opener](https://github.com/ryanb/letter_opener) that previews the email in the browser.
1149 | [[link](#local-smtp)]
1150 |
1151 | ```Ruby
1152 | # config/environments/development.rb
1153 |
1154 | config.action_mailer.smtp_settings = {
1155 | address: 'localhost',
1156 | port: 1025,
1157 | # more settings
1158 | }
1159 | ```
1160 |
1161 | *
1162 | Provide default settings for the host name.
1163 | [[link](#default-hostname)]
1164 |
1165 | ```Ruby
1166 | # config/environments/development.rb
1167 | config.action_mailer.default_url_options = { host: "#{local_ip}:3000" }
1168 |
1169 | # config/environments/production.rb
1170 | config.action_mailer.default_url_options = { host: 'your_site.com' }
1171 |
1172 | # in your mailer class
1173 | default_url_options[:host] = 'your_site.com'
1174 | ```
1175 |
1176 | *
1177 | If you need to use a link to your site in an email, always use the `_url`, not
1178 | `_path` methods. The `_url` methods include the host name and the `_path`
1179 | methods don't.
1180 | [[link](#url-not-path-in-email)]
1181 |
1182 | ```Ruby
1183 | # bad
1184 | You can always find more info about this course
1185 | <%= link_to 'here', course_path(@course) %>
1186 |
1187 | # good
1188 | You can always find more info about this course
1189 | <%= link_to 'here', course_url(@course) %>
1190 | ```
1191 |
1192 | *
1193 | Format the from and to addresses properly. Use the following format:
1194 | [[link](#email-addresses)]
1195 |
1196 | ```Ruby
1197 | # in your mailer class
1198 | default from: 'Your Name '
1199 | ```
1200 |
1201 | *
1202 | Make sure that the e-mail delivery method for your test environment is set to
1203 | `test`:
1204 | [[link](#delivery-method-test)]
1205 |
1206 | ```Ruby
1207 | # config/environments/test.rb
1208 |
1209 | config.action_mailer.delivery_method = :test
1210 | ```
1211 |
1212 | *
1213 | The delivery method for development and production should be `smtp`:
1214 | [[link](#delivery-method-smtp)]
1215 |
1216 | ```Ruby
1217 | # config/environments/development.rb, config/environments/production.rb
1218 |
1219 | config.action_mailer.delivery_method = :smtp
1220 | ```
1221 |
1222 | *
1223 | When sending html emails all styles should be inline, as some mail clients
1224 | have problems with external styles. This however makes them harder to maintain
1225 | and leads to code duplication. There are two similar gems that transform the
1226 | styles and put them in the corresponding html tags:
1227 | [premailer-rails](https://github.com/fphilipe/premailer-rails) and
1228 | [roadie](https://github.com/Mange/roadie).
1229 | [[link](#inline-email-styles)]
1230 |
1231 | *
1232 | Sending emails while generating page response should be avoided. It causes
1233 | delays in loading of the page and request can timeout if multiple email are
1234 | sent. To overcome this emails can be sent in background process with the help
1235 | of [sidekiq](https://github.com/mperham/sidekiq) gem.
1236 | [[link](#background-email)]
1237 |
1238 |
1239 | ## Active Support Core Extensions
1240 |
1241 | *
1242 | Prefer Ruby 2.3's safe navigation operator `&.` over `ActiveSupport#try!`.
1243 | [[link](#try-bang)]
1244 |
1245 | ```ruby
1246 | # bad
1247 | obj.try! :fly
1248 |
1249 | # good
1250 | obj&.fly
1251 | ```
1252 |
1253 | *
1254 | Prefer Ruby's Standard Library methods over `ActiveSupport` aliases.
1255 | [[link](#active_support_aliases)]
1256 |
1257 | ```ruby
1258 | # bad
1259 | 'the day'.starts_with? 'th'
1260 | 'the day'.ends_with? 'ay'
1261 |
1262 | # good
1263 | 'the day'.start_with? 'th'
1264 | 'the day'.end_with? 'ay'
1265 | ```
1266 |
1267 | *
1268 | Prefer Ruby's Standard Library over uncommon ActiveSupport extensions.
1269 | [[link](#active_support_extensions)]
1270 |
1271 | ```ruby
1272 | # bad
1273 | (1..50).to_a.forty_two
1274 | 1.in? [1, 2]
1275 | 'day'.in? 'the day'
1276 |
1277 | # good
1278 | (1..50).to_a[41]
1279 | [1, 2].include? 1
1280 | 'the day'.include? 'day'
1281 | ```
1282 |
1283 | *
1284 | Prefer Ruby's comparison operators over ActiveSupport's `Array#inquiry`, `Numeric#inquiry` and `String#inquiry`.
1285 | [[link](#inquiry)]
1286 |
1287 | ```ruby
1288 | # bad - String#inquiry
1289 | ruby = 'two'.inquiry
1290 | ruby.two?
1291 |
1292 | # good
1293 | ruby = 'two'
1294 | ruby == 'two'
1295 |
1296 | # bad - Array#inquiry
1297 | pets = %w(cat dog).inquiry
1298 | pets.gopher?
1299 |
1300 | # good
1301 | pets = %w(cat dog)
1302 | pets.include? 'cat'
1303 |
1304 | # bad - Numeric#inquiry
1305 | 0.positive?
1306 | 0.negative?
1307 |
1308 | # good
1309 | 0 > 0
1310 | 0 < 0
1311 | ```
1312 |
1313 | ## Time
1314 |
1315 | *
1316 | Config your timezone accordingly in `application.rb`.
1317 | [[link](#tz-config)]
1318 |
1319 | ```Ruby
1320 | config.time_zone = 'Eastern European Time'
1321 | # optional - note it can be only :utc or :local (default is :utc)
1322 | config.active_record.default_timezone = :local
1323 | ```
1324 |
1325 | *
1326 | Don't use `Time.parse`.
1327 | [[link](#time-parse)]
1328 |
1329 | ```Ruby
1330 | # bad
1331 | Time.parse('2015-03-02 19:05:37') # => Will assume time string given is in the system's time zone.
1332 |
1333 | # good
1334 | Time.zone.parse('2015-03-02 19:05:37') # => Mon, 02 Mar 2015 19:05:37 EET +02:00
1335 | ```
1336 |
1337 | *
1338 | Don't use `Time.now`.
1339 | [[link](#time-now)]
1340 |
1341 | ```Ruby
1342 | # bad
1343 | Time.now # => Returns system time and ignores your configured time zone.
1344 |
1345 | # good
1346 | Time.zone.now # => Fri, 12 Mar 2014 22:04:47 EET +02:00
1347 | Time.current # Same thing but shorter.
1348 | ```
1349 |
1350 | ## Bundler
1351 |
1352 | *
1353 | Put gems used only for development or testing in the appropriate group in the
1354 | Gemfile.
1355 | [[link](#dev-test-gems)]
1356 |
1357 | *
1358 | Use only established gems in your projects. If you're contemplating on
1359 | including some little-known gem you should do a careful review of its source
1360 | code first.
1361 | [[link](#only-good-gems)]
1362 |
1363 | *
1364 | OS-specific gems will by default result in a constantly changing
1365 | `Gemfile.lock` for projects with multiple developers using different operating
1366 | systems. Add all OS X specific gems to a `darwin` group in the Gemfile, and
1367 | all Linux specific gems to a `linux` group:
1368 | [[link](#os-specific-gemfile-locks)]
1369 |
1370 | ```Ruby
1371 | # Gemfile
1372 | group :darwin do
1373 | gem 'rb-fsevent'
1374 | gem 'growl'
1375 | end
1376 |
1377 | group :linux do
1378 | gem 'rb-inotify'
1379 | end
1380 | ```
1381 |
1382 | To require the appropriate gems in the right environment, add the
1383 | following to `config/application.rb`:
1384 |
1385 | ```Ruby
1386 | platform = RUBY_PLATFORM.match(/(linux|darwin)/)[0].to_sym
1387 | Bundler.require(platform)
1388 | ```
1389 |
1390 | *
1391 | Do not remove the `Gemfile.lock` from version control. This is not some
1392 | randomly generated file - it makes sure that all of your team members get the
1393 | same gem versions when they do a `bundle install`.
1394 | [[link](#gemfile-lock)]
1395 |
1396 | ## Managing processes
1397 |
1398 | *
1399 | If your projects depends on various external processes use
1400 | [foreman](https://github.com/ddollar/foreman) to manage them.
1401 | [[link](#foreman)]
1402 |
1403 | ## Logging
1404 |
1405 | When logging lot of output you can use a block as param instead of a string, specially if you have interpolated values. This avoids the cost of instantiating the unnecessary String objects if the allowed output level doesn't include that level.
1406 |
1407 | https://guides.rubyonrails.org/debugging_rails_applications.html#impact-of-logs-on-performance
1408 |
1409 | ```Ruby
1410 | # bad
1411 | Rails.logger.debug("Person attributes hash: #{@person.attributes.inspect}")
1412 |
1413 | # good
1414 | Rails.logger.debug { "Person attributes hash: #{@person.attributes.inspect}" }
1415 | ```
1416 |
1417 | # References
1418 |
1419 | [Original Ruby style guide](https://github.com/bbatsov/rails-style-guide)
1420 |
--------------------------------------------------------------------------------