Recently about Ruby

There are many ways to store sessions in Ruby on Rails, but by using JRuby, we get another option that I think supersedes the common solutions... Using the Java Server's built in session store.

First... The implementation

This is assuming that you are running JRuby and using a tool like warbler to deploy to a Java server like Tomcat.

config/initializers/session_store.rb
# check if we're running in a java container
if defined?($servlet_context)
    require 'action_controller/session/java_servlet_store'
    # tell rails to use the java container's session store
    ActionController::Base.session_store = :java_servlet_store
else
    # other session store for development in webrick/mongrel
# setup cookie based session
ActionController::Base.session = {
:key => '_dd_session',
:secret => '<Your really long random secret key>'
}
end
This will store your rails session in Tomcat's session store exactly as if you were running a Java web framework.

Second... Why?

There are a few common solutions starting with the rails default.

Cookie Store: This is not very secure, limited in size, and sends a lot of data back and forth to the client. It is strongly recommended to not use this for production systems.
File Store: Stores sessions on the file system. Every cookie access involves file system IO. Does not support clustering by default but can be achieved through a SAN or shared storage.
ActiveRecord: Stores sessions in the database. Slow due to database IO. Natively supports clustering.
Memcached: Stores sessions in Memcached instance. Very popular and works well with clusters. Can run into RAM issues on systems with lots of users. Requires an instance of Memcached running.
Java Servlet Store: Works just like your other Java applications. Java Servers can be configured to cluster sessions. Uses the JSESSIONID cookie or url param (in Tomcat)

Conclusion

This won't be the solution for everyone, but if you're deploying a JRuby on Rails app into an infrastructure based on Java, then it provides a simple and efficient solution.

Recently we have been using the excellent FakeFS (fake filesystem) gem in some specs to test code that reads and writes files on the filesystem. We are using the latest release version of this gem which is 0.2.1 as I am writing this. Some of the code under test uses the IO each_line method to iterate lines in relatively largish files. But we found out quickly that is a problem, since in version 0.2.1 the FakeFS::File class does not extend StringIO and so you don't get all its methods such as each_line. (The version on master in GitHub as I write this does extend StringIO, but it is not yet released as a formal version.) As an example suppose we have the following code that prints out the size of each line in a file as stars (asterisks):

def lines_to_stars(file_path)
  File.open(file_path, 'r').each_line { |line| puts '*' * line.size }
end

Let's say we use FakeFS to create a fake file like this:

require 'fakefs/safe'
require 'stringio'

FakeFS.activate!

File.open('/tmp/foo.txt', 'w') do |f|
  f.write "The quick brown fox jumped over the lazy dog\n"
  f.write "The quick red fox jumped over the sleepy cat\n"
  f.write "Jack be nimble, Jack be quick, Jack jumped over the candle stick\n"
  f.write "Twinkle, twinkle little star, how I wonder what you are\n"
  f.write "The End."
end

So far, so good. But now if we call lines_to_stars we get an error:

NoMethodError: undefined method `each_line' for #<FakeFS::File:0x000001012c22b8>

Oops. No each_line. If you don't want to use an unreleased version of the gem, you can add each_line onto FakeFS::File using the following code:

module FakeFS
  class File
    def each_line
      File.readlines(self.path).each { |line| yield line }
    end
  end
end

Basically all it does is define each_line so that it reads all the lines from a (fake) file on the (fake) filesystem and then yields them up one by one, so you can have code under test that iterates a file and work as expected. So now calling lines_to_stars gives a nice pretty bar chart containing the line sizes represented by stars:

********************************************
********************************************
***************************************************************
*******************************************************
********

Since we're using RSpec, to make this work nicely we added the above code that defines each_line into a file named fakefs.rb in the spec/support directory, since spec_helper requires supporting files in the spec/support directory and its subdirectories. So now all our specs automatically get the each_line behavior when using FakeFS.

Near Infinity recently announced the release of Grant, a Ruby on Rails plugin for securing and auditing access to your Rails model objects, and I'm here to tell you a little bit about it. There are two primary pieces of Grant, model security and model audit. I'll be focusing on model security for this post and will address model audit in a later entry.

Grant's model security is deliberately designed to force the developer to make conscious security decisions about what CRUD operations a user should be allowed to perform on your model objects. It doesn't care how you choose to authenticate and authorize your users to perform a CRUD operation, it only cares that you actually do it.

Rather than specify which operations are restricted, Grant restricts all CRUD operations unless they're explicitly granted to the user. It also restricts adding or removing items from has_many and has_and_belongs_to_many associations. Only allowing operations explicitly granted forces you to make conscious security decisions. While it obviously can't ensure you make the correct decisions, it should help ease the latent fear that you've inadvertently forgotten to secure something.

Enough talk, let me show you an example of how you might use it. To enable model security you simply include the Grant::ModelSecurity module in your model class. In this example you see three grant statements. The first grants find (aka read) permission to everyone. The second example grants create, update, and destroy permission when the passed block evaluates to true, which in this case happens when the model is editable by the current user. You can put any code you want in that block as long as it returns a boolean value. Similarly, the third grant statement permits additions and removals from the tags association when it's block evaluates to true. A Grant::ModelSecurityError is raised if any grant block evaluates to false or nil.

class EditablePage < ActiveRecord::Base
  include Grant::ModelSecurity
  has_many :tags

  grant(:find) { true }
  grant(:create, :update, :destroy) do |user, model| 
    model.editable_by_user? user 
  end
  grant(:add => :tags, :remove => :tags) do |user, model, associated_model| 
    model.editable_by_user? user 
  end

  def editable_by_user? user
    user.administrator?
  end
end

There's a lot more to the grant statement than shown in the above example. For instance, you can have multiple grant statements for the same action. Ultimate permission to perform the action will not be granted unless all grant blocks evaluate to true.

As you can see, Grant is pretty simple to use, but it's not going to do the dirty work for you. It's up to you to make the proper security decisions. Grant's just there to make sure you don't forget.

"We're missing some coverage..."

While creating coverage reports for a fairly new JRuby on Rails project, we noticed that our coverage numbers weren't quite right: certain classes were missing from the coverage reports. Rcov doesn't know about classes unless they are required: not a problem for models, but we were missing tests for some controllers and libraries.

Oops.

To properly correct this problem, I wrote the coverage_helper (to live alongside the test_helper). Basically, this causes all of the classes to be required so that rcov knows about them.

test\coverage_helper.rb

require 'test/unit'
require 'test_helper'

class CoverageHelper < Test::Unit::TestCase
  def test_coverage
    ['app', 'lib'].each {|path| Dir.glob("#{path}/**/*.rb") {|file| 
      require File.expand_path(file.chomp('.rb'))
    }}
    assert true
  end
end

Simply include this test in your rcov builds and the problem is solved.

Because of how rcov counts lines and the way Ruby class loading works, you'll never see files with 0% coverage. However, at least you will see all of your classes listed and those that aren't covered will have a low percentage.

Is this really necessary?

First, the File.expand_path makes sure that your files are only required once. I hate random warning messages because constants are initialized twice (among other issues).

Second, no, I didn't need to make this a test, but it just seemed nicer to. I added the assert true simply because I didn't feel right about not asserting something in the test.

Third, as long as one uses the Rails scripts to generate the skeletons for your code, this scenario should never happen (because Rails will create all of the appropriate tests). However, there is the tendency not to use the generated scripts when they don't output what you want, which is what we have discovered (Rails and Legacy Database Schemas aren't a perfect fit). Also, sometimes I just forget to use them.

What if you can't run rcov?

One minor glitch of running JRuby on Windows is that the File.separator is technically incorrect (it's '/' instead of '\'). This usually isn't an issue... except when using rcov. Since rcov executes from the shell, the arguments requiring file names and/or directories won't work because the separator is the wrong direction from what Windows is expecting.

The fix is to add a couple of methods to the File class to address the problem.

Windows Separator Fix

class File
  @@is_windows = ENV['OS'] && 
    (not ENV['OS'].downcase.match(/^windows/).nil?)

  def self.fix_name(name)
    @@is_windows ? name.tr(File::SEPARATOR, File::ALT_SEPARATOR) : name
  end

  def self.fixed_join(*files)
    self.fix_name(self.join files)
  end
end

The reason to do the ENV['OS'] truth check first is that in JRuby on Solaris (where our CI is), that property doesn't exist. We couldn't use the RUBY_PLATFORM variable either, as in JRuby it's always assigned to 'java'.

I should note that I've only use these fixed separator methods when necessary (in my rcov rake task). The 'normal' separator has worked in every other situation I've run into.

Renae Bair's post on The Ranting Rubyists hits a lot of nails on the head. I will freely admit to being a developer who is interested in continually learning new technologies - perhaps even at the expense of the ones I currently develop in - and I try to contribute a little back by blogging and speaking at conferences like No Fluff Just Stuff on a semi-frequent basis. But Renae's point is that many people in the development world seem to be all about the New, New Thing and ready to dismiss the old things without a second thought. My feeling is that the old things don't go away, often we just end up piling more things on top. (It's new technologies all the way down.) Sometimes there certainly is wholesale replacement, but from what I've experienced usually you just mix in the new things and things become that much more heterogeneous.

I think it's fine to continually push "forward" to newer and better technologies that help you do the same thing in half the time, or in half the code, or allow things to execute on twice the processors, or scale twice as much. But at the same time it is simply not cool or very intelligent to dismiss the very tools that get you paid and perhaps got you where you are today. Sometimes the intent is just that; to dismiss the old in favor of the new for the purpose of making money. Sometimes the intent is merely the intellectual curiosity the best developers usually possess, and in fact the best people in any field possess. A few years ago I told a friend "Get Comfortable Being Uncomfortable." What I meant was to learn new things and push yourself to think about doing things better and more efficiently than you currently are doing them. Sometimes this means switching or advocating a new tool; sometimes it means using your existing tools more effectively. And always it means you can't rest on your laurels and you are always challenging the status quo. Many people don't like this. Well, too bad, because reality is that things change and Resistance is futile.

My day job is still mainly Java and web applications, though I also have managed to squeeze Ruby, Groovy, and Python in there (and of course realized the power of JavaScript) over time. I speak on mostly Java-related stuff like Hibernate and Spring and Groovy a bit. And currently I'm learning about new things (to me anyway) like functional languages such as Lisp and Clojure and Scala. Not because I think I'm going to rewrite the application I'm currently working on in a different language and/or framework, but because over time I feel learning new and different things makes me a better developer, architect, designer, etc. I know that the Java code I write today, while still crap, is way better than the crap I wrote several years ago, and has been influenced by learning Python and Ruby and Groovy and others. While it is still Java, I don't try to write overly generic, overly engineered things like I used to (well, perhaps not as much as I used to anyway). I just try to get the tasks I need to get done, done. If I need to make something more generic later, I can do it. But in addition to the power of just learning new things, I think the more well-rounded you are the better off you are and the better equipped you are to solve new problems. And maybe you'll find a much better way to solve them because you have a more diverse knowledge "portfolio" at your disposal.

So, getting back to Renae's post, I think it's a great idea to continue learning new things and pushing better ways of doing things, if for no other reason than to ensure your own relevance and marketability as a developer but hopefully because you enjoy it! But while it's OK to voice your opinion and seek new and better things, don't just rip to shreds the things that got you to where you are. In the past I've made comments to people like "Java sucks" and "I'd rather be doing Blub programming" and I've tried to curb that and realize that things change, we know more today than yesterday, and to just "Get Comfortable Being Uncomfortable." You might not always get to program in Blub but that shouldn't stop you from expanding what you know, and by the way the sphere of your knowledge should include more than just technical knowledge and probably should include things like economics, finance, culture, art, literature, sports, etc. Whatever. Just make yourself more well-rounded and you'll be better for it, in all aspects of life.