a work on process

Viewing posts tagged: ruby on rails

Available for projects

7 November 2007 (4:06 pm)

By James Stewart
Filed under: Announcements, Meta
Tagged: , , , , ,

With a few projects coming to an end it turns out I have some time on my hands that I could do with filling with some paying work. I’m an experienced web developer, having been building sites and applications for eleven or twelve years now. I prefer to work with Ruby on Rails, and have been doing so for two years, but am comfortable in a range of environments and want my tools to match the project as well as possible. I frequently find myself working with drupal, and as an eight-year PHP veteran, that’s okay.

I’m particularly passionate about helping charities, music organisations, and magazine publishers make use of the web as effectively as possible. I can help such organisations work out how what they do in other environments translates to the web, and then to build the tools to make that happen. I prefer nuance to buzzwords, but I can throw around the appropriate Web 2.0 terms if I have to.

(and if you don’t fit in that list but have a project you think I might find interesting, I’m still interested in hearing from you)

I’m used to working solo, or in a small team, and have experience of leading teams as well as being a member. I’d prefer something in or around London, but usually work from home and am quite open to remote working.

I’ve got three, or maybe four days available for the next couple of months. You can reach me at james@jystewart.net to find out more.

Recommend this post:

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

 

Taking readers step-by-step through the creation of the RailsCoders.net website, Practical Rails Social Networking Sites is a well paced guide to building web applications that tick many of the boxes of the moment.

The book starts with basics, giving simple instructions for installing Rails on a variety of platforms, and then steps through simple content management, adding users and groups, building a blogging engine, adding a discussion forum and photo gallery, integrating with Google Maps and Flickr, and deployment. Along the way the various aspects of rails’ testing framework are introduced as they’re used. The style isn’t test-driven, and it would have been nice to see that style introduced, but tests are written after each piece of functionality, demonstrating some of their use and importance.

Judicious use is made of plugins with a number of recommendations made throughout the book. restful_authentication is referred to, but its functionality is largely duplicated in the code. That’s probably a sensible move so early in the book as it’s important that developers understand what the code is doing even if they’re going to employ a plugin for the implementation. YM4R/GM is used to implement the Google Maps functionality and it’s good to see that getting some attention in print.

Readers who have already built a couple of rails apps may well find themselves skipping large chunks of content as a lot of the code will be familiar. As Stephen pointed out in his summary, it is a little curious that “The Apress Roadmap” suggests this as a more advanced title when it would probably work better for an engaged beginner than an experienced developer.

Of course, the great problem with publishing any rails title right now is that version 2.0 is just around the corner, and with its release we’ll see the end of built-in pagination and a few changes to the routes. As a consequence there are likely to be a number of readers who find that the examples in the book fail to run on the latest stable rails by the time they come to try them. Hopefully Apress will be able to offer a brief supplement with the book or online to help readers update the code for the new features.

Practical Rails Social Networking Sites is a solid introduction illustrating how simple it can be to build useful web applications with Ruby on Rails. I’d hesitate to recommend it to anyone with rails experience, but it will be high on my list of recommendations for beginners who are wanting to dive straight in.

Disclaimer: I was sent a copy of this book for review by the publisher. You can find it at apress, amazon US, amazon UK and all sorts of other places.

Recommend this post:

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

 

Since I wrote my first piece on extending a rails app to accept OpenID quite a few other tutorials and an official plugin have appeared to make that process easier. OpenID is quickly becoming quite mainstream, at least amongst developers, and that is very good news.

It’s becoming so mainstream in fact, that recently I’ve been asked to implement an OpenID server on top of an existing user database so that those users can have an easy single-sign-on option across a range of sites. Writing the server side piece is not quite so straightforward and there’s not much documentation yet. A few sample servers are available but the rails examples don’t run cleanly on the latest gems, so while I took some code from them it made most sense to start from scratch. Over the past couple of days I’ve hacked together something that works for me and even though it could still do with some polish a few notes follow. Please do use the comments to correct anything I may have gotten wrong or skipped over.

Firstly, a word on the process. The OpenID process works along the lines of:

  1. The client sends a request to the server to establish communication, shared keys, etc.
  2. Having established the keys, the client then redirects the end-user to the server to log in
  3. The server logs the end-user in however it wishes
  4. The server redirects the end-user back to the client with a query string indicating success or failure
  5. The client then confirms the success of the request with the server

I was starting from a very simple rails app that provided a Users resource and a Session resource. It’s really little more than a skeleton app with restful_authentication installed and configured. Users can log in and anyone can see a user’s page, and that’s all there is to it. I also had the ruby-openid gem installed and used a fair bit of code from the sample apps supplied with the gem.

Preparing The Existing Site

