Rails 4 Application Development HOTSHOT (2014)

Chapter 5. Building a Customizable Content Management System

Content is the backbone of the Internet. A Content Management System (CMS) is essentially a software that helps you to easily and effectively manage the content of a website or a web application. There are several perspectives on CMS, with Drupal, Joomla!, and WordPress being the really popular ones. However, people still build tailor-made CMSes, because they want something that fits their needs exactly.

Mission briefing

This project deals with the creation of a Content Management System. This system will consist of two parts:

·        A backend that helps to manage content, page parts, and page structure

·        A frontend that displays the settings and content we just entered

We will start this by creating an admin area and then create page parts with types. Page parts, which are like widgets, are fragments of content that can be moved around the page. Page parts also have types; for example, we can display videos in our left column or display news. So, the same content can be represented in multiple ways. For example, news can be a separate page as well as a page part if it needs to be displayed on the front page. These parts need to be enabled for the frontend. If enabled, then the frontend makes a call on the page part ID and renders it in the part where it is supposed to be displayed. We will do a frontend markup in Haml and Sass.

The following screenshot shows what we aim to do in this project:

Mission briefing

Why is it awesome?

Everyone loves to get a CMS built from scratch that is meant to suit their needs really closely. We will try to build a system that is extremely simple as well as covers several different types of content. This system is also meant to be extensible, and we will lay the foundation stone for a highly configurable CMS. We will also spice up our proceedings in this project by using MongoDB instead of a relational database such as MySQL.

At the end of this project, we will be able to build a skeleton for a very dynamic CMS.

Your Hotshot objectives

While building this application, we will have to go through the following tasks:

·        Creating a separate admin area

·        Creating a CMS with the ability of handling different types of content pages

·        Managing page parts

·        Creating a Haml- and Sass-based template

·        Generating the content and pages

·        Implementing asset caching

Mission checklist

We need to install the following software on the system before we start with our mission:

·        Ruby 1.9.3 / Ruby 2.0.0

·        Rails 4.0.0

·        MongoDB

·        Bootstrap 3.0

·        Haml

·        Sass

·        Devise

·        Git

·        A tool for mockups

·        jQuery

·        ImageMagick and RMagick

·        Memcached

Creating a separate admin area

Since we have used devise for all our projects so far, we will use the same strategy in this project. The only difference is that we will use it to log in to the admin account and manage the site's data. This needs to be done when we navigate to the URL/admin. We will do this by creating a namespace and routing our controller through the namespace. We will use our default application layout and assets for the admin area, whereas we will create a different set of layout and assets altogether for our frontend. Also, before starting with this first step, create an admin role using CanCan and rolify and associate it with the user model. We are going to use memcached for caching, hence we need to add it to our development stack. We will do this by installing it through our favorite package manager, for example, apt on Ubuntu:

sudo apt-get install memcached

Note

Memcached is a key-value cache store that stores small fragments of data.

Prepare for lift off

In order to start working on this project, we will have to first add the mongoid gem to Gemfile:

Gemfile

gem 'mongoid'4', github: 'mongoid/mongoid'

Bundle the application and run the mongoid generator:

rails g mongoid:config

You can edit config/mongoid.yml to suit your local system's settings as shown in the following code:

config/mongoid.yml

development:

  database: helioscms_development

  hosts:

  - localhost:27017

  options:

  test:

  sessions:

    default:

      database: helioscms_test

      hosts:

       - localhost:27017

    options:

      read: primary

      max_retries: 1

      retry_interval: 0

We did this because ActiveRecord is the default Object Relationship Mapper (ORM). We will override it with the mongoid Object Document Mapper (ODM) in our application. Mongoid's configuration file is slightly different from the database.yml file for ActiveRecord. The session's rule in mongoid.yml opens a session from the Rails application to MongoDB. It will keep the session open as long as the server is up. It will also open the connection automatically if the server is down and it restarts after some time. Also, as a part of the installation, we need to add Haml to Gemfile and bundle it:

Gemfile

gem 'haml'

gem "haml-rails"

Engage thrusters

Let's get cracking to create our admin area now:

1.    We will first generate our dashboard controller:

2.  rails g controller dashboard index

3.  create  app/controllers/dashboard_controller.rb

4.  route  get "dashboard/index"

5.  invoke  erb

6.  create    app/views/dashboard

7.  create    app/views/dashboard/index.html.erb

8.  invoke  test_unit

9.        create    test/controllers/dashboard_controller_test.rb

10.invoke  helper

11.create    app/helpers/dashboard_helper.rb

12.invoke    test_unit

13.create      test/helpers/dashboard_helper_test.rb

14.invoke  assets

15.invoke    coffee

16.      create      app/assets/javascripts/dashboard.js.coffee

17.invoke    scss

      create      app/assets/stylesheets/dashboard.css.scss

