Ruby on Roda API

MaJeD BoJaN
7 min readDec 17, 2019

--

Roda is a lightweight and productive framework for building web applications using Ruby. as mentioned in Roda’s website ,Roda is designed to be simple, both internally and externally, reducing cognitive overhead. Roda uses a routing tree which means At any point during routing, it allows you to operate on the current request.

Let’s stop praising Roda and start building some useful things.

Before you go through this article you need to have some knowledge about ruby.

Getting started

Installing Roda

$ gem install bundler
$ gem install roda

Unfortunately there are currently no site generators in Roda like there is in Ruby on Rails (rails new myapp). which means we have to generate our application directories and files manually or we can use the roda’s creator’s stack roda-sequel-stack.

Also there is no ActiveRecord as database ORM in Roda, roda uses Sequal. It’s better to take a glance at Sequel .

Options to structure the project files

  1. Using my repo, i tried to keep the structure as we used to in Rails
git clone https://github.com/MajedBojan/roda_api/tree/setup_project_structure
cd roda_api
rm -rf .git
bundle install
# follow the developer guide over there to setup the database

2. Or you can use Jeremy Evans’s repo

$ git clone https://github.com/jeremyevans/roda-sequel-stack
$ mv roda-sequel-stack my_app
$ cd my_app
$ rake setup[MyApp]

The main file in the app is app.rb and this file has been loaded via config.ru to serve the application with rackup command

# frozen_string_literal: truerequire './config/application.rb'Config::DB.connectclass App < Rodaplugin :hash_routesSequel::Model.plugin :validation_helpersSequel::Model.plugin :timestamps, update_on_create: true # To populate timestamps on record creation# use Rack::Session::Cookie, secret: 'some_nice_long_random_string_DSKJH4378EYR7EGKUFH', key: '_roda_app_session'# use Rack::Protection# plugin :csrfplugin :headplugin :json, classes: [Array, Hash, Sequel::Model], content_type: 'application/json'plugin :json_parserplugin :all_verbsplugin :haltrequire './app/models/user.rb'
require './app/models/category.rb'
require './app/models/item.rb'
require './app/models/order.rb'
Unreloader.require('app/controllers/api/v1') {}route do |r|r.root do{ success: true, message: 'Application server is up', env: ENV['RACK_ENV'] }endr.hash_routesend
end

Run rackup now, make GET request to our app’s root / URL, and you should see server status response.

{
"success": true,
"message": "Application server is up",
"env": "development"
}

let’s start build our app it’s simple app to show you how easy we can create an application in roda framework.

we are going to build simple app which is displaying items and user can purchase these items.

Our schema should look like this

if you cloned my repo you don’t need to do step 0

Step 0

touch app/models/user.rb db/migrate/001_create_users.rb# db/migrate/001_create_users.rbSequel.migration dochange docreate_table(:users) doprimary_key :id, unique: trueString :name, null: falseString :email, unique: true, null: falseString :password_hash, null: falseendendend

app/controllers/api/v1/users.rb

# frozen_string_literal: true
class App
hash_branch('users') do |r|
r.get do
page = r.params[:page] || 1
{ message: 'Data found', data: { users: User.paginate(page, 10).map(&:to_json) } }
end
r.post do
@user = User.create(user_params(r))
{ message: 'User Created successfully', data: { user: @user } }
endr.is 'user', Integer do |user_id|# user/:id used to match get, put and delete request, to provide# getting user by id, updating and deleting user@user = User[user_id]# use halt to return 404 without evaluating rest of the blockr.halt(404) unless @userr.get do{ user: @user.to_json }{ message: 'Data found', data: { user: @user } }endr.put do@user.update(user_params(r)){ message: 'User Updated successfully', data: { user: @user } }{ user: @user }endr.delete do@user.destroyresponse.status = 204{ message: 'User Deleted successfully', data: { user_id: user_id } }endendendprivatedef user_params(r){ name: r.params['name'], email: r.params['email'], password_hash: r.params['password'] }endend

Step 1

Create the models files for category, item and order

touch app/models/category.rb app/models/item.rb app/models/order.rb

