Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Api #17

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open

Api #17

Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d4fe5f1
basic lapse api
May 30, 2014
f9d4953
updated paperclip to v 4.1.1
May 30, 2014
755dc2c
update models with association attribute methods
May 30, 2014
ad191d1
removed extra routes
May 30, 2014
c452bca
cleaned up extra controller methods in api
May 30, 2014
43fd908
added request spec @eprothro will you take a look something in rspec …
May 30, 2014
bdf287c
api covered with test
Jun 4, 2014
619d9d8
removed the puts breaking rspec progress bar
Jun 4, 2014
56a1aaa
removed controller specs
Jun 5, 2014
8036785
cleaned up request specs
Jun 5, 2014
9f84809
changed return method
Jun 5, 2014
bda1185
removed unused helper method
Jun 5, 2014
3633940
code review changes
Jun 5, 2014
ba4ea50
extended spec coverage on moments
Jun 5, 2014
1f8fe82
extended spec coverage on lapses
Jun 5, 2014
aa29ad5
updated lapse api errors
Jun 9, 2014
b569e4b
dirty research with yard
eprothro Jun 5, 2014
b67272c
first stab and purely static docs
eprothro Jun 6, 2014
1d3b47f
fixup
eprothro Jun 6, 2014
ee6137a
some quick and dirty styles
eprothro Jun 6, 2014
6158223
docs with js examples and helpers
eprothro Jun 9, 2014
0183f7d
hacky json layout appending root node
eprothro Jun 9, 2014
9873ae3
Merge pull request #18 from rocketmobile/api-doc
eprothro Jun 9, 2014
f3352f2
syntax highlighting
eprothro Jun 9, 2014
1ce6014
error refactor
Jun 9, 2014
963a974
style tweaks
eprothro Jun 9, 2014
03172b9
update error response format
Jun 10, 2014
f839691
change to json builder gem
Jun 10, 2014
151a23e
add specs for lapse patch with invalid id
Jun 10, 2014
3a4069b
remove erb template and replace with jbuilder
Jun 10, 2014
ad13699
add response status to doc example
eprothro Jun 10, 2014
12d3336
change 422 response to 400
Jun 10, 2014
4f7184b
url param inputs JS
eprothro Jun 10, 2014
7fefa15
update specs for error responses/active record error respose concern
Jun 10, 2014
d92356c
doc copy
eprothro Jun 10, 2014
9043acf
tweaked the param and full messages method
Jun 10, 2014
daa4de7
doc style tweak
eprothro Jun 10, 2014
3d566fb
spec tweak
Jun 10, 2014
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ gem 'will_paginate', '~> 3.0.5'
gem 'delayed_paperclip', '~> 2.6.1' # asyncronous image processing
gem 'devise', '~> 3.2.2'
gem 'hashie', '~> 2.0.5'
gem 'paperclip', '~> 3.5.0' # easy object attachment with s3 storage
gem 'paperclip', '~> 4.1.1'
gem 'ruby-progressbar', '~> 1.4.2'

# operations
Expand All @@ -62,6 +62,9 @@ gem 'pg', '~> 0.17'
# gem 'sidekiq-failures', '~> 0.3'
# gem 'sinatra', '~> 1.4.4'

# API
gem "apipie-rails", "~> 0.0.24"

group :development do
gem 'awesome_print'
gem 'better_errors'
Expand Down
11 changes: 7 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ GEM
thread_safe (~> 0.1)
tzinfo (~> 1.1)
addressable (2.3.6)
apipie-rails (0.0.24)
rails (>= 3.0.10)
arel (5.0.1.20140414130214)
asset_sync (1.0.0)
activemodel
Expand Down Expand Up @@ -251,7 +253,7 @@ GEM
nokogiri (1.6.2.1)
mini_portile (= 0.6.0)
orm_adapter (0.5.0)
paperclip (3.5.4)
paperclip (4.1.1)
activemodel (>= 3.0.0)
activesupport (>= 3.0.0)
cocaine (~> 0.5.3)
Expand Down Expand Up @@ -376,7 +378,7 @@ GEM
eventmachine (>= 1.0.0)
rack (>= 1.0.0)
thor (0.19.1)
thread_safe (0.3.3)
thread_safe (0.3.4)
tilt (1.4.1)
timers (1.1.0)
tins (1.3.0)
Expand All @@ -396,7 +398,7 @@ GEM
polyglot (>= 0.3.1)
typhoeus (0.6.8)
ethon (>= 0.7.0)
tzinfo (1.1.0)
tzinfo (1.2.0)
thread_safe (~> 0.1)
uglifier (2.1.2)
execjs (>= 0.3.0)
Expand All @@ -419,6 +421,7 @@ PLATFORMS
ruby

DEPENDENCIES
apipie-rails (~> 0.0.24)
asset_sync (~> 1.0.0)
awesome_print
aws-sdk (~> 1.30.0)
Expand Down Expand Up @@ -452,7 +455,7 @@ DEPENDENCIES
mailcatcher
modernizr-rails (~> 2.6.2.3)
newrelic_rpm (~> 3.7)
paperclip (~> 3.5.0)
paperclip (~> 4.1.1)
pg (~> 0.17)
pry-nav
pry-rails
Expand Down
46 changes: 46 additions & 0 deletions app/controllers/api/base_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
class Api::BaseController < ApplicationController
protect_from_forgery with: :null_session
layout false
respond_to :json

