Last active
August 15, 2020 02:34
-
-
Save jgaskins/6484e8037a9f0896aff9b54e7fb736a5 to your computer and use it in GitHub Desktop.
API app with Roda
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'authentication' | |
class MyApp < Roda | |
include Authentication | |
plugin :json | |
route do |r| | |
r.on('docs') { r.run Docs } | |
authenticate! ### EVERYTHING BELOW THIS LINE IS AUTHENTICATED ### | |
r.on('users') { r.run Users } | |
r.on('groups') { r.run Groups } | |
# etc | |
end | |
def authenticate! | |
unauthenticated! unless current_user | |
end | |
def unauthenticated! | |
request.halt :unauthenticated, error: 'Must be authenticated' | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This mixin depends on the including class defining an `env` method | |
# that returns a Rack env hash. | |
module Authentication | |
def current_user | |
# We memoize things onto the `env` hash instead of on instances | |
# because of how it's based on the request rather than the class | |
# of the application. We need it to work when passing the request | |
# between app classes. | |
env[:current_user] ||= AuthToken | |
.find_by(id: auth_token) | |
&.user | |
end | |
def auth_token | |
env[:auth_token] ||= env['HTTP_AUTHORIZATION'] | |
.to_s | |
.sub(/\ABearer /, '') | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'primalize' | |
class Route < Roda | |
# Automatically JSONify hashes and serializers. I prefer the primalize | |
# gem for JSON serializers, so we explicitly mention that the response | |
# serializers should be included for automatic JSON conversion, as well. | |
plugin :json, classes: [Hash, Primalize::Many] | |
# If we receive a JSON request body, parse it | |
plugin :json_parser, parser: -> str { JSON.parse str, symbolize_names: true } | |
# By default, Roda only provides support for GET and POST because | |
# they're the only ones that HTML forms support | |
plugin :all_verbs | |
# This plugin lets you do things like: | |
# response.status = :forbidden | |
# instead of: | |
# response.status = 403 | |
plugin :symbol_status | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Inherit from our application route superclass | |
class Users < Route | |
route do |r| | |
# This is the root of *this* handler after being handed off from a parent route or the main app | |
r.root do | |
users = User.includes(:groups) | |
UsersResponse.new(users: users, groups: users.map(&:groups).uniq) | |
end | |
# Kinda weird that this is how Roda wants you to post to the root path, but it's fine | |
r.post '' do | |
user = User.new(r.params[:user]) | |
if user.save | |
UserResponse.new(user: user, groups: user.groups) | |
else | |
response.status = :unprocessable_entity | |
{ errors: user.errors } | |
end | |
end | |
r.on String do |user_id| | |
user = User.find(user_id) | |
r.get do | |
UserResponse.new(user: user, groups: user.groups) | |
end | |
r.put do | |
user.update! r.params[:user] | |
UserResponse.new(user: user, groups: user.groups) | |
end | |
r.delete do | |
user.destroy | |
{} | |
end | |
end | |
end | |
class UserSerializer < Primalize::Single | |
attributes( | |
id: string, | |
name: string, | |
email: string, | |
group_ids: array(string), | |
) | |
end | |
class UsersResponse < Primalize::Many | |
attributes( | |
users: enumerable(UserSerializer), | |
groups: enumerable(GroupSerializer), | |
) | |
end | |
class UserResponse < Primalize::Many | |
attributes( | |
user: UserSerializer, | |
groups: enumerable(GroupSerializer), | |
) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment