You're viewing all posts tagged with ruby

overriding rails validations metaprogramatically

I had a need to modify the validations for a model in a Rails 2.3.5 app from a plugin.  I did not want to directly modify the source code; in my case, I was writing my code as a plugin for an existing Rails application so I could upgrade the base code without worrying about losing my changes, or dealing with the hassle of reapplying my patches.  There are several really great Rails frameworks out there that are applications in their own right, such as Redmine and Spree, and writing my customizations as plugins or extensions to them is simply a more flexible solution than modifying the applications directly.

I was modifying a User class, extending the login length to 255 characters. We were using emails as the login, and where I work they can get pretty long because they are usually a concatenation of one’s first and last names. And some of my co-workers have obscenely long names. Directly modifying User would be trivial — you would just increase the value associated with the maximum attribute for the validates_length_of validation we are changing:

validates_length_of :login, :maximum => 255

But, as I mentioned, I did not want to change the original code. So, we need to get at the list of validation callbacks. Luckily, that’s easy: there’s a @validate_callbacks instance method available to us. So, we just need to iterate over it and find the callback that we don’t want.

Each entry in @validate_callbacks is an instance of ActiveSupport::Callbacks::Callback. This class contains an accessor called method which returns the Proc that is executed to run the validation. Unfortunately, we want to get at the values being sent to that proc. We know that the validates_length_of method takes in an attrs parameter, so we just eval that against the callback method’s binding to find the list of attributes that this proc will validate.

Because we know we only passed one attribute in (instead of a chain of them), we just check the first value, and double check that the maximum option was set to 30 (which is the original value). This also allows us to easily filter out the other validations on the login field that we aren’t interested in, such as validates_format_of.  The finished product looks something like this:

require_dependency 'user'
require 'dispatcher'
module UserPatch
def self.included(base)
  base.class_eval do
    @validate_callbacks.reject! { |c| true if Proc === c.method &&
      eval("attrs", c.method.binding).first == :login && c.options[:maximum] == 30 rescue false }
    validates_length_of :login, :maximum => 255 # new value
end end Dispatcher.to_prepare do unless User.included_modules.include?(UserPatch) User.send(:include, UserPatch) end end

building a redmine plugin

I was experimenting with the Redmine, a popular Ruby on Rails open source project management web application.  It’s pretty full featured: you can set up multiple projects, each with its own issue tracking, wikis, document stores, and calendars, all within the same instance of Redmine.  But it didn’t have everything I wanted.

One problem that I quickly realized I wouldn’t be able to live with is that emails sent from a Redmine instance all use the same emission address.  For me, this was a showstopper, since I had a requirement that each project would be tied to its own email address, which our users email for tech support.  So I set out to figure out how to customize Redmine.  While it would be trivial to simply modify Redmine’s source code directly, I wanted to be able to upgrade the original system, while preserving my customizations.  The ideal way to do that is to create a plugin.

To start a Redmine plugin project is simple - there is a Rails generator for that:

ruby script/generate redmine_plugin <plugin_name>

In my case, I called it “project_email”.  This creates your standard folder hierarchy for a Rails app: controllers, helpers, models, views, db, as well as a lib folder.  The most important file is init.rb, which is invoked when the plugin is loaded.  This contains some information that Redmine needs.

require 'redmine'
require 'mailer_patch'
require 'project_patch'

Redmine::Plugin.register :redmine_redmine_project_email do
name 'Redmine Project Email plugin'
author 'Lawrence McAlpin'
description 'Adds a per-project email emission address'
version '0.0.1'
url 'http://github.com/lmcalpin/redmine_project_email'
author_url 'http://www.lmcalpin.com/'
end

Like any Rails plugin, we can add our own tables and fields.  We simply create a new migration that looks like this:

class AddMailFromToProject < ActiveRecord::Migration
def self.up
add_column :projects, :mail_from, :string
end

def self.down
remove_column :projects, :mail_from
end
end

… and run rake db:migrate:plugins to load it up! Now, the “project” model will automagically have a new property called “mail_from.”

