rails3 engine asset pattern? run generator on boot

If any rails-ists are reading, interested in feedback if this makes sense.

In Rails2 “engine” hack, your plugin assets would be copied to the local app in a special directory on app startup, and then referred to with a special :plugin key in rails asset helpers.

This was hacky. Rails3 doesn’t do it anymore. So what do you do with gem/plugin provided assets in Rails3?  Some people hack in middleware to make your Rails app serve the plugin assets directly from the plugin directory. There are various other ways to try and do the same thing, get the Rails app to serve assets directly from the plugin directory–rather than ‘middleware’, configuring or over-riding other parts of Rails3.

But it seems generally acknowledged that that’s a bad hack, that you probably wouldn’t want to do in production. It also makes it a lot harder to use various “asset packing” methods to combine/minify several CSS and/or javascript files in one file (something that will be built into Rails3).  I don’t like fighting with Rails like this, and I don’t like doing something different in development than production — if development were serving assets directly from the plugin but production weren’t, it makes it too easy to deploy something that was working fine in development but breaks in production because you forgot to add some assets.

So another simple approach is to provide a generator for your gem/plugin/engine that copies over all the assets to the local app. That actually works pretty well for ‘end-users’. But it’s annoying for developement of the gem/plugin/engine itself — when you’re hacking on the assets, you have to be constantly re-running your install generator to see the changes? Bah.

So here’s what I figured. Yeah, stick with the generator approach, generating the plugin/gem/engine assets into the local app. DO make sure you put them all in a subdirectory named after your gem, to avoid confusion. DO put some comments on the top warning the end-user that they probably don’t want to edit these files, they were generated. DO provide a generator that only generates assets, so someone can refresh their assets whenever they want without needing to run the rest of your ‘install’ generator, if you have one.  If you have a main “install” generator, it can call out to a secondary asset only generator no problem, “generate(‘my_gem:assets’)”.

But okay, it’s still annoying when you’re a developer hacking on those engine-provided assets to have to be constantly re-running that generator to see your changes.

So, okay, I thought, why can’t we do what Rails2 engines did here, in the sense of automatically copying those gem assets over to the local app on startup? We’d probably not want to do that in ‘production’ environment, but only in ‘development’. So we maybe want some line in environments/development.rb to do it. Can we invoke a Rails3 generator in development.rb?  Turns out, yes, if you do a couple things.

First thing is your generator needs a few explicit ‘require’ lines in it, that it doesn’t ordinarily need when run from the command line with “rails generate…”.  I guess “rails generate” implicitly ‘requires’ the infrastructure, but if you want to be able to call your generator programmatically, you need to be explicit, at the top of your generator file:

require 'rails/generators'
require 'rails/generators/base'

No problem. Now in your development.rb (or presumably anywhere else you want to programmatically invoke), you do have to specifically require/load the generator file so it’ll be available. In my gem, I already had a constant defined with the current runtime location of the installed gem, although it still ends up being a kind of ugly line:

require File.join(Blacklight.root, "lib", "generators", "blacklight", "assets_generator.rb")

And then you just need to programmatically invoke the generator class you just loaded. Took me a bunch of poking around to do that, ultimately having to consult the Thor spec files themselves, wasn’t actually documented anywhere else, and trying to look at the code itself wasn’t getting me anywhere. Then I remembered, hey, the specs might provide an example, and they did, leading me here (with some arguments):

Blacklight::AssetsGenerator.start(["--force", "--quiet"])

Okay, stick those in my environments/development.rb, and all my assets are silently copied over (over-writing what was there before) on startup. Matching Rails2 engine workflow for development better.  You still need to restart the app to see the changes, but you don’t need to remember the generator name and call it.

Curious what anyone else thinks of this pattern.

Advertisement

5 thoughts on “rails3 engine asset pattern? run generator on boot

  1. I was thinking about this tonight… I think you’re right that, for Rails 3.0, there needs to be a generator that will install these files into the receiving app’s assets directory. I’m not so sure how I feel just yet about using a generator to automatically do this in development. Something like this works for Compass, because files need to be compiled, but for static assets, I want the least amount of fuss possible.

    I usually follow the testing pattern of having my dev/testing app within my rspec tests, so I have a directory structure like Rails.root/spec/rails_app. What I was toying with was setting config.paths.public within my test app, like this:
    config.paths.public.javascripts = File.expand_path(File.join(Rails.root, '..', '..', 'lib', 'generators', 'templates', 'assets', 'javascripts'))
    I haven’t gotten this working just yet, though, so take that with a grain of salt.

    Apparently, for Rails 3.1, this might be the approach you should use.

  2. Thanks carpeliam. On a related note you mention — do you then have a test_app actually in your checked in source for your engine/gem? This is something we’ve had some divided opinions about among developers of our gem, some suggest that the test_app should not be in checked in source, but itself generated after checkout when you want to run tests. Do you have a sense of how typical it is to have a test_app in checked in source for a gem/engine?

  3. I’ve built a few related Rails 3 gems around engines, and I’ve always included the test_app in my tests. This makes sense to me, because often I’m testing the app to make sure that my features work within it. Governor is a project I’ve been working on that follows this pattern. Platformatec’s popular devise gem is the same way, though test_unit instead of rspec. Aslak Hellesoy, responsible for Cucumber, says to have a test app that uses your engine, and that has cucumber and rspec tests, though he doesn’t specify if the test app should sit within your app or not.

    It looks like this will be a little easier in 3.1 as well, so if you’re comfortable with working with edge, it might be worth it to try that… or at least, it makes for a good pattern example.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s