Archive for September, 2007

Merb Plugins? Oh yeah… they’re pretty frickin’ cool too

So there’s something kind of weird about having an entire plugin system depend on scraping a wiki page and subversion, but it doesn’t have to work that way. See, Ruby already has this great packaging system called gems. They have built-in version control, dependencies, and a distribution model. The only flaw is that actually putting a gem together was formerly a difficult process.

With that as a preamble, let’s dive into how Merb plugins work. Let’s start with how you make a plugin:

Making a Plugin

merb ––generate-plugin merb_foo

Note the prefix “merb_” — it’s not mandatory, but is highly recommended that merb plugins be prefixed with merb_, since they will be living in the gemspace with many other, similarly named gems.

Next, go into Rakefile and modify the metadata at the top for the plugin’s name, version, and so on. Throw your name into the LICENSE (by default, MIT), and update the README.

Inside of merb_foo.rb, you get a block that tests whether we’re running inside Merb. For plugins that require Merb, make sure to pop your code inside there.

Merb automatically provides you with a config hash in the Merb::Plugins.config[:merb_foo], which you can use to pass information from your plugins back into Merb, and vice versa.

Finally, you can use Merb::Plugins.add_rakefiles to add any rakefiles you would like to get added to the Merb app. It’s all pretty simple, and everything is blocked out and commented for you.

Once you’re done, you can do rake package and get your gem all made. Install/release the gem using normal install/release procedures, and then…

Using a Plugin in Your App

This part is ridiculously simple. There’s a file in your generated Merb app called config/dependencies.rb. Inside it, you’ll run:

dependencies "merb_foo", "merb_bar"

or

dependencies "merb_foo", "> 0.5"

or

dependencies "merb_foo" => "> 0.5", "merb_bar" => ">= 0.6.1"

Any of these will work. Dependencies are loaded before the app is loaded in. If you need a dependency to wait until after the app has loaded, you can enclose it in the Merb::Server.after_app_loads that’s already provided in the generated dependencies.rb file.

One more thing…

While it’s nice to be able to just use gems for everything, you might want to burn your plugins into your app for easy deployment. If that is what you wish, your wish is our command. In the root, simply run:

gem install merb_foo -i deps

That wonderful, already existing command, will install the gem into the deps folder in your app. Through the magic of Merb, the deps folder becomes an alternate gem repository, so you can deploy the merb directory to a remote server (even without access to the gem repository), and it’all Just Work. Cool huh?

Merb’s Parts are pretty rocking too

Once upon a time, there was a thing called “Rails Components.” They were heavyweight and nobody used them, so the powers that be took the axe to them. They live no more.

That said, there is a real need for a lightweight component system in MVC frameworks. Something that:

  • works like Rails partials, in that you can reuse common, existing code
  • use existing controller variables like params and session to do different things on the basis of what’s happening in the current request
  • most importantly, not force you to give up the C in MVC simply because you want to reuse some code outside of a single controller

Merb parts are the simple answer to that problem. First of all, Merb parts work just like Merb controllers; you get a new filter stack, layouts (if needed), and the full gamut of render functions that you have in normal Merb controllers. You also get to share all of the regular controller variables (request, params, cookies, headers, session, response). And because Merb’s philosophy is to have actions return strings, you can use the render methods, or any other string manipulation in your action, without fear of running into a “double-render” issue.

Here’s a simple example:

class Foo < Application
def foo
render
end
end

class Bar < Merb::PartController
before :foo

def foo
@x = 1
end

def baz
render
end
end

app/views/foo/foo.erb
<%= part Bar => :baz %>

app/parts/views/bar/baz.erb
<%= params[:hello] %>
<%= @x %>

In the example above, the view in the controller proper calls out to the part. The part runs just like a regular controller, so it has a before filter, which sets the @x instance variable to 1. It then calls the baz method, which renders its action. In the action, you’ll have access to the params from the original controller. You’ll probably want to handle that stuff in the part controller, but this just demonstrates that it percolates all the way down.

Because parts reuse the existing controller stuff, they’re very fast, and not the red-headed stepchild of controllers. Just like mailers, we benefit from the existence of AbstractController, which gives us the full filter stack, the ability to throw to stop the filter (and return an associated string as the result of the part), and all the other niceties of controllers.

Good stuff, huh? I have some more things to post about Merb, but soon… a comprehensive example!

Merb’s Mailer is Awesome (if I do say so myself)