At this point, I need to override some of Redmine’s controllers and models.  The problem is: anything loaded by the plugin will be overwritten by the base application.  That is not quite what we want.  Luckily, Ruby makes it incredibly easy to tame those classes: through metaprogramming.

We set up a few modules with our patches and force the class to include it. 

require_dependency 'mail_handler'

module MailerPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :issue_add, :project_emission_email
# ... override the rest of the methods as well
end
end
module InstanceMethods
def issue_add_with_project_emission_email(issue)
from_project issue
issue_add_without_project_emission_email issue
end
def from_project(container)
unless container.nil? || container.project.nil? || container.project.mail_from.nil? || container.project.mail_from.empty?
from container.project.mail_from
end
end
end
end

This code is straightforward: we override the issue_add method.   alias_method_chain takes in two parameters, the first being a symbol representing the method we are overriding, as well as a suffix.  In our case, we use “project_emission_email” as the suffix, so the alias_method_chain call will rename the original issue_add method to “issue_add_without_project_emission_email” and rename the “issue_add_with_project_emission_email” method that we define to “issue_add”.  Any existing code that calls issue_add will end up calling our “issue_add_with_project_emission_email” method.

We simply override the original mailer to set the from address to the value set in the new “mail_from” field we added to our project.  If no customized “mail_from” is set for a project, the default emission email will be used.

But now we have a problem: Redmine ignores any attributes not specifically marked as “safe.”  So we need to modify the project model to add a call to

safe_attributes 'mail_from'

No problem! Monkey patching to the rescue!

module ProjectPatch
def self.included(base) # :nodoc:
base.class_eval do
unloadable
safe_attributes 'mail_from'
end
end
end

Unfortunately, in development mode, our model appears to be reloaded upon every request!  And, you know what?  The same thing happens to our mailer!  Oh nos.  But hey, no problem, we’ll just patch the Rails dispatcher to reapply the patch every time:

Dispatcher.to_prepare do
unless Project.included_modules.include?(ProjectPatch)
Project.send(:include, ProjectPatch)
end
unless Mailer.included_modules.include?(MailerPatch)
Mailer.send(:include, MailerPatch)
end
end

Now we’re almost done!  We just need to modify the view.  The easiest way would be to simply add our own customized _form.rhtml in the app/views/projects folder.  Unlike controllers and models, the views in our plugin take precedence, so our _form.rhtml will be loaded instead of the one included in Redmine.

Redmine provides a hook that lets you add new fields, without overriding the entire view file.  This would be the better approach (since a future version of Redmine may have other UI changes that we want) but it’s late, and this beer isn’t going to drink itself, so we’ll just stop here.  We’ll learn about plugin hooks another time.

i stuck my cucumber in it

We’re building out an application to support this high profile effort that involves something like 10 different teams.  My team’s application has over a million lines of code, 8000 classes, and Needs To Work(tm).  Or heads will roll.  And mine doesn’t like to do that, because it might get all scruffed up.  So naturally we decided to get down with the BDD and use the Cucumber.  But since we’re enterprise Java developers, things get a little more complicated.  Because that’s how we roll.

We use JRuby so our Ruby code can talk to our Java code and it’s totally awesome and all that.  But when it comes to deploying, right now we have a simple process that involves a script (using a proprietary internal tool that is kind of like SCP except it leaves me wishing it actually was) that bundles up all my deliverables and sticks it on the target server.  The application servers and Java platforms and so on are already there, usually maintained by other teams.

I wanted to build the application on a build server where the features will be run whenever code changes (using hudson to trigger the scripts).  I know what versions of Java will be available on the target servers, since there are architectural standards within the enterprise that I can count on.  And, among those standards, I can count on not having any variety of Ruby anywhere unless I put it there myself.  But that complicates our process, because I really shouldn’t be adding things to the server (which my team doesn’t own or control).  And even if I did, then the server would have an additional component that existed nowhere else which would need to be maintained and loved and cared for, and I’m not at all into that sort of thing.

So I says to myself: how do I simplify this process?  And myself says back: stick your cucumber in it.

