Runcobo is a general purpose framework built on Crystal.
runcobo [<commands>...] [<arguments>...]
Commands:
routes - Print all routes of the app.
version - Print the current version of the runcobo.
help - Print usage synopsis.
Options:
-h, --help Print usage synopsis.
-v, --version Print the current version of the runcobo.lib/ # Library
src/
main.cr # Entry file
actions/ # Actions Directory
...
assets/ # Assets Directory
views/ # Views Directory
layouts/ # Layouts directory
...
models/ # Models Directory
...
shards.yml # The packages congfiuration file
shards.lock # The lock file for packages congfiuration file
Keep in this architecture to use layouts macro, render macro.
MVC (Model-View-Controller)
- Simple Design must be simple, both in implementation and interface.
- Intuitive Design must be intuitive.
- Consistent Design must be consistent.
Crystal is a language for humans and computers.
Crystal is a type safe, compiled language inspired by the simplicity of Ruby. Type safety means more errors are caught by the compiler during development, so you can be more confident about your code working in production.
See https://crystal-lang.org/install/ to install Crystal.
You can install Runcobo from sources. Other installations are working on.
curl -L https://github.com/runcobo/runcobo/archive/stable.zip --output runcobo.zip
unzip runcobo.zip
cd runcobo-stable/
sudo make install
runcobo -v1.Init a Crystal project.
crystal init app demo && cd demo2.Add the dependency to your shard.yml and run shards install.
dependencies:
runcobo:
github: runcobo/runcobo3.Write down the following code in src/demo.cr.
require "runcobo"
class Api::V1::Add < BaseAction
get "/api/v1/add"
query NamedTuple(a: Int32, b: Int32)
call do
sum = params[:a] + params[:b]
render_plain sum.to_s
end
end
Runcobo.start4.Run server.
crystal src/demo.cr5.Send request.
curl "http://0.0.0.0:3000/api/v1/add?a=1&b=2"6.Auto restart server.
# Use nodemon to watch file changed and auto restart server.
sudo npm install -g nodemon
nodemon -e "cr,water,jbuilder,yml" --exec "crystal run" src/demo.crRuncobo declares routes in every Actions. If you access the route, it will run into related Action.
In this way, you can know the routes of current Action quickly.
You can declare RESTful routes as you want or not.
In my personal view, RESTful is too abstact when you were far way from a CRUD system like Backend Management System.
When writing API, I believe urls should be named like functions, not objects or resources.
When writing a Backend Management System, enjoy RESTful.
Runcobo declares routes by following methods: get, post, put, patch, delete, options, head.
An Action can bind to one or more routes.
class Example < BaseAction
get "/books"
get "/books/:id"
post "/books"
put "/books/:id"
patch "/books/:id"
delete "/books/:id"
options "/books/:id"
head "/books/:id"
call do
render_plain "Hello World"
end
endURL Params can be declared in the route like /add/:apple_count/:banana_count. And then you should use url method to declare the type of URL params.
class Example < BaseAction
get "/add/:apple_count/:banana_count"
url NamedTuple(apple_count: Int32, banana_count: Int32)
call do
sum = params[:apple_count] + params[:banana_count]
render_plain sum.to_s
end
endIf you need a route with custom HTTP method, such as LINK, UNLINK, FIND or PURGE, then you can use route method to declare it.
class Example < BaseAction
route "LINK", "/books/:id"
call do
render_plain "Hello World"
end
endRuncobo use one action one file.
class BeforeExample < BaseAction
before required_login
def required_login
Runcobo::Log.info { "Required Login" }
end
get "/before_example"
call do
render_plain "Hello World!"
end
endclass AfterExample < BaseAction
after log_params
def log_params
Runcobo::Log.info { "#{params}" }
end
get "/after_example"
call do
render_plain "Hello World!"
end
endclass BaseAction
before required_login
def required_login
Runcobo::Log.info { "Required Login" }
end
end
class SkipExample < BaseAction
skip required_login
get "/skip_example"
call do
render_plain "Hello World!"
end
endParams in Runcobo are type-safe.
- First, declare what params you expect and what type you expect by following methods:
url,query,formandjson. - Next, Runcobo parses request and wraps params to a local variable called
params. - Third, use
paramsvariable to get or set request params.
class UrlExample < BaseAction
get "/url_example/:a/:b"
url NamedTuple(a: Int32, b: Int32)
call do
sum = params[:a] + params[:b]
render_plain sum.to_s
end
endclass QueryExample < BaseAction
get "/query_example"
query NamedTuple(a: Int32, b: Int32)
call do
sum = params[:a] + params[:b]
render_plain sum.to_s
end
endclass FormExample < BaseAction
post "/form_example"
form NamedTuple(a: Int32, b: Int32)
call do
sum = params[:a] + params[:b]
render_plain sum.to_s
end
endclass JsonExample < BaseAction
post "/json_example"
json NamedTuple(a: Int32, b: Int32)
call do
sum = params[:a] + params[:b]
render_plain sum.to_s
end
endYou can declare various kinds of params in a action. If params are in same key, they will be merged in following order:
Query Params < Form Params < JSON Params < Url Params
class WaterExample < BaseAction
get "/water_example"
call do
render_water "examples/index"
end
endclass PlainExample < BaseAction
get "/plain_example"
call do
render_plain "Hello World!"
end
endclass BodyExample < BaseAction
get "/body_example"
call do
render_body "Hello World!"
end
endclass JbuilderExample < BaseAction
get "/jbuilder_example"
call do
render_jbuilder "examples/index"
end
endRuncobo renders JSON by Jbuilder, renders HTML by Water. Jbuilder is a template engine designed for json using plain Crystal. Water is a template engine designed for html using plain Crystal.
All methods or variables defined in the action are available in the views. This is because the views are compiled in the same scope as the action.
You can override the default layout conventions in your actions by using the layout declaration. For example:
class BaseAction
layout "application"
#...
endRuncobo renders partial view by build-in read_file macro. There's no magic about partial view.
For example,
src/views/books/index.jbuilder
json.array! "books", books do |json, book|
{{ read_file("src/views/books/_base_book.jbuilder").id }}
endsrc/views/books/_base_book.jbuilder
json.book_id book.id
json.author book.author
json.name book.name
json.published_at book.published_atsrc/controllers/books/index.cr
class Books::Index < BaseAction
get "/books"
call do
books = Book.all
render_jbuilder "books/index"
end
endsrc/views/books/index.jbuilder
json.array! "books", books do |json, book|
json.book_id book.id
json.author book.author
json.name book.name
json.published_at book.published_at
endThen, output a JSON string.
{
"books": [{
"book_id": 1,
"author": "David",
"name": "Crystal Programming",
"published_at": "2020-08-08T20:00:00+00:00"
}]
}src/views/books/index.jbuilder
json.array! "books", books do |json, book|
{{ read_file("src/views/books/_base_book.jbuilder").id }}
endsrc/views/books/_base_book.jbuilder
json.book_id book.id
json.author book.author
json.name book.name
json.published_at book.published_atsrc/controllers/books/index.cr
class Books::Index < BaseAction
get "/books"
call do
books = Book.all
render_water "books/index"
end
endsrc/views/books/index.water
table %|class="table table-hover"| {
thead {
tr {
th "ID"
th "Author"
th "Name"
th "Published At"
}
}
tbody {
books.each do |book|
tr {
td book.id
td book.author
td book.name
td book.published_at
}
end
}
}Then, output a HTML string.
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Author</th>
<th>Name</th>
<th>Published At</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>David</td>
<td>Crystal Programming</td>
<td>2020-08-02 14:07:41 +08:00</td>
</tr>
</tbody>
</table>src/views/books/index.jbuilder
{{ read_file("src/views/books/table.water").id }}src/views/books/table.water
table %|class="table table-hover"| {
thead {
tr {
th "ID"
th "Author"
th "Name"
th "Published At"
}
}
tbody {
books.each do |book|
tr {
td book.id
td book.author
td book.name
td book.published_at
}
end
}
}You can see all ORMs from [awesome-crystal].(https://github.com/veelenga/awesome-crystal#ormodm-extensions)
Runcobo prefers to use jennifer.cr , you can check its docs and api.
jennifer.cr is an Active Record pattern implementation with flexible query chainable builder and migration system.
- Fork it (https://github.com/runcobo/runcobo/fork)
- Create your feature branch (
git checkout -b my-new-feature) - Write and execute specs (
crystal spec) and formatting checks (crystal tool format) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
- Shootingfly - creator and maintainer