18.          We will then create a namespace called admin in our routes.rb file:

19.config/routes.rb

20.namespace :admin do

21.  get '', to: 'dashboard#index', as: '/'

end

22.          We have also modified our dashboard route such that it is set as the root page in the admin namespace.

23.          Our dashboard controller will not work anymore now. In order for it to work, we will have to create a folder called admin inside our controllers and modify our DashboardController to Admin::DashboardController. This is to match the admin namespace we created in the routes.rb file:

24.app/controllers/admin/dashboard_controller.rb

25.class Admin::DashboardController < ApplicationController

26.  before_filter :authenticate_user!

27. 

28.  def index

29.  end

end

30.          In order to make the login specific to the admin dashboard, we will copy our devise/sessions_controller.rb file to the controllers/admin path and edit it. We will add the admin namespace and allow only the admin role to log in:

31.app/controllers/admin/sessions_controller.rb

32.class Admin::SessionsController < ::Devise::SessionsController

33. 

34.  def create

35.  user = User.find_by_email(params[:email])

36.  if user && user.authenticate(params[:password]) && user.has_role? "admin"

37.    session[:user_id] = user.id

38.    redirect_to admin_url, notice: "Logged in!"

39.  else

40.    flash.now.alert = "Email or password is invalid / Only Admin is allowed "

41.  end

42. end

end

Objective complete – mini debriefing

In the preceding task, after setting up devise and CanCan in our application, we went ahead and created a namespace for the admin.

In Rails, the namespace is a concept used to separate a set of controllers into a completely different functionality. In our case, we used this to separate out the login for the admin dashboard and a dashboard page as soon as the login happens. We did this by first creating the admin folder in our controllers. We then copied our Devise sessions controller into the admin folder. For Rails to identify the namespace, we need to add it before the controller name as follows:

class Admin::SessionsController < ::Devise::SessionsController

In our route, we defined a namespace to read the controllers under the admin folder:

  namespace :admin do

end

We then created a controller to handle dashboards and placed it within the admin namespace:

  namespace :admin do

    get '', to: 'dashboard#index', as: '/'

  end

We made the dashboard the root page after login. The route generated from the preceding definition is localhost:3000/admin. We have already seen how to add roles in our previous project, hence we assumed here that the admin role can be created. We ensured that if someone tries to log in by clicking on the admin dashboard URL, our application checks whether the user has a role of admin or not. In order to do so, we used has_role from rolify along with user.authenticate from devise:

if user && user.authenticate(params[:password]) && user.has_role? "admin"

This will make devise function as part of the admin dashboard. If a user tries to log in, they will be presented with the devise login page as shown in the following screenshot:

Objective complete – mini debriefing

After logging in successfully, the user is redirected to the link for the admin dashboard:

Objective complete – mini debriefing

Creating a CMS with the ability to create different types of pages

A website has a variety of types of pages, and each page serves a different purpose. Some are limited to contact details, while some contain detailed information about the team. Each of these pages has a title and body. Also, there will be subpages within each navigation; for example, the About page can have TeamCompany, and Careers as subpages. Hence, we need to create a parent-child self-referential association. So, pages will be associated with themselves and be treated as parent and child.

Engage thrusters

In the following steps, we will create page management for our application. This will be the backbone of our application.

1.    Create a model, view, and controller for page. We will have a very simple page structure for now. We will create a page with title, body, and page type:

2.  app/models/page.rb

3.  class Page

4.    include Mongoid::Document

5.   

6.    field :title, type: String

7.    field :body, type: String

8.    field :page_type, type:  String

9.    

10.  validates :title, :presence => true

11.  validates :body, :presence => true

12. 

13.  PAGE_TYPE= %w(Home News Video Contact Team Careers)

end

14.          We need a home page for our main site. So, in order to set a home page, we will have to assign it the type home. However, we need two things from the home page: it should be the root of our main site and the layout should be different from the admin. In order to do this, we will start by creating an action called home_page in pages_controller:

15.app/models/page.rb

16.  scope :home, ->{where(page_type: "Home")}

17. 

18. 

19.app/controllers/pages_controller.rb

20.  def home_page

21. 

22.  @page = Page.home.first rescue nil

23. 

24.  render :layout => 'page_layout'

end

25.          We will find a page with the home type and render a custom layout called page_layout, which is different from our application layout. We will do the same for the show action as well, as we are only going to use show to display the pages in the frontend:

26.app/controllers/pages_controller.rb

27.  def show

28.  render :layout => 'page_layout'

  end

29.          Now, in order to effectively manage the content, we need an editor. This will make things easier as the user will be able to style the content easily using it. We will use ckeditor in order to style the content in our application:

30.Gemfile

31.gem "ckeditor", :github => "galetahub/ckeditor"

32.gem 'carrierwave', :github => "jnicklas/carrierwave"

33. 