So I did.  But what does that mean, you say?  Well, besides being what the cool Java kids are calling it these days, what I mean is that I stuck Cucumber and its dependent gems inside the JRuby jar which meant that all I needed to do was bundle a single extra jar in with the application that we build.  This makes deploying the exact same version of Ruby with the exact same gems to any machine or environment really easy: you just copy one file around the network.  Even a caveman could do it.  But TCP/Smoke Signal was notoriously slow and error prone so they usually didn’t.

The process is super easy to do.  First of all, you need to download the source code.  You can get it from github (http://github.com/jruby/jruby) or just download the source jar: http://jruby.org/download

What we want to do is build the jar-complete version of the distributable, which is a completely standalone JRuby environment encapsulated as a single jar.  If you inspect the ant build script (which is like a make file in the Java world), you’ll see the target “jar-complete” invokes a command to install additional gems defined in the “complete.jar.gems” property.  This will by default be found in the “default.build.properties” file.  I just edited it directly, and added a reference to a new property “cucumber.gems” to the end:

complete.jar.gems=${rspec.gem} ${rake.gem} {$ruby.debug.gem} ${ruby.debug.base.gem} ${columnize.gem} ${cucumber.gems}

Then we need to define the cucumber gems we want.  To ensure the build is predictable, I specify each gem and dependency along with its version:

cucumber.gems=${build.lib.dir}/cucumber-0.9.2.gem ${build.lib.dir}/builder-2.1.2.gem ${build.lib.dir}/diff-lcs-1.1.2.gem ${build.lib.dir}/gherkin-2.2.9-java.gem ${build.lib.dir}/json-1.4.6-java.gem ${build.lib.dir}/term-ansicolor-1.0.5.gem

Now you need to get the gem files and put them in the jruby_src/build_lib folder.  When you run the build, it will download the actual gem sources.  I just copied the gem files from an existing JRuby installation’s gem cache (jruby/lib/ruby/gems/1.8/cache), but you can do it any way you want.

Now run “ant dist-jar-complete” and you’ll be presented with a lovely, customized version of jruby-complete-1.x.y.jar in your jruby_src/dist folder ready for the cukes or whatever else you want.

You could then invoke it by calling:

java -jar jruby-complete-1.5.3.jar -S cucumber features

hello web from ada

I decided to mess around with some Ada.  This is an old school compiled language, that is popular with the Department of Defense and almost nobody else.  Except me.  Mainly because I like the name.  It’s pretty.

So here’s what you do:

1) Download the GPL version of GNAT from AdaCore.  Make sure you grab AWS also.

2) If you are using Windows, you will need to have a Unix shell prompt available to you.  You can get this using Cygwin or MINGW.

3) Install GNAT.

4) Unzip the AWS source code.  In a moment, we will build this library.  For some reason, on my machine, it didn’t auto-detect the install directory of GNAT properly, so first edit makefile.conf and update the prefix line to point to that directory you installed GNAT into.

5) Run “make setup build install” to install AWS libraries so your projects can find them. 

6) Go into the templates_parser subdirectory and edit makefile.setup to change your installation directory and run “make install”. 

There is no standard way to install libraries so you have to actually read the README and INSTALL files.

7) Use GPS (which comes with GNAT) or install GNATBench (Eclipse plugin) to set up a new Ada project.  Declare the main source file to be “Server.adb”.

8) Create “Server.adb”:

Server.adb:

with GNAT.IO; use GNAT.IO;
with AWS.Default;
with AWS.Server;
with ServerCallback;

procedure Server is
WS : AWS.Server.HTTP;
begin
AWS.Server.Start (WS, "Hello World",
Max_Connection => 1,
Callback => ServerCallback.Echo'Access);
AWS.Server.Wait (AWS.Server.Q_Key_Pressed);
AWS.Server.Shutdown (WS);
end Server;

This sets up a new web server and declares that the method Echo in the file ServerCallback.adb will handle our HTTP requests.  The .EXE will run until you hit the Q key.  It will listen, by default, on port 8080.

9) Define the server callback files “ServerCallback.adb” and “ServerCallback.ads”.

