Noel Rappin Writes Here

Overriding Refinery, Extending Globalize, and Pow!

UncategorizedNoel Rappin6 Comments
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 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 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 -- 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.