34.gem 'carrierwave-mongoid', :require => 'carrierwave/mongoid'

35. 

gem 'mongoid-grid_fs', github: 'ahoward/mongoid-grid_fs'

36.          Add the ckeditor gem to Gemfile and run bundle install:

37.helioscms$ rails generate ckeditor:install --orm=mongoid --backend=carrierwave

38. 

39.  create  config/initializers/ckeditor.rb

40.  route  mount Ckeditor::Engine => '/ckeditor'

41.  create  app/models/ckeditor/asset.rb

42.  create  app/models/ckeditor/picture.rb

43.  create  app/models/ckeditor/attachment_file.rb

      create      app/uploaders/ckeditor_attachment_file_uploader.rb

44.          This will generate a carrierwave uploader for CKEditor, which is compatible with mongoid.

45.          In order to finish the configuration, we need to add a line to application.js to load the ckeditor JavaScript:

46.app/assets/application.js

//= require ckeditor/init

47.          We will display the editor in the body as that's what we need to style:

48.views/pages/_form.html.haml

49.  .field

50.    = f.label :body

51.    %br/

    = f.cktext_area :body, :rows => 20, :ckeditor => {:uiColor => "#AADC6E", :toolbar => "mini"}

52.          We also need to mount the ckeditor in our routes.rb file:

53.config/routes.rb

mount Ckeditor::Engine => '/ckeditor'

54.          The editor toolbar and text area will be generated as seen in the following screenshot:

Engage thrusters

55.          In order to display the content on the index page in a formatted manner, we will add the html_safe escape method to our body:

56.views/pages/index.html.haml

  %td= page.body.html_safe

57.          The following screenshot shows the index page after the preceding step:

Engage thrusters

58.          At this point, we can manage the content using pages. However, in order to add nesting, we will have to create a parent-child structure for our pages. In order to do so, we will have to first generate a model to define this relationship:

helioscms$ rails g model page_relationship

59.          Inside the page_relationship model, we will define a two-way association with the page model:

60.app/models/page_relationship.rb

61.class PageRelationship

62.  include Mongoid::Document

63.  field :parent_idd, type: Integer

64.  field :child_id, type: Integer

65. 

66.  belongs_to :parent, :class_name => "Page"

67.  belongs_to :child, :class_name => "Page"

end

68.          In our page model, we will add inverse association. This is to check for both parent and child and span the tree both ways:

69.  has_many :child_page, :class_name => 'Page', :inverse_of => :parent_page

  belongs_to :parent_page, :class_name => 'Page', :inverse_of => :child_page

70.          We can now add a page to the form as a parent. Also, this method will create a tree structure and a parent-child relationship between the two pages:

71.app/views/pages/_form.html.haml

72.  .field

73.    = f.label "Parent"

74.    %br/

75.    = f.collection_select(:parent_page_id, Page.all, :id, :title, :class => "form-control")

76..field

77. 

78.    = f.label :body

79. 

80.    %br/

81. 

82.    = f.cktext_area :body, :rows => 20, :ckeditor => {:uiColor => "#AADC6E", :toolbar => "mini"}

83. 

84.    %br/

85. 

86.  .actions

87. 

88.    = f.submit :class=>"btn btn-default"

89. 

    =link_to 'Cancel', pages_path, :class=>"btn btn-danger"

90.          We can see the the drop-down list with names of existing pages, as shown in the following screenshot:

Engage thrusters

91.          Finally, we will display the parent page:

92.views/pages/_form.html.haml

93.  .field

94.    = f.label "Parent"

95.    %br/

    = f.collection_select(:parent_page_id, Page.all, :id, :title, :class => "form-control")

96.          In order to display the parent, we will call it using the association we created:

97.app/views/pages/index.html.haml

98.  - @pages.each do |page|

99. 

100.     %tr

101.    

102.     %td= page.title

103.    

104.     %td= page.body.html_safe

105.    

  %td= page.parent_page.title if page.parent_page

Objective complete – mini debriefing

Mongoid is an ODM that provides an ActiveRecord type interface to access and use MongoDB. MongoDB is a document-oriented database, which follows a no-schema and dynamic-querying approach. In order to include Mongoid, we need to make sure we have the following module included in our model:

include Mongoid::Document

Mongoid does not rely on migrations such as ActiveRecord because we do not need to create tables but documents. It also comes with a very different set of datatypes. It does not have a datatype called text; it relies on the string datatype for all such interactions. Some of the different datatypes are as follows:

·        Regular expressions: This can be used as a query string, and matching strings are returned as a result

·        Numbers: This includes integer, big integer, and float

·        Arrays: MongoDB allows the storage of arrays and hashes in a document field

·        Embedded documents: This has the same datatype as the parent document

We also used Haml as our markup language for our views. The main goal of Haml is to provide a clean and readable markup. Not only that, Haml significantly reduces the effort of templating due to its approach.

