Rails Test Prescriptions Blog

Keeping Your Application Healthy Since 2008

Overriding Refinery, Extending Globalize, and Pow!

Here are a few random tips that have come up while working on an application using Refinery CMS, Globalize, and who the heck knows what else…

Extending stuff from Refinery

Refinery is about as extendable as a Rails framework gets. Although it provides you with a lot of default behavior, it’s not that hard to override.

Refinery is made up of a collection of Rails Engines. For example, the main display of pages is in the refinerycms-pages sub-gem. If you want to override a controller or view element in these gems (which include Page and User), Refinery provides a Rake task refinery:override which copies the file into your main application, effectively overriding the default version.

It’s a little different if you create your own data types. Refinery generates a Rails Engine for each type, which means that all the controller, model, and view files are actually in your vendor/engines directory and accessible to you (although they do require a development server restart when you change them…) However, a lot of the default CRUD behavior is defined by Refinery in the refinerycms-core gem via the Refinery crudify method, so if you want to change some default behaviors, you either need to look up and copy the existing Refinery behavior, or try and implement your new behavior as a before filter (preferred, if possible).

If you want to extend one of the existing Refinery models, most notably Page or User, you can re-open them to monkey patch in your app, but it took us a few tries to learn the magic require statement:

  require Refinery::Pages::Engine.config.root + 'app' + 'models' + 'page'

Globalize

The Globalize3 gem is used by refinery to manage i18n content. Globalize works by setting up a separate translation table tied to your model. In your model, you specify which columns are translated (plus you need to specify a migration to create a translation table — Globalize has a shortcut for that):

  translate :description, :text

Behind the scenes, when you access the ActiveRecord object, Globalize joins the object with the row in the translation table corresponding to the current locale, and allows you to transparently access the translated versions of the fields corresponding to that locale. Very cool. (Note that once you have added the description and text fields to the translation table, you no longer need them in the main model table.)

A couple of things to be clear on:

Setting locale

I’m not completely sure whether this comes from Refinery or Globalize, but there seem to be two sources of truth as to the what locale is used. Rails uses ::I18n.locale, while Refinery uses Thread.current[:globalize_locale]. We had some difficulty with what appeared to be confusion over these being in synch, and at least temporarily have fixed the problem with a before filter, along the lines as the Rails guide on i18n suggests:

  before_filter :set_locale
  def set_locale
    ::I18n.locale = params[:locale]
    Thread.current[:globalize_locale] = params[:locale]
  end

That seems to solve our problems with a single source of truth, but it does feel a little string-and-sealing-wax, so there’s probably a better way.

Adding functionality to Translation models

One nice side effect of the way Globalize manages locale-specific content is that you aren’t limited to things that are technically translations, anything that is different in each locale can be added to the translate list. For example, if you need to track the author of each particular translation, you can add a :user_id column to the translates call, and place the corresponding column in the MODEL_translations database table and you are good to go.

Except for two things. First, if you want to actually treat that user_id as an ActiveRecord association, you need to add a belongs_to method to the Translation model. Unfortunately, that model is created by Globalize using some metaprogramming magic and doesn’t have a physical location that you can use to update the class.

Happily, this is Ruby, and you can always re-open a class. It took me a few tries to figure out exactly the best place to open the class so that I could actually be opening the right class, but I eventually got to this:

  class MyModel
    translates :text, :user_id
     
    class Translation
      belongs_to :user
    end
  end

Yep. Nested classes. In Ruby. In essence, the outer class is doubling as a module, which implies the translation class could be defined elsewhere, but I’m not sure that’s worth more times to track down.

The other issue is that the Globalize command to automatically create a translation table based on the translates command for a class does not like it if you have non-string columns, such as user_id, in the list of columns to translate. You need to create the translate table using a normal migration. This is even true if you add the user_id column later on after the initial creation, a user running the migrations from scratch after the user_id is added will still have problems.

Pow

I started using Pow to manage my development server. I like it quite a bit, it gives me what I used to have with the Passenger preference pane, but with a support for RVM and less overhead. I recommend the powder gem for a little more command line support.

Anyway, I needed to debug a Facebook issue, which requires me to have my development environment temporarily use the .com address that Facebook knows about in order to use Facebook connect from my system. But how?

Here’s how I did it using Pow:

  • Use the Mac application Gas Mask to change my /etc/hosts file to a file where http://www.myapp.com points to localhost. Gas Mask just makes managing these files easier, you could just edit the file directly

  • Create a symlink in the .pow directory used by Pow named “www.myapp”, and pointing to my application directory. With Powder you can do this from the command line in the root of the application with the command powder link http://www.myapp. This is fine to do even if you have another symlink from the .pow directory to the app

  • Create a .powconfig file in your home directory. Add the line export POW_DOMAINS=dev,com, save the file. This tells POW to try and apply itself to .com requests from your machine. Pow seems to be smart enough to pass through .com requests that don’t match it’s files, so you shouldn’t be cutting yourself off from the entire internet.

  • Restart Pow. Unfortunately this is not like restarting Pow for your one dev server, you need to restart the whole Pow system so that the config file is re-read. As of right now, the only way to do this is to go to the application monitor, find the Pow app, and quit it from there, or the command-line equivalent using ps -ax | grep pow to find the PID and then killing it from the command line. (If I get around to it, this is my pull request for Powder)

And that should be it — http://www.myapp.com should be picked up by the Pow server and run in your dev environment, making Facebook connect happy.

When you are done, comment out the line in the .pow config and restart Pow again. Also change the /etc/hosts file back. You don’t need to remove the symlink in the .pow directory, but you can.

That’s all I’ve got today. Hope that helps somebody.

Advertisements

6 responses to “Overriding Refinery, Extending Globalize, and Pow!

  1. Sam Stephenson May 6, 2011 at 5:40 pm

    Hey Noel-

    Thanks for writing about Pow!

    I don’t recommend adding com to POW_DOMAINS. If you re-install or upgrade Pow, the installer will read your setting and add a /etc/resolver/com file that will redirect all *.com DNS queries to Pow’s resolver (see this pull request for an example of what will happen).

    Pow 0.3 will be released in the next few days and adds support for a special POW_EXT_DOMAINS option where you can safely list domains like com. It won’t serve DNS requests for these domains and won’t add them to /etc/resolver.

  2. Charles de Bueger May 28, 2011 at 12:17 pm

    Thanks for the info on translations. Do you happen to know where a getting started type guide for i18n for refinery is? Have you used refinerycms-translations? All I can find is info on how I can submit some translations of the refinery engine itself. I’ve been trooping through the refinery source for quite a while now, and lots of what you say above is brand new. I’ve been looking in the wrong places obviously…

  3. noelrap May 31, 2011 at 9:14 am

    Most of the translation stuff in Refinery is built on top of the Rails support and Globalize3. The Rails guide is at http://guides.rubyonrails.org/i18n.html. I don’t think there’s great documentation of Globalize3 floating around anywhere.

  4. Michael June 7, 2011 at 6:39 am

    Hi Noel, I managed to override all the MVC bits of refinery, but I want to expose routes.rb as I want the admin routed to /admin rather than /refinery – any tips on achieving this?

    Thanks for the tips as well…

    Cheers, Mike.

  5. Anatortoise House September 10, 2011 at 1:29 pm

    Thanks for your post. Refinery also relies on the routing-filter gem which localizes the url_for generators and changes your routes. Similar to the problem you mentioned about two sources for the “true” locale, if you are merging Refinery with an existing app, you may find you have two different approaches to handling locale info in your urls that can conflict, along the lines of the different approaches outlined in the Rails I18n guide.

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: