Skip to content

Commit 66e4965

Browse files
author
Tim Schmelmer
committed
Documenting TaggedItem(sController) and Tag(sController) using swagger_yard
1 parent 12da96f commit 66e4965

38 files changed

+210
-11101
lines changed

Gemfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@ group :production do
2828
gem 'rails_12factor'
2929
end
3030

31+
group :development, :production do
32+
gem 'swagger_yard', :git => 'git://github.com/tpitale/swagger_yard', :branch => 'master'
33+
end
34+
3135
ruby "2.1.1"

Gemfile.lock

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
GIT
2+
remote: git://github.com/tpitale/swagger_yard
3+
revision: 2f1a22d342838b0ea12fab259c1c20311cb230e8
4+
branch: master
5+
specs:
6+
swagger_yard (0.0.6)
7+
rails (>= 3.2.8)
8+
yard
9+
110
GEM
211
remote: https://rubygems.org/
312
specs:
@@ -104,6 +113,7 @@ GEM
104113
ansi
105114
minitest (~> 4)
106115
tzinfo (0.3.39)
116+
yard (0.8.7.4)
107117

108118
PLATFORMS
109119
ruby
@@ -120,4 +130,5 @@ DEPENDENCIES
120130
protected_attributes
121131
rails (= 4.0.4)
122132
rails_12factor
133+
swagger_yard!
123134
turn

README.rdoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,13 @@ workshop.
2222
`bundle exec rake db:fixtures:load`
2323

2424
* How to run the test suite
25+
`bundle exec rake test`
2526

