Rails and Rust? This is what I have been waiting for. I don’t know anything about Rust
, but I do know Ruby
and Rails
.
I have been looking for an opportunity to learn Rust
and again this is what I have been waiting for. I think that Loco
is a great opportunity to learn the hard way by doing something I am familiar with.
How hard can it be? Let’s find out…
Installation
Reading the getting started guide it reads:
cargo install loco-cli
cargo install sea-orm-cli # Only when DB is needed
I think that cargo is Rust’s package manager. So…
brew info cargo
Error: No available formula with the name "cargo". Did you mean cargo-c, argo, cairo, carton, cdargs or charge?
cargo is part of the rust formula:
brew install rust # BINGO!
I was right.
Now. I use asdf
to version my languages and asdf-rust exists, so I am going to continue with that. But brew install rust
is a great alternative.
asdf plugin-add rust https://github.com/asdf-community/asdf-rust.git
asdf list
>.... rust
No versions installed
asdf list-all rust
>... 1.80.0
1.80.1
1.81.0
asdf install rust latest
# create a directory at your pleasure and cd into it
asdf local rust latest
cargo --version
> cargo 1.81.0 (2dbb1af80 2024-08-20)
Fantastic. Continuing with loco’s installation now that we have rust set up.
cargo install loco-cli
> ... installing and compiling a bunch of dependencies ...
> warning: be sure to add `.cargo/bin` to your PATH to be able to run the installed binaries # got this important warning!!!!
cargo install sea-orm-cli # Only when DB is needed
...
Add it to my path:
echo 'export PATH="/Users/me/.cargo/bin:$PATH"' >> ~/.zshrc
Everything ready for a new app.
Creating a new app
This were my choices:
loco new
✔ ❯ App name? · something
✔ ❯ What would you like to build? · SaaS app (with DB and user auth)
✔ ❯ Select a DB Provider · Sqlite
✔ ❯ Select your background worker type · Async (in-process tokyo async tasks)
✔ ❯ Select an asset serving configuration · Client (configures assets for frontend serving)
🚂 Loco app generated successfully in:
/Users/me/LocoRS/something
- assets: You've selected `clientside` for your asset serving configuration (remember to build your frontend in `frontend/`)
Starting the app:
cargo loco start
> ... more compilation and installation of stuff...
> Error: Message("one of the static path are not found, Folder `frontend/dist` fallback: `frontend/dist/index.html`") # Beautiful message
What happened is that since I wanted my project to interact with a frontend app and there is none… well, it failed. Fixing that!
cd frontend
ls -lh
> # What???
There is something there… a React app with rsbuild
and things I don’t know.
asdf local nodejs latest # inside the frontend folder
npm install
npm run build # damn... that was REALLY fast
… and now we can go back where we were:
cargo loco start # all good and running
# in other terminal
curl localhost:5150/_ping
{"ok":true}% # Nice!
curl localhost:5150/_health
{"ok":true}% # Double nice!
Coding a new app
So on this part I want to focus on the potential similarities between Loco
and Rails
. As mentioned I don’t know anything about Rust
.
I am going to “tweak” the commands because I don’t like to copy/paste everything in the tutorial to forget it right away.
cargo loco generate controller home
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.55s
Running `target/debug/something-cli generate controller home`
Error: Message("Error: One of `kind`, `htmx`, `html`, or `api` must be specified.")
Hmmm…
cargo loco generate controller --help
...Actions
Options:
-k, --kind <KIND> The kind of controller actions to generate [possible values: api, html, htmx]
--htmx Use HTMX controller actions
--html Use HTML controller actions
--api Use API controller actions
-e, --environment <ENVIRONMENT> Specify the environment [default: development]
-h, --help Print help
-V, --version Print version
Again and the comparison:
Loco
cargo loco generate controller home --api
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s
Running `target/debug/something-cli generate controller home --api`
added: "src/controllers/home.rs"
injected: "src/controllers/mod.rs"
injected: "src/app.rs"
added: "tests/requests/home.rs"
injected: "tests/requests/mod.rs"
* Controller `Home` was added successfully.
* Tests for controller `Home` was added successfully. Run `cargo run test`.
Rails
be rails g controller home
create
invoke erb
create app/views/home
invoke test_unit
create test/controllers/home_controller_test.rb
invoke helper
create app/helpers/home_helper.rb
invoke test_unit
Without checking the Loco
files I can infer that what happened is pretty similar to what happened in Rails
. Controller file, some tests and the configuration of the home
route to its controller.
// cat src/controllers/home.rs
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::unnecessary_struct_initialization)]
#![allow(clippy::unused_async)]
// Guessing these are dependencies
use loco_rs::prelude::*;
use axum::debug_handler;
// index
#[debug_handler]
pub async fn index(State(_ctx): State<AppContext>) -> Result<Response> {
format::empty() // the response
}
// May be routes configuration. What is in 'routes.rb'
pub fn routes() -> Routes {
Routes::new()
.prefix("homes/") // "home" was pluralized. An inflection apparently
.add("/", get(index))
}
Rails counterpart:
# cat app/controllers/home_controller.rb
class HomeController < ApplicationController
end
The Rails
controller by default doesn’t have any route. I like that Loco
does. It helps me to get started…
Without restarting Loco
’s the server:
curl localhost:5150/homes
<!doctype html><html><head><title>Loco SaaS Starter</title><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><script defer="defer" src="/static/js/lib-polyfill.aebdf906.js"></script><script defer="defer" src="/static/js/lib-react.ae7929d9.js"></script><script defer="defer" src="/static/js/index.6c1b7b37.js"></script><link href="/static/css/index.0441752a.css" rel="stylesheet"></head><body><div id="root"></div></body></html>%
After:
<h1 class="text-3xl font-bold mb-4">Welcome to Loco!</h1>
<p class="mb-12">
It looks like you've just started your Loco server, and this is the fallback
page displayed when a route doesn't exist.
</p>
<div class="text-left space-y-4">
<div>
<h3 class="text-xl font-semibold">Remove this Fallback Page</h3>
<p>
To remove this fallback page, adjust the configuration in your
<code class="bg-gray-200 rounded px-2 py-1">config/development.yaml</code>
file. Look for the
<code class="bg-gray-200 rounded px-2 py-1">fallback:</code> setting and
disable or customize it.
</p>
</div>
<div>
<h3 class="text-xl font-semibold">Scaffold Your Application</h3>
<p>Use the Loco CLI to scaffold your application:</p>
<pre
class="my-1"
><code class="bg-gray-200 rounded px-2 py-1">cargo loco generate scaffold movie title:string</code></pre>
<p>This creates models, controllers, and views.</p>
</div>
</div>
It seems it doesn’t like that I used cUrl
, but when opened on the browser. It is just an empty page.
Edited the code with:
pub async fn index(State(_ctx): State<AppContext>) -> Result<Response> {
format::text("A future rustacean!")
}
and got that on the browser.
Following a little bit more the guide added this code:
pub async fn echo(req_body: String) -> String {
req_body
}
pub fn routes() -> Routes {
Routes::new()
.prefix("homes/")
.add("/", get(index))
.add("/echo", post(echo))
}
cUrl
ing it:
curl -v -d "{'data': 'Loco rocks'}" localhost:5150/homes/echo
< HTTP/1.1 200 OK
< content-type: text/plain; charset=utf-8
< content-length: 22
< vary: origin, access-control-request-method, access-control-request-headers
< access-control-allow-origin: *
< access-control-expose-headers: *
< content-security-policy: default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src 'unsafe-inline' 'self' https:; style-src 'self' https: 'unsafe-inline'
< strict-transport-security: max-age=631138519
< x-content-type-options: nosniff
< x-download-options: noopen
< x-frame-options: sameorigin
< x-permitted-cross-domain-policies: none
< x-xss-protection: 0
< x-powered-by: loco.rs
< x-request-id: fd9de454-4e11-4249-b0f5-435658c4bc1a
< date: Wed, 09 Oct 2024 20:20:09 GMT
<
* Connection #0 to host localhost left intact
{'data': 'Loco rocks'}%
Neat!
Conclusion
This was enough for me to gain a LOT of interest on this project. Knowing the structure will be easy for me to find the correct files to achieve what I want, I believe.
In terms of coding:
... -> Result<Response>
// plus
format::text("A future rustacean!")
// and
-> String {
... req_body
Just tell there are different formatters and ways manipulate the response body with “types”. This file is just pointing me in the right direction of where and what to look. I am going to continue working with this framework… I am definitely convinced that it will teach me things.
Forking!