rescue_from Exception, with: :internal_error
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pierce-h is there a reason to rescue Exception instead of StandardError here?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, that should be standard error. I'll admit when it comes to catching errors and exceptions I am not 100% confident in my implementation. I have a rough idea of how I think it all should work.

rescue_from Timeout::Error, with: :timeout
rescue_from Rack::Timeout::Error, with: :timeout
rescue_from ActiveRecord::RecordNotFound, with: :not_found

protected
def bad_request
return render "api/errors/bad_request", status: 400
end
def conflict
return render "api/errors/conflict", status: 409
end
def forbidden
return render "api/errors/forbidden", status: 403
end
def internal_error(exception)
return render "api/errors/internal_error", status: 500
end
def invalid_resource
return render "api/errors/invalid_resource", status: 400
end
def invalid_version
return render "api/errors/invalid_version", status: 400
end
def not_found(exception)
return render "api/errors/not_found", status: 404
end
def throttled
return render "api/errors/throttled", status: 429
end
def timeout(exception)
# If the timeout occured during the middle of a persistence layer query, we need to cancel the
# query so that a "closed connection" exception isn't raised in the middle
# of the next request that executes a query
ActiveRecord::Base.connection.reset!
return render "api/errors/timeout", status: 503
end
def unauthenticated
return render "api/errors/unauthenticated", status: 401
end
end
46 changes: 46 additions & 0 deletions app/controllers/api/v1/lapses_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
class Api::V1::LapsesController < Api::BaseController
before_action :set_lapse, only: [:show, :update, :destroy]

def index
@lapses = Lapse.all
end

def show
render 'api/v1/lapses/show', status: 200
end

def create
@lapse = Lapse.new(lapse_params)
if @lapse.save
render 'api/v1/lapses/show', status: 201
else
@resourceful_errors = @lapse.errors.full_messages
render 'api/errors/resourceful_error', status: 422
end
end

def update
if @lapse.update(lapse_params)
render 'api/v1/lapses/show', status: 200
else
@resourceful_errors = @lapse.errors.full_messages
render 'api/errors/resourceful_error', status: 422
end
end

def destroy
@lapse.destroy
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be a destroy! so RecordNotDestroyed would be raised on an error destroying?

render json: { message: success_message }, status: 200
end

private
def set_lapse
@lapse = Lapse.find(params[:id])
end

def lapse_params
{
name: params[:name]
}
end
end
41 changes: 41 additions & 0 deletions app/controllers/api/v1/moments_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
class Api::V1::MomentsController < Api::BaseController
before_action :set_moment, only: [:show, :destroy]
before_action :current_lapse, only: [:create]

def index
@moments = Moment.all
end

def show
render 'api/v1/moments/show', status: 200
end

def create
@moment = Moment.new(moment_params)
@moment.lapse = @lapse
if @moment.save
render 'api/v1/moments/show', status: 201
else
@resourceful_errors = @moment.errors.full_messages
render 'api/errors/resourceful_error', status: 422
end
end

def destroy
@moment.destroy
render json: { message: success_message }, status: 200
end

private
def set_moment
@moment = Moment.find(params[:id])
end

def current_lapse
@lapse = Lapse.find(params[:lapse_id])
end

def moment_params
{ active: params[:active] }
end
end
2 changes: 1 addition & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class ApplicationController < ActionController::Base
include ResourcefulMessages
protect_from_forgery
protect_from_forgery with: :exception

if Rails.application.config.consider_all_requests_local == false
# note: these are rescued from bottom (most specific) to top (least)
Expand Down
7 changes: 7 additions & 0 deletions app/models/lapse.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Lapse < ActiveRecord::Base

has_many :moments, dependent: :destroy

validates :name, presence: true

end
11 changes: 11 additions & 0 deletions app/models/moment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Moment < ActiveRecord::Base

belongs_to :lapse
has_attached_file :image, styles: { medium: "300x300>", thumb: "100x100>" }

validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
# validates :image, presence: true
validates :lapse, presence: true
validates :active, presence: true