In this task, we created a page model and a controller. We added a field called page_type to our page. In order to set a home page, we created a scope to find the documents with the page type home:

scope :home, ->{where(page_type: "Home")}

We then called this scope in our controller, and we also set a specific layout to our show page and home page. This is to separate the layout of our admin and pages.

The website structure can contain multiple levels of nesting, which means we could have a page structure like the following: About Us | Team | Careers | Work Culture | Job Openings

In the preceding structure, we were dealing with a page model to generate different pages. However, our CMS should know that About Us has a child page called Careers and in turn has another child page called Work Culture. In order to create a parent-child structure, we need to create a self-referential association. In order to achieve this, we created a new model that holds a reference on the same model page.

We first created an association in the page model with itself. The line inverse_of allows us to trace back in case we need to span our tree according to the parent or child:

 has_many :child_page, :class_name => 'Page', :inverse_of => :parent_page

  belongs_to :parent_page, :class_name => 'Page', :inverse_of => :child_page

We created a page relationship to handle this relationship in order to map the parent ID and child ID. Again, we mapped it to the class page:

  belongs_to :parent, :class_name => "Page"

  belongs_to :child, :class_name => "Page"

This allowed us to directly find parent and child pages using associations.

In order to manage the content of the page, we added CKEditor, which provides a feature-rich toolbar to format the content of the page. We used the CKEditor gem and generated the configuration, including carrierwave. For carrierwave to work with mongoid, we need to add dependencies to Gemfile:

gem 'carrierwave', :github => "jnicklas/carrierwave"

gem 'carrierwave-mongoid', :require => 'carrierwave/mongoid'

gem 'mongoid-grid_fs', github: 'ahoward/mongoid-grid_fs'

MongoDB comes with its own filesystem called GridFs. When we extend carrierwave, we have an option of using a filesystem and GridFs, but the gem is required nonetheless. carrierwave and CKEditor are used to insert and manage pictures in the content wherever required.

We then added a route to mount the CKEditor as an engine in our routes file. Finally, we called it in a form:

  = f.cktext_area :body, :rows => 20, :ckeditor => {:uiColor => "#AADC6E", :toolbar => "mini"}

CKEditor generates and saves the content as HTML. Rails sanitizes HTML by default and hence our HTML is safe to be saved.

The admin page to manage the content of pages looks like the following screenshot:

Objective complete – mini debriefing

Managing page parts

This task deals with the creation and management of page parts. Page parts are snippets of code, which we will use to render in the page. These parts can be banners, YouTube video channels, photos, polls, and so on. We will create a model for page parts and this will effectively manage content for different parts of our page.

Engage thrusters

We will begin by adding page parts to our CMS system:

1.    Generate the page parts model:

2.  heliouscms$rails g model part title:string content:string meta:string part_type_id:string

3.    invoke  mongoid

4.    create    app/models/part.rb

5.    invoke    test_unit

6.    create      test/models/part_test.rb

  create      test/fixtures/parts.yml

7.    We will now generate the model for part_types:

8.  :~/helioscms$ rails g model part_type name:string

9.    invoke  mongoid

10.  create    app/models/part_type.rb

11.  invoke    test_unit

12.  create      test/models/part_type_test.rb

  create      test/fixtures/part_types.yml

13.          We will now associate the parts and part_types fields:

14.app/models/part_type.rb

15.class PartType

16.  include Mongoid::Document

17.  field :name, type: String

18. 

19.  has_many :parts

20. 

21.end

22. 

23.app/models/part.rb

24.class Part

25.  include Mongoid::Document

26.  field :title, type: String

27.  field :part_type_id, type: String

28.  field :content, type: String

29.  field :meta, type: String

30.  field :user_id, type: String

31. 

32.  belongs_to :page

33.  belongs_to :part_type

34. 

end

35.          Let's add some parts by firing up the Rails console:

36.helioscms$ rails c

37.Loading development environment (Rails 4.0.0)

38.1.9.3-p327 :001 > part = Part.new

39. => #<Part _id: a833e8207277751d1a000000, title: nil, part_type_id: nil, content: nil, meta: nil, user_id: nil,>

40.1.9.3-p327 :002 > part.title = "YouTube Channel"

41. => "YouTube Channel"

42.1.9.3-p327 :003 > part.save!

43.  MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 3.5448ms

44.  MOPED: 127.0.0.1:27017 INSERT       database=project5_development collection=parts documents=[{"_id"=>BSON::ObjectId('a833e8207277751d1a000000'), "title"=>"YouTube Channel"}] flags=[]

45.                         COMMAND      database=project5_development command={:getlasterror=>1, :w=>1} runtime: 1.3837ms

 => true

46.          We will now add part types to the part form so that we can save it during their creation:

47.app/views/parts/_form.html.haml

48.  .field