The first thing you’ll want to do is make sure that the HTML pages for your user resources have the correct headers. Chances are you will want users to be able to use their page (eg. http://yourapp.com/users/jystewart) as their OpenID but that won’t actually be where the login takes place, so you’ll want to put something like:

<link rel="openid.server" href="<%= session_url %>" />
</link><link rel="openid2.provider" href="<%= session_url %>" />
</link>

in the header block of your user page HTML. From that the OpenID client will know to redirect users to your sessions_url for authentication.

Preparing The Models

To my database that so far held the user details, I added four tables:

create_table :nonces do |t|
  t.string :nonce
  t.integer :created
  t.timestamps 
end
 
create_table :associations do |t|
  t.binary :server_url, :secret
  t.string :handle, :assoc_type
  t.integer :issued, :lifetime
  t.timestamps 
end
 
create_table :settings do |t|
  t.string :setting
  t.binary :value
  t.timestamps 
end
 
create_table :trusts do |t|
  t.integer :user_id
  t.string :trust_root
  t.timestamps 
end

The first three hold the various details we need for the negotiations with the client, and the fourth stores the details of any sites a given user has said they want to always trust so that we don’t need to confirm those details every time.

We also need an implementation of the OpenID::Store class for the OpenID library we’re using. I simply copied the one from the sample rails app distributed with the gem. With the models and that interface in place we can move on to managing the requests.

Distinguishing OpenID Requests

I decided to use a Mime::Type alias to distinguish between requests made directly in my app and those coming through OpenID. To do that I added:

Mime::Type.register_alias "text/html", :openid

in RAILS_ROOT/config/initializers/mime_types.rb and added the following code in my controllers:

before_filter :check_for_openid_request
 
private
  def openid_server # :nodoc:
    @openid_server ||= OpenID::Server::Server.new(ActiveRecordOpenIDStore.new)
  end
 
  def openid_request
    @openid_request ||= openid_server.decode_request(
      openid_params.delete_if{ |key, val|
        key == 'openid.session_type' && val == ''})
  end
 
  def check_for_openid_request
    @openid_params = session[:openid_params] if session[:openid_params]
 
    if openid_request.is_a?(OpenID::Server::CheckIDRequest)
      session[:openid_params] = @openid_params
      request.format = :openid 
    end
  end

Because there are going to be several steps to our process during which we want to be able to hang on to the OpenID request details, I chose to store them in the session

Handling The Requests

Having defined our Mime::Type alias, we can now use openid as a type in respond_to blocks. The first step then is to change our SessionsController#create action from:

def create
  self.current_user = User.authenticate(params[:login], params[:password])
  if logged_in?
    if params[:remember_me] == "1"
      self.current_user.remember_me
      cookies[:auth_token] = { 
        :value => self.current_user.remember_token, 
        :expires => self.current_user.remember_token_expires_at }
    end
 
    flash[:notice] = "Logged in successfully"
    redirect_back_or_default(user_path(current_user))
  else
    render :action => 'new'
  end
end

to:

def create
  self.current_user = User.authenticate(params[:login], params[:password])
  if logged_in?
    if params[:remember_me] == "1"
      self.current_user.remember_me
      cookies[:auth_token] = { 
        :value => self.current_user.remember_token, 
        :expires => self.current_user.remember_token_expires_at }
    end
 
    respond_to do |wants|
      wants.html { 
        flash[:notice] = "Logged in successfully"
        redirect_back_or_default(user_path(current_user))
      }
      wants.openid { 
        if @trust = current_user.trusts.find_by_trust_root(openid_params['openid.trust_root'])
          successful_openid_login 
        else
          redirect_to new_trust_url
        end
      }
    end
  else
    render :action => 'new'
  end
end

This checks for a successful login and if it’s an OpenID request then checks to see if we already trust that site. If we do, we call the ’successful_openid_login’ method and if not, we redirect the user to ask if they want to trust the client site. The successful_openid_login method looks like:

def successful_openid_login
  session[:openid_params] = nil
  @resp = openid_request.answer(true)
  @resp.add_field(nil, 'is_valid', 'true')
  add_sreg
  render_openid_response
end
 
def render_openid_response
  response.headers['Content-Type'] = 'charset=utf-8'
  @resp = openid_server.encode_response(@resp)
 
  case @resp.code
    when OpenID::Server::HTTP_OK
      render :text => @resp.body, :status => 200
    when OpenID::Server::HTTP_REDIRECT
      redirect_to @resp.redirect_url
    else
      render :text => @resp.body, :status => 400
  end   
end

And is almost entirely taken from the code supplied with the gem. I ran into some trouble with the ‘is_valid’ field not being added to the responses but being needed by my clients, so added the line

@resp.add_field(nil, 'is_valid', 'true')

The TrustsController implementation is quite straightforward and I’ll not go into details here, but will include it in the zip file you can download at the end of this entry.

This code will handle most of the process, but we have one remaining query to handle, and that is the final check_authentication request that is used to confirm everything fell into place. The actual request will come as a POST to your /session path so you will want to be ready to handle it there. I decided to implement a before_filter to intercept it and handle it separately rather than clutter my create method still further:

before_filter :handle_openid_check_auth_request
 
def handle_openid_check_auth_request
  if openid_params['openid.mode'] == "check_authentication"
    signatory = OpenID::Server::Signatory.new(ActiveRecordOpenIDStore.new)
    @resp = openid_request.answer(signatory)
    @resp.add_field(nil, 'is_valid', 'true')
    render_openid_response
  end
end

Once I had the code working acceptably I pulled it out into a separate module in the ‘lib’ folder. As the code matures it would probably be a good candidate for a rails plugin, but I’m not going to have time to put that together on my own for a while. If anyone wants to collaborate on that, get in touch in the comments (or by email) and maybe we can make it happen.

All the code above along with various other utility methods can be found in this zip file.

Recommend this post:

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

 

Book Review: Pro ActiveRecord

17 October 2007 (9:53 am)

By James Stewart
Filed under: Book Reviews
Tagged: , , , , ,

Right at the start of Pro Active Record the authors address a possible problem some may have with it: that there’s not enough in Active Record to warrant a full book. They point out that the basics are well covered as sections elsewhere but that this is the first book to really dig into working with legacy schema and other ‘advanced’ uses. That’s fair enough, but after reading the book I am still left with the question of why, then, they dedicate the first half to covering ActiveRecord’s most basic concepts?

Judging from postings on the rails email list, there’s certainly a lot of confusion about ActiveRecord, associations, observers, how to work with legacy table names and primary keys, and so on. But in a book with a title prefix of “Pro” I was expecting to jump straight into the nitty gritty of topics like compound/composite primary keys and performance tuning, probably with some real world examples, and maybe with a serious exploration of AR’s internals. As it is, such topics only get a quick treatment in the final chapter (the compound/composite primary keys section is a paragraph referring users to http://compositekeys.rubyforge.org).

It’s almost always instructive reading other developers’ code and it would be unfair to claim that I didn’t spot a couple of tips that may prove useful, but they were passing things. And sometimes I found myself wondering what happened to the tech review process, particularly in the coverage of the has_one association, where not only is the variable naming confusing, but they seem to be calling the each method on a single ActiveRecord instance.

I’m left wondering what the audience is for this book. The title and blurbs suggest it’s pitched at people who want to go deeper into ActiveRecord than they have before, but the content is better suited for someone with some database experience who wants to pick up ActiveRecord to write some scripts. As it is, if you’ve worked with ActiveRecord before your time will be better spent writing plugins and exploring the internals for yourself, and if you’ve not you’ll get most of the same material from a decent Rails book and some time exploring.

Disclaimer: I was sent a copy of this book for review by the publisher. You can find it at apress, amazon US, amazon UK and all sorts of other places.

Recommend this post:

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

 

Rails Geo Plugins: GeoX

16 October 2007 (2:26 pm)

By James Stewart
Filed under: Commentary
Tagged: , , , ,

GeoX is the latest kid on the Ruby on Rails geocoding block. The plugin was announced a couple of weeks ago and I’ve been meaning to explore it ever since, just in case it had any new features and also so that I can add it to my comparison chart.

The feature set of GeoX is fairly straightforward. It supports a number of geocoding back ends: obviously google and yahoo are covered, but also mapquest’s relatively recent API. The standard lookup process is much like that provided by several other plugins—the sample given is:

require 'geox'
 
# uses the google engine
geocoder = GeoX::Geocoder.new(:geoengine => GeoX::Google) 
location = {:address => '701 Ocean St', :post_code => '95060'}
geocode = geocoder.geocode(location)
 
# geocode will be a hash containing the geocode data returned from the server
puts geocode.inspect

What is most notably lacking is any ActiveRecord integration such as that offered by acts_as_locateable or acts_as_geocodable. That is apparently by design as the intention is for the library to be usable outside rails but the plugin relies on the rails-specific blank? method and personally I prefer the graticule/acts_as_geocodable split where functionality equivalent to that of GeoX is bundled in the graticule gem and then ActiveRecord hooks are provided by the plugin. It is definitely nice to have geocoding handled manually when models are added or updated, but more than that it is very useful to have support provided for searching based on location.

Probably the most intriguing aspect of the plugin is the facility to compare the specificity of two different sets of coordinates. That’s achieved by returning an object type appropriate to the specificity. So if you have street-level data, the object returned is an instance of GeoX::Street. Operator overloading allow you to compare the objects:

puts "Street wins!" if GeoX::Street.new < GeoX::Block.new

The README file suggests putting the API keys in the file lib/geox_api_keys.rb within the plugin. That’s probably not the best of ideas, but it would be easy enough to rework it so that the configuration is done in the usual rails way and the plugin can be included with svn:externals or piston.

I can’t see myself switching to GeoX from the options I currently use and it’s a shame that its distinctive features weren’t submitted as patches to existing projects rather than adding yet another plugin to the increasingly crowded scene, but it’ll be interesting to see how it evolves over the coming months.

Recommend this post:

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

 
« Previous PageNext Page »