Tag Archive: Rails


Rails has this shiny feature. Maybe you have heard of it… It is called the asset pipeline, and it is designed to make everything easier when it comes to managing your sites assets. For the most part, it does just that. Many of the those “best practices” Steve Souders has been telling you to do for years are now bundled up in a nice process. I was recently playing with the asset pipeline and stumbled a bit. I wasn’t quite sure where my JavaScript templates fit in to this whole process.

JST, where should you be?

After doing a bit of research, I quickly realized that the asset pipeline in rails is actually built off the sprockets gem. https://github.com/sstephenson/sprockets

This is good news, since sprockets already has built in support for jst templates. Score!

Assuming our application.js manifest file is requiring all our asset files, all we need to do is drop a .jst anywhere file in the /app/assets/javascripts folder.

For our very simple case I dropped in a file called test.jst with the contents below.

"Hi. <%= name %>."

Fiddling around in the chrome developer tools you can see that JST['test'] is defined when the page loads. By using the _.template() function we can actually compile the template. In the case below, we are simply providing the “name” variable to our template and logging the output.


Let’s recap what happened real quick. Rails automatically loaded our jst file via the asset pipeline, converted it to a javascript string in a file “test.js”, and assigned “test” as a property on the global JST object. We can now access JST['test'] anywhere in our JavaScript code to get our template string, as evidence from the dev tools above.

Precompiling Templates

The above works well, however we can improve on it a bit. Most tutorials on client side templating will tell you to always “precompile” your template when the application starts up. Precompiling is the process of converting your string template into a JavaScript function. Something like the code below would do the trick with underscore. Notice how compiled_template actually becomes a reusable function. To execute our compiled template it is as easy as calling compiled_template({ name: ‘jason’ })

var compiled_template = _.template("Hi. <%= name %>.");

// In dev tools, outputting compiled_template displays
function (obj) {
obj || (obj = {});
var __t, __p = '', __e = _.escape, __d = obj.obj || obj;
__p += 'Hi. ' +
((__t = ( obj.name )) == null ? '' : __t) +
'.';
return __p
}

The code above will work nicely, however the client still has to take the time to compile each of our templates before they can be used. What if we did the compilation on the server and then passed the resulting function to the client instead of the template string?

EJS Server side compiling

Luckily, again rails has a gem for that. To use EJS simply add it to your gemfile and bundle.

  gem 'ejs'

NOTE: You will need to change any .jst files to .jst.ejs extensions. This will instruct EJS to precompile any templates before sending them to the client.

Let’s take a look at what our new test.jst.ejs file looks like when processed on the client.

Clearly, JST['test'] is now returning our precompiled function. Executing JST['test'] with our argument also gives us back the expected result of “Hi. jason.”

Conclusion

The asset pipeline is quite the tool in the tool belt, and can significantly help you manage not only static assets, but also dynamic client side templates. It seems as if rails has thought of everything :)

Using Rails can be fun, however sometimes you have a legacy Oracle table that is bringing you down. This table was possibly created long ago, and does not have a sequence setup to handle the primary key. Since Oracle has no auto number type this can be a problem. Rails usually would have a hard time with this since it likes to stick to it’s “sensible defaults”. However, if we set a few things manually we can whip Rails and Oracle into shape.

First thing we need to do is set the table_name and primary_key attributes for our legacy table. Then we need to set the before_save property to increment our column id by selecting the highest legacy id and adding one to it. Pretty simple.

class LegacyTable < ActiveRecord::Base
  self.table_name = 'crappy_legacy_table'
  self.primary_key = 'legacy_id_column'

  before_save :increment

  attr_accessible :legacy_id_column, :another_column, :yet_another_column

  def increment
    self[:legacy_id_column] = LegacyTable.select(:legacy_id_column).order('legacy_id_column desc').first.legacy_id_column.to_i + 1
  end
end

This is for sure not a best practice, but if you cannot create a sequence and need rails to work with legacy oracle, this will do the trick.

Sometimes it does not make sense to store session within a cookie. It might be a size issue, or it could be sensitive information you do not want to go across the wire with each request. Whatever the reason, another option is to database the session information. Luckily rails makes it really easy to do this via active record store.

Active record store

To start databasing our sessions, we need to setup rails to use the active record store. To enable active record store we need to edit the config/initializers/session_store.rb file. We need to comment out the cookie store option and add the active record store option.

#Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
Dummy::Application.config.session_store :active_record_store

Creating the database

Next step is to create the session database table. To do that we will run the following commands…

rake db:sessions:create
rake db:migrate

Session data

When you create the session each created session will be entered as a record in the database. There will be id, session_id, data, created_at, and updated_at columns. The session_id will stored in a cookie and be used to retrieve the data column which contains the databased session information. The data column will store the information as a base64 encoded string. Like so.

eyJfY3NyZl90b2tlbiI6IlU5V3pTNGp3UmdBcmY4WW5EZ1o1TVVXWmo0U3NQTWczSFJLaDJRZzdZNzg9In0=

Which can be marshaled to a ruby object upon request.

{"_csrf_token"=>"ThiYRHCnUpMAoHFXdOQX8WSCXNXICdgwk49xMkIzP+U=", "last_seen"=>"2012-07-18 16:24:25", "user_id"=>"kadrm002"}

Making rails share

This works pretty well for storing sessions in rails. However, there is a problem. Ruby is using it’s internal marshaler when storing the session information. This means that any other language, such as php, coldfusion, etc cannot decode the data unless you write the marshaler :(

There may however, be a way around this. What if we could override rails and tell it to save the data as base64 encoded JSON instead of marshaling it? Every other language could then decode base64 strings and serialize them from JSON to native objects.

If we again edit our session_store.rb file, we can override some internal rails methods when handling sessions. Below we are overriding base the SessionStore class.

module ActiveRecord
  class SessionStore < ActionDispatch::Session::AbstractStore
    module ClassMethods # :nodoc:
      def marshal(data)
        ActiveSupport::Base64.encode64( ActiveSupport::JSON.encode(data) ) if data
      end

      def unmarshal(data)
        ActiveSupport::JSON.decode( ActiveSupport::Base64.decode64(data) ) if data
      end

      def drop_table!
        connection_pool.clear_table_cache!(table_name)
        connection.drop_table table_name
      end

      def create_table!
        connection_pool.clear_table_cache!(table_name)
        connection.create_table(table_name) do |t|
          t.string session_id_column, :limit => 255
          t.text data_column_name
        end
        connection.add_index table_name, session_id_column, :unique => true
      end
    end
  end
end

If you notice we are only overriding the marshal and unmarshal methods to preform a JSON.encode and JSON.decode instead of the traditional Marshal.load and Marshal.dump.

We can then easily read this information out in another language.

// Coldfusion
<cfset decoded_session = ToString( BinaryDecode( data, "Base64" ) )>
<cfset session_struct = DeserializeJSON( decoded_session )>

// PHP
$decoded_session = base64_decode(data);
$session_object = json_decode($decoded_session);