26-
* Services (job queues, cache servers, search engines, etc.)
27+
* API endpoints
28+
* The `tags_service` comes with a Swagger UI that describes, and lets you try
29+
out, all exposed API endpoints.
30+
* Just navigate to [`http://localhost/swagger/doc`](http://localhost/swagger/doc)
31+
to see and test-drive the full set of APIs /
2732

2833
* Deployment instructions
2934
* general instructions here: https://devcenter.heroku.com/articles/getting-started-with-rails4

app/controllers/tagged_items_controller.rb

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,26 @@
1+
# @resource Tagged Items
2+
#
3+
# @resource_path /tagged_items
4+
#
5+
# API for managing associations between tags and items.
6+
#
17
class TaggedItemsController < ApplicationController
28
include ApplicationHelper
39
before_action :find_tag
410
before_action :find_tagged_item, except: [:index, :create]
5-
# Show a single tagged items for a given tag
6-
# Example:
7-
# `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags/android/tagged_items/1.json'`
11+
12+
##
13+
# @summary Fetches a single tagged item for tag {tag_name} and item ID {id}.
14+
#
15+
# @notes Example: `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags/android/tagged_items/1.json'`
16+
#
17+
# @path [GET] /tags/{tag_name}/tagged_items/{id}.json
18+
#
19+
# @response_type [TaggedItem]
20+
#
21+
# @error_message 304 The content has not changed in relation to the request ETag / If-Modified-Since
22+
# @error_message 404 The requested tagged item cannot be found
23+
#
824
def show
925
Rails.logger.debug "Tagged item is #{@tagged_item.inspect}"
1026
render_if_stale(@tagged_item, last_modified: @tagged_item.updated_at.utc, etag: @tagged_item) do |tagged_item_presenter|
@@ -14,9 +30,17 @@ def show
1430
expires_in caching_time, public: true
1531
end
1632

17-
# List all tagged items for a given tag
18-
# Example:
19-
# `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags/android/tagged_items.json'`
33+
##
34+
# @summary Returns all tagged items for tag {tag_name}
35+
#
36+
# @notes Example: `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags/android/tagged_items.json'`
37+
#
38+
# @path [GET] /tags/{tag_name}/tagged_items.json
39+
#
40+
# @response_type [array<TaggedItem>]
41+
#
42+
# @error_message 304 The content has not changed in relation to the request ETag / If-Modified-Since
43+
#
2044
def index
2145
all_tagged_items = @tag.tagged_items
2246
return json_response([]) unless newest_tagged_item = all_tagged_items.sort_by(&:updated_at).last
@@ -28,10 +52,20 @@ def index
2852
expires_in caching_time, public: true
2953
end
3054

31-
# Create a new tagged_item for a given tag and item ID.
32-
# Example:
33-
# `curl -v -H "Content-type: application/json" -X POST 'http://localhost:3000/api/v1/tags/android/tagged_items.json' \
34-
# -d '{"id":1}'`
55+
##
56+
# @summary Tags an item with ID {id} with tag by name {tag_name}
57+
#
58+
# @notes Example: `curl -v -H "Content-type: application/json" -X POST 'http://localhost:3000/api/v1/tags/android/tagged_items.json' \
59+
# -d '{"id":1}'`
60+
#
61+
# @path [POST] /tags/{tag_name}/tagged_items.json
62+
#
63+
# @parameter [integer] id(required) ID of the item to be tagged
64+
#
65+
# @response_type [string]
66+
#
67+
# @error_message 422 Unprocessable Entity
68+
#
3569
def create
3670
item = TaggedItem.find_or_initialize_by(item_id: params[:id], tag_id: @tag.id)
3771
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?
@@ -43,6 +77,18 @@ def create
4377
end
4478
end
4579

80+
##
81+
# @summary Deletes an existing association between a tag (by {tag_name}) and a tagged item (by {id}).
82+
#
83+
# @notes Example: `curl -v -H "Content-type: application/json" -X DELETE 'http://localhost:3000/api/v1/tags/android/tagged_items/1.json'`
84+
#
85+
# @path [DELETE] /tags/{tag_name}/tagged_items/{id}.json
86+
#
87+
# @response_type [string]
88+
#
89+
# @error_message 400 Bad Request, cannot destroy tagged item because there were errors deleting the tag
90+
# @error_message 404 The requested tagged item to be deleted cannot be found
91+
#
4692

4793
# Delete a tagged_item entry for a given tag and item ID
4894
# Example:

app/controllers/tags_controller.rb

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
1+
# @resource Tags
2+
#
3+
# @resource_path /tags
4+
#
5+
# API for tag management.
6+
#
17
class TagsController < ApplicationController
2-
# Show a single tag
3-
# Example:
4-
# `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags/paranoid.json'
8+
9+
##
10+
# @summary Fetches a single tag by its name {name}.
11+
#
12+
# @notes Example: `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags/paranoid.json'
13+
#
14+
# @path [GET] /tags/{name}.json
15+
#
16+
# @response_type [Tag]
17+
#
18+
# @error_message 304 The content has not changed in relation to the request ETag / If-Modified-Since
19+
# @error_message 404 A tag of the provided name cannot be found
20+
#
521
def show
622
not_found_with_max_age(caching_time) and return unless (@tag = Tag.find_by_name(params[:id]))
723
Rails.logger.debug "Tag with name #{@tag.name} is #{@tag.inspect}"
@@ -13,25 +29,46 @@ def show
1329
expires_in caching_time, public: true
1430
end
1531

16-
# List all tags (can be filtered by "item_id" parameter)
17-
# Example:
18-
# `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags.json'`
32+
##
33+
# @summary Fetches all tags (filterable by tags for a given {item_id})
34+
#
35+
# @notes Example: `curl -v -H "Content-type: application/json" 'http://localhost:3000/api/v1/tags.json'`
36+
#
37+
# @path [GET] /tags.json
38+
#
39+
# @parameter [integer] item_id(query) ID of the tagged inventory item by which to filter the tags
40+
#
41+
# @response_type [array<Tag>]
42+
#
43+
# @error_message 304 The content has not changed in relation to the request ETag / If-Modified-Since
44+
#
1945
def index
2046
item_id = params[:item_id].to_i
2147
all_tags = (item_id > 0) ? Tag.joins(:tagged_items).where(tagged_items: {item_id: item_id}) : Tag.all
2248
return json_response([]) unless newest_tag = all_tags.sort_by(&:updated_at).last
23-
Rails.logger.info "newest_tag is #{newest_tag.inspect}"
49+
Rails.logger.info "newest_tag is #{newest_tag.inspect}, all tags are of size: #{all_tags.count}"
2450
render_if_stale(all_tags, last_modified: newest_tag.updated_at.utc, etag: newest_tag) do |tag_presenters|
2551
tag_presenters.map(&:hash)
2652
end
2753
# explicitly setting the Cache-Control response header to public and max-age, to make the response cachable by proxy caches
2854
expires_in caching_time, public: true
2955
end
3056

31-
# Create a new tag.
32-
# Example:
33-
# `curl -v -H "Content-type: application/json" -X POST 'http://localhost:3000/api/v1/tags.json' \
34-
# -d '{"name":"android"}'`
57+
##
58+
# @summary Creates a new tag by the given name
59+
#
60+
# @notes Example: `curl -v -H "Content-type: application/json" -X POST 'http://localhost:3000/api/v1/tags.json' \
61+
# -d '{"name":"android"}'`
62+
#
63+
# @path [POST] /tags.json
64+
#
65+
# @parameter [string] name(required,body) Name (unique) of the tag to be created
66+
#
67+
# @response_type [string]
68+
#
69+
# @error_message 409 Conflict, a Tag with given name already exists
70+
# @error_message 422 Unprocessable Entity, the tag cannot be created because there were errors saving
71+
#
3572
def create
3673
tag = Tag.find_or_initialize_by(params.slice(:name))
3774
render(json: {error: "Tag with name #{params[:name]} already exists."}, status: :conflict) and return unless tag.new_record?
@@ -43,10 +80,21 @@ def create
4380
end
4481
end
4582

46-
# Update an existing tag.
47-
# Example:
48-
# `curl -v -H "Content-type: application/json" -X PUT 'http://localhost:3000/api/v1/tags/android.json' \
49-
# -d '{"name":"paranoid"}'`
83+
##
84+
# @summary Updates an existing tag's name, referenced by its current name
85+
#
86+
# @notes Example: `curl -v -H "Content-type: application/json" -X PUT 'http://localhost:3000/api/v1/tags/android.json' \
87+
# -d '{"name":"paranoid"}'`
88+
#
89+
# @path [PUT] /tags/{name}.json
90+
#
91+
# @parameter [string] new_name(required,body) The tag's new name
92+
#
93+
# @response_type [string]
94+
#
95+
# @error_message 404 Not Found, a Tag with given name does not exists
96+
# @error_message 422 Unprocessable Entity, the tag cannot be updated because there were errors saving
97+
#
5098
def update
5199
tag = Tag.find_by_name(params[:id])
52100
render(json: {error: "Tag with name #{params[:id]} does not exists."}, status: :not_found) and return if tag.nil?
@@ -59,9 +107,18 @@ def update
59107
end
60108
end
61109

62-
# Delete a tag
63-
# Example:
64-
# `curl -v -H "Content-type: application/json" -X DELETE 'http://localhost:3000/api/v1/tags/paranoid.json'`
110+
##
111+
# @summary Deletes an existing tag referenced by its name
112+
#
113+
# @notes Example: `curl -v -H "Content-type: application/json" -X DELETE 'http://localhost:3000/api/v1/tags/paranoid.json'`
114+
#
115+
# @path [DELETE] /tags/{name}.json
116+
#
117+
# @response_type [string]
118+
#
119+
# @error_message 404 A tag of the provided name cannot be found
120+
# @error_message 422 Unprocessable Entity, the tag cannot be deleted because there were errors destroying it
121+
#
65122
def destroy
66123
tag = Tag.find_by_name(params[:id])
67124
render(json: {error: "Tag with name #{params[:id]} does not exists."}, status: :not_found) and return if tag.nil?

app/models/tag.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1+
#
2+
# @model Tag
3+
#
4+
# @property name(required) [string] Name of the tag given to the item
5+
# @property path(required) [string] Path of the resource representing this tag
6+
#
7+
# example:
8+
# ````
9+
# {
10+
# "name": "bacon",
11+
# "tagged_items": {
12+
# "count": 1,
13+
# "items": [
14+
# { "id": 3,
15+
# "url": "http://inventory-service-development.herokuapp.com/api/v1/inventory_items/3"}
16+
# ]
17+
# },
18+
# "path": "api/v1/tags/bacon"
19+
# }
20+
# ````
21+
#
122
class Tag < ActiveRecord::Base
223
attr_accessible :name, :id
324

app/models/tagged_item.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
#
2+
# @model TaggedItem
3+
#
4+
# @property tagged_item_id(required) [integer] ID of the item tagged
5+
# @property url [string] URI of the resource for the item that has been tagged
6+
# @property tag_name(required) [string] Name of the tag given to the item
7+
#
8+
# example:
9+
# ````
10+
# {
11+
# "tagged_item_id": 42,
12+
# "url": "http://inventory-service-development.herokuapp.com/api/v1/inventory_items/42",
13+
# "tag_name": "bacon"
14+
# }
15+
# ````
16+
#
117
class TaggedItem < ActiveRecord::Base
218
attr_accessible :item_id, :tag_id
319

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
config_file = File.join( File.dirname(__FILE__) + '/../swagger-yard.yml' )
2+
conf = YAML.load_file(config_file)[Rails.env]
3+
4+
unless Rails.env.test?
5+
SwaggerYard.configure do |config|
6+
config.swagger_version = "1.2"
7+
config.api_version = "1.0"
8+
config.reload = Rails.env.development?
9+
10+
# where your swagger spec json will show up
11+
config.swagger_spec_base_path = "http://#{conf['host']}/swagger/api"
12+
# where your actual api is hosted from
13+
config.api_base_path = "http://#{conf['host']}/api/v1"
14+
end
15+
end

config/routes.rb

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,6 @@
11
TagsService::Application.routes.draw do
2-
# The priority is based upon order of creation: first created -> highest priority.
3-
# See how all your routes lay out with "rake routes".
4-
5-
# You can have the root of your site routed with "root"
6-
# root 'welcome#index'
7-
8-
# Example of regular route:
9-
# get 'products/:id' => 'catalog#view'
10-
11-
# Example of named route that can be invoked with purchase_url(id: product.id)
12-
# get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
13-
14-
# Example resource route (maps HTTP verbs to controller actions automatically):
15-
# resources :products
16-
17-
# Example resource route with options:
18-
# resources :products do
19-
# member do
20-
# get 'short'
21-
# post 'toggle'
22-
# end
23-
#
24-
# collection do
25-
# get 'sold'
26-
# end
27-
# end
28-
29-
# Example resource route with sub-resources:
30-
# resources :products do
31-
# resources :comments, :sales
32-
# resource :seller
33-
# end
34-
35-
# Example resource route with more complex sub-resources:
36-
# resources :products do
37-
# resources :comments
38-
# resources :sales do
39-
# get 'recent', on: :collection
40-
# end
41-
# end
42-
43-
# Example resource route with concerns:
44-
# concern :toggleable do
45-
# post 'toggle'
46-
# end
47-
# resources :posts, concerns: :toggleable
48-
# resources :photos, concerns: :toggleable
49-
50-
# Example resource route within a namespace:
51-
# namespace :admin do
52-
# # Directs /admin/products/* to Admin::ProductsController
53-
# # (app/controllers/admin/products_controller.rb)
54-
# resources :products
55-
# end
2+
mount SwaggerYard::Engine, at: "/swagger" unless Rails.env.test?
3+
564
scope 'api' do
575
scope 'v:version' do
586
resources :tags, except: [:new, :edit] do

0 commit comments

Comments
 (0)