So as I mentioned in the last part of rendered, we built a fairly simple rendering engine using Rails’ built-in templating engine ERB. And that’s fine, but there’s still a lot of code in our controllers and Rails has a full featured rendering system called ActionView, which not only includes the rendering methods but tons of helpers to build pre-filled forms, etc.
So first let’s add ActionView to our Gemfile:
1 2 3 4 5 6 |
ruby '2.1.0' source 'https://rubygems.org' gem 'rack', '~>1.5.2' gem 'actionpack', '~> 4.1.0.beta1' gem 'actionview', '~> 4.1.0.beta1' gem 'railties', '~> 4.1.0.beta1' |
And after a bundle install
, ActionView is available. It also adds the “erubis” gem, which is better, safer, and faster ERB handling than the simple built-in ERB handler.
We’re also going to add the railtie to our application.rb just so that ActionView has access to the various Rails configuration files:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
require File.expand_path('../boot', __FILE__) require 'rails' require 'action_controller/railtie' require 'action_view/railtie' Bundler.require module GenericCMS class Application < Rails::Application # you could put some custom stuff here if you wanted config.secret_key_base = "rails really wants this to be defined" end end |
And lastly, add the renderer to our controller:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
class MainpageController < ActionController::Metal include AbstractController::Rendering include ActionController::UrlFor include Rails.application.routes.url_helpers #required to tell UrlFor which routes to use include ActionController::Redirecting include ActionView::Rendering include ActionController::Rendering include ActionController::ImplicitRender include AbstractController::Callbacks include ActionController::Rescue include ActionController::Instrumentation before_filter :doing_stuff rescue_from ZeroDivisionError, with: :divided_by_zero prepend_view_path(File.join(Rails.root,'app','views')) def doing_stuff @hi = 'sup' end def index end def show if params[:id] == "1" # do nothing, thus implicit render else render text: "<h1>404 Not Found</h1>", status: 404 end end def divided_by_zero self.response_body = "<h1>You divided by zero. This is not allowed as you may break all of physics." end end |
In a Metal, you need to define where the views are location, such the line in #18. But notice that we got rid of all that rendering code. And our pages now render!
Going over all the different helpers would take far too long, so I’m just going to mention them as I get into them. The Guide on Form Helpers is a better resource. However, there is a couple of big notes to add:
First, HTML safety. Nasty insertable HTML and Javascript to steal your cookies and delete your pages is everywhere, so we don’t want to trust anything from the database. So every String that passes through ActionView is sanitized unless it’s marked as safe. So for example, if we change our @hi variable to '@hi = '
, then what gets outputted in our index is: Front Page! <h1>sup</h1> It actually will convert so that it displays the HTML. This is a safety first option, but if for some reason you can trust the output, then you can use #.html_safe in the ERB to mark as safe:
sup
1 |
<h1>Front Page! <%= @hi.html_safe %></h1> |
This marks it as safe to straight output vs. sanitizing.
And secondly, layouts! Should be obviously useful as any webpage has the same headers and footers, so let’s render the inside from the outside. To enable, just change to ActionView::Layouts:
1 2 3 4 5 6 7 8 9 10 |
class MainpageController < ActionController::Metal include AbstractController::Rendering include ActionController::UrlFor include Rails.application.routes.url_helpers #required to tell UrlFor which routes to use include ActionController::Redirecting include ActionView::Layouts include ActionController::Rendering include ActionController::ImplicitRender ... |
And you’re good to go. While you can use the ActionController#layout method to define custom layouts, by default it uses the name of the controller to use as a layout. (If a layout doesn’t exist, then it will just render straight pages.) You put layouts in the app/views/layouts folder, separate from the controller views.
So let’s make a really simple layout:
1 2 3 4 5 6 7 8 9 10 11 12 |
<!DOCTYPE html> <html> <head> <title>GenericCMS</title> </head> <body> <h1>GenericCMS!</h1> <p><a href="/">Index page</a></p> <p><a href="/page/1">Page page</a></p> <%= yield %> </body> </html> |
And that’s it! With the magic filename, you have included a layout. Note that for layouts to render their insides, you must include a yield, which returns the inside of the layout.
Just to finalize things up, we’re going to set the controller to ActionController::Base now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class MainpageController < ActionController::Base before_filter :doing_stuff rescue_from ZeroDivisionError, with: :divided_by_zero def doing_stuff @hi = '<h1>sup</h1>' end def index end def show if params[:id] == "1" # do nothing, thus implicit render else render text: "<h1>404 Not Found</h1>", status: 404 end end def divided_by_zero self.response_body = "<h1>You divided by zero. This is not allowed as you may break all of physics." end end |
ActionController::Base is nothing but the Metal, but with all the batteries included. So it includes ALL the modules (and more!) that we’ve mentioned, preloads the view paths, and all that good stuff.
And just as a small detail, most Rails apps don’t have their controllers from the Base, but include an ApplicationController and use a hierarchical format:
1 2 3 4 5 6 7 8 9 10 11 12 |
class ApplicationController < ActionController::Base before_filter :doing_stuff rescue_from ZeroDivisionError, with: :divided_by_zero def doing_stuff @hi = '<h1>sup</h1>' end def divided_by_zero self.response_body = "<h1>You divided by zero. This is not allowed as you may break all of physics." end end |
1 2 3 4 |
class MainpageController < ApplicationController def index end ... |
Because of hierarchy, this allows the filters to act for all inherited controllers, and is typically a good idea to have all controllers inherit from this.
And that’s ActionController. We have now built a very, very fancy method of generating webpages, but there’s no meat here. We’ll be going over the basics of the form generation soon, but to do that, we need something to save against, so we’ll be working next on persistence.
If you want to get ahead in the next few lessons, read up on the basics of SQL commands. (We’ll eventually be using Postgres for this project.) However, unlike HTML where you need to know the basic tags and such, you technically don’t need to know anything about SQL since Rails/ActiveRecord completely abstracts it out. However, it’s good to know that the abstraction is just a fancy SQL generator, so knowing why it works gets a leg up.
As always, working code is available here: https://github.com/ROFISH/ruby_for_programmers/tree/master/15