Sunday, July 20, 2008

10 Simple Steps to create taggable extensions in Radiant CMS under Rails 2.1

During my attempts to add a “vacancies” extension in Radiant 0.6.7 I faced several problems, as the tutorial on the website http://wiki.radiantcms.org/Creating_Radiant_Extensions is outdated and applicable not on rails 2.1, after solving those problems I decided to write a blog on how to deal with them step by step through an example creating a “vacancies” tutorial.

First, refer to the website mentioned above to do all the regular steps starting from installing rails till creating our first Radiant application. Next it is required to start generating an extension that enables the admin to easily add/remove/edit job vacancies through a wizard and to appear on the website itself dynamically.

Step 1: A new extension named vacancies will be created through the command

script/generate extension vacancies


Notice that the bold word could be replaced by the name of your new extension, but as for our case its name is vacancies. The following output should be seen:

create vendor/extensions/vacancies/app/controllers
create vendor/extensions/vacancies/app/helpers
create vendor/extensions/vacancies/app/models
create vendor/extensions/vacancies/app/views
create vendor/extensions/vacancies/db/migrate
create vendor/extensions/vacancies/lib/tasks
create vendor/extensions/vacancies/README
create vendor/extensions/vacancies/vacancies_extension.rb
create vendor/extensions/vacancies/lib/tasks/vacancies_extension_tasks.rake
create vendor/extensions/vacancies/spec/controllers
create vendor/extensions/vacancies/spec/models
create vendor/extensions/vacancies/spec/views
create vendor/extensions/vacancies/spec/helpers
create vendor/extensions/vacancies/Rakefile
create vendor/extensions/vacancies/spec/spec_helper.rb
create vendor/extensions/vacancies/spec/spec.opts


Now you can see that a folder named vacancies is created under vendor/extensions this folder contains all the details of such extension (as a separate rails application with its separate apps, model, controller, views...etc).

Step 2: If you are operating from UNIX environment you now have to change the mode of the folder to be enabled for altering, so go to the vendor/extensions directory through the shell and type in

chmod 777 vacancies -R



Step 3: Open the vacancies_extension.rb file in the vacancies folder and change its contents to be:

class VacanciesExtension < Radiant::Extension
version "1.0"
description "Allows you to add a job-vacancies facility to your Web site."
url "http://yourwebsite.com/vacancies"

define_routes do |map|
map.namespace :admin do |admin|
# Directs /admin/vacancies/* to Admin::VacanciesController (/admin/vacancies_controller.rb)
admin.resources :vacancies
end
end

def activate
admin.tabs.add "Vacancies", "/admin/vacancies", :before => "Layouts"
Page.send :include, VacanciesTags
end

def deactivate
admin.tabs.remove "Vacancies"
end
end


Step 4:
Now start the server by typing the command:

script/server -e development

Open the browser on the URL http://localhost:3000 something like that will appear when you choose the extensions link on the top right corner.

Step 5: Next, we will create the model to the extension by typing the command:

script/generate extension_model Vacancies admin/Vacancy

and the output would be
exists app/models/admin
exists spec/models/admin
create app/models/admin/vacancy.rb
create spec/models/admin/vacancy_spec.rb
exists db/migrate
create db/migrate/001_create_admin_vacancies.rb

If you look at the output of the command, you will notice that it generated:
the file for
  • The Vacancy model
  • The rspec test
  • The migration
  • The migration file would be like this:


class CreateVacancies < ActiveRecord::Migration
def self.up
create_table :vacancies do |t|
t.string :title
t.string :ref
t.text :description

t.timestamps
end
end

def self.down
drop_table :vacancies
end
end

Now run the migration by typing:

rake development db:migrate:extensions


Step 6: Now we will create the controller of the extension by the command:

script/generate extension_controller Vacancies admin/vacancies

The output would be like this:

create app/controllers/admin
create app/helpers/admin
create app/views/admin/vacancies
create spec/controllers/admin
create spec/helpers/admin
create spec/views/admin/vacancies
create spec/controllers/admin/vacancies_controller_spec.rb
create spec/helpers/admin/vacancies_helper_spec.rb
create app/controllers/admin/vacancies_controller.rb
create app/helpers/admin/vacancies_helper.rb
The reason behind admin/ part is that the vacancies is managed via the admin.
If you look at the output of the command, you will notice that it generated:
The Admin::LinksController
The functional test for the controller
The folder for the link controller views

Step 7: Now we will create the views,
create file index.html.erb in views/admin/vacancies folder and in it write:

<h1>Listing Vacancies</h1>
<table>
<tr>
<th>Title</th>
<th>Ref.</th>
<th>Description</th>
</tr>

