{"action":"created","comment":{"url":"https://api.github.com/repos/dockyard/reefpoints/pulls/comments/41912727","id":41912727,"diff_hunk":"@@ -0,0 +1,198 @@\n+---\n+layout: post\n+title: \"Ember Best Practices: Actions Down, Data Up... wait what?\"\n+social: true\n+author: Marin Abernethy\n+twitter: \"pairinMarin\"\n+github: maabernethy\n+published: true\n+tags: ember, javascript, best practices\n+---\n+\n+Welcome back for to the fourth installment of DockYard's Ember Best Practices Blog Post Series! Last time Lin\n+Reid gave us a tour of the Page Object Pattern, and other useful tips to tidying up your Ember accpetance tests.\n+In case you missed it, his blog can be found [here](https://dockyard.com/blog/2015/09/25/ember-best-practices-acceptance-test),\n+but remember to come back to this one afterwards!\n+\n+## Data Down, Actions Up\n+\n+Two way databinding is what originally attracted many to Ember.Js. Data changes that affect the model are\n+immediately propagated to the corresponding template, and conversely, any changes made in the UI are \n+promptly reflected in the underlying model. Magic! This conveniently removes unnecessary boilerplate code.\n+\n+However, when employed in large scale and complex applications, two-way databinding can often become more of a\n+headache than a blessing. Have you struggled with where to mutate your data? Have you spent unnecessary time\n+tracing the source of mutated data? You are not alone. The Ember core team in their quest for continuous growth and\n+improvement, has decided that in Ember 2.X, one-way bindings will be the default. And to ability to opt in to two-way\n+data binding will be provided. This new adopted data flow model will simplify communication between components and\n+can be encapsulated in the phrase: \"Data down, actions up\". \n+\n+Thats all well and good, but what does that actually look like in the wild? \n+\n+## Enough jabbering and show us!\n+\n+Let's build an app together. Our app will be a simple bugeting tool. Picture the money mangaging application,\n+[Mint](https://www.mint.com/), and more specifically an elementary version of it's [budgeting feature](https://www.mint.com/how-mint-works/budgets).\n+\n+```\n+// application/index/route.js\n+\n+import Ember from 'ember';\n+const { set } = Ember;\n+\n+export default Ember.Route.extend({\n+  model () {\n+   return this.store.findAll('expense');\n+  },\n+  \n+  setupController(controller, model) {\n+    set(controller, 'expenses', model);\n+  }\n+});\n+```\n+\n+Assume our server responds with a list of `expenses`, where each `expense` has a `category` and `amount`.\n+In our template we provide an input box for the user to tell us their `income`. Then we iterate over the `expenses`,\n+wrapping each `expense` in a component. And lastly, we display their `total` remaining money (`income - expenses`).\n+\n+```\n+// application/index/template.hbs\n+\n+<h4>Income:</h4> \n+{{input class=\"income-input\" type=\"integer\" value=income}}\n+\n+<table class='expenses'>\n+  <tbody>\n+  \t{{#each expenses as |expense|}}\n+    \t{{expense-summary expense=expense}}\n+  \t{{/each}}\n+ \t</tbody>\n+</table>\n+\n+<h4>Total: {{total}}</h4>\n+```\n+\n+```\n+// application/index/controller.js\n+import Ember from 'ember';\n+const { set } = Ember;\n+\n+export default Ember.Controller.extend({\n+  income:  0,\n+  total: Ember.computed('expenses', 'income', {\n+    get() {\n+      return Ember.get(this, 'income') - Ember.get(this, 'expenseTotal');\n+    }\n+  })\n+```\n+\n+In each row of our expenses table, we have a `expenseSummaryComponent` that displays the expense `category`,\n+provides an input box for the expense `amount`, and has a \"Save\" button.\n+\n+```\n+// expense-summary/template.hbs\n+<tr>\n+\t<td>{{expense.category}}</td>\n+\t<td>{{input type=\"integer\" value=expense.amount}}</td>\n+  <td><button {{action 'amountChanged'}}>Save</button></td>\n+</tr>\n+```\n+\n+In our `expense-summary` component we implement the `save` action.\n+\n+```\n+// expense-summary/component.js\n+import Ember from 'ember';\n+const { set } = Ember;\n+\n+export default Ember.Component.extend({\n+  actions: {\n+    save() {\n+      const expense = get(this, 'expense');\n+      expense.save();\n+    }\n+  }\n+});\n+```\n+\n+Yay! Done! However, we forgot something - we haven't applied our new montra, \"Data down, Actions up\", to this code yet.\n+The issue is that the `expenseSummaryComponent` is updating the data (`expense.amount`), but the data should solely\n+belong to our `IndexController`. Not only did the `IndexController` give the `expenseSummaryComponent` the `expense`\n+data, but it also uses the `expenses` data to calculate the `total`. Meaning, if the `IndexController` was out of\n+sync and had stale `expense` data, it would be displaying the incorrect `total`. While that may not be a huge concern\n+in this simple scenario, it can be really difficult to trace in more extensive and intricate applications.\n+\n+### The Better Way\n+\n+Instead we need to pass an action to our `expenseSummaryComponent`.\n+\n+```\n+// parent component: expense-table/template.hbs\n+<table class='expenses'>\n+  <tbody>\n+  \t{{#each expense in expenses}}","path":"source/posts/2015-10-14-best-practices-data-down-actions-up.md","position":133,"original_position":133,"commit_id":"6ca477e1b5f4e33768842682462dad4453a5a991","original_commit_id":"6ca477e1b5f4e33768842682462dad4453a5a991","user":{"login":"brzpegasus","id":1691398,"avatar_url":"https://avatars.githubusercontent.com/u/1691398?v=3","gravatar_id":"","url":"https://api.github.com/users/brzpegasus","html_url":"https://github.com/brzpegasus","followers_url":"https://api.github.com/users/brzpegasus/followers","following_url":"https://api.github.com/users/brzpegasus/following{/other_user}","gists_url":"https://api.github.com/users/brzpegasus/gists{/gist_id}","starred_url":"https://api.github.com/users/brzpegasus/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/brzpegasus/subscriptions","organizations_url":"https://api.github.com/users/brzpegasus/orgs","repos_url":"https://api.github.com/users/brzpegasus/repos","events_url":"https://api.github.com/users/brzpegasus/events{/privacy}","received_events_url":"https://api.github.com/users/brzpegasus/received_events","type":"User","site_admin":false},"body":"Fix indentation.","created_at":"2015-10-13T19:29:31Z","updated_at":"2015-10-13T19:29:31Z","html_url":"https://github.com/dockyard/reefpoints/pull/407#discussion_r41912727","pull_request_url":"https://api.github.com/repos/dockyard/reefpoints/pulls/407","_links":{"self":{"href":"https://api.github.com/repos/dockyard/reefpoints/pulls/comments/41912727"},"html":{"href":"https://github.com/dockyard/reefpoints/pull/407#discussion_r41912727"},"pull_request":{"href":"https://api.github.com/repos/dockyard/reefpoints/pulls/407"}}},"pull_request":{"url":"https://api.github.com/repos/dockyard/reefpoints/pulls/407","id":47563573,"html_url":"https://github.com/dockyard/reefpoints/pull/407","diff_url":"https://github.com/dockyard/reefpoints/pull/407.diff","patch_url":"https://github.com/dockyard/reefpoints/pull/407.patch","issue_url":"https://api.github.com/repos/dockyard/reefpoints/issues/407","number":407,"state":"open","locked":false,"title":"Ember best practices: data down actions up","user":{"login":"maabernethy","id":4601777,"avatar_url":"https://avatars.githubusercontent.com/u/4601777?v=3","gravatar_id":"","url":"https://api.github.com/users/maabernethy","html_url":"https://github.com/maabernethy","followers_url":"https://api.github.com/users/maabernethy/followers","following_url":"https://api.github.com/users/maabernethy/following{/other_user}","gists_url":"https://api.github.com/users/maabernethy/gists{/gist_id}","starred_url":"https://api.github.com/users/maabernethy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maabernethy/subscriptions","organizations_url":"https://api.github.com/users/maabernethy/orgs","repos_url":"https://api.github.com/users/maabernethy/repos","events_url":"https://api.github.com/users/maabernethy/events{/privacy}","received_events_url":"https://api.github.com/users/maabernethy/received_events","type":"User","site_admin":false},"body":"I wasn't sure which blog post would be published right before mine - so if its not Lin, then I can change the intro.","created_at":"2015-10-13T17:32:44Z","updated_at":"2015-10-13T19:29:31Z","closed_at":null,"merged_at":null,"merge_commit_sha":"cab26596814c100524a0ae6e6b8694639f8547d5","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/dockyard/reefpoints/pulls/407/commits","review_comments_url":"https://api.github.com/repos/dockyard/reefpoints/pulls/407/comments","review_comment_url":"https://api.github.com/repos/dockyard/reefpoints/pulls/comments{/number}","comments_url":"https://api.github.com/repos/dockyard/reefpoints/issues/407/comments","statuses_url":"https://api.github.com/repos/dockyard/reefpoints/statuses/6ca477e1b5f4e33768842682462dad4453a5a991","head":{"label":"dockyard:maabernethy-patch-3","ref":"maabernethy-patch-3","sha":"6ca477e1b5f4e33768842682462dad4453a5a991","user":{"login":"dockyard","id":873981,"avatar_url":"https://avatars.githubusercontent.com/u/873981?v=3","gravatar_id":"","url":"https://api.github.com/users/dockyard","html_url":"https://github.com/dockyard","followers_url":"https://api.github.com/users/dockyard/followers","following_url":"https://api.github.com/users/dockyard/following{/other_user}","gists_url":"https://api.github.com/users/dockyard/gists{/gist_id}","starred_url":"https://api.github.com/users/dockyard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dockyard/subscriptions","organizations_url":"https://api.github.com/users/dockyard/orgs","repos_url":"https://api.github.com/users/dockyard/repos","events_url":"https://api.github.com/users/dockyard/events{/privacy}","received_events_url":"https://api.github.com/users/dockyard/received_events","type":"Organization","site_admin":false},"repo":{"id":2262874,"name":"reefpoints","full_name":"dockyard/reefpoints","owner":{"login":"dockyard","id":873981,"avatar_url":"https://avatars.githubusercontent.com/u/873981?v=3","gravatar_id":"","url":"https://api.github.com/users/dockyard","html_url":"https://github.com/dockyard","followers_url":"https://api.github.com/users/dockyard/followers","following_url":"https://api.github.com/users/dockyard/following{/other_user}","gists_url":"https://api.github.com/users/dockyard/gists{/gist_id}","starred_url":"https://api.github.com/users/dockyard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dockyard/subscriptions","organizations_url":"https://api.github.com/users/dockyard/orgs","repos_url":"https://api.github.com/users/dockyard/repos","events_url":"https://api.github.com/users/dockyard/events{/privacy}","received_events_url":"https://api.github.com/users/dockyard/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/dockyard/reefpoints","description":"DockYard Blog","fork":false,"url":"https://api.github.com/repos/dockyard/reefpoints","forks_url":"https://api.github.com/repos/dockyard/reefpoints/forks","keys_url":"https://api.github.com/repos/dockyard/reefpoints/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dockyard/reefpoints/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dockyard/reefpoints/teams","hooks_url":"https://api.github.com/repos/dockyard/reefpoints/hooks","issue_events_url":"https://api.github.com/repos/dockyard/reefpoints/issues/events{/number}","events_url":"https://api.github.com/repos/dockyard/reefpoints/events","assignees_url":"https://api.github.com/repos/dockyard/reefpoints/assignees{/user}","branches_url":"https://api.github.com/repos/dockyard/reefpoints/branches{/branch}","tags_url":"https://api.github.com/repos/dockyard/reefpoints/tags","blobs_url":"https://api.github.com/repos/dockyard/reefpoints/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dockyard/reefpoints/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dockyard/reefpoints/git/refs{/sha}","trees_url":"https://api.github.com/repos/dockyard/reefpoints/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dockyard/reefpoints/statuses/{sha}","languages_url":"https://api.github.com/repos/dockyard/reefpoints/languages","stargazers_url":"https://api.github.com/repos/dockyard/reefpoints/stargazers","contributors_url":"https://api.github.com/repos/dockyard/reefpoints/contributors","subscribers_url":"https://api.github.com/repos/dockyard/reefpoints/subscribers","subscription_url":"https://api.github.com/repos/dockyard/reefpoints/subscription","commits_url":"https://api.github.com/repos/dockyard/reefpoints/commits{/sha}","git_commits_url":"https://api.github.com/repos/dockyard/reefpoints/git/commits{/sha}","comments_url":"https://api.github.com/repos/dockyard/reefpoints/comments{/number}","issue_comment_url":"https://api.github.com/repos/dockyard/reefpoints/issues/comments{/number}","contents_url":"https://api.github.com/repos/dockyard/reefpoints/contents/{+path}","compare_url":"https://api.github.com/repos/dockyard/reefpoints/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dockyard/reefpoints/merges","archive_url":"https://api.github.com/repos/dockyard/reefpoints/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dockyard/reefpoints/downloads","issues_url":"https://api.github.com/repos/dockyard/reefpoints/issues{/number}","pulls_url":"https://api.github.com/repos/dockyard/reefpoints/pulls{/number}","milestones_url":"https://api.github.com/repos/dockyard/reefpoints/milestones{/number}","notifications_url":"https://api.github.com/repos/dockyard/reefpoints/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dockyard/reefpoints/labels{/name}","releases_url":"https://api.github.com/repos/dockyard/reefpoints/releases{/id}","created_at":"2011-08-24T16:55:40Z","updated_at":"2015-07-15T04:01:34Z","pushed_at":"2015-10-13T17:32:45Z","git_url":"git://github.com/dockyard/reefpoints.git","ssh_url":"git@github.com:dockyard/reefpoints.git","clone_url":"https://github.com/dockyard/reefpoints.git","svn_url":"https://github.com/dockyard/reefpoints","homepage":"https://dockyard.com/blog","size":91762,"stargazers_count":33,"watchers_count":33,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":63,"mirror_url":null,"open_issues_count":12,"forks":63,"open_issues":12,"watchers":33,"default_branch":"master"}},"base":{"label":"dockyard:master","ref":"master","sha":"ae6b85e29a647e545abcdc2d83b0de03784502e4","user":{"login":"dockyard","id":873981,"avatar_url":"https://avatars.githubusercontent.com/u/873981?v=3","gravatar_id":"","url":"https://api.github.com/users/dockyard","html_url":"https://github.com/dockyard","followers_url":"https://api.github.com/users/dockyard/followers","following_url":"https://api.github.com/users/dockyard/following{/other_user}","gists_url":"https://api.github.com/users/dockyard/gists{/gist_id}","starred_url":"https://api.github.com/users/dockyard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dockyard/subscriptions","organizations_url":"https://api.github.com/users/dockyard/orgs","repos_url":"https://api.github.com/users/dockyard/repos","events_url":"https://api.github.com/users/dockyard/events{/privacy}","received_events_url":"https://api.github.com/users/dockyard/received_events","type":"Organization","site_admin":false},"repo":{"id":2262874,"name":"reefpoints","full_name":"dockyard/reefpoints","owner":{"login":"dockyard","id":873981,"avatar_url":"https://avatars.githubusercontent.com/u/873981?v=3","gravatar_id":"","url":"https://api.github.com/users/dockyard","html_url":"https://github.com/dockyard","followers_url":"https://api.github.com/users/dockyard/followers","following_url":"https://api.github.com/users/dockyard/following{/other_user}","gists_url":"https://api.github.com/users/dockyard/gists{/gist_id}","starred_url":"https://api.github.com/users/dockyard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dockyard/subscriptions","organizations_url":"https://api.github.com/users/dockyard/orgs","repos_url":"https://api.github.com/users/dockyard/repos","events_url":"https://api.github.com/users/dockyard/events{/privacy}","received_events_url":"https://api.github.com/users/dockyard/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/dockyard/reefpoints","description":"DockYard Blog","fork":false,"url":"https://api.github.com/repos/dockyard/reefpoints","forks_url":"https://api.github.com/repos/dockyard/reefpoints/forks","keys_url":"https://api.github.com/repos/dockyard/reefpoints/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dockyard/reefpoints/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dockyard/reefpoints/teams","hooks_url":"https://api.github.com/repos/dockyard/reefpoints/hooks","issue_events_url":"https://api.github.com/repos/dockyard/reefpoints/issues/events{/number}","events_url":"https://api.github.com/repos/dockyard/reefpoints/events","assignees_url":"https://api.github.com/repos/dockyard/reefpoints/assignees{/user}","branches_url":"https://api.github.com/repos/dockyard/reefpoints/branches{/branch}","tags_url":"https://api.github.com/repos/dockyard/reefpoints/tags","blobs_url":"https://api.github.com/repos/dockyard/reefpoints/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dockyard/reefpoints/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dockyard/reefpoints/git/refs{/sha}","trees_url":"https://api.github.com/repos/dockyard/reefpoints/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dockyard/reefpoints/statuses/{sha}","languages_url":"https://api.github.com/repos/dockyard/reefpoints/languages","stargazers_url":"https://api.github.com/repos/dockyard/reefpoints/stargazers","contributors_url":"https://api.github.com/repos/dockyard/reefpoints/contributors","subscribers_url":"https://api.github.com/repos/dockyard/reefpoints/subscribers","subscription_url":"https://api.github.com/repos/dockyard/reefpoints/subscription","commits_url":"https://api.github.com/repos/dockyard/reefpoints/commits{/sha}","git_commits_url":"https://api.github.com/repos/dockyard/reefpoints/git/commits{/sha}","comments_url":"https://api.github.com/repos/dockyard/reefpoints/comments{/number}","issue_comment_url":"https://api.github.com/repos/dockyard/reefpoints/issues/comments{/number}","contents_url":"https://api.github.com/repos/dockyard/reefpoints/contents/{+path}","compare_url":"https://api.github.com/repos/dockyard/reefpoints/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dockyard/reefpoints/merges","archive_url":"https://api.github.com/repos/dockyard/reefpoints/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dockyard/reefpoints/downloads","issues_url":"https://api.github.com/repos/dockyard/reefpoints/issues{/number}","pulls_url":"https://api.github.com/repos/dockyard/reefpoints/pulls{/number}","milestones_url":"https://api.github.com/repos/dockyard/reefpoints/milestones{/number}","notifications_url":"https://api.github.com/repos/dockyard/reefpoints/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dockyard/reefpoints/labels{/name}","releases_url":"https://api.github.com/repos/dockyard/reefpoints/releases{/id}","created_at":"2011-08-24T16:55:40Z","updated_at":"2015-07-15T04:01:34Z","pushed_at":"2015-10-13T17:32:45Z","git_url":"git://github.com/dockyard/reefpoints.git","ssh_url":"git@github.com:dockyard/reefpoints.git","clone_url":"https://github.com/dockyard/reefpoints.git","svn_url":"https://github.com/dockyard/reefpoints","homepage":"https://dockyard.com/blog","size":91762,"stargazers_count":33,"watchers_count":33,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":63,"mirror_url":null,"open_issues_count":12,"forks":63,"open_issues":12,"watchers":33,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/dockyard/reefpoints/pulls/407"},"html":{"href":"https://github.com/dockyard/reefpoints/pull/407"},"issue":{"href":"https://api.github.com/repos/dockyard/reefpoints/issues/407"},"comments":{"href":"https://api.github.com/repos/dockyard/reefpoints/issues/407/comments"},"review_comments":{"href":"https://api.github.com/repos/dockyard/reefpoints/pulls/407/comments"},"review_comment":{"href":"https://api.github.com/repos/dockyard/reefpoints/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/dockyard/reefpoints/pulls/407/commits"},"statuses":{"href":"https://api.github.com/repos/dockyard/reefpoints/statuses/6ca477e1b5f4e33768842682462dad4453a5a991"}}}}