{"id":18449592,"url":"https://github.com/jetthoughts/how-we-work","last_synced_at":"2025-10-05T18:07:53.793Z","repository":{"id":21726010,"uuid":"25047665","full_name":"jetthoughts/how-we-work","owner":"jetthoughts","description":"How We Work","archived":false,"fork":false,"pushed_at":"2019-11-18T19:15:19.000Z","size":116,"stargazers_count":23,"open_issues_count":17,"forks_count":7,"subscribers_count":26,"default_branch":"master","last_synced_at":"2025-05-18T17:03:04.147Z","etag":null,"topics":["guide","practices","principles","workflow"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jetthoughts.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-10-10T18:20:14.000Z","updated_at":"2023-09-08T16:51:16.000Z","dependencies_parsed_at":"2022-08-17T16:25:26.662Z","dependency_job_id":null,"html_url":"https://github.com/jetthoughts/how-we-work","commit_stats":null,"previous_names":[],"tags_count":0,"template":true,"template_full_name":null,"purl":"pkg:github/jetthoughts/how-we-work","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetthoughts%2Fhow-we-work","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetthoughts%2Fhow-we-work/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetthoughts%2Fhow-we-work/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetthoughts%2Fhow-we-work/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jetthoughts","download_url":"https://codeload.github.com/jetthoughts/how-we-work/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jetthoughts%2Fhow-we-work/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278494644,"owners_count":25996414,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-05T02:00:06.059Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["guide","practices","principles","workflow"],"created_at":"2024-11-06T07:20:50.937Z","updated_at":"2025-10-05T18:07:53.772Z","avatar_url":"https://github.com/jetthoughts.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"JTology - How We Work\n===========\n\n## Values \u0026 Core Principles\n\n* Fairness\n* Joy\n* [Kaizen](https://en.wikipedia.org/wiki/Kaizen)\n\n## Other Guides:\n\n* [For Product Owners](product.md)\n* [Thoughtbot's Guides](https://github.com/thoughtbot/guides)\n\n## Base Strucutring Projects\n\n* SCSS: Structure is based on [RSCSS](https://github.com/rstacruz/rscss)\n* Vanila JavaScript: Strucutre is based on [RSJS](https://github.com/rstacruz/rsjs)\n* Other common structure of JavaScript Page logic: https://github.com/tastejs/todomvc/blob/gh-pages/examples/jquery/js/app.js\n\n## Formatting/Conventions\n\nBase styles guides:\n\n* Ruby - [bbatsov/ruby-style-guide](https://github.com/bbatsov/ruby-style-guide)\n* CSS - [JT CSS Style Guide](https://github.com/jetthoughts/how-we-work/blob/master/css.md)\n* JavaScript - [airbnb/javascript](https://github.com/airbnb/javascript)\n\nOur updates:\n\n* Limit lines to 110 characters, in order to remove horizontal scrolling on GitHub when we are doing code review\n* Align parameters like:\n\n```ruby\n# good (normal indent) PREFERABLE\ndef send_mail(source)\n  Mailer.deliver(\n    to: 'bob@example.com',\n    from: 'us@example.com',\n    subject: 'Important message',\n    body: source.text\n  )\nend\n\n# not so bad\ndef send_mail(source)\n  Mailer.deliver(\n    to: 'bob@example.com', from: 'us@example.com'\n  )\nend\n\n# good, but DEPRECATED\ndef send_mail(source)\n  Mailer.deliver(to: 'bob@example.com',\n                 from: 'us@example.com',\n                 subject: 'Important message',\n                 body: source.text)\nend\n```\n* Use prefix `_` for memo variables:\n \n```ruby\nclass TestObject\n  attr_reader :attr_name\n  \n  def attr_name\n    @attr_name ||= lazy_calculation_for_attr_name\n  end\n  \n  def public_method_with_memo\n    @_public_method_with_memo ||= public_method_with_memo_calculcations\n  end\nend\n```\n\n* Stick to [Newlines](https://github.com/airbnb/ruby#newlines) section of Airbnb's Ruby style guide for grouping the code within methods.\n\n## Working with Bugs\n\n* Confirm on the master, the staging and finally on development environment. \n* Add tests with reproduced bugs before add fixes\n\n## Ruby on Rails Conventions\n\n* We use Decorators (in code we called them `Carrier`) as View and Form objects: [introduction how to use them](https://goo.gl/photos/nN1yNNqUoyKEK6an7)\n* We do not use full Presenters (TODO: add link to first post about them) with any tag generations\n* We do not overuse of [Concerns](https://blog.codeship.com/when-to-be-concerned-about-concerns/)\n* \u003ca name=\"prevent-syntax-sugar-overuse\"\u003e\u003c/a\u003eWe do not overuse (in 99% of our code we do not see such stuff): `send`, `\u0026.`, `**`. To prevent misusage of Ruby Syntax Sugars \u003csup\u003e[[link](#prevent-syntax-sugar-overuse)]\u003c/sup\u003e\n\n## Test Conventions\n\n### Principles\n\n* TDD\n* Clarity means that a test should serve as readable documentation for humans, describing the code being tested in terms of its public APIs. \n* A test is complete when its body contains all of the information you need to understand it, and concise when it doesn't contain any other distracting information.\n\nRef: [Testing on the Toilet: What Makes a Good Test?](https://testing.googleblog.com/2014/03/testing-on-toilet-what-makes-good-test.html)\n\n### Rules\n\n* \u003ca name=\"tdd-tests\"\u003e\u003c/a\u003eTests First\u003csup\u003e[[link](#tdd-tests)]\u003c/sup\u003e\n  * \u003ca name=\"black-box-tests\"\u003e\u003c/a\u003eUse Black Box testing strategy (there is no other way to support TDD)\u003csup\u003e[[link](#black-box-tests)]\u003c/sup\u003e\n\n    By ignoring the code (Black box testing), it demonstrates a different value system - the tests are valuable alone.\n* \u003ca name=\"meaningful-names-in-tests\"\u003e\u003c/a\u003eWe use the meaningful names in the test cases. It makes easier to understand the business logic.\u003csup\u003e[[link](#meaningful-names-in-tests)]\u003c/sup\u003e\n\n```ruby\n# bad\ndef test_email_is_normalized\n  user = User.create! email: 'Foo@quux.com'\n  assert_equal 'foo@quux.com', user.email\nend\n\n# good\ndef test_email_is_normalized\n  user = User.create! email: ' thisIsAMixedCaseEmail@example.com'\n  assert_equal 'thisisamixedcaseemail@example.com', user.email\nend\n```\n* \u003ca name=\"big-fixtures-in-tests\"\u003e\u003c/a\u003eThe test is building or referencing a larger fixture than is needed to verify the functionality in question. Do not load more data than needed to test your code. For each test create exactly what it needs and not more. More variations provides more complexities. More details could be found on http://xunitpatterns.com/Obscure%20Test.html#General%20Fixture \u003csup\u003e[[link](#big-fixtures-in-tests)]\u003c/sup\u003e\n\n  Message for reviewer: \n\n  \u003e Each check must be isolated and shouldn't depend or interfere with others. This will make your tests clear and well organized and help to clearly see the cause of the failure. Please check https://github.com/jetthoughts/how-we-work/blob/master/README.md#big-fixtures-in-tests\n\n```ruby\n# BAD\nlet!(:user_with_big_name) { ... }\nlet!(:user_with_small_name) { ... }\n\nit 'this test use only user_with_big_name' { expects(response).to have(user_with_big_name) }\nit 'this test use only user_with_small_name' { expects(response).to have(user_with_small_name) }\n```\n\n```ruby\n# GOOD\ncontext 'with big name' do\n  let(:user) { ... }\n  \n  it 'this test use only user_with_big_name' { expects(response).to have(user) }\nend\n\ncontext 'with small name' do\n  let(:user) { ... }\n  \n  it 'this test use only user_with_small_name' { expects(response).to have(user) }\nend\n```\n\n```ruby\n# BAD\nsetup do\n  big_file_with_all_users = compile_file_from_users(User.all)\nend\n\ntest 'this test use only user_with_big_name' { assert_includes big_file_with_all_users, 'magic string with all big letter' }\ntest 'this test use only user_with_small_name' { assert_includes big_file_with_all_users, 'magic string with all small letter' }\n```\n\n```ruby\n# GOOD\ntest 'this test use only user_with_big_name' do\n  result = compile_file_from_users(User.new(name: 'Big Name'))\n  assert_includes result, 'Big Name'\nend\n\ntest 'this test use only user_with_small_name' do\n  result = compile_file_from_users(User.new(name: 'Samll Name'))\n  assert_includes result, 'Small Name'\nend\n```\n\n* \u003ca name=\"dynamic-cases-in-tests\"\u003e\u003c/a\u003eNo Dynamic Test generation.\u003csup\u003e[[link](#dynamic-cases-in-tests)]\u003c/sup\u003e\n\n```ruby\n# BAD\n{ string: 'zone_id', array: %w[zone_id], hash: { \"0\" =\u003e \"zone_id\" } }.each do |type, group_by|\n  it \"returns valid json if group_by is #{type}\" do\n  end\nend\n```\n\n```ruby\n# GOOD\nit \"returns zone_id json if group_by is string\" { }\nit \"returns [zone_id] json if group_by is array\" { }\nit \"returns { '0' =\u003e 'zone_id' } json if group_by is hash\" { }\n```\n* \u003ca name=\"irrelevant-information\"\u003e\u003c/a\u003eIrrelevant Information\u003csup\u003e[[link](#irrelevant-information)]\u003c/sup\u003e\n\n  Message for reviewer:\n\n  \u003e The test is exposing a lot of irrelevant details about the fixture that distract the test reader from what really affects the behavior of the subject under test.\n\n\n```ruby\ndescribe 'PUT Update' do\n  let!(:user) { create :user }\n\n  # Bad\n  it 'updates user' do\n    attributes = { first_name: '..', last_name: '..', role: '..', email: '..' }\n    put: :update, params: { attributes }\n    expects(response.body).to eq attributes\n  end\n\n  # Good\n  it 'updates first_name' do\n    put: :update, params: { first_name: 'John Dou' }\n    expects(response.body).to have_attribute { first_name: 'John Dou' }\n  end\n\n  it 'updates email' do\n    put: :update, params: { email: 'email@example.com' }\n    expects(response.body).to have_attribute { email: 'email@example.com' }\n  end\n```\n\n## Setup Development Environment\n\n* `bin/setup` - cold setup\n* `bin/update` - update environment on new code updates\n\n## Delivery Flow\n\nOur flow is based on GitHub flow with Heroku Review\n\n* create PR (we convert issues into PR)\n* deploy PR to Heroku (we use Heroku Review to do this automatically)\n* verify on Heroku yourselves\n* ask verify, to code review and to merge (we have Code Review)\n\n## TODO/FIXME Notes\n\n* add issue on GitHub per each note\n* create TODO/FIXME note in the code\n* add in code's note link to GitHub's issue\n \n## Git/GitHub\n\n* [Basic git crash course for minimum git commands enough to work on the project](https://jtway.co/how-do-we-git-it-in-jetthoughts-a002b4dba223)\n* Prefix feature branch names with issue number: *123-issue-name*;\n* Do not mess with cosmetics changes. Move them in separate commit or pr;\n* Squash multiple trivial commits into a single commit;\n* Convert an existing issue into a pull request: `hub pull-request -i 123`;\n* \u003ca name=\"git-commit-message\"\u003e\u003c/a\u003eWrite a good commit message based on http://chris.beams.io/posts/git-commit/ with some requirements\u003csup\u003e[[link](#git-commit-message)]\u003c/sup\u003e:\n  ```\n  Capitalized, the imperative mood, short (50 chars or less) subject (#\u003cgithub_id\u003e)\n  \n  Body after space line. Wrap the body at 72 characters.\n  Use the body to explain what and why.\n  \n  Closes #\u003cother_github_issue_id\u003e, fixes #\u003canother_github_issue_id\u003e\n  ```\n  Also some examples:\n\n  * http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html\n  * https://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message\n  * https://github.com/RomuloOliveira/commit-messages-guide\n  \n* \u003ca name=\"git-issue-template\"\u003e\u003c/a\u003e Create Issue Template\u003csup\u003e[[link](#git-issue-template)]\u003c/sup\u003e\n    * **Title:** Jobs to be done\n    * **Body:**\n    \n      *1. Acceptance Criteria*\n      \n      Acceptance criteria is that a developer knows they need to complete before that issue can be considered 'done'.\n      They are the points against which the issue will be tested.\n\n      *2. A screenshot or mock up of a new design*\n   \n       A picture which illustrates what the issue should be creating or a screenshot of an existing page showing where a bug is occurring.\n       Remember to include mock ups of both mobile and desktop views.\n\n* Tutorials:\n  * https://www.atlassian.com/git/tutorials/\n  * http://git-scm.com/docs/gittutorial\n  * https://try.github.io/ \n\n## Development Best Practices\n\n* [Exceptions should be exceptional](https://jacopretorius.net/2009/10/exceptions-should-be-exceptional.html)\n* [KISS ... your ARSE :P](http://code.mumak.net/2012/02/simple-made-easy.html)\n* Stub/Mock only external services or code with hard simulation: https://www.youtube.com/watch?v=z9quxZsLcfo\u0026feature=youtu.be\u0026t=21m00s\n* [Four-Phase Test](http://xunitpatterns.com/Four%20Phase%20Test.html)\n\n## Other Tools and Practices to use\n\n* [UX from Good UI](http://www.goodui.org/)\n* [Material design](https://material.io/)\n\n## Recomended Tutorials\n\n* HTML/CSS3:\n  * [Learn CSS Layout](http://learnlayout.com/)\n  * [A Complete Guide to Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)\n* Web Performance:\n  * [Website Performance Optimization: The Critical Rendering Path](https://www.udacity.com/course/ud884)\n* ES6: https://www.eventbrite.com/engineering/tag/learning-es6/\n* React.js/Redux: https://egghead.io/browse/frameworks/react\n  \n\n## Constraints which makes fun development\n\n* 2 days per PR\n* 2 Issues/PR per Developer\n* [1000/100/6 performance model](https://www.paulirish.com/2015/advanced-performance-audits-with-devtools/)\n* 5 min to pass all test suites\n* 20 sec to run one test\n* 20 sec to boot\n\n## Tools:\n* Collaboration:\n  * Chat: [Discord](https://discordapp.com/)\n  * Issues Plannings: [Github](https://github.com), [Trello](https://trello.com)\n  * Information board: [Trello](https://trello.com)\n  * Screenshots: [CloudApp](https://www.getcloudapp.com/), [Joxi](http://joxi.net/) (Ubuntu)\n  * Screencast: [Loom](https://www.useloom.com), Kazam (Ubuntu)\n  * Video call: [Appear.in](https://appear.in/)\n* CI:\n  * [CircleCI](https://circleci.com/)\n* Server Configuation: [Ansible](https://www.ansible.com/)\n* Deployments: Capistrano\n* Assets and File Uploads CDN hosting: AWS, CloudFront, Cloudinary (Images Uploads)\n* PAAS for Isolated Staging Testing: [Heroku](https://heroku.com) (Ruby on Rails apps), [surge.sh](http://surge.sh/) (Static HTML)\n* Cache: Memcached/Redis Cloud from Redis Lab\n* JavaScript Base Frameworks: [Stimulus](https://github.com/stimulusjs/stimulus), Vanilla JS, [Vue.js](https://vuejs.org/), [React](https://reactjs.org/)\n* SCM: [GitHub](https://github.com)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjetthoughts%2Fhow-we-work","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjetthoughts%2Fhow-we-work","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjetthoughts%2Fhow-we-work/lists"}