diff --git a/app/controllers/tagged_items_controller.rb b/app/controllers/tagged_items_controller.rb index ce3e4fe..38f9f90 100644 --- a/app/controllers/tagged_items_controller.rb +++ b/app/controllers/tagged_items_controller.rb @@ -5,6 +5,7 @@ class TaggedItemsController < ApplicationController # Show a single tagged items for a given tag # Example: # `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags/android/tagged_items/1.json'` + # `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v2/tags/android/tagged_items/1.json?item_type=city'` def show Rails.logger.debug "Tagged item is #{@tagged_item.inspect}" render_if_stale(@tagged_item, last_modified: @tagged_item.updated_at.utc, etag: @tagged_item) do |tagged_item_presenter| @@ -17,8 +18,9 @@ def show # List all tagged items for a given tag # Example: # `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags/android/tagged_items.json'` + # `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v2/tags/android/tagged_items.json?item_type=city'` def index - all_tagged_items = @tag.tagged_items + all_tagged_items = filter_by_item_type(@tag.tagged_items, params[:version].to_i, params[:item_type]) return json_response([]) unless newest_tagged_item = all_tagged_items.sort_by(&:updated_at).last Rails.logger.info "newest_tagged_item is #{newest_tagged_item.inspect}" render_if_stale(all_tagged_items, last_modified: newest_tagged_item.updated_at.utc, etag: newest_tagged_item) do |tagged_item_presenters| @@ -32,9 +34,11 @@ def index # Example: # `curl -v -H "Content-type: application/json" -X POST 'http://localhost:3000/api/v1/tags/android/tagged_items.json' \ # -d '{"id":1}'` + # `curl -v -H "Content-type: application/json" -X POST 'http://localhost:3000/api/v2/tags/android/tagged_items.json' \ + # -d '{"id":1, "item_type": "city"}'` def create - item = TaggedItem.find_or_initialize_by(item_id: params[:id], tag_id: @tag.id) - render(json: {error: "Tagged item combination {tag-name: #{@tag.name}, item_id: #{item.item_id}] already exists."}, status: :conflict) and return unless item.new_record? + item = TaggedItem.find_or_initialize_by(item_id: params[:id], tag_id: @tag.id, item_type: TaggedItem.item_type_id_for(params[:item_type])) + render(json: {error: "Tagged item combination {tag-name: #{@tag.name}, item_id: #{item.item_id}, item_type: #{item.item_type}] already exists."}, status: :conflict) and return unless item.new_record? if item.save render text: '{"success": true}', status: :created, location: tagged_item_url(item, (params[:version])) else @@ -47,6 +51,7 @@ def create # Delete a tagged_item entry for a given tag and item ID # Example: # `curl -v -H "Content-type: application/json" -X DELETE 'http://localhost:3000/api/v1/tags/android/tagged_items/1.json'` + # `curl -v -H "Content-type: application/json" -X DELETE 'http://localhost:3000/api/v2/tags/android/tagged_items/1.json?item_type=city'` def destroy Rails.logger.debug "Tagged item is #{@tagged_item.inspect}" if @tagged_item.destroy @@ -65,7 +70,21 @@ def find_tag end def find_tagged_item - @tagged_item = @tag.tagged_items.where(item_id: params[:id], item_type: TaggedItem.item_type_id_for(params[:type])).first + @tagged_item = @tag.tagged_items.where(item_id: params[:id], item_type: TaggedItem.item_type_id_for(params[:item_type])).first not_found_with_max_age(caching_time) and return unless @tagged_item end + + def filter_by_item_type(tagged_items, version = 1, item_type_name = 'all') + case version + when 2 + if item_type_name.nil? || (item_type_name == 'all') + tagged_items + else + tagged_items.where(item_type: TaggedItem.item_type_id_for(item_type_name)) + end + + else + tagged_items + end + end end diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index 34436fa..f852f44 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -15,10 +15,10 @@ def show # List all tags (can be filtered by "item_id" parameter) # Example: - # `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags.json'` + # `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags.json{?item_id=1}'` + # `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v2/tags.json{?item_id=1&item_type=city}'` def index - item_id = params[:item_id].to_i - all_tags = (item_id > 0) ? Tag.joins(:tagged_items).where(tagged_items: {item_id: item_id}) : Tag.all + all_tags = filter_by_item_it_and_type(params[:item_id].to_i, params[:item_type], params[:version].to_i) return json_response([]) unless newest_tag = all_tags.sort_by(&:updated_at).last Rails.logger.info "newest_tag is #{newest_tag.inspect}" render_if_stale(all_tags, last_modified: newest_tag.updated_at.utc, etag: newest_tag) do |tag_presenters| @@ -73,4 +73,28 @@ def destroy end end + private + + def filter_by_item_it_and_type(item_id, item_type = 'all', version = 1) + item_id = params[:item_id].to_i + case version + when 2 + case + when item_id == 0 && item_type = 'all' + Tag.all + when item_id == 0 && item_type != 'all' + Tag.joins(:tagged_items).where(tagged_items: { item_type: TaggedItem.item_type_id_for(item_type) }) + when item_id > 0 && item_type == 'all' + Tag.joins(:tagged_items).where(tagged_items: { item_id: item_id }) + when item_id > 0 && item_type != 'all' + Tag.joins(:tagged_items).where(tagged_items: {item_id: item_id, item_type: TaggedItem.item_type_id_for(item_type)}) + end + else + if item_id > 0 + Tag.joins(:tagged_items).where(tagged_items: {item_id: item_id, item_type: TaggedItem.item_type_id_for('inventory_item')}) + else + Tag.all + end + end + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 1413f84..f56190a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,5 +1,10 @@ module ApplicationHelper def tagged_item_url(tagged_item, version = 1) - "http://inventory-service-development.herokuapp.com/api/v#{version}/inventory_items/#{tagged_item.item_id}" + case TaggedItem.item_type_name_for(tagged_item.item_type) + when 'city' + "http://cities-service-development.herokuapp.com/api/v#{version}/cities/#{tagged_item.item_id}" + else + "http://inventory-service-development.herokuapp.com/api/v#{version}/inventory_items/#{tagged_item.item_id}" + end end end diff --git a/app/models/tagged_item.rb b/app/models/tagged_item.rb index 7b6e02d..c3a5d3b 100644 --- a/app/models/tagged_item.rb +++ b/app/models/tagged_item.rb @@ -5,12 +5,21 @@ class TaggedItem < ActiveRecord::Base validates_presence_of :item_id, :tag_id, :item_type validates_uniqueness_of :item_id, scope: [:tag_id, :item_type] - def self.item_type_id_for(item_type = 'inventory_item') - case item_type + def self.item_type_id_for(item_type_name = 'inventory_item') + case item_type_name when 'city' 2 else 1 end end + + def self.item_type_name_for(item_type_id = 1) + case item_type_id + when 2 + 'city' + else + 'inventory_item' + end + end end diff --git a/app/presenters/presenters.rb b/app/presenters/presenters.rb index bae9822..7fe53a6 100644 --- a/app/presenters/presenters.rb +++ b/app/presenters/presenters.rb @@ -3,4 +3,9 @@ module V1 autoload :TagPresenter, "v1/tag_presenter" autoload :TaggedItemPresenter, "v1/tagged_item_presenter" end + + module V2 + autoload :TagPresenter, "v2/tag_presenter" + autoload :TaggedItemPresenter, "v2/tagged_item_presenter" + end end diff --git a/app/presenters/v1/tag_presenter.rb b/app/presenters/v1/tag_presenter.rb index 79fa892..9ab6e52 100644 --- a/app/presenters/v1/tag_presenter.rb +++ b/app/presenters/v1/tag_presenter.rb @@ -7,18 +7,18 @@ def initialize(item) super(@tag) end - def to_hash(item = tag) + def to_hash(tg = tag) HashWithIndifferentAccess.new( { - name: tag.name, + name: tg.name, tagged_items: { - count: tag.tagged_items.count, - items: tag.tagged_items.map do |item| + count: tg.tagged_items.count, + items: tg.tagged_items.map do |item| { id: item.item_id, url: tagged_item_url(item, self.class.version_number) } end }, - path: tag_path(self.class.version_number, tag.name) + path: tag_path(self.class.version_number, tg.name) }) end end diff --git a/app/presenters/v2/tag_presenter.rb b/app/presenters/v2/tag_presenter.rb new file mode 100644 index 0000000..0312e2c --- /dev/null +++ b/app/presenters/v2/tag_presenter.rb @@ -0,0 +1,19 @@ +class Presenters::V2::TagPresenter < Presenters::V1::TagPresenter + + def to_hash(tg = tag) + HashWithIndifferentAccess.new( + { + name: tg.name, + tagged_items: { + count: tg.tagged_items.count, + items: tg.tagged_items.map do |item| + { id: item.item_id, + type: item.item_type, + url: tagged_item_url(item, self.class.version_number) } + end + }, + path: tag_path(self.class.version_number, tg.name) + }) + end + +end diff --git a/app/presenters/v2/tagged_item_presenter.rb b/app/presenters/v2/tagged_item_presenter.rb new file mode 100644 index 0000000..752da77 --- /dev/null +++ b/app/presenters/v2/tagged_item_presenter.rb @@ -0,0 +1,12 @@ +class Presenters::V2::TaggedItemPresenter < Presenters::V1::TaggedItemPresenter + + def to_hash(tagged_item = item) + HashWithIndifferentAccess.new( + { + tagged_item_id: tagged_item.item_id, + tagged_item_type: tagged_item.item_type, + url: tagged_item_url(tagged_item, self.class.version_number), + tag_name: tagged_item.tag.name + }) + end +end diff --git a/public/api_docs/v2/api-docs.json b/public/api_docs/v2/api-docs.json new file mode 100644 index 0000000..34aca0b --- /dev/null +++ b/public/api_docs/v2/api-docs.json @@ -0,0 +1,15 @@ +{ + "apiVersion": "2.0", + "swaggerVersion": "1.2", + "basePath": "http://tags-service-development.herokuapp.com/api_docs/v2", + "apis": [ + { + "path": "/tagged_items.{format}", + "description": "Managing associating tags with items" + }, + { + "path": "/tags.{format}", + "description": "Tag management" + } + ] +} diff --git a/public/api_docs/v2/tagged_items.json b/public/api_docs/v2/tagged_items.json new file mode 100644 index 0000000..d3880c0 --- /dev/null +++ b/public/api_docs/v2/tagged_items.json @@ -0,0 +1,165 @@ +{ + "apiVersion": "2.0", + "swaggerVersion": "1.2", + "basePath": "http://tags-service-development.herokuapp.com/api/v2", + "apis": [ + { + "path": "/tags/{tag_name}/tagged_items.json", + "operations": [ + { + "summary": "Fetches all tagged items for tag {tag_name}", + "parameters": [ + { + "paramType": "path", + "name": "tag_name", + "type": "string", + "description": "Tag name for which all tagged items are retrieved", + "required": true + }, + { + "paramType": "query", + "name": "item_type", + "type": "string", + "description": "Name of the type of item by which to filter the items tagged, e.g. 'city'; defaults to 'all'", + "required": false + } + ], + "responseMessages": [ + { + "code": 304, + "message": "The content has not changed in relation to the request ETag / If-Modified-Since" + } + ], + "method": "get", + "nickname": "TaggedItems#index" + } + ] + }, + { + "path": "/tags/{tag_name}/tagged_items.json", + "operations": [ + { + "summary": "Tags an item with ID {id} with tag by name {tag_name}", + "parameters": [ + { + "paramType": "path", + "name": "tag_name", + "type": "string", + "description": "Tag name to be applied to the tagged item", + "required": true + }, + { + "paramType": "form", + "name": "id", + "type": "integer", + "description": "ID of the item to be tagged", + "required": true + }, + { + "paramType": "form", + "name": "item_type", + "type": "string", + "description": "Name of the type of item to be tagged, e.g. 'city'; defaults to 'inventory_item'", + "required": false + } + ], + "responseMessages": [ + { + "code": 422, + "message": "Unprocessable Entity" + } + ], + "method": "post", + "nickname": "TaggedItems#create" + } + ] + }, + { + "path": "/tags/{tag_name}/tagged_items/{id}.json", + "operations": [ + { + "summary": "Fetches a single tagged item for tag {tag_name} and item ID {id}", + "parameters": [ + { + "paramType": "path", + "name": "id", + "type": "integer", + "description": "ID of the tagged item", + "required": true + }, + { + "paramType": "path", + "name": "tag_name", + "type": "string", + "description": "Name of the tag that is was applied to the tagged item", + "required": true + }, + { + "paramType": "query", + "name": "item_type", + "type": "string", + "description": "Name of the item type by which to filter the applicable tags (e.g., 'city'), so that just one tagged item is ever returned; defaults to 'inventory_item'", + "required": false + } + ], + "responseMessages": [ + { + "code": 304, + "message": "The content has not changed in relation to the request ETag / If-Modified-Since" + }, + { + "code": 404, + "message": "Not Found" + } + ], + "method": "get", + "nickname": "TaggedItems#show" + } + ] + }, + { + "path": "/tags/{tag_name}/tagged_items/{id}.json", + "operations": [ + { + "summary": "Deletes an existing association between a tag (by name) and a tagged item", + "parameters": [ + { + "paramType": "path", + "name": "id", + "type": "integer", + "description": "ID of the item that is currently tagged", + "required": true + }, + { + "paramType": "path", + "name": "tag_name", + "type": "string", + "description": "name of the tag to be removed from the item", + "required": true + }, + { + "paramType": "query", + "name": "item_type", + "type": "string", + "description": "Name of the item type by which to filter the applicable tags (e.g., 'city'), so that just one tagged item is ever destroyed; defaults to 'inventory_item'", + "required": false + } + ], + "responseMessages": [ + { + "code": 404, + "message": "Not Found" + }, + { + "code": 400, + "message": "Bad Request" + } + ], + "method": "delete", + "nickname": "TaggedItems#destroy" + } + ] + } + ], + "resourcePath": "/tagged_items" +} diff --git a/public/api_docs/v2/tags.json b/public/api_docs/v2/tags.json new file mode 100644 index 0000000..b5d7d9e --- /dev/null +++ b/public/api_docs/v2/tags.json @@ -0,0 +1,210 @@ +{ + "apiVersion": "2.0", + "swaggerVersion": "1.2", + "basePath": "http://tags-service-development.herokuapp.com/api/v2", + "apis": [ + { + "path": "/tags.json", + "operations": [ + { + "summary": "Fetches all tags (filterable by tags for a given item_id & item_type)", + "parameters": [ + { + "paramType": "query", + "name": "item_id", + "type": "integer", + "description": "ID of the tagged inventory item by which to filter the tags", + "required": false + }, + { + "paramType": "query", + "name": "item_type", + "type": "string", + "description": "name of the tagged item type (e.g., 'city') by which to filter the tags", + "required": false + } + ], + "responseMessages": [ + { + "code": 304, + "message": "The content has not changed in relation to the request ETag / If-Modified-Since" + } + ], + "method": "get", + "nickname": "Tags#index" + } + ] + }, + { + "path": "/tags.json", + "operations": [ + { + "summary": "Creates a new inventory item", + "parameters": [ + { + "paramType": "form", + "name": "tag", + "type": "Tag", + "description": "Name (unique) of the tag to be created", + "required": true + } + ], + "responseMessages": [ + { + "code": 422, + "message": "Unprocessable Entity" + } + ], + "method": "post", + "nickname": "Tags#create" + } + ] + }, + { + "path": "/tags/{name}.json", + "operations": [ + { + "summary": "Fetches a single tag by its name", + "parameters": [ + { + "paramType": "path", + "name": "name", + "type": "string", + "description": "Tag name", + "required": true + } + ], + "responseMessages": [ + { + "code": 304, + "message": "The content has not changed in relation to the request ETag / If-Modified-Since" + }, + { + "code": 404, + "message": "Not Found" + } + ], + "method": "get", + "nickname": "Tags#show" + } + ] + }, + { + "path": "/tags/{name}.json", + "operations": [ + { + "summary": "Updates an existing tag referenced by its name", + "parameters": [ + { + "paramType": "path", + "name": "name", + "type": "string", + "description": "Tag name", + "required": true + }, + { + "paramType": "form", + "name": "tag", + "type": "Tag", + "description": "The tag's new name", + "required": true + } + ], + "responseMessages": [ + { + "code": 404, + "message": "Not Found" + }, + { + "code": 422, + "message": "Unprocessable Entity" + } + ], + "method": "patch", + "nickname": "Tags#update" + } + ] + }, + { + "path": "/tags/{name}.json", + "operations": [ + { + "summary": "Updates an existing tag referenced by its name", + "parameters": [ + { + "paramType": "path", + "name": "name", + "type": "string", + "description": "Tag name", + "required": true + }, + { + "paramType": "form", + "name": "tag", + "type": "Tag", + "description": "The tag's new name", + "required": true + } + ], + "responseMessages": [ + { + "code": 404, + "message": "Not Found" + }, + { + "code": 422, + "message": "Unprocessable Entity" + } + ], + "method": "put", + "nickname": "Tags#update" + } + ] + }, + { + "path": "/tags/{name}.json", + "operations": [ + { + "summary": "Deletes an existing tag referenced by its name", + "parameters": [ + { + "paramType": "path", + "name": "name", + "type": "string", + "description": "name of the tag to delete", + "required": true + } + ], + "responseMessages": [ + { + "code": 404, + "message": "Not Found" + }, + { + "code": 422, + "message": "Unprocessable Entity" + } + ], + "method": "delete", + "nickname": "Tags#destroy" + } + ] + } + ], + "resourcePath": "/tags", + "models": { + "Tag": { + "id": "Tag", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Name (unique) of the tag." + } + }, + "description": "A tag to be applied to items." + } + } +}