<% for vacancy in @vacancies %>
<tr>
<td><%=h vacancy.title %></td>
<td><%=h vacancy.ref %></td>
<td><%=h vacancy.description %></td>
<td><%= link_to 'Show', admin_vacancy_path(vacancy.id) %></td>
<td><%= link_to 'Edit', edit_admin_vacancy_path(vacancy) %></td>
<td><%= link_to 'Destroy', admin_vacancy_path(vacancy.id), :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>

<br />

<%= link_to 'New Vacancy', new_admin_vacancy_path %>

create file new.html.erb in views/admin/vacancies folder and in it write:

<h1>New Vacancy</h1>

<%= error_messages_for :vacancy %>

<% form_for(@vacancy) do |f| %>
<p>
<b>Title</b><br />
<%= f.text_field :title %>
</p>

<p>
<b>Ref.</b><br />
<%= f.text_field :ref %>
</p>

<p>
<b>Description</b><br />
<%= f.text_area :description %>
</p>

<p>
<%= f.submit "Create" %>
</p>
<% end %>

<%= link_to 'Back', admin_vacancies_path %>

create file edit.html.erb in views/admin/vacancies folder and in it write:

<h1>Editing Vacancy</h1>

<%= error_messages_for :vacancy %>

<% form_for(@vacancy) do |f| %>
<p>
<b>Title</b><br />
<%= f.text_field :title %>
</p>

<p>
<b>Ref.</b><br />
<%= f.text_field :ref %>
</p>

<p>
<b>Description</b><br />
<%= f.text_area :description %>
</p>

<p>
<%= f.submit "Update" %>
</p>
<% end %>

<%= link_to 'Show', @vacancy %> |
<%= link_to 'Back', admin_vacancies_path %>

create file show.html.erb in views/admin/vacancies folder and in it write:

<p>
<b>Title:</b>
<%=h @vacancy.title %>
</p>

<p>
<b>Ref.:</b>
<%=h @vacancy.ref %>
</p>

<p>
<b>Description:</b>
<%=h @vacancy.description %>
</p>


<%= link_to 'Edit', edit_admin_vacancy_path(@vacancy) %> |
<%= link_to 'Back', admin_vacancies_path %>

Step 8: Modify the controller to display in view by putting:

# GET /vacancies
# GET /vacancies.xml
def index
@vacancies = Admin::Vacancy.find(:all)

respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @vacancies }
end
end

# GET /vacancies/1
# GET /vacancies/1.xml
def show
@vacancy = Admin::Vacancy.find(params[:id])

respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @vacancy }
end
end

# GET /vacancies/new
# GET /vacancies/new.xml
def new
@vacancy = Admin::Vacancy.new

respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @vacancy }
end
end

# GET /vacancies/1/edit
def edit
@vacancy = Admin::Vacancy.find(params[:id])
end

# POST / vacancies
# POST /vacancies .xml
def create
@vacancy = Admin::Vacancy.new(params[:admin_vacancy])

respond_to do |format|
if @vacancy.save
flash[:notice] = 'Vacancy was successfully created.'
format.html { redirect_to(@vacancy) }
format.xml { render :xml => @vacancy, :status => :created, :location => @vacancy }
else
format.html { render :action => "new" }
format.xml { render :xml => @vacancy.errors, :status => :unprocessable_entity }
end
end
end

# PUT /vacancies/1
# PUT /vacancies/1.xml
def update
@vacancy = Admin::Vacancy.find(params[:id])

respond_to do |format|
if @vacancy .update_attributes(params[:admin_vacancy])
flash[:notice] = 'Vacancy was successfully updated.'
format.html { redirect_to(@vacancy) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @vacancy.errors, :status => :unprocessable_entity }
end
end
end

# DELETE /vacancies/1
# DELETE /vacancies/1.xml
def destroy
@vacancy = Admin::Vacancy.find(params[:id])
@vacancy.destroy

respond_to do |format|
format.html { redirect_to(admin_vacancies_url) }
format.xml { head :ok }
end
end


Step 9: Restart the server, add vacancies, edit, show and delete them by accessing admin and clicking on the vacancies tab.
Step 10: Now it's time to create custom tags, create a new file in the “app/models” directory of your extension called “vacancies_tags.rb” and add the following code:

module VacanciesTags
include Radiant::Taggable

tag 'vacancies' do |tag|
tag.expand
end

tag 'vacancies:each' do |tag|
result = []
Admin::Vacancy.find(:all, :order => 'title ASC').each do |vacancy|
tag.locals.vacancy = vacancy
result << tag.expand
end
result
end

tag 'vacancies:each:vacancy' do |tag|
vacancy = tag.locals.vacancy
%{#{vacancy.title}-#{vacancy.ref}}
end
end

To activate the tags add this line to the activate method in vacancies_extension.rb file

Page.send :include, VacanciesTags


In the page or snippet or layout add those tags:

<ul>
<r:vacancies:each>
<li><r:vacancy/></li>
</r:vacancies:each>
</ul>