49.    = f.label :part_type_id

    = f.select(:part_type_id, options_from_collection_for_select(PartType.all, :id, :name), {:prompt => 'Please Choose'}, :class => "form-control")

50.          The following code shows what the full form looks like:

51.views/plans/_form.html.haml

52. 

53.= form_for @part do |f|

54.  - if @part.errors.any?

55.  #error_explanation

56.  %h2= "#{pluralize(@part.errors.count, "error")} prohibited this part from being saved:"

57.  %ul

58.  - @part.errors.full_messages.each do |msg|

59.  %li= msg

60. 

61.  .field

62.    = f.label :title

63.    = f.text_field :title, :class=>"form-control"

64.  .field

65.    = f.label :part_type_id

66.    = f.select(:part_type_id, options_from_collection_for_select(PartType.all, :id, :name), {:prompt => 'Please Choose'}, :class => "form-control")

67.  .field

68.  = f.label :content

69.  = f.cktext_area :content, :rows => 20, :ckeditor => {:uiColor => "#AADC6E", :toolbar => "mini"}

70.  .field

71.    = f.label :meta

72.    = f.text_field :meta, :class=>"form-control"

73.  .field

74.    = f.hidden_field :user_id, :value=>current_user.id

75.  %br/

76.  .actions

77.    = f.submit 'Save',:class=>"btn btn-default"

    = link_to 'Cancel', parts_path,:class=>"btn btn-danger

78.          In order to call the page_parts extension, we will use the association between page and page_parts.

79.          To see this, we will make a call on page and then call the parts related to that page. As a result, you will see the following screenshot:

Engage thrusters

Objective complete – mini debriefing

We created a page parts model in this task. We also created page part types in order to classify and arrange them. We have also created an association between page and page parts. Hence, we can now assign a single page part to multiple pages. Also, we can see all the parts associated with a page.

At the end of this task, our page part creation page should look like the following screenshot:

Objective complete – mini debriefing

Creating a Haml- and Sass-based template

Now that we have the content defined for our pages, we will start building the frontend. We will keep the frontend as simple as possible and just render the information we have created using our CMS. This task deals with the creation of the frontend and how to separate it from the backend.

Engage thrusters

Let's get started with the process of frontend creation:

1.    Inside your app/assets folder, create a file called front.css. In Rails we have an advantage of asset pipeline. We can use this to separate the frontend assets. We will create a manifest file called front.css and define front end-related stylesheets under it:

2.  app/assets/stylesheets/front.css

3.  /*

4.   * This is a manifest file that'll be compiled into front.css, which will include all the files

5.   * listed below.

6.   *

7.   * indicates any CSS and SCSS file within the lib/assets/stylesheets/front_end, vendor/assets/stylesheets/front_end directory.

8.   * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.

9.   *

10. * You're free to add application-wide styles to this file and they'll appear at the top of the

11. * compiled file, but it's generally better to create a new file per style scope.

12. *

13. *= require_self

 */

14.          We will also place these files under the folder called front_end and create a blank SCSS file under it:

15.helioscms/app/assets/stylesheets$ mkdir front_end

helioscms/app/assets/stylesheets/front_end$ touch structure.scss

16.          We will now load the structure.scss file from our manifest file:

17.app/assets/stylesheets/front.css

18./*

19. * This is a manifest file that'll be compiled into front.css, which will include all the files

20. * listed below.

21. *

22. * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,

23. * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.

24. *

25. * You're free to add application-wide styles to this file and they'll appear at the top of the

26. * compiled file, but it's generally better to create a new file per style scope.

27. *

28. *= require_self

29.*= require font-awesome

30. *= require front_end/structure

31.

 */

32.          We will follow the same procedure to create a front.js manifest file in assets/javascripts.

33.          Let's quickly define a simple three column layout with a header, footer, and a container. We will do this inside our structure.scss file:

34.app/assets/stylesheets/front_end/structure.scss

35.#primary, #content, #secondary {

36.  height: 300px;

37.  padding: 50px 0;

38.}

39. 

40.#container {

41.  width: 1200px;

42.  margin: 0 auto;

43.}

44. 

45.#primary {

46.  float: left;

47.  width: 150px;

48.  background: #eee;

49.  padding-right: 10px;

50.  padding-left: 10px;

51.}

52. 

53.#content {

54.  float: left;

55.  width: 800px;

56.  height: 300px;

57.  background: #ccc;

58.  padding-left: 10px;

59.  padding-right: 10px;

60.}

61. 

62.#secondary {

63.  float: left;

64.  width: 150px;

65.  background: #ddd;

66.  padding-left: 10px;

67.  padding-right: 10px;

68.}

69. 

70.#footer {

71.  clear: both;

72.  padding-top:3px;

73.}

74. 

75.#header {

76.  background: #fff;

77.  height: 100px;

}

78.          We will also add a basic horizontal menu to the application:

