Brandon Harris |

Rails Simple Page Caching

Memcache is great, and Redis is even better. But you are still bound to a database, and running services. We are so caught up in making everything dynamic, that I think we don't often realize that we are still serving up mostly static html. The good news is that Rails has static html caching built in, and I rarely notice anyone talking about it.

The scenario: You want to input your page content via an administrative interface, but this data rarely changes. Another way to look at it, the content only changes if you or someone who administers the site has a need to change it. Rails page caching is your friend.

Let's say you have this code:

 1 # application.rb
 2 config.action_controller.page_cache_directory = "#{::Rails.root}/public/cache"
 3 
 4 # routes.rb
 5 match '/:url' => 'pages#show'
 6 
 7 # Simple page class page.rb
 8 class Page < ActiveRecord::Base
 9 end
10 
11 #simple pages controller pages_controller.rb
12 class PagesController < ApplicationController
13   caches_page :show, :cache_path => lambda {|controller| controller.params}
14 
15   def show
16     @page = Page.where(:url => params[:url]).first
17     render :file => File.join(::Rails.root,"public","404.html") if @page.nil?
18   end
19 
20 end

Now, let's skip ahead real quick and write up a quick show template for this dynamic page.

1   -# show.html.haml
2   %h2= @page.title
3   = @page.body.html_safe

The good stuff occurs in the pages_controller.rb file. Line #13 states: "Cache pages for the show action, use the params for the file name". This is a little lazy on my part, since the params should only contain the :url, this will work. If there are more complex params, you will need to modify that singleton. Since we set the page_cache_directory to a cache folder in public, we can expect to see our generated files appearing in there.

The best part about this setup is that it works, and no plugins are needed. You will need to modify your webserver to look in the cache directory first, then proceed to the rails application.

What if you make changes? Wont you see the generated version instead of the updated version? In this case, yes. You will need to write a cache sweeper, a very trivial task. Here's a version of one that I found, but it may not be what you want. I am not making use of ActionController::Caching::Sweeping, because I have more global cache sweeping needs

 1 # config/application.rb
 2 config.active_record.observers = :cache_sweeper
 3 
 4 # app/models/cache_sweeper.rb
 5 class CacheSweeper < ActiveRecord::Observer
 6   # Add any other models that Page may depend on here
 7   observe Page
 8 
 9   def self.sweep_cache!
10     Dir.glob("#{ActionController::Base.page_cache_directory}/**/*.html") do |f|
11       File.delete(f)
12     end
13   end
14 
15   def after_save(*_)
16     self.class.sweep_cache!
17   end
18 
19   def after_destroy(*_)
20     self.class.sweep_cache!
21   end
22 
23 end

This good because it errors in favor of regenerating files when any change is made. This is bad if you are extremely worried about performance and don't like it when any of your users touches your database to regenerate a "dynamic" page.

If you are really concerned about page performance on mostly static sites, check out jekyll, this blog runs on it. Generate your pages once and never worry again. However, if you have requirements where people need to edit a page from your web app, rails caching is your free friend.

JSON Templates in Rails

Sometimes, you just don't want to do call the "to_json" message on an object. It could be that the object has a lot of attributes, the json needs are very simple, or you simply don't like the default structure.

Fortunately, there is a very easy solution: JSON templates. This is built in, and you might have not even known it.

In my case, I wanted to plot a bunch of people and their locations on a map. I had the following model:

1 class Person < ActiveRecord::Base
2   # ...
3 
4   def geo_location
5     [latitude,longitude].compact.join(",")
6   end
7 
8   # ...
9 end

I have the following controller:

1 class PeopleController < ApplicationController
2   respond_to :html, :json
3 
4   def index
5    @people = Person.find(:all)
6    respond_with(@people)
7   end
8   # ...
9 end

Now, create index.json.erb. I tried doing this with haml, but I really didn't like the way I had to handle the necessary whitespace. Your call if you want to use it.

1 [
2 <%- @people.each do |p| %>
3   {
4     "id": "<%=p.id %>",
5     "location": "<%= p.location %>"
6   },
7 <% end %>
8 {}]

There is a wart, the trailing {}. I didn't want to include any logic, as it would slow down this rather large dataset. The whole reason I am doing it this way is for speed. I have no need to display all the attribute data from the "people" table, I just need to plot a heat map. Suggestions are welcome, the side effect is that the last point that is attempted to be plotted, is ignored.

Painless Upgrade to Bundler and Bamboo Stack

I just upgraded this site to use bundler and the heroku Bamboo stack.

The steps were:

1. Remove the .gems file, and create a Gemfile.
2. Patch the Rails 2.3 installation for bundler
3. Run heroku command:

heroku stack:migrate bamboo-ree-1.8.7

4.Run command:

bundle install

5. Commit all changes to git, and push to heroku.

