Skip to content

Commit

Permalink
feat: improve documentation and remove deprecations
Browse files Browse the repository at this point in the history
  • Loading branch information
stakach committed May 17, 2023
1 parent 36bae1d commit 4ef707e
Show file tree
Hide file tree
Showing 35 changed files with 470 additions and 343 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ jobs:
stable: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Install crystal
uses: crystal-lang/install-crystal@v1.5.3
uses: crystal-lang/install-crystal@v1
with:
crystal: ${{ matrix.crystal }}

Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Deploy docs

on:
push:
branches: [ master ]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: crystal-lang/install-crystal@v1
with:
crystal: latest
- name: "Install shards"
run: shards install
- name: "Generate docs"
run: crystal docs
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs
195 changes: 1 addition & 194 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,197 +87,4 @@ class Books < Application
end
```

The older style usage, below, is still functional and may be preferrable.
However this newer style, inspired by Athena is less error prone, simpler to test and easier for a script to generate documentation. Hence we recommend using it.

## Usage

Supports many of the helpers that Rails provides for controllers. i.e. before and after filters

```crystal
require "action-controller"
# Abstract classes don't generate routes
abstract class Application < ActionController::Base
before_action :ensure_authenticated
rescue_from DivisionByZeroError do |error|
render :bad_request, text: error.message
end
private def ensure_authenticated
render :unauthorized unless cookies["user"]
end
end
# Full inheritance support (concrete classes generate routes)
class Books < Application
# this is automatically configured based on class name and namespace
# it can be overriden here
base "/books"
# route => "/books/"
def index
book = params["book"]
redirect_to Books.show(id: book) if book
render json: ["book1", "book2"]
end
# route => "/books/:id"
def show
# Using the Accepts header will select the appropriate response
# If the Accepts header isn't present it defaults to the first in the block
# None of the code is executed (string interpolation, xml builder etc)
# unless it is to be sent to the client
respond_with do
text "the ID was #{params["id"]}"
json({id: params["id"]})
xml do
XML.build(indent: " ") do |xml|
xml.element("id") { xml.text params["id"] }
end
end
end
end
# Websocket support
# route => "/books/realtime"
ws "/realtime", :realtime do |socket|
SOCKETS << socket
socket.on_message do |message|
SOCKETS.each { |socket| socket.send "Echo back from server: #{message}" }
end
socket.on_close do
SOCKETS.delete(socket)
end
end
SOCKETS = [] of HTTP::WebSocket
end
```


### Code Expansion

```crystal
require "action-controller"
class MyResource < ActionController::Base
base "/resource"
before_action :check_id, only: show
def index
render text: "index"
end
def show
render json: {id: params["id"]}
end
put "/custom/route", :route_name do
render :accepted, text: "simple right?"
end
private def check_id
if params["id"] == "12"
redirect "/"
end
end
end
```

Results in the following high performance code being generated:

```crystal
class MyResource < ActionController::Base
getter render_called
getter action_name : Symbol
getter params : HTTP::Params
getter cookies : HTTP::Cookies
getter request : HTTP::Request
getter response : HTTP::Server::Response
def initialize(context : HTTP::Server::Context, params : Hash(String, String), @action_name)
@render_called = false
@request = context.request
@response = context.response
@cookies = @request.cookies
@params = @request.query_params
# Add route params to the HTTP params
# giving preference to route params
params.each do |key, value|
values = @params.fetch_all(key) || [] of String
values.unshift(value)
@params.set_all(key, values)
end
end
def index
@render_called = true
ctype = @response.headers["Content-Type"]?
@response.content_type = "text/plain" unless ctype
@response.print("index")
return
end
def show
@render_called = true
ctype = @response.headers["Content-Type"]?
@response.content_type = "application/json" unless ctype
output = {id: params["id"]}
if output.is_a?(String)
@response.print(output)
else
@response.print(output.to_json)
end
return
end
def route_name
@render_called = true
@response.status_code = 202
ctype = @response.headers["Content-Type"]?
@response.content_type = "text/plain" unless ctype
@response.print("simple right?")
return
end
private def check_id
if params["id"] == "12"
@response.status_code = 302
@response.headers["Location"] = "/"
@render_called = true
end
end
def self.draw_routes(router)
# Supports inheritance
super(router)
# Implement the router.cr compatible routes:
router.get "/resource/" do |context, params|
instance = MyResource.new(context, params)
instance.index
context
end
router.get "/resource/:id" do |context, params|
instance = MyResource.new(context, params)
instance.check_id unless instance.render_called
if !instance.render_called
instance.show
end
context
end
router.put "/resource/custom/route" do |context, params|
instance = MyResource.new(context, params)
instance.route_name
context
end
end
end
```
For more details on usage, see [the documentation](https://spider-gazelle.net/).
2 changes: 1 addition & 1 deletion shard.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: action-controller
version: 5.6.4
version: 6.0.0
crystal: ">= 1.5.0"

dependencies:
Expand Down
2 changes: 1 addition & 1 deletion spec/response_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ describe "end to end requests and responses" do

it "should list routes" do
BobJane.__route_list__.should eq([
{"BobJane", :index, :get, "/bob_jane/"},
{"BobJane", :redirect, :get, "/bob_jane/redirect"},
{"BobJane", :param_id, :get, "/bob_jane/params/:id"},
{"BobJane", :deep_show, :get, "/bob_jane/params/:id/test/:test_id"},
Expand All @@ -171,7 +172,6 @@ describe "end to end requests and responses" do
{"BobJane", :modified_session, :get, "/bob_jane/modified_session"},
{"BobJane", :modified_session_with_redirect, :get, "/bob_jane/modified_session_with_redirect"},
{"BobJane", :urlencoded, :get, "/bob_jane/urlencoded"},
{"BobJane", :index, :get, "/bob_jane/"},
])
end

Expand Down
28 changes: 12 additions & 16 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -178,18 +178,15 @@ end

# Testing ID params
class Container < ActionController::Base
id_param :container_id

def show
get "/:container_id", :show do
render text: "got: #{params["container_id"]}"
end
end

class ContainerObjects < ActionController::Base
base "/container/:container_id/objects"
id_param :object_id

def index
get "/", :index do
respond_with do
json do
data = {"id" => 1}
Expand All @@ -198,7 +195,7 @@ class ContainerObjects < ActionController::Base
end
end

def show
get "/:object_id", :show do
render text: "#{params["object_id"]} in #{params["container_id"]}"
end
end
Expand All @@ -207,7 +204,7 @@ class TemplateOne < ActionController::Base
template_path "./spec/views"
layout "layout_main.ecr"

def index
get "/", :index do
data = client_ip # ameba:disable Lint/UselessAssign
if params["inline"]?
render html: template("inner.ecr")
Expand All @@ -216,7 +213,7 @@ class TemplateOne < ActionController::Base
end
end

def show
get "/:id", :show do
data = params["id"] # ameba:disable Lint/UselessAssign
if params["inline"]?
render html: partial("inner.ecr")
Expand All @@ -234,7 +231,7 @@ end
class TemplateTwo < TemplateOne
layout "layout_alt.ecr"

def index
get "/", :index do
data = 50 # ameba:disable Lint/UselessAssign
render template: "inner.ecr"
end
Expand All @@ -246,8 +243,7 @@ class BobJane < ActionController::Base
before_action :modify_session, only: :modified_session
add_responder "text/plain" { |io, result| result.to_s(io) }

# Test default CRUD
def index
get "/", :index do
session["hello"] = "other_route"
render text: "index"
end
Expand Down Expand Up @@ -305,7 +301,7 @@ end
class Users < ActionController::Base
base "/users"

def index
get "/", :index do
head :unauthorized if request.headers["Authorization"]? != "X"

if params["verbose"] = "true"
Expand Down Expand Up @@ -340,13 +336,13 @@ class HelloWorld < Application

before_action :render_early, only: :update

def show
get "/:id", :show do
raise "set_var was set!" if @me
res = 42 // params["id"].to_i
render text: "42 / #{params["id"]} = #{res}"
end

def index
get "/", :index do
respond_with do
text "set_var #{@me}"
json({set_var: @me})
Expand All @@ -371,15 +367,15 @@ class HelloWorld < Application
render text: "var is #{@me}"
end

def update
patch "/:id", :update do
render :accepted, text: "Thanks!"
end

private def render_early
render :forbidden, text: "Access Denied"
end

def destroy
delete "/:id", :destroy do
head :accepted
end

Expand Down
1 change: 0 additions & 1 deletion src/action-controller.cr
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@ alias AC = ActionController
require "./action-controller/router"
require "./action-controller/errors"
require "./action-controller/base"
require "./action-controller/file_handler"
require "./action-controller/log_handler"
require "./action-controller/error_handler"
Loading

0 comments on commit 4ef707e

Please sign in to comment.