79.app/assets/stylesheets/front_end/structure.scss

80.#menu ul

81.{

82.  margin: 0px;

83.  padding: 0px;

84.  list-style-type: none;

85.}

86. 

87.#menu a

88.{

89.  display: block;

90.  width: 8em;

91.  color: white;

92.  background-color: #000099;

93.  text-decoration: none;

94.  text-align: center;

95.}

96. 

97.#menu a:hover

98.{

99.  background-color: #6666AA;

100.   }

101.   #menu li

102.   {

103.     float: left;

104.     margin-right: 0.5em;

}

105.     Our front end layout is currently different. We will make a call on the manifest CSS and JS files in our header and define sections in the page. Also, we should change our extension's layout to Haml because the first layout created is html.erb by default:

106.   app/views/layouts/page_layout.html.haml

107.   !!!

108.   %html

109.     %head

110.       %title

111.       = stylesheet_link_tag "front"

112.       = javascript_include_tag "front"

113.       = csrf_meta_tags

114.     %body

115.       #container

116.         #header

117.    

118.     #menu

119.    

120.     #primary

121.       %p Primary Sidebar

122.         #content

123.    

124.     #secondary

125.       %p Secondary Sidebar

      #footer

126.     Finally, we will add some fonts before we start rendering the content into our page. We will use Google Fonts for simple usage:

127.   views/layouts/page_layout.html.haml

128.    %link{href: "http://fonts.googleapis.com/css?family=Cherry+Swash", rel: "stylesheet", type: "text/css"}/

    %link{href: "http://fonts.googleapis.com/css?family=Flamenco", rel: "stylesheet", type: "text/css"}/

129.     We need to apply this layout only to the limited actions in our controller:

130.   app/controllers/pages_controllers.rb

131.    

132.    

  layout 'page_layout', only: [:home_page, :show]

Objective complete – mini debriefing

In this task, we concentrated on creating a frontend and separated it completely from the backend in terms of look and feel. The advantage of this approach is that we can use any CSS and JS framework we want in the frontend without interfering with the backend. Zurb Foundation, Bootstrap, Less, or any other HTML5 and CSS3 framework can be used for the frontend.

= stylesheet_link_tag "front"  = javascript_include_tag "front"

= javascript_include_tag "front"

Finally, we had to apply this layout to some select actions in our controller. This is because our page controller's actions—index, new, edit, create, and update—are administrator's actions. The actions show and home_page are supposed to display the page. Hence, the layout is applied to only these actions:

  layout 'page_layout', only: [:home_page, :show]

The final output of our work in creating the frontend page is as seen in the following screenshot:

Objective complete – mini debriefing

Generating the content and pages

We have already created the backend and also set the base for the frontend. However, we need to start rendering the content in the front end. We also want a dynamically generated menu from the pages we have created. We want the backend to play well with the front end page we just created. In this task, we will add site-related information that renders all the content on the front end page.

Engage thrusters

The following steps are used to render the content and also add some general site details:

1.    We will first create a scaffold for the site details:

2.    $ rails g scaffold site_detail title:string organization:string address:string facebook:string twitter:string google_plus:string skype:string linkedin:string google_analytics:string telephone:string

3.    invoke  mongoid

4.    create    app/models/site_detail.rb

5.    invoke    test_unit

6.    create      test/models/site_detail_test.rb

7.    create      test/fixtures/site_details.yml

8.    invoke  resource_route

9.    route    resources :site_details

10.  invoke  inherited_resources_controller

11.      create    app/controllers/site_details_controller.rb

12.  invoke    erb

13.  create      app/views/site_details

14.  create      app/views/site_details/index.html.erb

15.  create      app/views/site_details/edit.html.erb

16.  create      app/views/site_details/show.html.erb

17.  create      app/views/site_details/new.html.erb

18.  create      app/views/site_details/_form.html.erb

19.  invoke    test_unit

20.      create      test/controllers/site_details_controller_test.rb

21.  invoke    helper

22.  create      app/helpers/site_details_helper.rb

23.  invoke      test_unit

24.      create        test/helpers/site_details_helper_test.rb

25.  invoke    jbuilder

26.      create      app/views/site_details/index.json.jbuilder

27.      create      app/views/site_details/show.json.jbuilder

28.  invoke  assets

29.  invoke    coffee

30.      create      app/assets/javascripts/site_details.js.coffee

31.  invoke    scss

32.      create      app/assets/stylesheets/site_details.css.scss

33.  invoke  scss

  identical    app/assets/stylesheets/scaffolds.css.scss

34.          Be sure to remove the scaffolds.css.scss file, otherwise it will conflict with our default CSS.

35.          First generate the carrierwave uploader and call the uploaded file:

helioscms$ rails g uploader file

36.          We will then add a field to the SiteDetail model:

37.app/models/site_detail.rb

38.class SiteDetail

