{"action":"created","issue":{"url":"https://api.github.com/repos/cerebris/jsonapi-resources/issues/486","labels_url":"https://api.github.com/repos/cerebris/jsonapi-resources/issues/486/labels{/name}","comments_url":"https://api.github.com/repos/cerebris/jsonapi-resources/issues/486/comments","events_url":"https://api.github.com/repos/cerebris/jsonapi-resources/issues/486/events","html_url":"https://github.com/cerebris/jsonapi-resources/pull/486","id":111045751,"number":486,"title":"Feature/custom links defined per resource","user":{"login":"AKHarris","id":5504772,"avatar_url":"https://avatars.githubusercontent.com/u/5504772?v=3","gravatar_id":"","url":"https://api.github.com/users/AKHarris","html_url":"https://github.com/AKHarris","followers_url":"https://api.github.com/users/AKHarris/followers","following_url":"https://api.github.com/users/AKHarris/following{/other_user}","gists_url":"https://api.github.com/users/AKHarris/gists{/gist_id}","starred_url":"https://api.github.com/users/AKHarris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AKHarris/subscriptions","organizations_url":"https://api.github.com/users/AKHarris/orgs","repos_url":"https://api.github.com/users/AKHarris/repos","events_url":"https://api.github.com/users/AKHarris/events{/privacy}","received_events_url":"https://api.github.com/users/AKHarris/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":2,"created_at":"2015-10-12T20:05:26Z","updated_at":"2015-10-14T20:36:20Z","closed_at":null,"pull_request":{"url":"https://api.github.com/repos/cerebris/jsonapi-resources/pulls/486","html_url":"https://github.com/cerebris/jsonapi-resources/pull/486","diff_url":"https://github.com/cerebris/jsonapi-resources/pull/486.diff","patch_url":"https://github.com/cerebris/jsonapi-resources/pull/486.patch"},"body":"based on conversation in #464 and some code I managed to extract from a recent spike using this library.\r\n\r\ncc @ceritium\r\n\r\n### Overview\r\n\r\nThis PR is for adding custom links to a particular resource's `links` node. The first argument is the name of the link in the JSON response, the second argument is the 'type' of url you are building (a modification of self url, modification of relationship url, or a completely custom url built with a lambda).\r\n\r\n**Edit**: from @beechnut 's comment below, the methods you call in a custom link's lambda need to be defined on the Resource instance or accessed through the Resource instance's model like `model.some_method`.\r\n\r\n### Examples\r\n\r\nIn this example, the url simply hangs off the the normal 'self' link.\r\n````ruby\r\nclass SpecificationResource < JSONAPI::Resource\r\n  attributes :title, :description #etc\r\n\r\n  custom_link :raw, :self, extension: :xml\r\nend\r\n````\r\n\r\ngenerates the following\r\n````\r\n{\r\n  \"data\": [\r\n  {\r\n    \"id\": \"26\",\r\n    \"type\": \"specifications\",\r\n    \"links\": {\r\n      \"self\": \"http://example.com/api/specifications/26\",\r\n      \"raw\": \"http://example.com/api/specifications/26/raw.xml\"\r\n    }\r\n}\r\n````\r\n\r\nYou can define a custom path extension from the self url as well\r\n````ruby\r\nclass SpecificationResource < JSONAPI::Resource\r\n  attributes :title, :description #etc\r\n\r\n  custom_link :raw, :self, path: 'super/duper/path', extension: :csv\r\nend\r\n````\r\ngenerates\r\n\r\n````\r\n{\r\n  \"data\": [\r\n  {\r\n    \"id\": \"26\",\r\n    \"type\": \"specifications\",\r\n    \"links\": {\r\n      \"self\": \"http://example.com/api/specifications/26\",\r\n      \"raw\": \"http://example.com/api/specifications/26/super/duper/path.csv\"\r\n    }\r\n}\r\n````\r\n\r\nHere's an example with an `if` option, which takes a lambda. The custom link is added only if the lambda call is true\r\n\r\n````ruby\r\nclass ProjectProposalResource < JSONAPI::Resource\r\n  attributes :title, :description, :approved, :zoning_code\r\n\r\n  has_one :department\r\n  has_many :tasks\r\n\r\n  # make the `approved?` method available to the Resource instance\r\n  delegate :approved? to: :model\r\n\r\n  custom_link :some_weird_gov_metadata_standard, :self, extension: :xml, \r\n    if: ->(instance) { instance.approved? } \r\n  #only add the link if the project has been approved\r\nend\r\n````\r\n\r\n````\r\n# example.com/api/project-proposals\r\n# \r\n# ProjectProposal 12 is approved but ProjectProposal 13 is not\r\n{\r\n  \"data\": [\r\n  {\r\n    \"id\": \"12\",\r\n    \"type\": \"projectProposals\",\r\n    \"links\": {\r\n      \"self\": \"http://example.com/api/project-proposals/12\",\r\n      \"some_weird_gov_metadata_standard\": \"http://example.com/api/project-proposals/12/some_weird_gov_metadata_standard.xml\"\r\n    },\r\n    \"attributes\": {\r\n      ...\r\n    }\r\n  {\r\n    \"id\": \"13\",\r\n    \"type\": \"projectProposals\",\r\n    \"links\": {\r\n      \"self\": \"http://example.com/api/project-proposals/13\",\r\n      // no gov metadata link\r\n    },\r\n    \"attributes\": {\r\n      ...\r\n    }\r\n  }\r\n}\r\n````\r\n\r\nHere are a couple of contrived examples that use lambdas.\r\n\r\n````ruby\r\nclass ArticleResource < JSONAPI::Resource\r\n  attributes :title, :body, :published_at\r\n\r\n  # make the method `original_source` available to the model\r\n  delegate :original_source, to: :model\r\n\r\n  custom_link :original_source, :custom, with: ->(instance) { instance.original_source }\r\nend\r\n````\r\n\r\n````\r\n{\r\n  \"data\": [\r\n  {\r\n    \"id\": \"2\",\r\n    \"type\": \"articles\",\r\n    \"links\": {\r\n      \"self\": \"http://example.com/api/articles/2\",\r\n      \"original_source\": \"http://www.cerebris.com/blog/2015/06/04/jsonapi-1-0/\"\r\n    }\r\n}\r\n````\r\n\r\n````ruby\r\nclass ArticleResource < JSONAPI::Resource\r\n  attributes :title, :body, :published_at\r\n\r\n  custom_link :v1_api_link, :custom, \r\n    with: ->(instance) { Figaro.env.domain + \"/v1/some-other-part-of-your-api\" }\r\nend\r\n````\r\n\r\n````\r\n{\r\n  \"data\": [\r\n  {\r\n    \"id\": \"2\",\r\n    \"type\": \"articles\",\r\n    \"links\": {\r\n      \"self\": \"http://example.com/api/v2/articles/2\",\r\n      \"v1_api_link\": \"http://example.com/v1/some-other-part-of-your-api\"\r\n    }\r\n}\r\n````"},"comment":{"url":"https://api.github.com/repos/cerebris/jsonapi-resources/issues/comments/148189329","html_url":"https://github.com/cerebris/jsonapi-resources/pull/486#issuecomment-148189329","issue_url":"https://api.github.com/repos/cerebris/jsonapi-resources/issues/486","id":148189329,"user":{"login":"AKHarris","id":5504772,"avatar_url":"https://avatars.githubusercontent.com/u/5504772?v=3","gravatar_id":"","url":"https://api.github.com/users/AKHarris","html_url":"https://github.com/AKHarris","followers_url":"https://api.github.com/users/AKHarris/followers","following_url":"https://api.github.com/users/AKHarris/following{/other_user}","gists_url":"https://api.github.com/users/AKHarris/gists{/gist_id}","starred_url":"https://api.github.com/users/AKHarris/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AKHarris/subscriptions","organizations_url":"https://api.github.com/users/AKHarris/orgs","repos_url":"https://api.github.com/users/AKHarris/repos","events_url":"https://api.github.com/users/AKHarris/events{/privacy}","received_events_url":"https://api.github.com/users/AKHarris/received_events","type":"User","site_admin":false},"created_at":"2015-10-14T20:36:20Z","updated_at":"2015-10-14T20:36:20Z","body":"@beechnut good catch, I updated the top comment with your corrections. :+1: \r\n\r\nI think the following should work as well.\r\n````ruby\r\nclass ProjectResource < JSONAPI::Resource\r\n  u = Rails.application.routes.url_helpers\r\n  delegate :presence, to: :model\r\n  delegate :next, to: :model\r\n  delegate :previous, to: :model\r\n\r\n  custom_link :next,     :custom, with: ->(i) { u.project_url(i.next) if i.next }\r\n  custom_link :previous, :custom, with: ->(i) { u.project_url(i.previous) if i.previous }\r\nend\r\n````"}}