Using external sources
One of Nanoc’s strengths is the ability to pull in data from external systems. In this guide, we’ll show how to do this, and what you can achieve with it.
As an example, assume you work for a company that has a SQL database containing an employee directory, and you want to extract that information into a nice-looking website. Nanoc to the rescue!
Setting up the database
This example will use the following SQLite 3 schema:
CREATE TABLE employees (
id INTEGER PRIMARY KEY NOT NULL,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
photo_url TEXT NOT NULL
)
To pull in this data into Nanoc, we can generate an item for every employee. Such an item would not have any content (except perhaps the employee’s bio, if any), but the item would store the employee details as attributes.
In this example, we’ll use Sequel in combination with SQLite3. To install the dependencies:
% gem install sequel
% gem install sqlite3
Create a sample database with sample data:
require 'sequel'
DB = Sequel.sqlite('test.db')
sql = <<EOS
CREATE TABLE employees (
id INTEGER PRIMARY KEY NOT NULL,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
photo_url TEXT NOT NULL
)
EOS
DB.run(sql)
DB[:employees].insert(
id: 1,
first_name: 'Denis',
last_name: 'Defreyne',
photo_url: 'http://employees.test/photos/1.png'
)
At this point, we have a database set up, with some sample data to be used.
Writing the data source
Create the file lib/data_sources/employee_db.rb and put in the following:
require 'sequel'
class HRDataSource < ::Nanoc::DataSource
end
A data source is responsible for loading data, and is represented by the Nanoc::DataSource
class. Each data source has a unique identifier, which is a Ruby symbol. Add the line identifier :hr
inside the newly created data source class:
class HRDataSource < ::Nanoc::DataSource
identifier :hr
end
Data sources have an #up
method, which can be overridden to perform actions to establish a connection with the remote data source. In this example, implement it so that it connects to the database:
class HRDataSource < ::Nanoc::DataSource
identifier :hr
def up
@db = Sequel.sqlite('test.db')
end
end
Then we can generate items. For every employee, represented by a row from the employees
table in the database, we create an item:
class HRDataSource < ::Nanoc::DataSource
identifier :hr
def up
@db = Sequel.sqlite('test.db')
end
def down
@db.disconnect
end
def items
@db[:employees].map do |employee|
new_item(
'',
employee,
"/employees/#{employee[:id]}"
)
end
end
end
The first argument to the #new_item
is the content (empty in this case), the second is the attributes hash (which will have the keys first_name
, last_name
, and photo_url
) and the third argument is the identifier.
The data source implementation is now ready to be used.
Using the data source
Configure the data source in nanoc.yaml:
data_sources:
-
type: hr
items_root: /external/hr
The type
is the same as the identifier of the data source, and items_root
is a string that will be prefixed to all item identifiers coming from that data source. For example, employees will have identifiers like "/external/hr/employees/1"
.
Create the file lib/helpers.rb and put in one function that will make it easier to find employees:
def sorted_employees
employees = @items.find_all('/external/hr/employees/*')
employees.sort_by do |e|
[ e[:last_name], e[:first_name] ]
end
end
Now it’s time to create the employee directory page. Create the file content/employees.erb and put in the following code, which will find all employees and print them:
<h1>Employees</h1>
<table>
<thead>
<tr>
<th>Photo</th>
<th>First name</th>
<th>Last name</th>
</tr>
</thead>
<tbody>
<% sorted_employees.each do |e| %>
<tr>
<td><%= e[:photo_url] %></td>
<td><%= e[:first_name] %></td>
<td><%= e[:last_name] %></td>
</tr>
<% end %>
</tbody>
</table>
Ensure the rules for this new employees page filters it with :erb
and writes it out with a html extension:
compile '/employees.*' do
filter :erb
write ext: 'html'
end
Finally you have to stop Nanoc from writing out pages for every employee item provided by the data source. For this, the #ignore
rule comes in handy. Add this at the top of your Rules file:
ignore '/external/**/*'
Now compile, and you will see the employee directory!