end
2 changes: 2 additions & 0 deletions app/views/api/errors/bad_request.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object false
node(:error) { I18n.t(:bad_request, scope: "api.errors") }
2 changes: 2 additions & 0 deletions app/views/api/errors/conflict.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object false
node(:error) { I18n.t(:conflict, scope: "api.errors") }
2 changes: 2 additions & 0 deletions app/views/api/errors/forbidden.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object false
node(:error) { I18n.t(:forbidden, scope: "api.errors") }
2 changes: 2 additions & 0 deletions app/views/api/errors/internal_error.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object false
node(:error) { I18n.t(:internal_error, scope: "api.errors") }
2 changes: 2 additions & 0 deletions app/views/api/errors/invalid_resource.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object false
node(:error) { I18n.t(:invalid_resource, scope: "api.errors") }
2 changes: 2 additions & 0 deletions app/views/api/errors/invalid_version.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object false
node(:error) { I18n.t(:invalid_version, scope: "api.errors") }
2 changes: 2 additions & 0 deletions app/views/api/errors/not_found.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object false
node(:error) { I18n.t(:not_found, scope: "api.errors") }
2 changes: 2 additions & 0 deletions app/views/api/errors/resourceful_error.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object false
node(:error) { @resourceful_errors }
2 changes: 2 additions & 0 deletions app/views/api/errors/throttled.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object false
node(:error) { I18n.t(:throttled, scope: "api.errors") }
Empty file.
2 changes: 2 additions & 0 deletions app/views/api/errors/unauthenticated.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object false
node(:error) { I18n.t(:unauthenticated, scope: "api.errors") }
2 changes: 2 additions & 0 deletions app/views/api/v1/lapses/index.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
collection @lapses
attributes :id, :name
2 changes: 2 additions & 0 deletions app/views/api/v1/lapses/show.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object @lapse
attributes :id, :name
2 changes: 2 additions & 0 deletions app/views/api/v1/moments/index.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
collection @moments
attributes :id, :active
2 changes: 2 additions & 0 deletions app/views/api/v1/moments/show.rabl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
object @moment
attributes :id, :active
14 changes: 14 additions & 0 deletions config/initializers/apipie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Apipie.configure do |config|
config.app_name = "RailsBaseApp"
config.app_info = "This is an example api."
config.copyright = ""
config.api_base_url = "1"
config.api_controllers_matcher = "#{Rails.root}/app/controllers/api/**/*.rb"
config.ignored_by_recorder = %w[]
config.doc_base_url = "/"
config.use_cache = Rails.env.production?
config.validate = false
config.force_dsl = true
config.reload_controllers = Rails.env.development?
config.default_version = "v1"
end
28 changes: 28 additions & 0 deletions config/initializers/rabl_init.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# config/initializers/rabl_init.rb
require 'rabl'
Rabl.configure do |config|
# Commented as these are defaults
# config.cache_all_output = false
# config.cache_sources = Rails.env != 'development' # Defaults to false
# config.cache_engine = Rabl::CacheEngine.new # Defaults to Rails cache
# config.perform_caching = false
# config.escape_all_output = false
# config.json_engine = nil # Class with #dump class method (defaults JSON)
# config.msgpack_engine = nil # Defaults to ::MessagePack
# config.bson_engine = nil # Defaults to ::BSON
# config.plist_engine = nil # Defaults to ::Plist::Emit
# config.include_json_root = true
# config.include_msgpack_root = true
# config.include_bson_root = true
# config.include_plist_root = true
# config.include_xml_root = false
# config.include_child_root = true
# config.enable_json_callbacks = false
# config.xml_options = { :dasherize => true, :skip_types => false }
# config.view_paths = []
# config.raise_on_missing_attribute = true # Defaults to false
# config.replace_nil_values_with_empty_strings = true # Defaults to false
# config.replace_empty_string_values_with_nil_values = true # Defaults to false
# config.exclude_nil_values = true # Defaults to false
# config.exclude_empty_values_in_collections = true # Defaults to false
end
13 changes: 12 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.

en:
hello: "Hello world"
api:
errors:
bad_request: "The data given to this server does not meet our criteria."
conflict: "The request conflicts with an existing resource."
forbidden: "The action you requested was forbidden."
internal_error: "Something has gone wrong."
invalid_resource: "The current resource was deemed invalid."
invalid_version: "This action is not available in the given version of the api."
not_found: "The requested resource could not be found."
throttled: "You have gone over your allotted amount of requests and have been throttled."
timeout: "A timeout has occurred."
unauthenticated: "This action requires authentication to continue."

actioncontroller:
# override these default messages by defining
Expand Down
12 changes: 11 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
require 'constraints/production_path_constraint'
require 'constraints/api_constraints'

RailsBaseApp::Application.routes.draw do

# api*
constraints subdomain: /\Aapi/ do
# apipie
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this have to do with apipie?

scope module: "api/v1", constraints: ApiConstraints.new(version: 1, default: true) do
resources :lapses, only: [:index, :show, :create, :update, :destroy], shallow: true do
resources :moments, only: [:index, :show, :create, :destroy]
end
end
end

root to: 'pages#home'
get 'timeout', to: 'pages#timeout'

# catch rest of production paths with 404 page
match '*path', to: 'pages#not_found', via: [:get, :post],
constraints: ProductionPathConstraint.new

end
8 changes: 8 additions & 0 deletions db/migrate/20140529205817_create_lapses.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class CreateLapses < ActiveRecord::Migration
def change
create_table :lapses do |t|
t.string :name
t.timestamps
end
end
end
8 changes: 8 additions & 0 deletions db/migrate/20140529205934_create_moments.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class CreateMoments < ActiveRecord::Migration
def change
create_table :moments do |t|
t.integer :lapse_id
t.timestamps
end
end
end
Loading