39.  include Mongoid::Document

40.  field :title, type: String

41.  field :organization, type: String

42.  field :address, type: String

43.  field :facebook, type: String

44.  field :twitter, type: String

45.  field :google_plus, type: String

46.  field :skype, type: String

47.  field :linkedin, type: String

48.  field :google_analytics, type: String

49.  field :telephone, type: String

50. 

51.  mount_uploader :logo, FileUploader

end

52.          The form to save the site details looks as follows:

Engage thrusters

53.          Now, in order to display these values in our site frontend, we will first make a call on SiteDetail and Page. We will call the SiteDetail and page value:

54.app/controllers/pages_controllers.rb

55. 

56.  before_action :set_site, only: [:home_page, :show]

57. 

58.  layout 'page_layout', only: [:home_page, :show]

59. 

60.def home_page

61.  @page = Page.find_by(page_type: "Home") rescue nil

62.  @pages = Page.all

63. 

64. 

65.end

66. 

67.  # GET /pages/1

68.  # GET /pages/1.json

69.  def show

70. 

71.  @pages = Page.all

72. 

73.end

74. 

75.  private

76.  def set_site

77. 

78.  @site = SiteDetail.first

79. 

end

80.          We will then add these values to the page. First add the page and site value in the title bar:

81."app/" before the path "views/layouts/page_layout.html.haml"

82.views/layouts/page_layout.html.haml

83.  !!!

84.%html

85.  %head

86.    %title

87.      = @page.title

      | #{@site.title}

88.          We will then add site values to the footer, site title, and logo to the header.

89.          We will also call all the pages and loop them in line to generate our page navigation. We will lastly call the body inside the content tag so that the content is rendered there:

90.app/views/layouts/page_layout.html.haml

91.%body

92.  #container

93.  #header

94.  %p= image_tag @site.logo_url.to_s, :alt=>"#{@site.title}"

95.  #menu

96.  %ul

97.  - @pages.each do |page|

98.  %li= link_to page.title, page

99.  #primary

100.     %p Primary Sidebar

101.     #content

102.     %p= @page.body.html_safe

103.     #secondary

104.     %p Secondary Sidebar

105.     #footer

106.     %h3= @site.organization

107.     %p

108.     = link_to '<i class="fa fa-facebook"></i>'.html_safe, @site.facebook, :target=>"blank"

109.      | #{link_to '<i class="fa fa-twitter"></i>'.html_safe, @site.twitter, :target=>"blank"} | #{link_to '<i class="fa fa-linkedin"></i>'.html_safe, @site.linkedin, :target=>"blank"} | #{link_to '<i class="fa fa-skype"></i>'.html_safe, @site.skype, :target=>"blank"}

110.     %p

111.     = @site.address

112.     %br/

  = @site.telephone

Objective complete – mini debriefing

At the end of this task, we created a model for storing the site details. In our controller, we called the site details and assigned the instance variable to actions where we need our site object:

before_action :set_site, only: [:home_page, :show]

private

  def set_site

  @site = SiteDetail.first

end

We called the values of site title, address, and contact details in the footer, and content in the content tag. The advantage of using Haml and Sass is clean markup, with very good indentation and code readability. Sass is like an extension of CSS, which compiles to CSS code. One of the main advantages of using Sass is the usage of a variable to make some of the code reusable. Values such as font sizes, colors, and font-family can easily be made dry using Sass variables. We can do a quick refactor of our Sass using a variable for defining the font-family as follows:

app/assets/stylesheets/front_end/structure.scss

$primary-font: 'Cherry Swash', cursive;

#footer {

  clear: both;

  padding-top:3px;

  font-family: $primary-font;

}

#header {

  background: #fff;

  height: 100px;

  font-family: $primary-font;

}

