Moving on to actually generating some certificates.
We start with a feature test. Create a new file in your features folder, named certificate_generation.feature and add the following scenario:
# features/certificate_generation.feature
Feature: As a course administrator,
In order to be able to issue the right certificates for a course,
I want to display course title, course date and the
participants name on the certificate
Scenario: Generate a certificate
Given the delivery for the course "Basic" is set to "2015-12-01"
And the data file for "2015-12-01" is imported
And I am on 2015-12-01 show page
And I click "Generate certificates" link
Then 3 certificates should be generated
That will do for now. Run cucumber and see the tests fail.
Add some new step definition in your application_steps.rb
# features/step_definitions/application_steps.rb
And(/^the data file for "([^"]*)" is imported$/) do |date|
steps %q(
And I am on the Course index page
And I click on "#{date}" for the "Basic programming" Course
When I select the "students.csv" file
And I click "Submit" link
)
end
Then(/^([^"]*) certificates should be generated$/) do |count|
pdf_count = Dir['pdf/**/*.pdf'].length
expect(pdf_count).to eq count.to_i
end
Also, add the Generate certificates link to the deliveries/show.erb template:
# lib/views/courses/deliveries/show.erb
...
<div>
<% if @delivery.students.any? %>
Students:
<% @delivery.students.each do |student| %>
<%= [student.full_name, ''].join(' ') %>
<% end %>
<div>
<%= link_to 'Generate certificates', "/courses/generate/#{@delivery.id}" %>
</div>
<% end %>
</div>
Okay, running the scenario now will tell us that no certificates have been created:
$ cucumber features/certificate_generation.feature
Feature: As a course administrator,
In order to be able to issue the right certificates for a course,
I want to be able display course title, course date and the
participants name on the certificate
Scenario: Generate a certificate # features/certificate_generation.feature:7
Given the delivery for the course "Basic" is set to "2015-12-01" # features/step_definitions/application_steps.rb:56
And the data file for "2015-12-01" is imported # features/step_definitions/application_steps.rb:66
And I am on 2015-12-01 show page # features/step_definitions/web_steps.rb:19
And I click "Generate certificates" link # features/step_definitions/application_steps.rb:3
Then 3 certificates should be generated # features/step_definitions/application_steps.rb:85
expected: 3
got: 0
(compared using ==)
(RSpec::Expectations::ExpectationNotMetError)
./features/step_definitions/application_steps.rb:87:in `/^([^"]*) certificates should be generated$/'
./features/support/database_cleaner.rb:7:in `block in <top (required)>'
features/certificate_generation.feature:12:in `Then 3 certificates should be generated'
Failing Scenarios:
cucumber features/certificate_generation.feature:7 # Scenario: Generate a certificate
1 scenario (1 failed)
5 steps (1 failed, 4 passed)
0m0.467s
This is as far as we can go with our feature test at the moment. Let's create a Certificate class and the module for generating pdf's.
From your terminal, run these commands to create your spec and tour class file:
Make sure you require the class file in your controller:
# lib/application.rb
require './lib/certificate'
Let's start with adding some specs:
# spec/certificate_spec.rb
describe Certificate do
it { is_expected.to have_property :id }
it { is_expected.to have_property :identifier }
it { is_expected.to belong_to :delivery }
it { is_expected.to belong_to :student }
end
And add the class definition:
# lib/certificate.rb
class Certificate
include DataMapper::Resource
property :id, Serial
property :identifier, Object
property :created_at, ParanoidDateTime
belongs_to :delivery
belongs_to :student
end
Make sure you run your specs after every update.
We also need to update relationship in the Student models. We start by writing specs for that:
# spec/student_spec.rb
...
it { is_expected.to have_many :certificates }
...
Then the implementation
# lib/student.rb
...
has n, :certificates
...
We are going to add a mechanism to generate a unique identifier for each certificate using a callback that will be invoked when the Certificate is created. We also want to make sure that the certificate has access to all the relevant information that we are going to use while creating the pdf.
In the certificate_spec.rb add the following test:
# spec/certificate_spec.rb
...
describe 'Creating a Certificate' do
before do
course = Course.create(title: 'Learn To Code 101', description: 'Introduction to programming')
delivery = course.deliveries.create(start_date: '2015-01-01')
student = delivery.students.create(full_name: 'Thomas Ochman', email: 'thomas@random.com')
@certificate = student.certificates.create(created_at: DateTime.now, delivery: delivery)
end
it 'adds an identifier after create' do
expect(@certificate.identifier.size).to eq 64
end
it 'has a Student name' do
expect(@certificate.student.full_name).to eq 'Thomas Ochman'
end
it 'has a Course name' do
expect(@certificate.delivery.course.title).to eq 'Learn To Code 101'
end
it 'has a Course delivery date' do
expect(@certificate.delivery.start_date.to_s).to eq '2015-01-01'
end
end
And make the following additions to your Certificate class:
# lib/certificate.rb
class Certificate
...
before :save do
student_name = self.student.full_name
course_name = self.delivery.course.title
generated_at = self.created_at.to_s
self.identifier = Digest::SHA256.hexdigest("#{student_name} - #{course_name} - #{generated_at}")
self.save
end
end
If you run into some errors with the database you need to add the following code to your spec_helper.rb:
Now we need to add a module that will handle the pdf creation for us. We will be using a gem called Prawn. Prawn is a PDF document generator for Ruby.
Let's start by adding it to our Gemfile and install it using bundle install
# Gemfile
gem 'prawn'
(Since we'll only be using Prawn in our pdf generation module, we don't need to load it in our controller.)
Create a new file called certificate_generator.rb in your lib folder and add the following code to it:
# lib/certificate_generator.rb
require 'prawn'
module CertificateGenerator
def self.generate(certificate)
details = {name: certificate.student.full_name,
date: certificate.delivery.start_date.to_s,
course_name: certificate.delivery.course.title,
course_desc: certificate.delivery.course.description}
output = "pdf/#{details[:name]}-#{details[:date]}.pdf"
File.delete(output) if File.exist?(output)
Prawn::Document.generate(output) do |pdf|
pdf.text 'CERTIFICATE'
pdf.text 'This is to certify, that'
pdf.text details[:name]
pdf.text 'has successfully participated in '
pdf.text details[:course_name]
pdf.text details[:course_desc]
pdf.text "Issued on #{details[:date]}"
pdf.text certificate.identifier
end
end
end
Modify your Certificate class by adding an after :create callback to it so that we invoke the CertificateGenerator every time an instance of Certificate is created:
# lib/certificate.rb
require './lib/certificate_generator'
class Certificate
include CertificateGenerator
...
after :create do
CertificateGenerator.generate(self)
end
...
Alright, let's shift our attention back to the controller and the feature tests. We left off with the failing step that tested if 3 certificate was being created, remember?
Then 3 certificates should be generated # features/step_definitions/application_steps.rb:85
expected: 3
got: 0
We need to create a route and tell it that we want to generate a certificate for each Student that has a relationship with that Delivery
# lib/application.rb
get '/courses/generate/:id' do
@delivery = Delivery.get(params[:id])
@delivery.students.each do |student|
c = student.certificates.new(created_at: DateTime.now, delivery: @delivery)
c.save
end
redirect "/courses/deliveries/show/#{@delivery.id}"
end
Before we run our tests again head over to your terminal and create a folder named pdf
$ mkdir pdf
Now run your tests with cucumber features/certificate_generation.feature
We will be getting some conflicts in our tests if we keep the generated pdf's in the pdf folder while running our tests. We need to clear that folder after each run. In our database cleaner strategy, let's add a command to purge all files from that directory after each test:
# features/support/database_cleaner.rb
...
Around do |scenario, block|
DatabaseCleaner.cleaning(&block)
FileUtils.rm_rf Dir['pdf/**/*.pdf']
end
...
And in our certificate_spec.rb we can add an after action to the describe block that tests the generator:
# spec/certificate_spec.rb
...
describe 'Creating a Certificate' do
before do
course = Course.create(title: 'Learn To Code 101', description: 'Introduction to programming')
delivery = course.deliveries.create(start_date: '2015-01-01')
student = delivery.students.create(full_name: 'Thomas Ochman', email: 'thomas@random.com')
@certificate = student.certificates.create(created_at: DateTime.now, delivery: delivery)
end
after { FileUtils.rm_rf Dir['pdf/**/*.pdf'] }
...
That pretty much concludes this part. In the next step we will make the certificate look a little better with a custom background, fonts and colors.