Migrations

let’s create migration files for those models as well category, item and order

touch db/migrate/002_create_categories.rb db/migrate/003_create_items.rb db/migrate/004_create_orders.rb

Copy the next lines to the models to define the relationship between them.

app/models/category.rb

# frozen_string_literal: trueclass Item < Sequel::Model
plugin :json_serializer
one_to_many :orders
many_to_one :category
end

app/models/item.rb

# frozen_string_literal: trueclass Item < Sequel::Model
plugin :json_serializer
one_to_many :orders
many_to_one :category
end

app/models/order.rb

# frozen_string_literal: trueclass Order < Sequel::Model
plugin :json_serializer
many_to_one :item
many_to_one :user
end

Add migration to predefined migration files

db/migrate/002_create_categories.rb

Sequel.migration do
change do
create_table(:categories) do
primary_key :id, unique: true
String :name, null: false
end
end
end

db/migrate/002_create_orders.rb

Sequel.migration do
change do
create_table(:orders) do
primary_key :id, unique: true
foreign_key :user_id, :users, null: false
foreign_key :item_id, :items, null: false
String :name, null: false
String :address, null: false
end
end
end

db/migrate/003_create_items.rb

Sequel.migration do
change do
create_table(:items) do
primary_key :id, unique: true
foreign_key :category_id, :categories, null: false
String :name, null: false
String :price, null: false
String :currency, null: false
String :color, null: false
end
end
end

Here we go!!!! run rake db:migrate

The next step is to create CRUD operations for our classes, RODA provides a plugin that helps to split out the controllers which is plugin :hash_routes

let’s create the controller’s files for categories, items and orders

touch app/controllers/api/v1/categories.rb app/controllers/api/v1/items.rb app/controllers/api/v1/orders.rb

let’s start with categories and create the CRUD operations app/controllers/api/v1/categories.rb

# frozen_string_literal: trueclass Apphash_branch('categories') do |r|r.get do{ message: 'Data found', data: { categories: Category.all } }endr.post do@category = Category.create(category_params(r)){ message: 'Category Created successfully', data: { category: @category } }endr.is 'category', Integer do |user_id|# category/:id used to match get, put and delete request, to provide# getting category by id, updating and deleting category@category = Category[user_id]# use halt to return 404 without evaluating rest of the blockr.halt(404) unless @categoryr.get do{ message: 'Data found', data: { category: @category } }endr.put do@category.update(category_params(r)){ message: 'Category Updated successfully', data: { category: @category } }endr.delete do@category.destroyresponse.status = 204{ message: 'Category Deleted successfully', data: { user_id: user_id } }endendendprivatedef category_params(r){ name: r.params['name'] }endend

app/controllers/api/v1/items.rb

# frozen_string_literal: trueclass Apphash_branch('items') do |r|r.get do{ message: 'Data found', data: { items: Item.all } }endr.post do@item = Item.create(item_params(r)){ message: 'Item Created successfully', data: { item: @item } }endr.is 'item', Integer do |user_id|# item/:id used to match get, put and delete request, to provide# getting item by id, updating and deleting item@item = Item[user_id]# use halt to return 404 without evaluating rest of the blockr.halt(404) unless @itemr.get do{ message: 'Data found', data: { item: @item } }endr.put do@item.update(item_params(r)){ message: 'Item Updated successfully', data: { item: @item } }endr.delete do@item.destroyresponse.status = 204{ message: 'Item Deleted successfully', data: { user_id: user_id } }end
end
endprivatedef item_params(r)prms = r.params{name: prms['name'],category_id: prms['category_id'],price: prms['price'],currency: prms['currency'],color: prms['color']}endend

app/controllers/api/v1/orders.rb

