Building a Rails project¶
This example will show how to create a simple Rails project using vagga.
- Creating the project structure
- Configuring the database from environment
- Adding some code
- Caching with memcached
- We should try Postgres too
Creating the project structure¶
First, let’s create a directory for our new project:
$ mkdir -p ~/projects/vagga-rails-tutorial && cd ~/projects/vagga-rails-tutorial
Now we need to create our project’s structure, so let’s create a new container and tell it to do so.
Create the vagga.yaml
file and add the following to it:
containers:
rails:
setup:
- !Alpine v3.3
- !Install ❶
- libxml2
- libxslt
- zlib
- !BuildDeps ❶
- libxml2-dev
- libxslt-dev
- zlib-dev
- !Env
NOKOGIRI_USE_SYSTEM_LIBRARIES: 1 ❷
- !GemInstall [rails] ❸
environ:
HOME: /tmp ❹
- ❶ –
rails
depends on nokogiri, which needs these libs during build and runtime. - ❷ –
nokogiri
ships its own versions oflibxml2
andlibxslt
in order to make it easier to build, but here we are instructing it to use the versions provided by Alpine. Refer to nokogiri docs for details. - ❸ – tell
gem
to installrails
. - ❹ – The
rails new
command, which we are going to use shortly, will complain if we do not have a$HOME
. After our project is created, we won’t need it anymore.
And now run:
$ vagga _run rails rails new . --skip-bundle
This will create a new rails project in the current directory. The --skip-bundle
flag tells rails new
to not run bundle install
, but don’t worry, vagga
will take care of it for us.
Now that we have our rails project, let’s change our container fetch dependencies
from Gemfile
:
containers:
rails:
setup:
- !Alpine v3.3
- !Install
- libxml2
- libxslt
- zlib
- sqlite-libs ❶
- nodejs ❶
- !BuildDeps
- libxml2-dev
- libxslt-dev
- zlib-dev
- sqlite-dev ❶
- !Env
NOKOGIRI_USE_SYSTEM_LIBRARIES: 1
- !GemBundle ❷
- ❶ – we need
sqlite
for the development database andnodejs
for the asset pipeline (specifically, theuglifier
gem). - ❷ – install dependencies from
Gemfile
usingbundle install
.
Before we test our project, let’s add two gems into the Gemfile
:
# Gemfile
# ...
gem 'bigdecimal'
gem 'tzinfo-data'
# ...
Without these two gems, you may run into import errors.
To test if everything is Ok, let’s create a command to run our project:
commands:
run: !Command
container: rails
description: start rails development server
run: rails server
Run the project:
$ vagga run
Now visit localhost:3000
to see rails default page.
Note
You may need to remove “tmp/pids/server.pid” in subsequent runs, otherwise, rails will complain that the server is already running.
Configuring the database from environment¶
By default, the rails new
command will setup sqlite as the project database
and store the configuration in config/databse.yml
. However, we will use an
environment variable to tell rails where to find our database. To do so, delete
the rails database file:
$ rm config/database.yml
And then set the enviroment variable in our vagga.yaml
:
containers:
rails:
setup:
# ...
environ:
DATABASE_URL: sqlite3:db/development.sqlite3
This will tell rails to use the same file that was configured in database.yml
.
Now if we run our project, everything should be the same.
Adding some code¶
Before going any further, let’s add some code to our project:
$ vagga _run rails rails g scaffold article title:string:index body:text
Rails scaffolding will generate everything we need, we just have to run the migrations:
$ vagga _run rails rake db:migrate
Now we need to tell rails to use our articles index page as the root of our
project. Change config/routes.rb
as follows:
# config/routes.rb
Rails.application.routes.draw do
root 'articles#index'
resources :articles
# ...
end
Run the project now:
$ vagga run
You should see the articles list page rails generated for us.
Caching with memcached¶
Many projects use memcached to speed up things, so let’s try it out.
First, add dalli
, a pure ruby memcached client, to our Gemfile
:
gem 'dalli'
Then, open config/environments/production.rb
and add the following:
# config/environments/production.rb
Rails.application.configure do
# ...
if ENV['MEMCACHED_URL']
config.cache_store = :dalli_store, ENV['MEMCACHED_URL']
end
# ...
end
Create a container for memcached:
containers:
# ...
memcached:
setup:
- !Alpine v3.3
- !Install [memcached]
Create the command to run with caching:
commands:
# ...
run-cached: !Supervise
description: Start the rails development server alongside memcached
children:
cache: !Command
container: memcached
run: memcached -u memcached -vv ❶
app: !Command
container: rails
environ:
MEMCACHED_URL: memcached://127.0.0.1:11211 ❷
RAILS_ENV: production ❸
SECRET_KEY_BASE: my_secret_key ❹
RAILS_SERVE_STATIC_FILES: 1 ❺
run: |
rake assets:precompile ❻
rails server
- ❶ – run memcached as verbose so we see can see the cache working
- ❷ – set the cache url
- ❸ – tell rails to run in production environment
- ❹ – production environment requires a secret key
- ❺ – tell rails to serve static files on production environment
- ❻ – precompile assets
Now let’s change some of our views to use caching:
<!-- app/views/articles/show.html.erb -->
<%# ... %>
<% cache @article do %>
<p>
<strong>Title:</strong>
<%= @article.title %>
</p>
<p>
<strong>Body:</strong>
<%= @article.body %>
</p>
<% end %>
<%# ... %>
<!-- app/views/articles/index.html.erb -->
<%# ... %>
<table>
<%# ... %>
<tbody>
<% @articles.each do |article| %>
<% cache article do %>
<tr>
<td><%= article.title %></td>
<td><%= article.body %></td>
<td><%= link_to 'Show', article %></td>
<td><%= link_to 'Edit', edit_article_path(article) %></td>
<td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
<%# ... %>
Run the project with caching:
$ vagga run-cached
Try adding some records. Keep an eye on the console to see rails talking to memcached.
We should try Postgres too¶
We can test our project against a Postgres database, which is probably what we will use in production.
First, add gem pg
to our Gemfile
gem 'pg'
Then add the system dependencies for gem pg
containers:
rails:
setup:
- !Alpine v3.3
- !Install
- libxml2
- libxslt
- zlib
- sqlite-libs
- libpq ❶
- nodejs
- !BuildDeps
- libxml2-dev
- libxslt-dev
- zlib-dev
- sqlite-dev
- postgresql-dev ❷
- !Env
NOKOGIRI_USE_SYSTEM_LIBRARIES: 1
- !GemBundle
environ:
DATABASE_URL: sqlite3:db/development.sqlite3
- ❶ – runtime dependency
- ❷ – build dependency
Create the database container
containers:
# ...
postgres:
setup:
- !Ubuntu trusty
- !Install [postgresql]
- !EnsureDir /data
environ:
PGDATA: /data
PG_PORT: 5433
PG_DB: test
PG_USER: vagga
PG_PASSWORD: vagga
PG_BIN: /usr/lib/postgresql/9.3/bin
volumes:
/data: !Tmpfs
size: 100M
mode: 0o700
And then add the command to run with Postgres:
commands:
# ...
run-postgres: !Supervise
description: Start the rails development server using Postgres database
children:
app: !Command
container: rails
environ:
DATABASE_URL: postgresql://vagga:vagga@127.0.0.1:5433/test
run: |
touch /work/.dbcreation # Create lock file
while [ -f /work/.dbcreation ]; do sleep 0.2; done # Acquire lock
rake db:migrate
rails server
db: !Command
container: postgres
run: |
chown postgres:postgres $PGDATA;
su postgres -c "$PG_BIN/pg_ctl initdb";
su postgres -c "echo 'host all all all trust' >> $PGDATA/pg_hba.conf"
su postgres -c "$PG_BIN/pg_ctl -w -o '-F --port=$PG_PORT -k /tmp' start";
su postgres -c "$PG_BIN/psql -h 127.0.0.1 -p $PG_PORT -c \"CREATE USER $PG_USER WITH PASSWORD '$PG_PASSWORD';\""
su postgres -c "$PG_BIN/createdb -h 127.0.0.1 -p $PG_PORT $PG_DB -O $PG_USER";
rm /work/.dbcreation # Release lock
sleep infinity
Now run:
$ vagga run-postgres
We can also add some default records to the database, so we don’t have to add
them everytime we run our project. To do so, add the following to db/seeds.rb
:
# db/seeds.rb
Article.create([
{ title: 'Article 1', body: 'Lorem ipsum dolor sit amet' },
{ title: 'Article 2', body: 'Lorem ipsum dolor sit amet' },
{ title: 'Article 3', body: 'Lorem ipsum dolor sit amet' }
])
Now change the run-postgres
command to seed the database:
commands:
# ...
run-postgres: !Supervise
description: Start the rails development server using Postgres database
children:
app: !Command
container: rails
environ:
DATABASE_URL: postgresql://vagga:vagga@127.0.0.1:5433/test
run: |
touch /work/.dbcreation # Create lock file
while [ -f /work/.dbcreation ]; do sleep 0.2; done # Acquire lock
rake db:migrate
rake db:seed ❶
rails server
db: !Command
# ...
- ❶ – populate the database.
Now , everytime we run run-postgres
, we will have our database populated.