I am really impressed with the ease at which this was done. Heroku continues to impress, and Bundler is the best thing to happen to Ruby since Rails.

Oracle, Rails and Ubuntu 10.04

I recently setup an Ubuntu based server that needed ruby to talk to oracle and mysql. If you are a rails developer, you should be familiar with mysql, but what about Oracle? It isn’t quite as straightforward as you might assume, but it is within the grasp of mere mortals. Please keep in mind that due to changing version numbers, all version numbers are replaced with *. It is up to the reader to properly translate the following commands. Don’t simply copy and paste. I am making the assumption that you are installing on Ubuntu 10.04 (Lucid Lynx) and you are using the 64-bit version.

First Go to:
http://www.oracle.com/technology/software/tech/oci/instantclient/htdocs/linuxx86_64.html

Select the latest versions of:

oracle-instantclient*-basiclite-*.rpm
oracle-instantclient*-sqlplus-*.rpm
instantclient-sdk-*.zip

Replace the * above with the appropriate latest version number.

Now you will need to install the alien package management tool. It converts .rpm to .deb and works amazingly well most of the time.

sudo apt0get install alien

Now run the alien command on the rpms you downloaded from oracle:

sudo alien oracle-instantclient*-basiclite-*.rpm
sudo alien oracle-instantclient*-sqlplus-*.rpm

This will produce .deb files in your current directory. To install the freshly minted pacakges run:

sudo dpkg -i oracle-instantclient-*.deb

Now unzip the sdk contents, you need to extract the header files for everything to work later.

unzip instantclient-sdk-*
cd instantclient-sdk-*/include/
cp *.h /usr/include/oracle/<version>/client64/lib

If you are going to be using the tnsnames.ora file to specify your connections, you will need to set the TNS_ADMIN environment variable. In Debian based distros, you will want to edit /etc/environment. Editing this file ensures that environment variables are set for all users.

Add the following environment variables to your /etc/environment file.

LD_LIBRARY_PATH=/usr/lib/oracle/<version>/client64/lib
TNS_ADMIN="your path to your tnsnames.ora file"

You are all set for interacting with oracle from your Ubuntu server. To test just run:

sqlplus64

If you would like to enable your rails app to use oracle as well you have a few more steps.

sudo gem install ruby-oci8

If that fails, you may need to specify to LD_LIBRARY_PATH:

sudo gem install LD_LIBRARY_PATH=$LD_LIBRARY_PATH ruby-oci8

Pay close attention to errors if you receive them, they are actually very clear and coherent. Pay close attention to your environment variables and the inclusion of the header files.

If you have reached this point you can test your oci8 connection:

1 irb
2 irb(main):001:0>require 'rubygems'
3 => true
4 irb(main):002:0>require 'oci8'
5 => true
6 irb(main):003:0>oracle_connection = OCI8.new(user,password,schema)
7 => #<OCI8:RTD_ODBC>
8 irb(main):004:0> oracle.exec("select 1 from dual")
9 => #<OCI8::Cursor:0x7f9ea9a72430>

Now if you are planning on using Ruby on Rails with Oracle, you will need to install the activerecord adapter. I used the enhanced adapter, but the choice is yours.

sudo gem install activerecord-oracle_enhanced-adapter

Now the final part, your database.yml file should look like the following:

1 production: 
2   adapter: oracle_enhanced 
3   database: <oracle database>
4   username: <oracle username>
5   password: <oracle password>

The oracle enhanced adapter has some more config options for your database.yml file, please refer to:
http://wiki.github.com/rsim/oracle-enhanced/

Active Scaffold with Paperclip

I always recommend Active Scaffold to fellow Rails developers. There is a learning curve, but it can cut a lot of development time out of your clients custom CMS.

Paperclip has pretty much dominated the Rails attachment market for the past year. My company has migrated to it, and there are countless tutorials and testimonials regarding it’s ease of use.

I am assuming that you are using the latest version of Rails (2.3.5 as of this writing), Active Scaffold, and Paperclip. First we will install all the necessary components, then we will configure the rails app.

Install Active Scaffold:

script/plugin install git://github.com/activescaffold/active_scaffold.git

Active Scaffold is big so this takes a while.

Install Paperclip:

script/plugin install git://github.com/thoughtbot/paperclip.git

As of 6/14/2010 this is no longer needed, the bridge has been moved to the core
Install the Active Scaffold Paperclip Bridge:

Lets create a basic person model:

script/generate model Person

To create controllers in the admin namespace, I don’t use the rails controller generator. I don’t like cluttering up my projects with files I will never use, since I employ Active Scaffold for 99% of my admin interfaces. This is my personal preference, you may do this anyway that you wish.

mkdir app/controllers/admin && touch app/controllers/admin/people_controller.rb