# frozen_string_literal: trueclass Apphash_branch('orders') do |r|r.get do{ message: 'Data found', data: { orders: Order.all } }endr.post do@order = Order.create(order_params(r)){ message: 'Order Created successfully', data: { order: @order } }endr.is 'order', Integer do |user_id|# order/:id used to match get, put and delete request, to provide# getting order by id, updating and deleting order@order = Order[user_id]# use halt to return 404 without evaluating rest of the blockr.halt(404) unless @orderr.get do{ message: 'Data found', data: { order: @order } }endr.put do@order.update(order_params(r)){ message: 'Order Updated successfully', data: { order: @order } }endr.delete do@order.destroyresponse.status = 204{ message: 'Order Deleted successfully', data: { user_id: user_id } }endendendprivatedef order_params(r)prms = r.params{name:    prms['name'],user_id: prms['user_id'],item_id: prms['item_id'],address: prms['address']}
end
end

Now you can create some record using POSTMAN or Curl

curl -X GET \
http://localhost:9292/items \
-H 'Accept: */*' \
-H 'Cache-Control: no-cache' \
-H 'Connection: keep-alive' \
-H 'Host: localhost:9292' \
-H 'Postman-Token: cf32b100-1768-4e6a-a9f6-250f82c7818f,d2dbafc1-2abb-45a1-b6e8-9605ac964563' \
-H 'User-Agent: PostmanRuntime/7.11.0' \
-H 'accept-encoding: gzip, deflate' \
-H 'cache-control: no-cache' \
-H 'cookie: _roda_app_session=BAh7CUkiD3Nlc3Npb25faWQGOgZFVEkiRWVhYTJlNTkyMTVjNzI5NjBiYmE3%0AMjg5NmE5MjY4ZTgyM2ZkZTc1MDIxOWQxZTBiMDVjOTMyMTU0NmIwYTYzZGUG%0AOwBGSSIJY3NyZgY7AEZJIiU3ZWM0MTUzOTc1MjMyODJlMjI5ZmZiYTY4M2Vk%0AZGY2NgY7AEZJIg10cmFja2luZwY7AEZ7B0kiFEhUVFBfVVNFUl9BR0VOVAY7%0AAFRJIi0zZWFlODY4NjcwNGU3ODYzZTY0ZGE0Y2FmN2E1MzBjN2U4NTBkZWUx%0ABjsARkkiGUhUVFBfQUNDRVBUX0xBTkdVQUdFBjsAVEkiLWRhMzlhM2VlNWU2%0AYjRiMGQzMjU1YmZlZjk1NjAxODkwYWZkODA3MDkGOwBGSSIPY3NyZi50b2tl%0AbgY7AFRJIjF5NWt2L3Z2b2tOeEJUdWx2cXUwMEJXd1h5eUd2RTZkMUU4V0Fw%0ASE1aNXgwPQY7AEY%3D%0A--d7d71f817fed2b00103c5adc63370f29bf210b32' \
-b _roda_app_session=BAh7CUkiD3Nlc3Npb25faWQGOgZFVEkiRWVhYTJlNTkyMTVjNzI5NjBiYmE3%0AMjg5NmE5MjY4ZTgyM2ZkZTc1MDIxOWQxZTBiMDVjOTMyMTU0NmIwYTYzZGUG%0AOwBGSSIJY3NyZgY7AEZJIiU3ZWM0MTUzOTc1MjMyODJlMjI5ZmZiYTY4M2Vk%0AZGY2NgY7AEZJIg10cmFja2luZwY7AEZ7B0kiFEhUVFBfVVNFUl9BR0VOVAY7%0AAFRJIi0zZWFlODY4NjcwNGU3ODYzZTY0ZGE0Y2FmN2E1MzBjN2U4NTBkZWUx%0ABjsARkkiGUhUVFBfQUNDRVBUX0xBTkdVQUdFBjsAVEkiLWRhMzlhM2VlNWU2%0AYjRiMGQzMjU1YmZlZjk1NjAxODkwYWZkODA3MDkGOwBGSSIPY3NyZi50b2tl%0AbgY7AFRJIjF5NWt2L3Z2b2tOeEJUdWx2cXUwMEJXd1h5eUd2RTZkMUU4V0Fw%0ASE1aNXgwPQY7AEY%3D%0A--d7d71f817fed2b00103c5adc63370f29bf210b32

Here is the Complete Code

Inspired by

--

--

MaJeD BoJaN

Self-Taught Full-Stack ROR Developer || Tech enthusiast.