(0.6.0) 4 Little Known Mack Features

I thought it might be fun to start posting about some of the little known features in Mack. There are a treasure trove of them in there, so let’s pick a couple and start there.

render(:url)

This is a great little feature, one of my personal favorites. In your views you can do things like this:

<%= render(:url, "http://www.mackframework.com) %>

that will render the contents of http://www.mackframework.com into your view. You can also do ‘local’ urls.

<%= render(:url, "/users/1") %>

will make an internal request to your application and render the results of “/users/1″ into your view. The optional 3rd parameter to render allows you to do things like set the HTTP method:

<%= render(:url, "/users/1", :method => :post) %>

or add parameters you want to pass to the URL you want to render: 

<%= render(:url, "/users/1", :method => :post, :parameters => {:id => 1}) %>

Error handling in routes

Routing allows you to define controllers/actions you want to catch and handle exceptions that happen in other controllers. Let’s look at the following routes.rb file:

Mack::Routes.build do |r|
  r.resource :users
  r.home_page "/", :controller => :default, :action => :index
  r.handle_errors ArgumentError, :controller => :problems, :action => :arguments
  r.handle_errors DataMapper::ObjectNotFoundError, :controller => :problems, :action => :not_found
  r.defaults
end

What’s going on with r.handle_errors you ask? Well, first we tell the routing system which error we want to capture in our controllers, DataMapper::ObjectNotFoundError, then we tell it which controller and which action we want to handle that error.

When an exception is thrown during a request Mack checks to see if that exception has been registered, if it has been then the request gets forwarded to the defined controller and action for handling. So in the above example if a DataMapper::ObjectNotFoundError is raised, the request will be forwarded to the ProblemsController, not_found action.

One of the really nice things about this is that you have access to the original request, so you can’t get the page the person was trying to access, any parameters that were passed, etc… You also have access the exception itself with the caught_exception method.

Server-side redirects

Let’s be honest, redirects are the most exciting topic, and this is the first of two sections on it! I’ll try to be brief. When dealing with redirects it can sometimes be helpful to do a server-side redirect. The difference, for those who don’t know, between a server-side redirect and a regular redirect is the following. With a regular redirect the response is sent back down to the client’s browser, which then issues another response back to the server for the new url that was specified in the previous response. You’ll often hear this referred to as a client-side redirect. A server-side redirect sends you to a different url on the server, without first sending down a response to the client. Because of this the client only gets one response.

To do a server-side redirect in Mack is very easy. Here’s what a client side redirect in an action would look like:

redirect_to(users_index_url)

To make that a server-side redirect you would simply pass an extra option to the redirect_to method:

redirect_to(users_index_url, :server_side => true)

Redirects in routes

This is a cool little feature. Let’s say that you have changed a few urls around. You want a quick way to redirect people who have bookmarked the old urls to the new urls. You could have a controller that did nothing but that, but that seems like a lot of extra work, and it’s really something that your routing system should be doing for you anyway. Enter redirects in routes.

Mack::Routes.build do |r|
  r.old_foo "/my_old_foo", :redirect_to => "/my_new_foo", :status => 301
end

From now on anything comes in to “/my_old_foo” will be redirected to “/my_new_foo” with a status of 301.

Release 0.5.5

Finally, Mack 0.5.5 is released! In addition to some great bug fixes, there is now a new rendering engine, support for automatic mime-types, the ability to register new mime-types, Markaby and Haml support, and much much more! It’s a great release.

To find out more about the new rendering engine check out these two posts:

http://www.mackframework.com/2008/05/20/the-new-rendering-engine/

http://www.mackframework.com/2008/05/20/055-adding-pdfwriter-plugin-support-tutorial/

Changelog:

  • INCOMPATIBILITY NOTICE: Ripped apart the ENTIRE rendering engine and rewrote it from the ground up. This means that wherever you are using ‘render’ calls in your views and controllers need to be changed. The new format is render(type, value, options). Examples: render(:action, :show), render(:url, “http://www.mackframework.com”), etc…
  • INCOMPATIBILITY NOTICE: Files named *.xml.erb need to be changed to *.xml.builder to use the Builder::XmlMarkup library. If you leave the .erb extension on there the file will be run through Erubis.
  • INCOMPATIBILITY NOTICE: <%= @content_for_layout %> is now <%= yield_to :view %>
  • Added Markaby support.
  • Added Haml support.
  • Added content_for and yield_to methods in views.
  • Erubis compiled templates are now cached for increased performance.
  • Added render(:inline) and render(:template) support.
  • Refactored, and reorganized some files to clean up the gem.
  • Fixed bug with cookies not merging with configured app_config parameters.
  • Added mime-types. The ‘Content-Type’ header is now being set based on the format that is requested. Default is text/html.
  • Fixed r.defaults in routes so they are always the last routes to be checked, no matter where they are placed in the routes definitions.
  • render(:url) now recognizes ‘local’ urls and tries to run them through the app, mimicking most headers from the original request.
  • Added ‘options’ banners to the mack and mack_ring_server binaries.
  • gem: genosaurus 1.1.8
  • gem: mack_ruby_core_extensions 0.1.28
  • gem: markaby 0.5.0
  • gem: mack-data_mapper 0.5.5

Preview (0.5.5): Adding PDF::Writer Plugin Support Tutorial

Ok, let’s take the new rendering system out for a spin, shall we? Let’s add the PDF::Writer library to our Obligatory Blog Demo application. If you haven’t followed this demo you should do that now.

Let’s start by requiring the gem in our system. Open up your gems.rb file found in config/initializers and let’s add the gem:

require_gems do |gem|
  gem.add "pdf-writer", :version => "1.1.8", :libs => "pdf/writer"
end

Great! We’ve told Mack we want to use the ‘pdf-writer’ gem, version ‘1.1.8′, and we want to automatically require the file ‘pdf/writer’. Now, let’s install the gem:

$ sudo rake gems:install

See how easy this is? We’ve installed the gem, required the libraries, now we’re ready to write our plugin.

$ rake generate:plugin name=render_pdf

That should generate a few files/folders in our vendor/plugins directory. Let’s open up vendor/plugins/render_pdf/lib/render_pdf.rb and let’s start coding.

What we want to do is create a new Mack::Rendering::Engine::Base class so that when we call render(:action) it will have a new engine to render the view file as a PDF.

We’ll examine each section in a minute, but for now, let’s type this into our render_pdf.rb file:

module Mack
  module Rendering
    module Engine
      class Pdf < Mack::Rendering::Engine::Base

        def render(io, binding)
          @_pdf = ::PDF::Writer.new
          self.view_template.instance_variable_set("@_pdf", @_pdf)
          eval(io, binding)
          @_pdf.render
        end

        def extension
          :pdfw
        end

        module ViewHelpers
          def pdf
            @_pdf
          end
        end

      end
    end
  end
end
Mack::Rendering::ViewTemplate.send(:include, Mack::Rendering::Engine::Pdf::ViewHelpers)
Mack::Rendering::Engine::Registry.register(:action, :pdf)

Ok, so on line #4 we extended Mack::Rendering::Engine::Base. This will give us access to a view methods, and will allow us to write to a very simple API. The only method you are absolutely required to implement is the render method. As we can see on line #6, we did just that.

First thing we do in the render method is instantiate a new PDF::Writer class and assign it to an instance variable. We then set that instance variable into the Mack::Rendering::ViewTemplate object we have. We do that because the way the PDF::Writer object works you need to constantly reference the instance of the writer to do your work. Example:

@_pdf.text "Hello World", :font_size => 24, :justification => :center

On line #9 we eval the io and the binding we’ve been given. The io will be contents of the view file we have disk, as a String, and the binding will be that of the Mack::Rendering::ViewTemplate object we’ve been given.

In the extension method we tell the system that are files are going to be found with the extension, pdfw. Another example of this would be the Erubis engine which declares its extension as erb.

The Mack::Rendering::Engine::Pdf::ViewHelpers module we’ve declared on line #17 is there to hide the @_pdf instance variable with a nicer pdf method. On line #27 we include this module into Mack::Rendering::ViewTemplate so it has access to it.

Finally, and most importantly, we need to register the new engine we’ve built with the system. We do that on line #28 with this bit of code:

Mack::Rendering::Engine::Registry.register(:action, :pdf)

That’s saying whenever someone calls render(:action), consider me as an engine to render that. The way the selection of which engine to use is done, is very simple. First come first serve. The engines are in an array, and the first one to have a file with its extension on disk wins. Plain and simple.

Now, let’s see all this in action. Let’s add PDF support for our ’show’ page.

Open up views/posts/show.html.erb and add the following line:

<%= link_to("pdf", posts_show_url(:id => @post, :format => :pdf)) %>

That will give us a link that looks like ‘/posts/:id.pdf’. This will, of course, go to our PostsController and the show action. This method does not need to be altered. That’s right, you heard me. It does not need to change. Mack will handle the appropriate content-type headers for you. Just another great feature in 0.5.5.

Create a file called views/posts/show.pdf.pdfw. I know this might look a little weird, what with ‘pdf.pdfw’, but here’s the reason why. That’s break the file name down into its three parts. ’show’ is the name of the action. ‘pdf’ is the format of the request, think also html, xml, etc… ‘pdfw’ is the engine we want to use. If we hated ourselves we could do this all in erb with a file called show.pdf.erb, but why would we want to do that?

Anyway, let’s dump this nice block of code into our show.pdf.pdfw file:

pdf.select_font "Times-Roman"
pdf.fill_color(Color::RGB::Red)
pdf.text @post.title, :font_size => 24, :justification => :center
pdf.fill_color(Color::RGB::Black)
pdf.text "by #{@post.email}", :font_size => 12, :justification => :center
pdf.with_options(:font_size => 10, :justification => :left) do |p|
  p.text "\n\n"
  p.text @post.body
  p.text "\n\n"
  p.text "Created at: #{@post.created_at}"
  p.text "Updated at: #{@post.updated_at}"
end

Since this is not a tutorial on this particular gem, I’m not going to go into what all that does. Instead, let’s just have a look at it in action.

Fire up your server:

$ rake server

And go to: http://localhost:3000. If you don’t already have a post created, create one. Now click on the show link. You should have a link on your page that says ‘pdf’ click on that link. Voila! You should be seeing a wonderfully formatted PDF right now!

Congrats! You’ve built a plugin and a new rendering engine for Mack. Now, go crazy!

The source for all this can be found at: http://github.com/markbates/mack_blog_demo/tree/master

Preview (0.5.5): The New Rendering Engine

In the latest version of Mack the rendering engine has been completely re-written from the ground up. With this comes some new features, some incompatibility, and most importantly, extensibility. Let’s jump on in and see what we can expect with this release.

Incompatibility

  • Gone is <%= @content_for_layout %> in layouts. In is <%= yield_to :view %>.
  • Gone is render(options_hash) in controllers/views. In is render(type, value, options_hash)
    Examples:
    render(:action => :new) is now render(:action, :new)
    render(:url => “http://www.mackframework.com”, :parameters => {:message => “hi”}) is now render(:url, “http://www.mackframework.com”, :parameters => {:message => “hi”})
  • Gone is *.xml.erb. In is *.xml.builder
Let’s quickly talk about how these incompatibilities have come about. First there were several bugs that needed to be addressed with the rendering engine. For example, if you set an instance variable in a view, it wasn’t available in the layout. That’s a pain if you want to do things like programatically set the page title. There were also ‘hacks’ used to do things like render xml using the Builder::XmlMarkup library. It wasn’t clean, but it worked. Finally, the rendering engine itself wasn’t that extensible. All of that has now changed.

Render Me Softly

In the new rendering engine there are two parts to the system, Mack::Rendering::Type::* objects and Mack::Rendering::Engine::* objects. Let me explain the difference.

Mack::Rendering::Type::*

A type is something like :action, :text, :inline, :url, etc… That is the type of thing you want to do. I want to render an action. I want to render a url, etc… There are classes for each of these types, and you can easily add your own. These types do all sorts of work before they pass it off to an engine, if need be. For example, in the case of Mack::Rendering::Type::Partial the render method does the work of inserting an ‘_’ in the appropriate place, so the file can found.

<%= render(:partial, "users/form") %> # => "users/_form"

Once that happens it tries to find an engine to process the partial.

Mack::Rendering::Engine::*

An engine does the actual work of rendering the io, with the binding of the Mack::Rendering::ViewTemplate object, it’s been given by the results of the render method in the Mack::Rendering::Type::* object. Engine examples would be, Erubis (ERB), Markaby, Haml, and Builder::XmlMarkup, all of which are included with Mack in this release. New engines can easily be plugged in and registered with the system.

Coming soon a tutorial on adding PDF::Writer support using the new system.