ServerCallback.adb:

with Templates_Parser; use Templates_Parser;
with Templates_Parser.Utils; use Templates_Parser.Utils;
with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;

package body ServerCallback is
function Echo (Request : AWS.Status.Data) return AWS.Response.Data is
pragma Unreferenced (Request);
URI : constant String := AWS.Status.URI (Request);
Params : constant Translate_Table := (1 => Assoc("URI", URI));
Response : Unbounded_String;
begin
Response := Parse (Utils.Get_Program_Directory & ".." & Directory_Separator& "site.thtml", Params);
return AWS.Response.Build ("text/html", Response);
end Echo;
end ServerCallback;

ServerCallback.ads:

with AWS.Response;
with AWS.Status;

package ServerCallback is
function Echo (Request : AWS.Status.Data) return AWS.Response.Data;
end ServerCallback;

This loads a template file called site.thtml and passes in a parameter URI which gets resolves to the request URI.  The parameters are passed to the template_parser using TranslateTable or TranslateSet (I used TranslateTable because I knew in advance how many parameters I would have.)

The actual template itself I put in the root folder of the project (“..” from the obj_debug folder that server.exe gets deposited in.)  It’s basically html with tag declarations that the template_parser replaces with the parameters you supplied above.

site.thtml:

<html>
<body>
Hello from @_URI_@.
</body>
</html>

Now you can hit http://localhost:8080/whatever and you should see the response echo the URI you provided.

You might be wondering how the same program would look in Ruby. Well, like this (assuming you have Ruby and the Sintra gem installed):

require 'rubygems'
require 'sinatra'

get '*' do
"Hello from #{params[:splat]}"
end

But that’s nowhere near as fun.

(Of course, Ada isn’t really a language intended for web programming.  And I don’t think I would feel comfortable in an airplane where the fly-by-wire controls were powered by the Ruby VM and the software cobbled together by splicing together features from a half dozen open source gems.)

devise 1.3.3, rails 3 #fail

I’m starting a small Rails project for a webapp that I hope to have ready by January.  While working on building up the application’s foundation, I ran into a small  compatibility issue with Devise and Rails 3.

Devise 1.3.3 and Rails 3.0, on my Windows development machine at least, seems to have some kind of issue where the helpers don’t become available.  authenticate_user!, user_signed_in?, current_user, and user_session were unavailable to controllers or views.  Backing out to Devise 1.3.2 did the trick, everything works fine now.

a little sinatra oauth ditty

I have a toy Ruby webapp I created using Sinatra that I have hosted on Heroku.  It’s a small little ditty, nothing fancy.  It’s called “Tweet Tools” and I wrote it to make it easier to read tweets from a particular class of Twitter user who don’t seem to understand the point of Twitter: these people insist on making dozens upon dozens of rapid fire and related comments in a row.  Because Twitter and most Twitter clients display the most recent tweets first, their stories or observations or rants or whatever end up reading backwards.

It only took a few hours to write, and I did it a year or so ago and hadn’t messed with it until recently, when it suddenly stopped working.  This is because Twitter went and decided HTTP Basic Auth wasn’t good enough, and now we all have to upgrade to OAuth.  NO problem.  Here’s how you do it with your Ruby webapps in only three simple steps:

1) Go to http://www.twitter.com/apps/ and register your application.  You’ll get a nice little consumer key and a secret.  It really is a secret.  Don’t tell anyone, especially your nosy neighbor.

2) Install the Ruby gem oauth. Unless you want to do everything yourself. In which case, go for broke, but don’t ask me for help. I would rather get real work done.

3) Before every HTTP request, we need to do a few things.

First, we do some basic setup. We clear an area in the session where we intend to hold the OAuth tokens we get, and then construct a Consumer object that identifies who we are using the consumer_key and consumer_secret Twitter gave us, and it identifies Twitter as our OAuth provider.