The other option to keep the CSS code clean is using Less CSS (http://lesscss.org/). This extends the CSS to use features such as functions and mixins too. We can see in the following screenshot how Sass is compiled into and is rendered with the site details also displayed:

Objective complete – mini debriefing

Implementing asset caching

In our CMS there are several kinds of assets. As we build themes we will beautify them with varied JavaScript, CSS, and images. In order to keep the speed of our sites fast in the frontend, we will use asset caching in Rails.

Engage thrusters

The steps to follow will be to cache the content and speed up our site, as follows:

1.    We will first make sure we have the right asset-related gems in Gemfile:

2.  gem 'sass-rails', '~> 4.0.0'

3.  gem 'uglifier', '>= 1.3.0'

gem 'coffee-rails', '~> 4.0.0'

This will enable all kinds of assets and make it ready for production.

4.    We will first enable asset compression inside our production.rb file:

5.  config/environments/production.rb.

6.  config.assets.compress = true

7.    We will continue to edit the same file:

8.  config.assets.compress = true

9.  # Compress JavaScripts and CSS.

10.config.assets.js_compressor = :uglifier

config.assets.css_compressor = :sass

11.          We will now fix the asset folder to tmp/cache/assets:

12.config/environments/production.rb

config.assets.cache = ActiveSupport::Cache::FileStore.new("tmp/cache/assets")

13.          We need to make sure that we run the following step before deployment:

helioscms$rake assets:precompile

14.          In order to make use of memcached in our application, we will add a gem called dalli. The dalli gem is a replacement of the memcache client:

gem 'dalli'

15.          We will configure the cache in our production.rb file:

16.config/environments/production.rb

17.config.cache_store = :dalli_store

config.action_controller.perform_caching = true

18.          We will also add a simple action cache so that we always cache the layout along with the cache. In our pages_controller.rb file, we will add an action caching method:

19.app/controllers/pages_controller.rb

20.def home_page

21. 

22.  expires_in 5.minutes

23. 

24.  sleep 15

25. 

26. 

27.  @page = Page.home.first

28.  cache_client = Dalli::Client.new('localhost:11211')

29. 

30. 

31.  @pages = Page.all

32. 

end

33.          Lastly, we will initiate a cache client and store a cached object in it:

34.app/controllers/pages_controller.rb

35.  @page = Page.find_by(page_type: "Home")

36.  cache_client = Dalli::Client.new('localhost:11211')

37. 

38.  cache_client.set('Home', @page)

  value = cache_client.get('Home')

39.          So, finally our method for home page looks as follows:

40.def home_page

41. 

42.  expires_in 5.minutes

43. 

44.  sleep 15

45. 

46. 

47.  @page = Page.home.first

48.  cache_client = Dalli::Client.new('localhost:11211')

49. 

50. 

51.  cache_client.set('Home', @page)

52. 

53.  value = cache_client.get('Home')

54. 

55. 

56.  @pages = Page.all

57. 

end

Objective complete – mini debriefing

This is an extremely basic caching technique that comes as an extension to Rails. We added memcached for caching the page beforehand. This will help us to speed up our site's frontend. We looked at how to enable memcached for the application using the dalligem. Memcached is a distributed key-value store for storing memory objects like objects, sessions, strings, API, and data calls. In a way it's a technique to store and retrieve temporary data. This data is stored in the form of an array and is quickly retrieved as soon as the page loads, instead of going back to the database and calling the page again. This avoids unnecessary queries and thus reduces the database load. This technique also saves API calls because for applications such as Twitter clients, the number of requests is a very important criterion. Hence, we first installed memcached on our local system. We then enabled Dalli in our production.rb file:

Dalli::Client.new('localhost:11211')

This will directly access the memcached at its port number and initiate a new client object. We first defined the time for cache expiry:

expires_in 5.minutes

In order to keep the transaction fast, the application pre-fills the cache with a value. This, however, can lead to another problem. It is quite possible that the same key is being accessed by multiple clients. Also, if the cache is empty, it could be filled with multiple keys. Hence, memcached gives an option called sleep, which provides a lock for the time defined in sleep:

   sleep 15

If two processes are accessing the same key at the same time, then sleep will tell the other process that the cache is empty, while waiting for the sleep time to finish. Once done, the lock is released and autoassigned to the next value in the queue. In order to store a value in memcached, we used the following set method:

   cache_client.set('Home', @page)

The set method includes the key ('Home') and the value (@page). For retrieving the value of the page, we used a simple get method:

   value = cache_client.get('Home')

This value is retrieved using a key called Home. We must note that action caching is deprecated in Rails 4. We will have to use a third-party caching technique such as memcached to perform action caching. Fragment caching is another strategy where we cache certain parts of a page instead of the entire page, which is also a very commonly used technique and works nicely out of the box in Rails. We will cover this in our later projects.

Mission accomplished

We laid down the foundation for a dynamic CMS in this project. We built a backend admin with a functionality that could create pages and parts. We also created a frontend and did the markup using Haml and SCSS. SCSS is fast to load and easy to manage. It also fits neatly in the Rails asset pipeline. Hence, it is a recommended form of markup with Rails. Some of the ideas we looked at in this project were as follows:

·        We replaced ActiveRecord with Mongoid for our database and model

·        We created an admin area and made devise function only for the admin

·        We saw how namespaces in controllers and routes work

·        We created a self referential association, a parent and a child association on the same model

·        We integrated CKEditor with our application

·        We then created different layouts and manifests for our frontend and backend

·        We looked at how Haml and Sass markup are done and their probable advantages

·        Lastly, we looked at a memcached-based caching strategy for caching our actions

Hotshot challenges

We need to take our CMS to the next level. The exercise contains a few ideas worth trying out:

·        Using nested attributes assign parts to a page.

·        Adding a responsive HTML5 layout to the frontend.

·        Adding validation for the home page. There should always be only one page called home.

·        Adding tests to test content created with CKEditor.