Lets generate a new Sinatra application. It’s convenient to use the Corneal Gem, which will generate a new Sinatra application like how Rails can. You can find it here:
- https://github.com/thebrianemory/corneal - The Corneal Gem isn't up to date, but it is still useful in generating a new Sinatra App from scratch.
- https://thebrianemory.github.io/corneal/
NOTE: I am currently using a mac. If you would like to look at the code for the project created in this blog post, you can find it here: https://github.com/GoodGuyGuf/example_users_application
Corneal Gem App Generation:
- Open you terminal and Install the gem with:
gem install corneal
- Choose a place in your computer to generate a new Sinatra application
- Generate the application with
corneal new APP_NAME
which just for this post I will create a simple example application that only keeps track of users information. I will generate my application withcorneal new USERS_APPLICATION
- Once everything is created,
cd
into your project directory and runbundle install
- I am using visual studio code, and from the terminal can open VSC with running
code .
in the terminal.
This application is an API, we will not be using views/erb templates. Delete the views folder.
- In
ApplicationController
, remove the code for configuration and theget
method corneal generated for you. - We don't need the public folder, so we can delete it.
- For this application, we will be using
sqlite3
for our database. - Corneal already generates the connection to ActiveRecord and sqlite3 by default.
Now lets create our database.
- If you run
rake --tasks
in the terminal, you will most likely get abigdecimal
error. - If you include
gem 'bigdecimal', '1.4.2'
in your gemfile, the application will work but it will still give you a warning thatbigdecimal
is deprecated. Runningrake --tasks
now works. - If you want the warning to go away, run
rake --tasks
again. When the deprecated message pops up forbigdecimal
,cmd + click
and follow the route it gives you. This was the warning it gave me: /Users/user/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-4.2.11.3/lib/active_support/core_ext/object/duplicable.rb:111: warning: BigDecimal.new is deprecated; use BigDecimal() method instead.
- This will take you to a file called
duplicable.rb
. You'll see this code:
require 'bigdecimal'
class BigDecimal
# Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
# raises TypeError exception. Checking here on the runtime whether BigDecimal
# will allow dup or not.
begin
BigDecimal.new('4.56').dup
def duplicable?
true
end
rescue TypeError
# can't dup, so use superclass implementation
end
end
All you have to simply do is change: BigDecimal.new('4.56').dup
to BigDecimal('4.56').dup
require 'bigdecimal'
class BigDecimal
# Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead
# raises TypeError exception. Checking here on the runtime whether BigDecimal
# will allow dup or not.
begin
BigDecimal('4.56').dup
def duplicable?
true
end
rescue TypeError
# can't dup, so use superclass implementation
end
end
Now you don't have to include the bigdecimal
gem in your Gemfile.
Remove the gem and run bundle install
. The application now works with no bigdecimal
warning.
Why does this happen? The corneal gem generated the activerecord
gem for you which is an older version. The current version at the time this was written is 6.0
, so you can change the version in your gemfile to:
gem 'activerecord', '~> 6.0', '>= 6.0.3.2', :require => 'active_record'
Now you won't have to change the duplicable file. Bigdecimal
deprecated warning will go away as well. Update sqlite3
in the gemfile if it is an older version.
Lets create the migration for the users table.
There is a rake tasks command we can use to generate a new migration table.
rake db:create_migration NAME=create_users
Now we have a new empty migration table.
class CreateUsers < ActiveRecord::Migration[6.0]
def change
end
end
Lets add some user information.
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :name
t.string :username
t.string :password_digest
t.timestamps
end
end
end
password_digest
is used with the gem bcrypt
in order to protect passwords. There is more information on the gem bcrypt
here at:
What about timestamps
? timestamps
will give you two columns: created_at
& updated_at
. Here is a link for more on migration timestamps:
Active Record Migrations - Ruby on Rails Guides
Gem faker
is an awesome gem that is used to add many users into our database. I'll use it in this project just to show you how it works.
In our Gemfile
, lets change the section where it groups test together to this:
group :development do
gem 'pry'
gem 'tux'
gem 'sqlite3'
gem 'faker'
end
Why do we group Gems together?
Certain Gems will only be used in certain modes. For example, the gems we list in our development group, will not be installed on the server when the application is deployed. When in production mode, the other gems will not be used if grouped in another development mode.
Awesome. Now lets create our database with rake db:create
There is an issue. ActiveRecord::AdapterNotSpecified: 'development' database is not configured. Available: []
What do we need? A database.yml
file.
How to set up Sinatra with ActiveRecord
Add this section to your database.yml
file, which should be placed in your config
folder:
default: &default
adapter: sqlite3
pool: 5
timeout: 5000
development:
<<: *default
database: db/development.sqlite3
test:
<<: *default
database: db/test.sqlite3
production:
adapter: postgresql
encoding: unicode
pool: 5
host: <%= ENV['DATABASE_HOST'] || 'db' %>
database: <%= ENV['DATABASE_NAME'] || 'sinatra' %>
username: <%= ENV['DATABASE_USER'] || 'sinatra' %>
password: <%= ENV['DATABASE_PASSWORD'] || 'sinatra' %>
See how the database has a .sqlite3
ending? Make sure your environment.rb and database.yml file specify the same ending file name for sqlite3
. Otherwise the rake console will show that it did not establish a connection to your database to query the users table.
- run
rake db:create
to create our database - run
rake db:migrate
to migrate your table to the database - Create a
seeds.rb
file in yourdb
directory - Use Gem
faker
inseeds.rb
to create some fake users like so:
5.times do
User.create(
name: Faker::Name.name,
username: Faker::Internet.username,
password: Faker::Internet.password
)
end
Make sure to add has_secure_password
in the user
model in order for this user creation to work. It won't recognize the password
column without it.
run rake db:seed
to seed the data into the database. To test if this has worked, go into your rakefile and create a new task called rake console
:
desc "A console"
task :console do
Pry.start
end
Run rake console
in the terminal and run User.all
and you should have 5 users.
Now we will turn this application into a backend API.
NOTE: From this point forward you might have to look at the source code of each gem if you run into troubleshooting issues. "When in doubt, look at the source code."
To do this we will be adding on some gems in order for the application to work as an API.
gem "sinatra-cross_origin"
gem 'rack-contrib'
gem 'fast_jsonapi'
gem 'sinatra-contrib', require: false
These gems will allow us to use Sinatra as an API.
What do these gems do?
sinatra-cross_origin
gem "sinatra-cross_origin"
This gem enables CORS. IT allows an origin(domain) to make requests to your Sinatra API.
Since we are using a modular application, we have to register the cross origin. Add this code in your application controller:
register Sinatra::CrossOrigin
What is a modular application? It is one that inherits from Sinatra::Base
. This is what the Sinatra documentation states:
"Modular applications must include any desired extensions explicitly by calling
register ExtensionModule
within the application’s class scope."
The rest of your code should look like this and we'll break it down:
require './config/environment'
require 'sinatra/base' # Your file should require sinatra/base instead of sinatra; otherwise, all of Sinatra’s DSL methods are imported into the main namespace
require 'sinatra/json'
class ApplicationController < Sinatra::Base
register Sinatra::CrossOrigin
configure do
enable :cross_origin
set :allow_origin, "*" # allows any origin(domain) to send fetch requests to your API
set :allow_methods, [:get, :post, :patch, :delete, :options] # allows these HTTP verbs
set :allow_credentials, true
set :max_age, 1728000
set :expose_headers, ['Content-Type']
end
options '*' do
response.headers["Allow"] = "HEAD,GET,POST,DELETE,OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept"
200
end
end
configure do
What is this?
Method: Sinatra::Base.configure
Set configuration options for Sinatra and/or the app. Allows scoping of settings for certain environments.
enable :cross_origin
The enable
and disable
methods are sugar for setting a list of settings to true
or false
, respectively. The following two code examples below are equivalent:
enable :sessions, :logging
disable :dump_errors, :some_custom_option
Using set
:
set :sessions, **true**
set :logging, **true**
set :dump_errors, **false**
set :some_custom_option, **false**
Sinatra's set
method
the
set
method takes a setting name and value and creates an attribute on the application.
If you look at the source code for sinatra-cross_origin
, you'll see this:
def self.registered(app)
app.helpers CrossOrigin::Helpers
app.set :cross_origin, false
app.set :allow_origin, :any
app.set :allow_methods, [:post, :get, :options]
app.set :allow_credentials, true
app.set :allow_headers, ["*", "Content-Type", "Accept", "AUTHORIZATION", "Cache-Control"]
app.set :max_age, 1728000
app.set :expose_headers, ['Cache-Control', 'Content-Language', 'Content-Type', 'Expires', 'Last-Modified', 'Pragma']
app.before do
cross_origin if settings.cross_origin
end
end
Setting enable :cross_origin
sets the boolean value to true
. As you can see, our code in the application mirrors what you see in the self.registered
method. Here's the code again for reference in our configure
method:
configure do
enable :cross_origin # turns app.set :cross_origin, false to be app.set :cross_origin, true
set :allow_origin, "*" # allows your frontend to send fetch requests
set :allow_methods, [:get, :post, :patch, :delete, :options] # allows these HTTP verbs
set :allow_credentials, true
set :max_age, 1728000
set :expose_headers, ['Content-Type']
end
On the github page for sinatra-cross_origin
, they give a warning:
Responding to
OPTIONS
Many browsers send anOPTIONS
request to a server before performing a CORS request (this is part of the specification for CORS ). These sorts of requests are called preflight requests. Without a valid response to anOPTIONS
request, a browser may refuse to make a CORS request (and complain that the CORS request violates the same-origin policy).
Currently, this gem does not properly respond toOPTIONS
requests. See this issue. You may have to add code like this in order to make your app properly respond toOPTIONS
requests:
options "*" do
response.headers["Allow"] = "HEAD,GET,PUT,POST,DELETE,OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Cache-Control, Accept"
200
end
rack-contrib
gem 'rack-contrib'
This Gem will allow you to parse JSON. You have to include what this gem allows you to use in your rakefile. IN your rakefile you need four things:
use Rack::MethodOverride
use Rack::JSONBodyParser
use UsersController
run ApplicationController
Rack::JSONBodyParser
- without this, your application breaks and it won't log in users.
UsersController
ApplicationController
fast_jsonapi
gem 'fast_jsonapi'
This Gem will serialize our json so that we don't have to write it out by hand. You will have to create your serializer model by hand though.
More on JSON serialization:
What is deserialize and serialize in JSON?
You don't have to use this gem. You can serialize your json by hand, but this process was just easier and simpler for me to use in this example project.
In rails you could use commands to generate a serializer for a model, but in Sinatra you will have to write it by hand. Its just a few steps you'll need to take:
- Create a
serializers
directory in theapp
directory - Create a
user.rb
file - Create a class inside of the
user.rb
file calledUserSerializer
- Write this code inside of the class:
include FastJsonapi::ObjectSerializer
- Now lets write the attributes for the class:
class UserSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :username, :created_at, :updated_at
end
What does this do? JSON is JavaScript Object Notation. It is a JavaScript Object, except the whole object is converted into a string. This is what your get route will render on localhost:9393/users
When you render json, you have to choose what attributes you will be rendering on localhost:9393/users
.
Without using the fast_jsonapi
gem, things can get messy in your json renderings.
sinatra-contrib
gem 'sinatra-contrib', require: false
Why do you need the required false? Because of this issue:
undefined method desc
for Sinatra::Application:Class
OR just require sinatra/base
at the top of ApplicationController
.
Now let's make a fetch request to your API. Run shotgun
in your terminal, open developer tools in your browser console, and use this code:
fetch('http://localhost:9393/users')
.then(resp => resp.json())
.then(json => console.log(json))
You should see an output like this:
{data: Array(5)}
data: Array(5)
0: {id: "1", type: "user", attributes: {…}}
1: {id: "2", type: "user", attributes: {…}}
2: {id: "3", type: "user", attributes: {…}}
3: {id: "4", type: "user", attributes: {…}}
4: {id: "5", type: "user", attributes: {…}}
length: 5
__proto__: Array(0)
__proto__: Object
Top comments (0)