before do
session[:oauth] ||= {} # we'll store the request and access tokens here
host = request.host
host << ":4567" if request.host == "localhost"
consumer_key = CONSUMER_KEY # what twitter.com/apps says
consumer_secret = CONSUMER_SECRET # shhhh, its a secret
@consumer = OAuth::Consumer.new(consumer_key, consumer_secret, :site => "http://twitter.com")

OK, now we have our Consumer object. That represents our webapp, the OAuth consumer. The ‘host’ variable will come in handy later when we tell Twitter where to call back to. When running Sinatra on my localhost, it uses port :4567 so we append that to the hostname. That way, when we approve Tweet Tools’ permission to use my Twitter account (for browsing other people’s timelines) we’ll get redirected right back to the very same Internet Explorer window I started off in.

Now we need to get a request token. If we asked for one before, we store it in the session so we don’t need to ask for it again.

We will later exchange the request token for an access token by redirecting the user to Twitter and letting them approve our webapp.

  # generate a request token for this user session if we haven't already
request_token = session[:oauth][:request_token]
request_token_secret = session[:oauth][:request_token_secret]
if request_token.nil? || request_token_secret.nil?
# new user? create a request token and stick it in their session
@request_token = @consumer.get_request_token(:oauth_callback => "http://#{host}/auth")
session[:oauth][:request_token] = @request_token.token
session[:oauth][:request_token_secret] = @request_token.secret
else
# we made this user's request token before, so recreate the object
@request_token = OAuth::RequestToken.new(@consumer, request_token, request_token_secret)
end

Awesome. But we might already have an access token, if the user had already logged in and been authenticated by Twitter, so let’s check for that. If we do, we can create our OAuth client object and start perusing timelines.

Astute readers no doubt realize we don’t need to check the request token if we already have an access token. But I’ll leave those optimizations as an exercise for the reader. Mostly because I’m lazy.

  # this is what we came here for...   
access_token = session[:oauth][:access_token]
access_token_secret = session[:oauth][:access_token_secret]
unless access_token.nil? || access_token_secret.nil?
# the ultimate goal is to get here, where we can create our Twitter @client
# object
@access_token = OAuth::AccessToken.new(@consumer, access_token, access_token_secret)
oauth = Twitter::OAuth.new(consumer_key, consumer_secret)
oauth.authorize_from_access(@access_token.token, @access_token.secret)
@client = Twitter::Base.new(oauth)
end
end

OK, now how do we exchange our request token for the access token in the first place? Well, by asking Twitter nicely of course. Set up a route that redirects to the authorization URL. We can get that from the request token.

<a href="/request"><img src="/images/twitter_sign_in.png" />

get "/request" do
redirect @request_token.authorize_url
end

After the user is redirected to Twitter they will see this lovely screen.

Assuming they press “ALLOW”, they will come back to the URL you specified in the oauth_callback earlier.

# remember this code from earlier?  This is why we're here:
@request_token = @consumer.get_request_token(:oauth_callback => "http://#{host}/auth")

get "/auth" do
@access_token = @request_token.get_access_token(:oauth_verifier => params[:oauth_verifier])
session[:oauth][:access_token] = @access_token.token
session[:oauth][:access_token_secret] = @access_token.secret
redirect "/"
end

Hooray! Now we can be all up in the user’s tweets, checking their timelines and whatnot.

@client.user_timeline(:id=>@userid, :count=>35).each do |status|
puts "that's right I be all up in your timeline checking your #{status} and whatnot"
end

If you want to block users from accessing certain parts of the system unless they are logged on, you can simply redirect them to the front page or login page by simply checking for the presence of the @access_token.

redirect '/' unless @access_token

At this point in your webapp, you could implement SSO. When you get redirected to your Twitter landing page (/auth in my case), you could query the Twitter API to find the user’s login and check that against a column on your user table. If you find them, you automatically log them into their account. If no such Twitter user exists, you would redirect them to a page where they are asked to link it to their existing account or register for a new one (it’s easy and only takes… where’d they go?) Or whatever. I don’t have time to walk you through all that right now though, as I have some tweets I need to go read. (Backwards.)