{"action":"created","comment":{"url":"https://api.github.com/repos/sittercity/techblog/pulls/comments/27172078","id":27172078,"diff_hunk":"@@ -0,0 +1,241 @@\n+---\n+layout:post\n+title:'Design Patterns, Fast Tests, and Robust Software, Part III'\n+date:2015-02-23 12:01:00\n+categories: design patters, testing, software architecture\n+---\n+\n+<meta charset=\"utf-8\">\n+<!-- not sure this charset isn't pulled in from _includes --> \n+\n+#Part III: Putting it all together\n+\n+In the last installment, we talked about using the Command Pattern to manage our business logic and leveraging Dependency Injection to load in all of our collaborator classes.  Inside our command, or context, we made a call to our repository.  We talked about what a repository in Part I.  It centralizeis all of our ORM-specific code into a single class that returned instances of our custom entity.\n+\n+## Loose Ends\n+\n+The original implementation of our repository didn’t use dependency injection.  That's because I wanted to focus on explaining exactly what a repository class is and how it works with an entity class.\n+Here's what our first pass at a repository looked like.\n+\n+{% highlight ruby %}\n+\n+class ARUserRepository\n+\n+  def create(first_name, last_name, email)\n+    ar_user_object = User.create(first_name: first_name, last_name: last_name, email: email)\n+    user = translate_record(ar_user_object)\n+    user\n+  end\n+\n+  def translate_record(record)\n+    UserEntity.new(record.attributes)\n+  end\n+\n+end\n+\n+{% endhighlight %}\n+\n+\n+There are two classes that the repository depends on: the user model and our entity class.  Let’s revise our repository to inject those two dependencies.  To do this, we'll just override the default\n+\"initialize\" method that belongs to any Ruby class.\n+\n+\n+{% highlight ruby %}\n+\n+class ARUserRepository\n+\n+  def initialize(user_model, user_entity)\n+    @user_model = user_model\n+    @user_entity = user_entity\n+  end\n+\n+  def create(first_name, last_name, email)\n+    ar_user_object = User.create!(first_name: first_name, last_name: last_name, email: email)\n+    user = translate_record(ar_user_object)\n+    user\n+  end\n+\n+  private\n+ \n+  attr_reader :user_model, :user_entity\n+\n+  def translate_record(record)\n+    user_entity.new(record.attributes)\n+  end\n+\n+end\n+\n+{% endhighlight %}\n+\n+\n+Now, there’s still some big stuff left out of this repository.  For instance, we’re not doing any error handling and the only thing this repository can do is to create users.  But the main stucture\n+of the repository should be clear.\n+\n+\n+##The Call Chain\n+\n+Now that we’ve got this giant collection of design patterns all working on concert, let’s walk through how they all interact.  At SitterCity we would test that everything is wired up correctly by using a Cucumber test.  Before we wrote any repository, entity, context, or factory, we would write a Cucumber tests that captured the process of creating a user.  That test would remain failing until we’d implemented everything else.  Afterwards, it should pass.  \n+\n+Here’s what we’d see if we were dissecting the call chain.  Inside the controller, we’d see this:\n+\n+\n+{% highlight ruby %}\n+\n+class UsersController\n+\n+  def create\n+    context = UserFactory.create_user\n+    user = context.call(params[:first_name], params[:last_name], params[:email])\n+  end\n+\n+end\n+\n+\n+{% endhighlight %}\n+\n+\n+The controller is doing two things. First, it's using the UserFactory to instantiate a our CreateUser context.  Second, it's executing the context, by using the \"call\" method and passing along the\n+appropriate arguments.\n+\n+Let's dig int this first operation, where the factory constructs our context for us. Here's what he had origally written.\n+\n+\n+{% highlight ruby %}\n+\n+module UserFactory\n+\n+   self.create_user\n+    CreateUser.new(ARUserRepository.new, UserMailer.new)\n+   end\n+\n+\n+  private\n+\n+end\n+\n+{% endhighlight %}\n+\n+\n+But we've changed the repository so that it too is using dependency injection.  So our final factory might look more like this.\n+\n+\n+{% highlight ruby %}\n+\n+module UserFactory\n+\n+   self.create_user\n+     CreateUser.new(user_repository, mailer)\n+   end\n+\n+  private\n+\n+   self.user_repository\n+     ARUserRepository.new(user_model, user_entity)\n+   end\n+\n+  self.mailer\n+   UserMailer.new\n+  end\n+\n+  self.user_model\n+    User\n+  end\n+\n+  self.user_entity\n+    UserEntity\n+  end\n+\n+end\n+\n+{% endhighlight %}\n+\n+\n+The factory is the lynchpin for the whole operation.  That’s where we all of our contexts get created with their real, live, collaborator classes (as opposed to the mocks we used in testing).\n+\n+Now that our controller has an instance of our CreateUser context in it's handsl, let's take a look at what code is executed when the controller calls our context. In this case our business logic consists of creating a user in the database and sending\n+a “welcome” email. Let's take a look at what code is executed when the controller calls our context. \n+\n+\n+{% highlight ruby %}\n+\n+class CreateUser\n+  def initialize(user_repository, mailer)\n+    user_repository = repo\n+    mailer = mailer\n+  end\n+\n+  def call(first_name, last_name, email)\n+    user = user_repository.create(first_name, last_name, email)\n+    mailer.welcome_email(user.first_name, user.email)\n+    user\n+  end\n+\n+  private\n+ \n+  attr_reader :user_repository, :mailer\n+\n+end\n+\n+{% endhighlight %}\n+\n+Looking inside the call method, we can see that our context uses our repository, which has a pretty generic method called “create.”  The context expects that calling the \"create\" method will return some\n+object that it can call \"first_name\" and \"email\" on.  We know that object is our custom entity class. \n+\n+\n+{% highlight ruby %}\n+\n+class ARUserRepository\n+\n+  def initialize(user_model, user_entity)\n+    @user_model = user_model\n+    @user_entity = user_entity\n+  end\n+\n+  def create(first_name, last_name, email)\n+    ar_user_object = User.create!(first_name: first_name, last_name: last_name, email: email)\n+    user = translate_record(ar_user_object)\n+    user\n+  end\n+\n+  private\n+ \n+  attr_reader :user_model, :user_entity\n+\n+  def translate_record(record)\n+    user_entity.new(record.attributes)\n+  end\n+\n+end\n+\n+{% endhighlight %}\n+\n+Our call chain starts in the controller, which relies on the factory to create an instance of our context.  Our controller then calls that context, being sure to pass it the appropriate arguments.\n+Inside our context, we pass along the user attributes to our repository.  Our repository relies on an ORM (in this case, ActiveRecord) to create the record in the database and we take the result of that process and wrap it into our custom entity.  \n+\n+##Why?\n+\n+So even if we understand this elaborate call chain, we may have one lingering question, which is “Why would we ever to through all that trouble when Rails makes it so much easier to create a user?” \n+\n+The answer is: it depends. We might want it.  We might not want it.\n+\n+Whether it makes sense to use all these patterns together depends on what you’re building.  If you’re in a 24 hour hackathon, should just write your\n+code as quickly as possible.  You shouldn't worry about all these design pattens.  Or, if you’re taking a few days to crank out an MVP, then go\n+ahead of an just write an idiomatic Rails app.  Once you’ve validated your idea, then it's time to think about how you're going to architect your\n+app for the long term.  Using these design patterns makes sense for us, but you should understand your own business requirements and goals when considering whether this is right for you.\n+\n+So when you're weighing wether to use this constellation of design patterns, weigh the effors with writing factories, contexts, repositories, and\n+entities against these benefits.\n+\n+* Freedom.  We want to use the best tools available.  Maybe that means a different ORM than ActiveRecord or maybe it means using another data store\n+  entirely.  If we decide that user info belongs in a document store, all we need  to do is to write a new repository.  We always want to have the freedom to use the best tools available for the job.\n+* Testing.  At SitterCity, we pride ourselves on adding code through BDD and TDD. That means we have thousands of tests for each project we work on.","path":"_posts/2015-03-23-design-patterns-part-3.markdown","position":231,"original_position":231,"commit_id":"00ad355808ed7e7182b072bb887aef32e910475c","original_commit_id":"00ad355808ed7e7182b072bb887aef32e910475c","user":{"login":"ptrimble","id":2753372,"avatar_url":"https://avatars.githubusercontent.com/u/2753372?v=3","gravatar_id":"","url":"https://api.github.com/users/ptrimble","html_url":"https://github.com/ptrimble","followers_url":"https://api.github.com/users/ptrimble/followers","following_url":"https://api.github.com/users/ptrimble/following{/other_user}","gists_url":"https://api.github.com/users/ptrimble/gists{/gist_id}","starred_url":"https://api.github.com/users/ptrimble/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ptrimble/subscriptions","organizations_url":"https://api.github.com/users/ptrimble/orgs","repos_url":"https://api.github.com/users/ptrimble/repos","events_url":"https://api.github.com/users/ptrimble/events{/privacy}","received_events_url":"https://api.github.com/users/ptrimble/received_events","type":"User","site_admin":false},"body":"'SitterCity' again.","created_at":"2015-03-25T22:16:17Z","updated_at":"2015-03-25T22:16:17Z","html_url":"https://github.com/sittercity/techblog/pull/5#discussion_r27172078","pull_request_url":"https://api.github.com/repos/sittercity/techblog/pulls/5","_links":{"self":{"href":"https://api.github.com/repos/sittercity/techblog/pulls/comments/27172078"},"html":{"href":"https://github.com/sittercity/techblog/pull/5#discussion_r27172078"},"pull_request":{"href":"https://api.github.com/repos/sittercity/techblog/pulls/5"}}},"pull_request":{"url":"https://api.github.com/repos/sittercity/techblog/pulls/5","id":31888923,"html_url":"https://github.com/sittercity/techblog/pull/5","diff_url":"https://github.com/sittercity/techblog/pull/5.diff","patch_url":"https://github.com/sittercity/techblog/pull/5.patch","issue_url":"https://api.github.com/repos/sittercity/techblog/issues/5","number":5,"state":"open","locked":false,"title":"Bdowney","user":{"login":"bnd5k","id":769888,"avatar_url":"https://avatars.githubusercontent.com/u/769888?v=3","gravatar_id":"","url":"https://api.github.com/users/bnd5k","html_url":"https://github.com/bnd5k","followers_url":"https://api.github.com/users/bnd5k/followers","following_url":"https://api.github.com/users/bnd5k/following{/other_user}","gists_url":"https://api.github.com/users/bnd5k/gists{/gist_id}","starred_url":"https://api.github.com/users/bnd5k/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bnd5k/subscriptions","organizations_url":"https://api.github.com/users/bnd5k/orgs","repos_url":"https://api.github.com/users/bnd5k/repos","events_url":"https://api.github.com/users/bnd5k/events{/privacy}","received_events_url":"https://api.github.com/users/bnd5k/received_events","type":"User","site_admin":false},"body":"","created_at":"2015-03-25T02:42:34Z","updated_at":"2015-03-25T22:16:17Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b66f9fd18956d6235141ec9fff758e247151b76c","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/sittercity/techblog/pulls/5/commits","review_comments_url":"https://api.github.com/repos/sittercity/techblog/pulls/5/comments","review_comment_url":"https://api.github.com/repos/sittercity/techblog/pulls/comments{/number}","comments_url":"https://api.github.com/repos/sittercity/techblog/issues/5/comments","statuses_url":"https://api.github.com/repos/sittercity/techblog/statuses/00ad355808ed7e7182b072bb887aef32e910475c","head":{"label":"sittercity:bdowney","ref":"bdowney","sha":"00ad355808ed7e7182b072bb887aef32e910475c","user":{"login":"sittercity","id":311012,"avatar_url":"https://avatars.githubusercontent.com/u/311012?v=3","gravatar_id":"","url":"https://api.github.com/users/sittercity","html_url":"https://github.com/sittercity","followers_url":"https://api.github.com/users/sittercity/followers","following_url":"https://api.github.com/users/sittercity/following{/other_user}","gists_url":"https://api.github.com/users/sittercity/gists{/gist_id}","starred_url":"https://api.github.com/users/sittercity/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sittercity/subscriptions","organizations_url":"https://api.github.com/users/sittercity/orgs","repos_url":"https://api.github.com/users/sittercity/repos","events_url":"https://api.github.com/users/sittercity/events{/privacy}","received_events_url":"https://api.github.com/users/sittercity/received_events","type":"Organization","site_admin":false},"repo":{"id":29650233,"name":"techblog","full_name":"sittercity/techblog","owner":{"login":"sittercity","id":311012,"avatar_url":"https://avatars.githubusercontent.com/u/311012?v=3","gravatar_id":"","url":"https://api.github.com/users/sittercity","html_url":"https://github.com/sittercity","followers_url":"https://api.github.com/users/sittercity/followers","following_url":"https://api.github.com/users/sittercity/following{/other_user}","gists_url":"https://api.github.com/users/sittercity/gists{/gist_id}","starred_url":"https://api.github.com/users/sittercity/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sittercity/subscriptions","organizations_url":"https://api.github.com/users/sittercity/orgs","repos_url":"https://api.github.com/users/sittercity/repos","events_url":"https://api.github.com/users/sittercity/events{/privacy}","received_events_url":"https://api.github.com/users/sittercity/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/sittercity/techblog","description":"","fork":false,"url":"https://api.github.com/repos/sittercity/techblog","forks_url":"https://api.github.com/repos/sittercity/techblog/forks","keys_url":"https://api.github.com/repos/sittercity/techblog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sittercity/techblog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sittercity/techblog/teams","hooks_url":"https://api.github.com/repos/sittercity/techblog/hooks","issue_events_url":"https://api.github.com/repos/sittercity/techblog/issues/events{/number}","events_url":"https://api.github.com/repos/sittercity/techblog/events","assignees_url":"https://api.github.com/repos/sittercity/techblog/assignees{/user}","branches_url":"https://api.github.com/repos/sittercity/techblog/branches{/branch}","tags_url":"https://api.github.com/repos/sittercity/techblog/tags","blobs_url":"https://api.github.com/repos/sittercity/techblog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sittercity/techblog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sittercity/techblog/git/refs{/sha}","trees_url":"https://api.github.com/repos/sittercity/techblog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sittercity/techblog/statuses/{sha}","languages_url":"https://api.github.com/repos/sittercity/techblog/languages","stargazers_url":"https://api.github.com/repos/sittercity/techblog/stargazers","contributors_url":"https://api.github.com/repos/sittercity/techblog/contributors","subscribers_url":"https://api.github.com/repos/sittercity/techblog/subscribers","subscription_url":"https://api.github.com/repos/sittercity/techblog/subscription","commits_url":"https://api.github.com/repos/sittercity/techblog/commits{/sha}","git_commits_url":"https://api.github.com/repos/sittercity/techblog/git/commits{/sha}","comments_url":"https://api.github.com/repos/sittercity/techblog/comments{/number}","issue_comment_url":"https://api.github.com/repos/sittercity/techblog/issues/comments{/number}","contents_url":"https://api.github.com/repos/sittercity/techblog/contents/{+path}","compare_url":"https://api.github.com/repos/sittercity/techblog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sittercity/techblog/merges","archive_url":"https://api.github.com/repos/sittercity/techblog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sittercity/techblog/downloads","issues_url":"https://api.github.com/repos/sittercity/techblog/issues{/number}","pulls_url":"https://api.github.com/repos/sittercity/techblog/pulls{/number}","milestones_url":"https://api.github.com/repos/sittercity/techblog/milestones{/number}","notifications_url":"https://api.github.com/repos/sittercity/techblog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sittercity/techblog/labels{/name}","releases_url":"https://api.github.com/repos/sittercity/techblog/releases{/id}","created_at":"2015-01-22T03:26:50Z","updated_at":"2015-03-24T15:25:05Z","pushed_at":"2015-03-25T02:42:35Z","git_url":"git://github.com/sittercity/techblog.git","ssh_url":"git@github.com:sittercity/techblog.git","clone_url":"https://github.com/sittercity/techblog.git","svn_url":"https://github.com/sittercity/techblog","homepage":null,"size":542,"stargazers_count":1,"watchers_count":1,"language":"CSS","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":5,"forks":0,"open_issues":5,"watchers":1,"default_branch":"master"}},"base":{"label":"sittercity:master","ref":"master","sha":"c4b9dd7c27e2122b940fdcafb36982886c7edf16","user":{"login":"sittercity","id":311012,"avatar_url":"https://avatars.githubusercontent.com/u/311012?v=3","gravatar_id":"","url":"https://api.github.com/users/sittercity","html_url":"https://github.com/sittercity","followers_url":"https://api.github.com/users/sittercity/followers","following_url":"https://api.github.com/users/sittercity/following{/other_user}","gists_url":"https://api.github.com/users/sittercity/gists{/gist_id}","starred_url":"https://api.github.com/users/sittercity/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sittercity/subscriptions","organizations_url":"https://api.github.com/users/sittercity/orgs","repos_url":"https://api.github.com/users/sittercity/repos","events_url":"https://api.github.com/users/sittercity/events{/privacy}","received_events_url":"https://api.github.com/users/sittercity/received_events","type":"Organization","site_admin":false},"repo":{"id":29650233,"name":"techblog","full_name":"sittercity/techblog","owner":{"login":"sittercity","id":311012,"avatar_url":"https://avatars.githubusercontent.com/u/311012?v=3","gravatar_id":"","url":"https://api.github.com/users/sittercity","html_url":"https://github.com/sittercity","followers_url":"https://api.github.com/users/sittercity/followers","following_url":"https://api.github.com/users/sittercity/following{/other_user}","gists_url":"https://api.github.com/users/sittercity/gists{/gist_id}","starred_url":"https://api.github.com/users/sittercity/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/sittercity/subscriptions","organizations_url":"https://api.github.com/users/sittercity/orgs","repos_url":"https://api.github.com/users/sittercity/repos","events_url":"https://api.github.com/users/sittercity/events{/privacy}","received_events_url":"https://api.github.com/users/sittercity/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/sittercity/techblog","description":"","fork":false,"url":"https://api.github.com/repos/sittercity/techblog","forks_url":"https://api.github.com/repos/sittercity/techblog/forks","keys_url":"https://api.github.com/repos/sittercity/techblog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/sittercity/techblog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/sittercity/techblog/teams","hooks_url":"https://api.github.com/repos/sittercity/techblog/hooks","issue_events_url":"https://api.github.com/repos/sittercity/techblog/issues/events{/number}","events_url":"https://api.github.com/repos/sittercity/techblog/events","assignees_url":"https://api.github.com/repos/sittercity/techblog/assignees{/user}","branches_url":"https://api.github.com/repos/sittercity/techblog/branches{/branch}","tags_url":"https://api.github.com/repos/sittercity/techblog/tags","blobs_url":"https://api.github.com/repos/sittercity/techblog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/sittercity/techblog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/sittercity/techblog/git/refs{/sha}","trees_url":"https://api.github.com/repos/sittercity/techblog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/sittercity/techblog/statuses/{sha}","languages_url":"https://api.github.com/repos/sittercity/techblog/languages","stargazers_url":"https://api.github.com/repos/sittercity/techblog/stargazers","contributors_url":"https://api.github.com/repos/sittercity/techblog/contributors","subscribers_url":"https://api.github.com/repos/sittercity/techblog/subscribers","subscription_url":"https://api.github.com/repos/sittercity/techblog/subscription","commits_url":"https://api.github.com/repos/sittercity/techblog/commits{/sha}","git_commits_url":"https://api.github.com/repos/sittercity/techblog/git/commits{/sha}","comments_url":"https://api.github.com/repos/sittercity/techblog/comments{/number}","issue_comment_url":"https://api.github.com/repos/sittercity/techblog/issues/comments{/number}","contents_url":"https://api.github.com/repos/sittercity/techblog/contents/{+path}","compare_url":"https://api.github.com/repos/sittercity/techblog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/sittercity/techblog/merges","archive_url":"https://api.github.com/repos/sittercity/techblog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/sittercity/techblog/downloads","issues_url":"https://api.github.com/repos/sittercity/techblog/issues{/number}","pulls_url":"https://api.github.com/repos/sittercity/techblog/pulls{/number}","milestones_url":"https://api.github.com/repos/sittercity/techblog/milestones{/number}","notifications_url":"https://api.github.com/repos/sittercity/techblog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/sittercity/techblog/labels{/name}","releases_url":"https://api.github.com/repos/sittercity/techblog/releases{/id}","created_at":"2015-01-22T03:26:50Z","updated_at":"2015-03-24T15:25:05Z","pushed_at":"2015-03-25T02:42:35Z","git_url":"git://github.com/sittercity/techblog.git","ssh_url":"git@github.com:sittercity/techblog.git","clone_url":"https://github.com/sittercity/techblog.git","svn_url":"https://github.com/sittercity/techblog","homepage":null,"size":542,"stargazers_count":1,"watchers_count":1,"language":"CSS","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":5,"forks":0,"open_issues":5,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/sittercity/techblog/pulls/5"},"html":{"href":"https://github.com/sittercity/techblog/pull/5"},"issue":{"href":"https://api.github.com/repos/sittercity/techblog/issues/5"},"comments":{"href":"https://api.github.com/repos/sittercity/techblog/issues/5/comments"},"review_comments":{"href":"https://api.github.com/repos/sittercity/techblog/pulls/5/comments"},"review_comment":{"href":"https://api.github.com/repos/sittercity/techblog/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/sittercity/techblog/pulls/5/commits"},"statuses":{"href":"https://api.github.com/repos/sittercity/techblog/statuses/00ad355808ed7e7182b072bb887aef32e910475c"}}}}