Lets configure the “Person” migration for paperclip.

 1 class CreatePeople < ActiveRecord::Migration
 2   def self.up
 3     create_table :people do |t|
 4       t.string :name, :avatar_file_name, :avatar_content_type
 5       t.integer :avatar_file_size
 6       t.datetime :avatar_updated_at
 7       t.timestamps
 8     end
 9   end
10 
11   def self.down
12     drop_table :people
13   end
14 end

Configure the Person model to accept the paperclip attachment.

1 class Person < ActiveRecord::Base
2   has_attached_file :avatar, :styles => {:thumbnail => "75x75#"}
3 end

Setup your routing for the admin namespaced “People” controller.

1 ActionController::Routing::Routes.draw do |map|
2   map.namespace :admin do |admin|
3     admin.resources :people, :active_scaffold => true
4   end
5   map.connect ':controller/:action/:id'
6   map.connect ':controller/:action/:id.:format'
7 end

Create a layout file for admin, I use Haml, and I recommend that you do too.

1 !!!STRICT
2 %html{'xmlns' => "http://www.w3.org/1999/xhtml", :lang => "en"}
3   %head
4     = javascript_include_tag :defaults
5     = active_scaffold_includes
6   %body
7     #content
8       = yield

Finally, configure your active scaffolded controller. The key here is to include the attachment model name, in this case “avatar”.

 1 class Admin::PeopleController < ApplicationController
 2   layout 'admin'
 3   active_scaffold :person do |config|
 4     #restrict all columns to these three
 5     config.columns = [:name, :avatar]
 6 
 7     #include multipart for create and update forms
 8     config.create.multipart = true
 9     config.update.multipart = true
10   end
11 end

When you are done you can go to “/admin/people” and you will be greeted with the familiar active scaffold interface. Create a new Person and upload the avatar to be used for that Person. Your result should be similar to this image:
as_paperclip_done.png

You should download the source for this from my git repo here.

Viewing Gem Rdoc

This is painfully obvious if you actually read about the tools that you use. I couldn’t find anything on my first couple of google searches, so hopefully this can fill in that gap:

gem server

Your gem server will probably start on port 8808, so go to localhost:8808 and you will see a list of your installed gems. From here on, you probably know what to do.

Flickr Down, flickraw killed my app!

10/29/2009 Update

Per Mael’s (Flickraw author) response, I was able to provide a better fix.

1 FlickrawOptions = { :lazyload => true, :timeout => 2 }
2 require 'flickraw'

You can see more options in the documentation.

10/21/2009 Original Post
As of the writing of this, Flickr has been down off and on for almost 2 hours.

I noticed that a handful of our websites weren’t working. I traced the problem back to flickraw, there was one line in the ApplicationController:

1 require 'flickraw'

Not the best coding practice, but aside, it still needs to work without stalling on this line. In another project we had it enclosed in a begin end statement, but that didn’t do any good since no exceptions were being raised.

I temporarily patched it with the following code (don’t judge me).

1 begin
2   $STATUS = Timeout::timeout(2) {
3     require 'flickraw'
4   }
5 rescue
6 end

Then in the application controller where I make a call to the api, I use the $STATUS global. Gasp! A global variable in Ruby! Yes, get over it. It fixes an issue, and your clients will be happy to know that you no longer rely on Flickr for their site to work.

Nested Model Forms and attachment_fu

I think that a lot of Rails developers have moved on to Paperclip . Most of us started with attachment_fu , and I am willing to wager that we still have projects floating around using attachment_fu.

I updated one of these older projects to 2.3.4, and created a new form utilizing the built in nested form handling as of 2.3.

My models look like this:

 1 class Article < ActiveRecord::Base
 2   has_one :report
 3   accepts_nested_attributes_for :report
 4 end
 5 
 6 class Report < ActiveRecord::Base
 7   belongs_to :article
 8   has_attachment(
 9     :storage => :file_system,
10     :file_system_path => 'public/reports',
11     :max_size => 2.megabytes,
12     :content_type => 'application/pdf'
13   )
14   validates_as_attachment
15 end

The idea is that an article can have a pdf attachment. The view code for the nested form should look like this:

1  <%form_for :article, :url => admin_articles_url, :html => {:multipart => true} do |f| %>
2     <%=f.label :title%>
3     <%=f.text_field :title%>
4     ...
5     <%f.fields_for :report do |e|%>
6       <%=e.label :report %>
7       <%=e.file_field :uploaded_data%>
8     <%=f.submit 'Create'%>
9   <%end%>

Then in your parameters, you should see params[:article][:report_attributes], and by having accepts_nested_attributes in the Article model, Rails takes care of the rest.

However, I received this error:

Report(#70166977052900) expected, got HashWithIndifferentAccess(#70167015178720)

I google around, and found this thread which doesn’t provide a solution, in fact no where could I find the solution. So I did some of my own digging, and I noticed that my parameters looked like this:

params[:article] and params[:report]

I wasn’t seeing the expected:

params[:article][:report_attributes]

So given this information, I tried this:

1   <%form_for :article, :url => admin_articles_url, :html => {:multipart => true} do |f| %>
2     <%=f.label :title%>
3     <%=f.text_field :title%>
4     ...
5     <%f.fields_for :report_attributes do |e|%>
6       <%=e.label :report %>
7       <%=e.file_field :uploaded_data%>
8     <%=f.submit 'Create'%>
9   <%end%>

Voila! Problem solved. I haven’t dug into the code for Rails and Attachment_Fu to see why this needs to be done, but if you are having this problem and need it fixed, just append “_attributes” to your nested child model name in the view.

BlipTV API In Ruby

I recently needed to do some work for a client to integrate their site with blip. The blip tv wiki points ruby developers to a decent project.

However, my needs were a lot more intricate than what the current gem covers. I forked the project and added in the functionality that I needed (as is the case for most open source needs). Along the way, I found out that bliptv has a json api. I couldn’t resist and converted the bliptv gem to leverage the json api. Probably a faux pas, but it is faster than rss/xml parsing.

What I really needed was a way to pull private video embed codes, from an account that we had access to. The only way to do this was to login, save the cookie and then query the api. Yes, it feels hacky, but it serves the purpose.

Accomplishing this code in your project is as simple as the following code:

1   client = BlipTV::Base.new({:username => "barack_obama", :password => "michellexoxo"})
2   videos = client.all_videos_from_login

Where videos is an array of BlipTV::Video object.

See my fork on github if you are interested.

Why aren't you using Active Scaffold?

I do a lot of work creating admin interfaces in rails. A lot of it is redundant, so it would be nice to use a canned solution. Django has an admin interface built into every project. This makes a developers life easier.

I thought “Wouldn’t it be nice to have a plugin that read all my table associations and provided a simple crud interface?”

That is exactly what Active Scaffold tries to be.

How cool is active scaffold? Well, let’s pretend that we want to create the following tables:

 1 class CreatePeople < ActiveRecord::Migration
 2   def self.up
 3     create_table :people do |t|
 4       t.string :first_name, :last_name, :zip_code
 5       t.integer :title_id
 6       t.timestamps
 7     end
 8   #... and so on ...
 9   end  
10 
11 class CreateTitles < ActiveRecord::Migration
12   def self.up
13     create_table :titles do |t|
14      t.string :title
15      t.timestamps
16    end
17 end

Let's setup the following relationship in the Person model:

1 class Person < ActiveRecord::Base
2   belongs_to :title
3 end

We would have to write views for People and for Titles to create a robust and useful admin interface. With Active Scaffold you only have to do the following:

1. Include default javascript and active scaffold includes in the layout file:

2. Add the following to your people_controller and your title_controller respectively:

1 <%=javascript_include_tag :defaults %>
2 <%=active_scaffold_includes %>

And your done! ActiveScaffold takes care of the rest. The more skeptical among your are sneering right now, “what if I want to change the way the form is laid out, or what if I want to select a field from the title model instead of selecting an object….” You can do all of this with active scaffold and it isn’t super difficult.

To set the column order and have title appear as a drop down:

1 class TitlesController < ApplicationController
2   active_scaffold :titles
3 end
4 
5 class PeopleController < ApplicationController
6   active_scaffold :people
7 end

I recently had a record that was nested inside another record (such as title is nested inside people here) that had more columns than the horizontal space allowed. Since Active Scaffold uses ajax, a scrollbar wasn’t an option, so I found this:

1 active_scaffold :nested do |config|
2   config.subform.layout = :vertical
3 end

This sets this form vertical instead of horizontal in all forms that it belongs to. (That’s a mouthful).

Check it out, I am sure that you will find it useful.

Populate Object Instance Variables From Hash

I recently needed to write a class in ruby that I wanted to behave similarly to an ActiveRecord based class, but without the database ties. Particularly, I wanted to do this:

1 r = Record.new({:attribute1 => "a", :attribute2 => "b"})

It’s not too hard to hard code a simple class with attributes that are defined by a Hash, but in the pursuit of elegance, I wanted it to be more generic. I pulled up this little snippet:

1   attributes.each do |k, v|
2     if k.include?("(")
3       multi_parameter_attributes << [ k, v ]
4     else
5       respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
6     end
7   end

This is your most basic metaprogramming technique in ruby. Check if the object responds to the message, and if so, set it.

Below is all that I needed to accomplish my task:

1   def initialize(attributes={})
2     attributes.each do |k,v|
3       respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(NoMethodError, "Unknown method #{k}, add it to the record attributes")
4     end
5   end

Tags

  • e (1)