March 7, 2017
I spent the past two years+ (or so...) learning and experimenting a lot with mobile devices, making demo apps and such.
But we all know that apps aren't just the thing you install on your phone, there is also back-ends, APIs, services. For this I needed a very quick way to accomplish this without have too much time to put on it.
First, I am not a Rails developer by any means but i know the fundamentals of Ruby and with the help of Sinatra (a micro-framework that I personally love) and ActiveRecord on top of that is the perfect combo to get it done QUICKLY!.
It's necessary that you know Ruby programming basics to follow this example.
I choose RVM to work with ruby on my dev environment (and sometimes even on production, when using some kind of shared servers). Its a way to keep everything under control and don't mess up with my computer's/server packages when trying to work with different versions of ruby, gems, etc.
First we install RVM, then we use it to install ruby.... and finally the gem "bundler" that we are going to use to manage our application's dependencies.
# Installing RVM
curl -sSL https://rvm.io/mpapis.asc | gpg --import -
curl -L https://get.rvm.io | bash -s stable
source ~/.rvm/scripts/rvm
# Installing Ruby
rvm install 2.2.2
rvm --default use 2.2.2
# Install the bundler Gem
gem install bundler
First we create the Gemfile file (yes, a file without extension); where we list all the gems/libraries that we are going to use in our application.
# Gemfile
source 'https://rubygems.org'
# SinatraRB Framework
gem "sinatra"
# Our database gem/mapper/model
gem "activerecord"
gem "sinatra-activerecord"
gem "json"
# SQLite
gem "sqlite3"
# With this we are going to require tux only on development mode.
# tux is a tool that allows you to interact with your Sinatra app from a command line
# We are going to use that to insert/fill our DB and test a few things.
group :development do
gem "tux"
end
Now we just need to run a simple command and let bundler do the hard work for us. It may take a while to finish.
bundle install
Thanks to Sinatra this is very simple and we only need to create a few files. The code is pretty self-explanatory:
# Rakefile
require './application'
require 'sinatra/activerecord/rake'
# config.ru
require 'sinatra'
set :run, false
set :environment, ENV['RACK_ENV'] || 'development'
require './application'
run Sinatra::Application
# models.rb
# This file is empty for now.... going to work on it later
# application.rb
require 'sinatra'
require 'sinatra/activerecord'
# Our file with our model Classes
require './models'
configure do
# Config our Database, in our case is a simple file-based SQLITE
ActiveRecord::Base.establish_connection(
:adapter => "sqlite3",
:database => "development.db"
)
set :show_exceptions, true
# Un-comment this if you are using Vagrant or any type of VM/container such as Docker
# set :bind, '0.0.0.0'
end
################################
# Application Routes
################################
# Home
get "/" do
"Hello, this is our application homepage. But we aren't going to do anything with it."
end
# Service1: Return all our Text entities
get "/api/texts/all" do
# We are going to implement our service here later on....
end
# Service2: Return one Text entity, by ID
get "/api/texts/:id" do
# We are going to implement our service here later on....
end
################################
# 404
################################
not_found do
'Whatever you are looking for its not here!'
end
################################
# 500
################################
error do
'Sorry there was a nasty error - ' + env['sinatra.error'].name
end
Now to test our application.
ruby application.rb
That will tell your what URL to open on your web browser.
We are going to create a migration; if you are not familiar with these a migration is some sort of file where you write a couple instructions that in our case will create our Tables in our database. First we run:
rake db:create_migration NAME=rest_entities
This will create a file like: db/migrate/20170820014906_rest_entities.rb. (After running that command the path to edit is returned), so let's just do that:
# db/migrate/20170820014906_rest_entities.rb
class RestEntities < ActiveRecord::Migration[5.1]
def change
create_table :texts do |t|
t.string :name, :null => false
t.string :message
t.timestamps
end
end
end
Then to apply our changes to the DB:
rake db:migrate
Now lets add some content to our models.rb file that we created previously; here we define the class to map our table content.
#models.rb
class Text < ActiveRecord::Base
# Some logic could be here, but this is a simple example
end
Let's fill the database with a some data, for that we are going to use the tux gem... this opens a new shell to interact with our application:
vagrant@vagrant-ubuntu-trusty-64:/vagrant$ tux
>> text1 = Text.new
>> text1.name = "Jon"
>> text1.message = "Whats up?"
>> text1.save
>> text1 = Text.new
>> text1.name = "Annie"
>> text1.message = "Not much, swiping left"
>> text1.save
Before exit tux you can verify all the entries were created by simply:
>> Text.all
Type exit to close the shell.
Finally lets edit our application.rb file and add a few lines.
The magic happens when we use to_json, a very powerful helper that helps you serialize all kind of ActiveRecord objects. Check the examples bellow and also the next point where I explain a few extra options available.
# application.rb
# ......
# Service1: Return all our Text entities
get "/api/texts/all" do
# Sets the response content type
# Header: Content-Type: application/json
content_type :json
# Text.all.order returns all entities in the database
#
# to_json:
# It serializes the results into a JSON objects, in this
# case its an array of elements. Each element contains all the
# fields in the table: id,name,message,created_at,updated_at
#
Text.all.order(created_at: :desc).to_json
end
# Service2: Return ONLY ONE Text entity, by ID
get "/api/texts/:id" do
# Lets get one Text element from our DB by id
text = Text.find_by_id params[:id]
if text.nil?
halt 404
end
# Sets the response content type
# Header: Content-Type: application/json
content_type :json
# Now we return the serialized object.
#
# By using the :only option, we can especify what fields we want to return
#
text.to_json({:only => [:name, :message]})
end
# ......
This returns a JSON string representing the model. This can be returned as the HTTP response.
Returns a hash representing the model. This is useful when you want to build the response yourself, manually:
get "/api/texts/custom" do
# Sets the response content type
# Header: Content-Type: application/json
content_type :json
# Build our own object, just as example return two times the same query
data = {
:query1 => Text.all.as_json,
:query2 => Text.all.as_json,
:status => "OK"
}
# Now lets just return object response
data.to_json
end
This is an array of what fields you want to include (only), or the ones you don't.
get "/api/texts/test1" do
# Sets the response content type
# Header: Content-Type: application/json
content_type :json
# Returns only the id and name fields.
Text.all.to_json(:only => [:id, :name])
end
get "/api/texts/test2" do
# Sets the response content type
# Header: Content-Type: application/json
content_type :json
# Returns all the fields, except created and updated dates
Text.all.to_json(:except => [:created_at, :updated_at])
end
Here you can specify one or more methods of the ActiveRecord model to be added as a field to the JSON, the value is whatever the method returns.
#models.rb
class Text < ActiveRecord::Base
# our custom method
def my_method
"#{self.name} says: #{self.message}"
end
end
# application.rb
# .......
get "/api/texts/custom" do
# Sets the response content type
# Header: Content-Type: application/json
content_type :json
Text.all.to_json({:methods => [:my_method]})
end
# .......
This is how we can include entity associations as a field. Also, you can "nest" options for them.
# application.rb
# .......
get "/api/texts/custom" do
# Sets the response content type
# Header: Content-Type: application/json
content_type :json
# otherentity represents the associated entity (this isnt implemented in the example)
Text.all.to_json({:include => { :otherentity => {:only => [:id, :name]}}})
end
# .......
If you have several services you can implement as_json in the model itself to avoid some code duplication:
#models.rb
class Text < ActiveRecord::Base
def as_json(options)
super({:only => [:name, :message]}.merge(options))
end
end
I uploaded the working project directly to a Github so you can just copy-paste it without reading the article :)
I'm a 32-years old programmer and web developer currently living in Montevideo, Uruguay. I been programming since I was about 15 y.o, and for the past 12 actively working in the area.