{"action":"created","comment":{"url":"https://api.github.com/repos/namely/connect/pulls/comments/38031519","id":38031519,"diff_hunk":"@@ -1,116 +1,136 @@\n module Greenhouse\n   class Normalizer\n-    # Initializes an attribute mapper using Namely Fields\n-    def initialize(namely_fields)\n+    NAMELY_IDENTIFIER_FIELD = :greenhouse_id\n+\n+    def initialize(attribute_mapper:, namely_fields:)\n+      @attribute_mapper = attribute_mapper\n       @namely_fields = namely_fields\n     end\n \n-    def call(payload)\n-      custom_fields(candidate(payload)).\n-        merge(custom_fields(offer(payload))).\n-        merge(custom_fields(job(payload))).\n-        merge(basic_fields(payload)).\n-        select { |_, value| value.present? }\n+    def call(data)\n+      candidate = Payload.new(data, namely_fields: @namely_fields).to_hash\n+      imported = @attribute_mapper.import(candidate)\n+      imported.merge(\n+        candidate.slice(NAMELY_IDENTIFIER_FIELD, :salary, :home, :user_status)\n+      )\n     end\n \n     def namely_identifier_field\n-      :greenhouse_id\n+      NAMELY_IDENTIFIER_FIELD\n     end\n \n     private\n \n-    # TODO: refactor this method\n-    def basic_fields(payload)\n-      {\n-        first_name: candidate(payload).fetch(\"first_name\"),\n-        last_name: candidate(payload).fetch(\"last_name\"),\n-        email: email_for(candidate(payload), \"work\"),\n-        personal_email: email_for(candidate(payload), \"personal\"),\n-        user_status: \"active\",\n-        start_date: offer(payload).fetch(\"starts_at\"),\n-        home: home_address_for(candidate(payload)),\n-        namely_identifier_field => identifier(application(payload)).to_s,\n-        salary: salary_field(payload),\n-      }\n-    end\n+    class Payload\n+      def initialize(data, namely_fields:)\n+        @data = data\n+        @namely_fields = namely_fields\n+      end\n \n-    def custom_fields(payload)\n-      p = payload.fetch(\"custom_fields\", {})\n-      if p.present?\n-        Greenhouse::CustomFields.match(\n-          namely_fields: @namely_fields,\n-          payload: p\n-        )\n-      else\n-        {}\n+      def to_hash\n+        custom_fields(candidate).\n+          merge(custom_fields(offer)).\n+          merge(custom_fields(job)).\n+          merge(basic_fields).\n+          select { |_, value| value.present? }\n       end\n-    end\n \n-    def candidate(payload)\n-      application(payload).fetch(\"candidate\")\n-    end\n+      def basic_fields\n+        {\n+          first_name: candidate.fetch(\"first_name\"),\n+          last_name: candidate.fetch(\"last_name\"),\n+          work_email: email_for(\"work\"),\n+          personal_email: email_for(\"personal\"),\n+          user_status: \"active\",\n+          starts_at: offer.fetch(\"starts_at\"),\n+          home: home_address,\n+          NAMELY_IDENTIFIER_FIELD => identifier.to_s,\n+          salary: salary_field,\n+        }\n+      end\n \n-    def identifier(payload)\n-      payload.fetch(\"id\")\n-    end\n+      def custom_fields(payload)\n+        fields = payload.fetch(\"custom_fields\", {})\n+        if fields.present?\n+          Greenhouse::CustomFields.match(\n+            namely_fields: @namely_fields,\n+            payload: fields\n+          )\n+        else\n+          {}\n+        end\n+      end\n \n-    def application(payload)\n-      payload.fetch(\"application\")\n-    end\n+      def candidate\n+        application.fetch(\"candidate\")\n+      end\n \n-    def offer(payload)\n-      application(payload).fetch(\"offer\", { \"starts_at\" => \"\" })\n-    end\n+      def identifier\n+        application.fetch(\"id\")\n+      end\n \n-    def job(payload)\n-      application(payload).fetch(\"job\")\n-    end\n+      def application\n+        @data.fetch(\"application\")\n+      end\n+\n+      def offer\n+        application.fetch(\"offer\") { { \"starts_at\" => \"\" } }\n+      end\n+\n+      def job\n+        application.fetch(\"job\")\n+      end\n \n-    def salary_field(payload)\n-      custom_fields = offer(payload).fetch(\"custom_fields\", {})\n-      salary = custom_fields.fetch(\"salary\", nil)\n-      if salary.present?\n-        if salary.fetch(\"type\") == \"currency\"\n+      def salary_field\n+        custom_fields = offer.fetch(\"custom_fields\", {})\n+        salary = custom_fields.fetch(\"salary\", {})\n+        case salary[\"type\"]\n+        when \"currency\"\n           {\n             yearly_amount: salary.fetch(\"value\").fetch(\"amount\"),\n             currency_type: salary.fetch(\"value\").fetch(\"unit\"),\n-            date: offer(payload).fetch(\"starts_at\")\n+            date: offer.fetch(\"starts_at\")\n           }\n+        when nil\n+          {}\n         else\n           {\n             yearly_amount: salary.fetch(\"value\").to_i,\n             currency_type: \"USD\",\n-            date: offer(payload).fetch(\"starts_at\")\n+            date: offer.fetch(\"starts_at\")\n           }\n         end\n-      else\n-        {}\n       end\n-    end\n \n-    def email_for(payload, type)\n-      addresses = payload.fetch(\"email_addresses\", []) || []\n-      email_address = addresses.find do |email_address|\n-        email_address.fetch(\"type\") == type\n-      end\n-      if email_address.present?\n-        email_address.fetch(\"value\")\n-      else\n-        nil\n+      def email_for(type)\n+        email_address =\n+          email_addresses.detect { |address| address.fetch(\"type\") == type }\n+\n+        if email_address.present?\n+          email_address.fetch(\"value\")\n+        else\n+          nil\n+        end\n       end\n-    end\n \n+      def email_addresses\n+        candidate[\"email_addresses\"] || []\n+      end\n \n-    def home_address_for(candidate)\n-      home_address = candidate.fetch(\"addresses\", [])\n-      if home_address\n-        home_address = home_address.find do |address|\n-          address.fetch(\"type\") == \"home\"\n-        end || {}\n-        { address1: home_address.fetch(\"value\", \"\") }\n-      else\n-        nil\n+      def home_address\n+        home_address = candidate.fetch(\"addresses\", [])","path":"app/models/greenhouse/normalizer.rb","position":198,"original_position":198,"commit_id":"8b32d2f4b6091cf0bcb149193187ba92a317c9c8","original_commit_id":"8b32d2f4b6091cf0bcb149193187ba92a317c9c8","user":{"login":"jferris","id":7591,"avatar_url":"https://avatars.githubusercontent.com/u/7591?v=3","gravatar_id":"","url":"https://api.github.com/users/jferris","html_url":"https://github.com/jferris","followers_url":"https://api.github.com/users/jferris/followers","following_url":"https://api.github.com/users/jferris/following{/other_user}","gists_url":"https://api.github.com/users/jferris/gists{/gist_id}","starred_url":"https://api.github.com/users/jferris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jferris/subscriptions","organizations_url":"https://api.github.com/users/jferris/orgs","repos_url":"https://api.github.com/users/jferris/repos","events_url":"https://api.github.com/users/jferris/events{/privacy}","received_events_url":"https://api.github.com/users/jferris/received_events","type":"User","site_admin":false},"body":"It's actually different. This actually returns `nil`:\r\n\r\n``` ruby\r\n{ \"addresses\" => nil }.fetch(\"addresses\", [])\r\n```\r\n\r\nThat means this method can return any of the following:\r\n\r\n```\r\nnil\r\n{ address1: \"\" }\r\n{ address1: nil }\r\n{ address1: \"Address\" }\r\n```\r\n\r\nThis logic does seem overly defensive to me, but I haven't found a different combination which passes the tests. I'll take a look at the API to see if the tests are too paranoid.","created_at":"2015-08-26T20:35:42Z","updated_at":"2015-08-26T20:35:42Z","html_url":"https://github.com/namely/connect/pull/183#discussion_r38031519","pull_request_url":"https://api.github.com/repos/namely/connect/pulls/183","_links":{"self":{"href":"https://api.github.com/repos/namely/connect/pulls/comments/38031519"},"html":{"href":"https://github.com/namely/connect/pull/183#discussion_r38031519"},"pull_request":{"href":"https://api.github.com/repos/namely/connect/pulls/183"}}},"pull_request":{"url":"https://api.github.com/repos/namely/connect/pulls/183","id":43431528,"html_url":"https://github.com/namely/connect/pull/183","diff_url":"https://github.com/namely/connect/pull/183.diff","patch_url":"https://github.com/namely/connect/pull/183.patch","issue_url":"https://api.github.com/repos/namely/connect/issues/183","number":183,"state":"open","locked":false,"title":"Users can select mappings for Greenhouse accounts","user":{"login":"jferris","id":7591,"avatar_url":"https://avatars.githubusercontent.com/u/7591?v=3","gravatar_id":"","url":"https://api.github.com/users/jferris","html_url":"https://github.com/jferris","followers_url":"https://api.github.com/users/jferris/followers","following_url":"https://api.github.com/users/jferris/following{/other_user}","gists_url":"https://api.github.com/users/jferris/gists{/gist_id}","starred_url":"https://api.github.com/users/jferris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jferris/subscriptions","organizations_url":"https://api.github.com/users/jferris/orgs","repos_url":"https://api.github.com/users/jferris/repos","events_url":"https://api.github.com/users/jferris/events{/privacy}","received_events_url":"https://api.github.com/users/jferris/received_events","type":"User","site_admin":false},"body":"Because:\n\n* Greenhouse contains fields that Namely doesn't\n* Users can create custom fields in Namely for those fields\n\nThis commit:\n\n* Adds attribute mapping for Greenhouse connections\n* Maintains hard mappings for address and salary, which we can't map yet\n\nhttps://trello.com/c/kYjN3NrG","created_at":"2015-08-26T17:54:06Z","updated_at":"2015-08-26T20:35:42Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3680ea00da1422f60dc70ce6d681e825ad6227bb","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/namely/connect/pulls/183/commits","review_comments_url":"https://api.github.com/repos/namely/connect/pulls/183/comments","review_comment_url":"https://api.github.com/repos/namely/connect/pulls/comments{/number}","comments_url":"https://api.github.com/repos/namely/connect/issues/183/comments","statuses_url":"https://api.github.com/repos/namely/connect/statuses/8b32d2f4b6091cf0bcb149193187ba92a317c9c8","head":{"label":"namely:greenhouse-attribute-mapper","ref":"greenhouse-attribute-mapper","sha":"8b32d2f4b6091cf0bcb149193187ba92a317c9c8","user":{"login":"namely","id":1004321,"avatar_url":"https://avatars.githubusercontent.com/u/1004321?v=3","gravatar_id":"","url":"https://api.github.com/users/namely","html_url":"https://github.com/namely","followers_url":"https://api.github.com/users/namely/followers","following_url":"https://api.github.com/users/namely/following{/other_user}","gists_url":"https://api.github.com/users/namely/gists{/gist_id}","starred_url":"https://api.github.com/users/namely/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/namely/subscriptions","organizations_url":"https://api.github.com/users/namely/orgs","repos_url":"https://api.github.com/users/namely/repos","events_url":"https://api.github.com/users/namely/events{/privacy}","received_events_url":"https://api.github.com/users/namely/received_events","type":"Organization","site_admin":false},"repo":{"id":26101808,"name":"connect","full_name":"namely/connect","owner":{"login":"namely","id":1004321,"avatar_url":"https://avatars.githubusercontent.com/u/1004321?v=3","gravatar_id":"","url":"https://api.github.com/users/namely","html_url":"https://github.com/namely","followers_url":"https://api.github.com/users/namely/followers","following_url":"https://api.github.com/users/namely/following{/other_user}","gists_url":"https://api.github.com/users/namely/gists{/gist_id}","starred_url":"https://api.github.com/users/namely/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/namely/subscriptions","organizations_url":"https://api.github.com/users/namely/orgs","repos_url":"https://api.github.com/users/namely/repos","events_url":"https://api.github.com/users/namely/events{/privacy}","received_events_url":"https://api.github.com/users/namely/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/namely/connect","description":"Namely Connect – Integrations with the Namely HRIS System.","fork":false,"url":"https://api.github.com/repos/namely/connect","forks_url":"https://api.github.com/repos/namely/connect/forks","keys_url":"https://api.github.com/repos/namely/connect/keys{/key_id}","collaborators_url":"https://api.github.com/repos/namely/connect/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/namely/connect/teams","hooks_url":"https://api.github.com/repos/namely/connect/hooks","issue_events_url":"https://api.github.com/repos/namely/connect/issues/events{/number}","events_url":"https://api.github.com/repos/namely/connect/events","assignees_url":"https://api.github.com/repos/namely/connect/assignees{/user}","branches_url":"https://api.github.com/repos/namely/connect/branches{/branch}","tags_url":"https://api.github.com/repos/namely/connect/tags","blobs_url":"https://api.github.com/repos/namely/connect/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/namely/connect/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/namely/connect/git/refs{/sha}","trees_url":"https://api.github.com/repos/namely/connect/git/trees{/sha}","statuses_url":"https://api.github.com/repos/namely/connect/statuses/{sha}","languages_url":"https://api.github.com/repos/namely/connect/languages","stargazers_url":"https://api.github.com/repos/namely/connect/stargazers","contributors_url":"https://api.github.com/repos/namely/connect/contributors","subscribers_url":"https://api.github.com/repos/namely/connect/subscribers","subscription_url":"https://api.github.com/repos/namely/connect/subscription","commits_url":"https://api.github.com/repos/namely/connect/commits{/sha}","git_commits_url":"https://api.github.com/repos/namely/connect/git/commits{/sha}","comments_url":"https://api.github.com/repos/namely/connect/comments{/number}","issue_comment_url":"https://api.github.com/repos/namely/connect/issues/comments{/number}","contents_url":"https://api.github.com/repos/namely/connect/contents/{+path}","compare_url":"https://api.github.com/repos/namely/connect/compare/{base}...{head}","merges_url":"https://api.github.com/repos/namely/connect/merges","archive_url":"https://api.github.com/repos/namely/connect/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/namely/connect/downloads","issues_url":"https://api.github.com/repos/namely/connect/issues{/number}","pulls_url":"https://api.github.com/repos/namely/connect/pulls{/number}","milestones_url":"https://api.github.com/repos/namely/connect/milestones{/number}","notifications_url":"https://api.github.com/repos/namely/connect/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/namely/connect/labels{/name}","releases_url":"https://api.github.com/repos/namely/connect/releases{/id}","created_at":"2014-11-03T03:10:16Z","updated_at":"2015-07-29T20:15:07Z","pushed_at":"2015-08-26T18:05:22Z","git_url":"git://github.com/namely/connect.git","ssh_url":"git@github.com:namely/connect.git","clone_url":"https://github.com/namely/connect.git","svn_url":"https://github.com/namely/connect","homepage":"","size":4818,"stargazers_count":1,"watchers_count":1,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":4,"mirror_url":null,"open_issues_count":2,"forks":4,"open_issues":2,"watchers":1,"default_branch":"master"}},"base":{"label":"namely:master","ref":"master","sha":"39b8b0f9af8bcc4ca1c280c991a4d4da45ada202","user":{"login":"namely","id":1004321,"avatar_url":"https://avatars.githubusercontent.com/u/1004321?v=3","gravatar_id":"","url":"https://api.github.com/users/namely","html_url":"https://github.com/namely","followers_url":"https://api.github.com/users/namely/followers","following_url":"https://api.github.com/users/namely/following{/other_user}","gists_url":"https://api.github.com/users/namely/gists{/gist_id}","starred_url":"https://api.github.com/users/namely/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/namely/subscriptions","organizations_url":"https://api.github.com/users/namely/orgs","repos_url":"https://api.github.com/users/namely/repos","events_url":"https://api.github.com/users/namely/events{/privacy}","received_events_url":"https://api.github.com/users/namely/received_events","type":"Organization","site_admin":false},"repo":{"id":26101808,"name":"connect","full_name":"namely/connect","owner":{"login":"namely","id":1004321,"avatar_url":"https://avatars.githubusercontent.com/u/1004321?v=3","gravatar_id":"","url":"https://api.github.com/users/namely","html_url":"https://github.com/namely","followers_url":"https://api.github.com/users/namely/followers","following_url":"https://api.github.com/users/namely/following{/other_user}","gists_url":"https://api.github.com/users/namely/gists{/gist_id}","starred_url":"https://api.github.com/users/namely/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/namely/subscriptions","organizations_url":"https://api.github.com/users/namely/orgs","repos_url":"https://api.github.com/users/namely/repos","events_url":"https://api.github.com/users/namely/events{/privacy}","received_events_url":"https://api.github.com/users/namely/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/namely/connect","description":"Namely Connect – Integrations with the Namely HRIS System.","fork":false,"url":"https://api.github.com/repos/namely/connect","forks_url":"https://api.github.com/repos/namely/connect/forks","keys_url":"https://api.github.com/repos/namely/connect/keys{/key_id}","collaborators_url":"https://api.github.com/repos/namely/connect/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/namely/connect/teams","hooks_url":"https://api.github.com/repos/namely/connect/hooks","issue_events_url":"https://api.github.com/repos/namely/connect/issues/events{/number}","events_url":"https://api.github.com/repos/namely/connect/events","assignees_url":"https://api.github.com/repos/namely/connect/assignees{/user}","branches_url":"https://api.github.com/repos/namely/connect/branches{/branch}","tags_url":"https://api.github.com/repos/namely/connect/tags","blobs_url":"https://api.github.com/repos/namely/connect/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/namely/connect/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/namely/connect/git/refs{/sha}","trees_url":"https://api.github.com/repos/namely/connect/git/trees{/sha}","statuses_url":"https://api.github.com/repos/namely/connect/statuses/{sha}","languages_url":"https://api.github.com/repos/namely/connect/languages","stargazers_url":"https://api.github.com/repos/namely/connect/stargazers","contributors_url":"https://api.github.com/repos/namely/connect/contributors","subscribers_url":"https://api.github.com/repos/namely/connect/subscribers","subscription_url":"https://api.github.com/repos/namely/connect/subscription","commits_url":"https://api.github.com/repos/namely/connect/commits{/sha}","git_commits_url":"https://api.github.com/repos/namely/connect/git/commits{/sha}","comments_url":"https://api.github.com/repos/namely/connect/comments{/number}","issue_comment_url":"https://api.github.com/repos/namely/connect/issues/comments{/number}","contents_url":"https://api.github.com/repos/namely/connect/contents/{+path}","compare_url":"https://api.github.com/repos/namely/connect/compare/{base}...{head}","merges_url":"https://api.github.com/repos/namely/connect/merges","archive_url":"https://api.github.com/repos/namely/connect/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/namely/connect/downloads","issues_url":"https://api.github.com/repos/namely/connect/issues{/number}","pulls_url":"https://api.github.com/repos/namely/connect/pulls{/number}","milestones_url":"https://api.github.com/repos/namely/connect/milestones{/number}","notifications_url":"https://api.github.com/repos/namely/connect/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/namely/connect/labels{/name}","releases_url":"https://api.github.com/repos/namely/connect/releases{/id}","created_at":"2014-11-03T03:10:16Z","updated_at":"2015-07-29T20:15:07Z","pushed_at":"2015-08-26T18:05:22Z","git_url":"git://github.com/namely/connect.git","ssh_url":"git@github.com:namely/connect.git","clone_url":"https://github.com/namely/connect.git","svn_url":"https://github.com/namely/connect","homepage":"","size":4818,"stargazers_count":1,"watchers_count":1,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":4,"mirror_url":null,"open_issues_count":2,"forks":4,"open_issues":2,"watchers":1,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/namely/connect/pulls/183"},"html":{"href":"https://github.com/namely/connect/pull/183"},"issue":{"href":"https://api.github.com/repos/namely/connect/issues/183"},"comments":{"href":"https://api.github.com/repos/namely/connect/issues/183/comments"},"review_comments":{"href":"https://api.github.com/repos/namely/connect/pulls/183/comments"},"review_comment":{"href":"https://api.github.com/repos/namely/connect/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/namely/connect/pulls/183/commits"},"statuses":{"href":"https://api.github.com/repos/namely/connect/statuses/8b32d2f4b6091cf0bcb149193187ba92a317c9c8"}}}}