{"action":"created","comment":{"url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/comments/54318703","id":54318703,"diff_hunk":"@@ -0,0 +1,110 @@\n+---\n+layout: post\n+title: \"Five practices for robust Ruby on Rails applications\"\n+author: philippe\n+date: 2016-02-26 12:00\n+shared_description: A few extra keystokes here and there can make a Rails application robust.\n+draft: false\n+published: true\n+---\n+\n+\n+Whenever I come across a rails application with inconsistent data or bugs that are hard\n+to nail down I tell myself: \"They (the developers) were just a couple of keystokes away to prevent those issues from happening\".\n+\n+At [brewhouse](http://brewhouse.io), we follow five simple practices to make our Rails application robusts. It all comes down to failing early, loudly and often. We ensure that data is valid and the application behaves properly by catching issues early on.\n+\n+<!-- break -->\n+\n+## Use `Hash#fetch` to catch malformed hash\n+\n+`unexpected method 'upcase' for nil`... Whenever you expect a Hash to contain a key, prefere `fetch()` over\n+`[]`. `fetch()` will raise an error when the key is missing so you won't\n+pass `nil` values around and see unrelated errors happening down the\n+line.\n+\n+## `case ... else raise` to catch invalid data\n+\n+Always add an `else raise ...` clause to your `case` statements. You\n+want to know when you've received an unexpected value rather than\n+ignoring it and moving on.\n+\n+## Use ActiveRecord `!` methods to fail loudly\n+\n+Data is often being seen as the most valuable asset in a company. Failing silently to persist data can have a huge impact then.\n+Whenever you're not expecting an operation to fail, use the \"bang\"\n+version of `create!`, `update!` and `destroy!`. This extra key stroke\n+will save you from dealing with inconsistent data.\n+\n+Used in test code, it ensures that the setup doesn't fail silently...\n+there is nothing worst than a test that passes because the setup was\n+incorrect.\n+\n+Also, always wrap multiple calls into a sql transaction to prevent your data from getting into an in-between state.\n+\n+## Add ActiveRecord validations to perform live checks\n+\n+Pairing ActiveRecord validations with the use of 'bang methods' is a great way to ensure you persist valid data. For example:\n+\n+{% highlight ruby %}\n+class Post < ActiveRecord::Model\n+  validates :author, :blog, presence: true\n+  validates :published_by, presence: true, if: :published?\n+  validates :comment_count, numericality: { greater_or_equal_to: 0 }\n+  # ...\n+end\n+{% endhighlight %}\n+\n+## Use database constraints to ensure data consistency\n+\n+Your database is your best friend when it comes to ensuring data is\n+present, not duplicated and that no orphans are left in the database.\n+\n+### `null: false`\n+\n+Given the fact that the large majority of columns are required in a database, you should define columns with `null: false` by default.\n+\n+### `index ... unique: true`\n+\n+Did you know that rails `has_one` does not prevent duplicate\n+associations from being created?\n+\n+{% highlight ruby %}\n+class Account\n+  has_one :account_settings\n+end\n+\n+account = Account.create!\n+account.create_account_settings!\n+account.create_account_settings!\n+account.create_account_settings!\n+\n+account.account_settings\n+ # => one of the three account settings you've created... -_-\n+{% endhighlight %}\n+\n+The best way to prevent this from making data inconsistent is to add a unique index.\n+\n+{% highlight ruby %}\n+add_index :account_settings, :account_id, unique: true\n+{% endhighlight %}\n+\n+The database will throw an error if you attempt to persist a duplicate\n+record.\n+\n+### `foreign_key`\n+\n+You don't want orphan records in your database, do you?\n+\n+Foreign keys help such things from happening. I'd recommend using\n+[`schema_auto_foreign_keys`](https://github.com/SchemaPlus/schema_auto_foreign_keys) to automatically add foreign keys on your\n+behalf.\n+\n+## A few extra keystrokes goes a long way\n+\n+A few extra keystrokes here and there can save you from hours of\n+debugging or recovering from inconsistent data. Go `!`, `raise`,\n+`validate` and use database constraints, your coworkers and future-self","path":"_posts/2016-02-26-five-practices-for-robut-ruby-on-rails-applications.md","position":107,"original_position":107,"commit_id":"b07d579a4a2f4210b00c47393323b199104fc332","original_commit_id":"b07d579a4a2f4210b00c47393323b199104fc332","user":{"login":"pcreux","id":45299,"avatar_url":"https://avatars.githubusercontent.com/u/45299?v=3","gravatar_id":"","url":"https://api.github.com/users/pcreux","html_url":"https://github.com/pcreux","followers_url":"https://api.github.com/users/pcreux/followers","following_url":"https://api.github.com/users/pcreux/following{/other_user}","gists_url":"https://api.github.com/users/pcreux/gists{/gist_id}","starred_url":"https://api.github.com/users/pcreux/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pcreux/subscriptions","organizations_url":"https://api.github.com/users/pcreux/orgs","repos_url":"https://api.github.com/users/pcreux/repos","events_url":"https://api.github.com/users/pcreux/events{/privacy}","received_events_url":"https://api.github.com/users/pcreux/received_events","type":"User","site_admin":false},"body":"All right!","created_at":"2016-02-26T23:52:26Z","updated_at":"2016-02-26T23:52:26Z","html_url":"https://github.com/BrewhouseTeam/brewhouseteam.github.io/pull/24#discussion_r54318703","pull_request_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/24","_links":{"self":{"href":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/comments/54318703"},"html":{"href":"https://github.com/BrewhouseTeam/brewhouseteam.github.io/pull/24#discussion_r54318703"},"pull_request":{"href":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/24"}}},"pull_request":{"url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/24","id":60906720,"html_url":"https://github.com/BrewhouseTeam/brewhouseteam.github.io/pull/24","diff_url":"https://github.com/BrewhouseTeam/brewhouseteam.github.io/pull/24.diff","patch_url":"https://github.com/BrewhouseTeam/brewhouseteam.github.io/pull/24.patch","issue_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/issues/24","number":24,"state":"open","locked":false,"title":"Robust rails app","user":{"login":"pcreux","id":45299,"avatar_url":"https://avatars.githubusercontent.com/u/45299?v=3","gravatar_id":"","url":"https://api.github.com/users/pcreux","html_url":"https://github.com/pcreux","followers_url":"https://api.github.com/users/pcreux/followers","following_url":"https://api.github.com/users/pcreux/following{/other_user}","gists_url":"https://api.github.com/users/pcreux/gists{/gist_id}","starred_url":"https://api.github.com/users/pcreux/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pcreux/subscriptions","organizations_url":"https://api.github.com/users/pcreux/orgs","repos_url":"https://api.github.com/users/pcreux/repos","events_url":"https://api.github.com/users/pcreux/events{/privacy}","received_events_url":"https://api.github.com/users/pcreux/received_events","type":"User","site_admin":false},"body":"![screencapture-localhost-4000-2016-02-26-five-practices-for-robut-ruby-on-rails-applications-html-1456521877198](https://cloud.githubusercontent.com/assets/45299/13365930/c069191c-dc8c-11e5-808c-c8d6637056aa.png)\r\n","created_at":"2016-02-26T21:28:02Z","updated_at":"2016-02-26T23:52:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8a2c86a5ab51004c62218ca7f85c9987d707c4a9","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/24/commits","review_comments_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/24/comments","review_comment_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/comments{/number}","comments_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/issues/24/comments","statuses_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/statuses/b07d579a4a2f4210b00c47393323b199104fc332","head":{"label":"BrewhouseTeam:post/robust-rails-app","ref":"post/robust-rails-app","sha":"b07d579a4a2f4210b00c47393323b199104fc332","user":{"login":"BrewhouseTeam","id":6540110,"avatar_url":"https://avatars.githubusercontent.com/u/6540110?v=3","gravatar_id":"","url":"https://api.github.com/users/BrewhouseTeam","html_url":"https://github.com/BrewhouseTeam","followers_url":"https://api.github.com/users/BrewhouseTeam/followers","following_url":"https://api.github.com/users/BrewhouseTeam/following{/other_user}","gists_url":"https://api.github.com/users/BrewhouseTeam/gists{/gist_id}","starred_url":"https://api.github.com/users/BrewhouseTeam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BrewhouseTeam/subscriptions","organizations_url":"https://api.github.com/users/BrewhouseTeam/orgs","repos_url":"https://api.github.com/users/BrewhouseTeam/repos","events_url":"https://api.github.com/users/BrewhouseTeam/events{/privacy}","received_events_url":"https://api.github.com/users/BrewhouseTeam/received_events","type":"Organization","site_admin":false},"repo":{"id":17500041,"name":"brewhouseteam.github.io","full_name":"BrewhouseTeam/brewhouseteam.github.io","owner":{"login":"BrewhouseTeam","id":6540110,"avatar_url":"https://avatars.githubusercontent.com/u/6540110?v=3","gravatar_id":"","url":"https://api.github.com/users/BrewhouseTeam","html_url":"https://github.com/BrewhouseTeam","followers_url":"https://api.github.com/users/BrewhouseTeam/followers","following_url":"https://api.github.com/users/BrewhouseTeam/following{/other_user}","gists_url":"https://api.github.com/users/BrewhouseTeam/gists{/gist_id}","starred_url":"https://api.github.com/users/BrewhouseTeam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BrewhouseTeam/subscriptions","organizations_url":"https://api.github.com/users/BrewhouseTeam/orgs","repos_url":"https://api.github.com/users/BrewhouseTeam/repos","events_url":"https://api.github.com/users/BrewhouseTeam/events{/privacy}","received_events_url":"https://api.github.com/users/BrewhouseTeam/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/BrewhouseTeam/brewhouseteam.github.io","description":"brewhouse company pages + blog","fork":false,"url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io","forks_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/forks","keys_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/teams","hooks_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/hooks","issue_events_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/events","assignees_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/tags","blobs_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/languages","stargazers_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/stargazers","contributors_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/contributors","subscribers_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/subscribers","subscription_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/subscription","commits_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/merges","archive_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/downloads","issues_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/labels{/name}","releases_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/releases{/id}","deployments_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/deployments","created_at":"2014-03-07T02:19:27Z","updated_at":"2016-01-11T20:42:41Z","pushed_at":"2016-02-26T21:28:02Z","git_url":"git://github.com/BrewhouseTeam/brewhouseteam.github.io.git","ssh_url":"git@github.com:BrewhouseTeam/brewhouseteam.github.io.git","clone_url":"https://github.com/BrewhouseTeam/brewhouseteam.github.io.git","svn_url":"https://github.com/BrewhouseTeam/brewhouseteam.github.io","homepage":"http://brewhouse.io","size":62579,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"BrewhouseTeam:source","ref":"source","sha":"f6fff727b2d01bd40439b5308390a7112c9f734f","user":{"login":"BrewhouseTeam","id":6540110,"avatar_url":"https://avatars.githubusercontent.com/u/6540110?v=3","gravatar_id":"","url":"https://api.github.com/users/BrewhouseTeam","html_url":"https://github.com/BrewhouseTeam","followers_url":"https://api.github.com/users/BrewhouseTeam/followers","following_url":"https://api.github.com/users/BrewhouseTeam/following{/other_user}","gists_url":"https://api.github.com/users/BrewhouseTeam/gists{/gist_id}","starred_url":"https://api.github.com/users/BrewhouseTeam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BrewhouseTeam/subscriptions","organizations_url":"https://api.github.com/users/BrewhouseTeam/orgs","repos_url":"https://api.github.com/users/BrewhouseTeam/repos","events_url":"https://api.github.com/users/BrewhouseTeam/events{/privacy}","received_events_url":"https://api.github.com/users/BrewhouseTeam/received_events","type":"Organization","site_admin":false},"repo":{"id":17500041,"name":"brewhouseteam.github.io","full_name":"BrewhouseTeam/brewhouseteam.github.io","owner":{"login":"BrewhouseTeam","id":6540110,"avatar_url":"https://avatars.githubusercontent.com/u/6540110?v=3","gravatar_id":"","url":"https://api.github.com/users/BrewhouseTeam","html_url":"https://github.com/BrewhouseTeam","followers_url":"https://api.github.com/users/BrewhouseTeam/followers","following_url":"https://api.github.com/users/BrewhouseTeam/following{/other_user}","gists_url":"https://api.github.com/users/BrewhouseTeam/gists{/gist_id}","starred_url":"https://api.github.com/users/BrewhouseTeam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BrewhouseTeam/subscriptions","organizations_url":"https://api.github.com/users/BrewhouseTeam/orgs","repos_url":"https://api.github.com/users/BrewhouseTeam/repos","events_url":"https://api.github.com/users/BrewhouseTeam/events{/privacy}","received_events_url":"https://api.github.com/users/BrewhouseTeam/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/BrewhouseTeam/brewhouseteam.github.io","description":"brewhouse company pages + blog","fork":false,"url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io","forks_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/forks","keys_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/teams","hooks_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/hooks","issue_events_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/events","assignees_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/tags","blobs_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/languages","stargazers_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/stargazers","contributors_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/contributors","subscribers_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/subscribers","subscription_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/subscription","commits_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/issues/comments{/number}","contents_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/merges","archive_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/downloads","issues_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/labels{/name}","releases_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/releases{/id}","deployments_url":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/deployments","created_at":"2014-03-07T02:19:27Z","updated_at":"2016-01-11T20:42:41Z","pushed_at":"2016-02-26T21:28:02Z","git_url":"git://github.com/BrewhouseTeam/brewhouseteam.github.io.git","ssh_url":"git@github.com:BrewhouseTeam/brewhouseteam.github.io.git","clone_url":"https://github.com/BrewhouseTeam/brewhouseteam.github.io.git","svn_url":"https://github.com/BrewhouseTeam/brewhouseteam.github.io","homepage":"http://brewhouse.io","size":62579,"stargazers_count":0,"watchers_count":0,"language":"HTML","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/24"},"html":{"href":"https://github.com/BrewhouseTeam/brewhouseteam.github.io/pull/24"},"issue":{"href":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/issues/24"},"comments":{"href":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/issues/24/comments"},"review_comments":{"href":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/24/comments"},"review_comment":{"href":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/pulls/24/commits"},"statuses":{"href":"https://api.github.com/repos/BrewhouseTeam/brewhouseteam.github.io/statuses/b07d579a4a2f4210b00c47393323b199104fc332"}}}}