A few months ago, when I first took a look at Merb, I noticed a distinct lack of support for email. There was a simple wrapper around MailFactory (a Ruby library that is, imho, superior to TMail, and actually documented to boot), but nothing like Rails’ ActionMailer.

That said, ActionMailer suffers from some very serious deficiencies, which makes most Rails users rue the day when the pointy-headed boss throws some “simple” mail requirement at them. In fact, while most Rails users expect writing mails to be roughly equivalent to writing regular controllers and actions, ActionMailer barely resembles the much more manicured ActionController.

In fact, the company I work at Procore, maintains what is essentially a fork of ActionMailer with some of these deficiencies resolved. The problem is that because we dig deep into Rails internals, a Rails upgrade also requires tweaking to our fork.

Some simple examples:

  • ActionMailer “controllers” go in the models directory, but their views are mixed in with regular views
  • ActionMailer instance variables are not assigned and passed into ActionMailer views
  • ActionMailer does not support layouts
  • While ActionMailer does support some simple magic with mime-types, if you do anything that is not exactly the default, you are required to specify the parts of your email using tortured syntax.
  • The ActionMailer syntax for attaching files is also a bit tortured and not particularly well documented:

      attachment “application/octet-stream” do |a|
      a.body = attachment2.data
      a.filename = attachment2.name
      end

So my agenda was to clean up all of these pain points, in the context of a brand-new Merb::MailController. At the time, there was no way to subclass the base Controller class without getting all the request gunk along with it, but Ezra was more than willing to create an AbstractController class, which contained the parts of the Controller mechanism that were not related to request-handling.

It turned out to be a good deal, because Ezra later used it to create Merb’s “Parts,” partials that also have the lightweight AbstractController implementation along for the ride (more on that in another post).

With the AbstractController up and running, I proceeded to hack together a MailController implementation. Merb::MailController:

  • controllers go into the app/mailers directory.
  • views go into the app/mailers/views directory.
  • instance variables are assigned to their views, just like any other Merb controller.
  • supports layouts by default, just like any other Merb controller. Layouts go in the app/mailers/layouts directory.
  • supports HTML/Plaintext emails very well.
    • render_mail :text => :foo, :html => :bar will look for a foo.text.ext and a bar.html.ext. If either is not found, it’ll fall back to just foo.ext and bar.ext
    • render_mail :foo is the equivalent of render_mail :text => :foo, :html => :foo
    • render_mail is the equivalent of render_mail :text => :foo, :html => :foo when you are inside the :foo action
    • If you pass a string instead of a symbol in, it will render the actual string, so you can use regular render methods (since Merb render methods just return a string).
      Example: render :text => "FOO", :html => "&lt;p&gt;FOO&lt;/p&gt;"
    • You can also mix and match: render :text => "FOO", :html => :bar
    • Finally, you can also do stuff like render_mail :template => {:html => "foo/bar", :text => "foo/baz"}
  • supports attachments trivially:
    • attach File.open("foo")
    • which is the same as attach "foo"
    • attach [File.open("foo"), File.open("bar")]
    • which is the same as attach ["foo", "bar"]
    • attach File.open("foo"), "name_i_want_to_call_it"
    • attach [[File.open("foo"), ["name", "image/png"]], [File.open("bar"), ["bar_name", "application/octet-stream"]]]
    • which is the same as attach "foo" => ["name", "image/png"], "bar" => ["bar_name", "application/octet-stream"]
    • attach StringIO.new("Some data I want to send"), "the_file_name"
    • attach StringIO.new("Some data I want to send") => ["the_file_name"], StringIO.new("More data") => ["another_file"]
    • Because Merb already includes Mime::Types, you do not have to include a mime-type unless you want to override the default. Merb will automagically determine the mime-type and pass it along.

      This means that the most common case will be something like attach "foo", which will simply attach the file in the file-system called “foo”, with the name “foo”, and its mime-type based upon its file-type. And multiple-files is no more complex: attach ["foo", "bar"], which will do the same for both files.

The bottom line is that all the major pain-points in ActionMailer are closed up. And this is all done in 79 lines of code, thanks to Merb’s excellent architecture. In fact, this is a testament to the general excellence of the Merb architecture. Merb is not “a lightweight Rails”, as many often say.

It is true that it’s lightweight, but it’s not true that it’s missing giant swaths of useful features (and this will become less and less true as people start to write plugins for merb, which will provide functionality like Rails-style form helpers that are currently missing). In fact, there are a number of cases, like mail support, where the Merb version is dramatically more functional.