{"action":"created","issue":{"url":"https://api.github.com/repos/jdickey/new_poc/issues/258","labels_url":"https://api.github.com/repos/jdickey/new_poc/issues/258/labels{/name}","comments_url":"https://api.github.com/repos/jdickey/new_poc/issues/258/comments","events_url":"https://api.github.com/repos/jdickey/new_poc/issues/258/events","html_url":"https://github.com/jdickey/new_poc/pull/258","id":69078729,"number":258,"title":"Fixing Post entity migration breakage.","user":{"login":"jdickey","id":157938,"avatar_url":"https://avatars.githubusercontent.com/u/157938?v=3","gravatar_id":"","url":"https://api.github.com/users/jdickey","html_url":"https://github.com/jdickey","followers_url":"https://api.github.com/users/jdickey/followers","following_url":"https://api.github.com/users/jdickey/following{/other_user}","gists_url":"https://api.github.com/users/jdickey/gists{/gist_id}","starred_url":"https://api.github.com/users/jdickey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdickey/subscriptions","organizations_url":"https://api.github.com/users/jdickey/orgs","repos_url":"https://api.github.com/users/jdickey/repos","events_url":"https://api.github.com/users/jdickey/events{/privacy}","received_events_url":"https://api.github.com/users/jdickey/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/jdickey/new_poc/labels/extract-components","name":"extract-components","color":"fbca04"},{"url":"https://api.github.com/repos/jdickey/new_poc/labels/in%20progress","name":"in progress","color":"ededed"}],"state":"open","locked":false,"assignee":{"login":"jdickey","id":157938,"avatar_url":"https://avatars.githubusercontent.com/u/157938?v=3","gravatar_id":"","url":"https://api.github.com/users/jdickey","html_url":"https://github.com/jdickey","followers_url":"https://api.github.com/users/jdickey/followers","following_url":"https://api.github.com/users/jdickey/following{/other_user}","gists_url":"https://api.github.com/users/jdickey/gists{/gist_id}","starred_url":"https://api.github.com/users/jdickey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdickey/subscriptions","organizations_url":"https://api.github.com/users/jdickey/orgs","repos_url":"https://api.github.com/users/jdickey/repos","events_url":"https://api.github.com/users/jdickey/events{/privacy}","received_events_url":"https://api.github.com/users/jdickey/received_events","type":"User","site_admin":false},"milestone":null,"comments":3,"created_at":"2015-04-17T06:49:55Z","updated_at":"2015-04-17T10:26:32Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/jdickey/new_poc/pulls/258","html_url":"https://github.com/jdickey/new_poc/pull/258","diff_url":"https://github.com/jdickey/new_poc/pull/258.diff","patch_url":"https://github.com/jdickey/new_poc/pull/258.patch"},"body":"As per the \"Again, priorities\" list in [this comment](https://github.com/jdickey/new_poc/issues/240#issuecomment-93783853) on Issue #240.\r\n\r\nWhat will count as merge-worthy success is a green bar from *all* (existing, updated and new) specs using either `Newpoc::Entity::Post` or `Entity::Post` as the post-entity class. Then, of course, we can kill off the old `Newpoc::Entity::Post` entirely and be left with one remaining \"unbuilt\" Gem to merge back. :disappointed_relieved: "},"comment":{"url":"https://api.github.com/repos/jdickey/new_poc/issues/comments/93960361","html_url":"https://github.com/jdickey/new_poc/pull/258#issuecomment-93960361","issue_url":"https://api.github.com/repos/jdickey/new_poc/issues/258","id":93960361,"user":{"login":"jdickey","id":157938,"avatar_url":"https://avatars.githubusercontent.com/u/157938?v=3","gravatar_id":"","url":"https://api.github.com/users/jdickey","html_url":"https://github.com/jdickey","followers_url":"https://api.github.com/users/jdickey/followers","following_url":"https://api.github.com/users/jdickey/following{/other_user}","gists_url":"https://api.github.com/users/jdickey/gists{/gist_id}","starred_url":"https://api.github.com/users/jdickey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jdickey/subscriptions","organizations_url":"https://api.github.com/users/jdickey/orgs","repos_url":"https://api.github.com/users/jdickey/repos","events_url":"https://api.github.com/users/jdickey/events{/privacy}","received_events_url":"https://api.github.com/users/jdickey/received_events","type":"User","site_admin":false},"created_at":"2015-04-17T10:26:31Z","updated_at":"2015-04-17T10:26:31Z","body":"And we've discovered two new problems with `Entity::Post`.\r\n\r\n### First Problem\r\n\r\nIn [`PostsController::Action::Create#validate_post_data`](https://github.com/jdickey/new_poc/blob/ffd1a3382402f0dbdb8a463754ce042482d77a73/app/controllers/posts_controller/action/create.rb#L65), we tell whoever's interested what broke by serialising the `@entity` to JSON and broadcasting that serialisation. With `Newpoc::Entity::Post`, for en entity with no title (and after calling `#valid?`), that serialisation would look like\r\n\r\n```json\r\n{\"author_name\":\"Just Anybody\",\"body\":\"A Body\",\"image_url\":null,\"slug\":null,\"title\":null,\"pubdate\":null,\"created_at\":null,\"updated_at\":null,\"validation_context\":null,\"errors\":{\"title\":[\"can't be blank\"]}}\r\n```\r\n\r\nThe recovery code could either pull the errors out of the JSON, or pull it back into a Hash and use those attributes to initialise a new (invalid) instance matching the original.\r\n\r\nThe JSON for the corresponding `Entity::Post` instance at that point would look like\r\n\r\n```json\r\n\"attributes\":{\"attributes\":{\"body\":\"A Body\",\"author_name\":\"Just Anybody\"}},\"validators\":{\"validators\":{\"author_name\":{\"author_name\":\"Just Anybody\",\"errors\":[]},\"body\":{\"primary\":\"body\",\"other\":\"image_url\",\"main_attribute\":\"A Body\",\"other_empty\":true,\"errors\":[]},\"image_url\":{\"primary\":\"image_url\",\"other\":\"body\",\"main_attribute\":\"\",\"other_empty\":false,\"errors\":[]},\"title\":{\"title\":null,\"errors\":[]},\"image_url\":{\"primary\":\"image_url\",\"other\":\"body\",\"main_attribute\":\"\",\"other_empty\":false,\"errors\":[]},\"title\":{\"title\":null,\"errors\":[]}}}}\r\n```\r\n\r\nA pretty-print of the state of the `Entity::Post` would look like\r\n\r\n```\r\n=> #<Entity::Post:0x007fb047ef80b8\r\n @attributes=#<Entity::Post::AttributeContainer:0x007fb047f03a58 @attributes=#<#<Class:0x007fb047f03940>:0x007fb047f03670 @author_name=\"Just Anybody\", @body=\"A Body\">>,\r\n @validators=\r\n  #<Entity::Post::ValidatorGrouping:0x007fb047f01b40\r\n   @validators=\r\n    {:author_name=>#<Entity::Post::Validators::AuthorName:0x007fb047f0bb40 @author_name=\"Just Anybody\", @errors=[]>,\r\n     :body=>#<Entity::Post::Validators::Body:0x007fb047f0baf0 @errors=[], @main_attribute=\"A Body\", @other=:image_url, @other_empty=true, @primary=:body>,\r\n     :image_url=>#<Entity::Post::Validators::ImageUrl:0x007fb047f0ba28 @errors=[], @main_attribute=\"\", @other=:body, @other_empty=false, @primary=:image_url>,\r\n     :title=>#<Entity::Post::Validators::Title:0x007fb047f0b960 @errors=[], @title=nil>}>>\r\n```\r\n\r\nThe attributes (to recreate a new entity instance) are down in the `attributes` hash, and error information is...*distributed*.\r\n\r\n#### Possible Solution\r\n\r\nImplement a `#to_json` method on `Entity::Post` that returns a hash of the attributes and errors similar to what the default `#to_json` produces when handed a `Newpoc::Entity::Post` instance. You can argue that this is a \"quick-hack-level solution\", but I prefer to think of it as *information hiding*; you're telling the world exactly and only what it needs to know to handle the use cases where a JSON representation of the entity would be required &mdash; namely, serialisation of attributes and error information.\r\n\r\n### Second Problem\r\n\r\nWhile running down the earlier problem, I rediscovered another difference between the two entity implementations. The old entity uses an `ActiveModel::Errors` instance to hold its error data; the new one does its own validation and serialises error data in a form similar to but not compatible with with `ActiveModel::Errors#messages` — which is *not the same thing as* `#full_messages`.\r\n\r\nFor example, if `ActiveErrors#messages` were to return (reformatted for legibility):\r\n\r\n```ruby\r\n{\r\n  title: ['must not have leading whitespace', 'must not have trailing whitespace']\r\n}\r\n```\r\n\r\ncalling `#full_messages` on the same instance would produce\r\n\r\n```ruby\r\n[\r\n  'Title must not have leading whitespace',\r\n  'Title must not have trailing whitespace'\r\n]\r\n\r\nand `Entity::Posts` equivalent error serialisation would be\r\n\r\n```ruby\r\n[\r\n  { title: 'must not have leading whitespace' },\r\n  { title: 'must not have trailing whitespace' }\r\n]\r\n```\r\n\r\nNot the same thing at all here, either.\r\n\r\n#### Proposed Solutions\r\n\r\nThere are a few different ways we could handle this. One would be to bite the bullet and reimplement the [`Entity::Post::ValidatorGrouping` class](https://github.com/jdickey/new_poc/blob/ffd1a3382402f0dbdb8a463754ce042482d77a73/app/entities/post/validator_grouping.rb) so that it maintains, and wraps, an instance of `ActiveModel::Errors` to support the subset of that API presently supported by the class.\r\n\r\nAnother would be to implement an `#errors` method on `Entity::Post` that would build such an instance on the fly, add the entries in the existing error serialisation to that, and return it. A different approach to encapsulation, and I could easily be swayed as to the relative merits between the two."}}