Archive

Archive for the ‘Rails’ Category

Rails plugin/gem to sort nested set trees using drag-n-drop

May 11th, 2009

A Rails app I’m working on needs to handle a fairly big tree of active record models. For this I use a nested set implementation in the models.

Displaying parts of of the tree in my views was of course a piece of cake and fairly fast thanks to the way nested sets in the database works.
If you need a refresher on the db theory you can google on “nested set” or take a look at this article. Or maybe this article that believe was the first one from back in the nineties. The interesting stuff starts at the “Trees in SQL” section.

Anyway, trouble struck when I wanted to implement a drag-n-drop sort/rearrange feature using the sortable_element script.aculo.us helper that comes with Rails.

The default behavior of this helper is to send the the complete tree of id:s to the server when you “drop” your dragged item where you want it. This tree then reflects the how the new tree should look.
Needless to say this is a bit more information than required when you want to update your nested set model. In your controller your only need is to find out what was moved and where did it end up.

Parsing the tree sent in the request parameter and comparing it to the database isn’t an option if you have a very large tree and worry about speed. So I created a plugin, SortableElementForNestedSet, that helps you find the answers the what and where questions and you’ll find the first version of it on GitHub.

Installation and simple usage instructions are found in the readme. Enjoy!

Nested set implementation for Rails:

Rails , ,

Selenium on Rails DSL

January 3rd, 2009

When upgrading Rails to version 2.2.2 on one of the applications I work on, it was clear that the SeleniumOnRails plugin was not compatible. This was easily fixed by upgrading the plugin with the non-official version available at here.

The test cases written in RSelanese use a DSL (Domain Specific Language) module to help keep the test code clean and easy to read.

At the time I set this up I was fairly new to Selenium and SeleniumOnRails, and followed some advice in this article that suggested to hack the the RSelenese class to include a custom module for your DSLs. As a Selenium newbie I was probably focusing on the tests, and forgot to fix the weird hack suggested. Needless to say this came back an bit me when I upgraded SeleniumOnRails and reminded me to fix it. This is how I did it.

Why a DSL?

Let me start with a short intro to what I wanna do.

The application I’m working on uses AJAX to login. To do this in my Selenium test cases I would run the following .rsel snippet:

As several test cases will need to log in before getting down to business, pasting that snippet into the test cases would produce enormous amount of duplication. One way to get rid of the duplicated code would be to put the login steps in a .rsel partial and execute the login that way. The tests would in that case include the partial using the following syntax:

I guess you would agree this is not a very tasteful syntax. I’d rather see a method call like login("quentin", "test"). If we define a login method in a module, we could include that in the RSelenese class and make it available to our test cases.

Including the DSL module in RSelenese

With the DSL module in place it still needs to be included in the class SeleniumOnRails::RSelenese. As this setup is only needed in the test environment, the class_eval is done in the test environment config. It is put in an after_initialize block to allow the SeleniumOnRails plugin to be loaded before the inclusion.

That’s it!
Just remember that you need to restart your environment when you make any updates to your DSL module.

Rails , ,

Using rspec-rails during gem development

December 15th, 2008

Update:

I been having all sorts of problems using this approach after updates in rspec and rspec-rails and no longer recommend using it!

The source code has been updated to use another approach in which I include a minimal Rails application in the gem, to run tests against. Until I get a chance to write a more complete post on this new approach take a look at the source, available at GitHub.

Not long ago I started using rspec and rspec-rails for testing in my Rails apps. I’ve quickly became hooked on Behaviour Driven Design (BDD) and feel it has boosted the quality (and readability) of my tests.

But when I wanted to move a view helper from one project into a gem for easy reuse in other apps it took me a while to figure out how to get the tests to run in isolation.

The main issue I had was the rspec-rails gem relying on there being a rails project present which of course isn’t the case with a stand-alone gem. Specifically, it wants to load the project’s ApplicationController which in turn will load a whole bunch of other stuff.

The second problem I ran into was how to write tests for helpers that output text with a non-output code block (i.e., <% … %>).

Let’s look at the stripped down version of the rspec for my boxed_content helper that wraps its containing block in a number of divs:

When testing gems I don’t rely on the specs to follow the directory layout defined by rspec-rails. Instead I pass the :helper type on line 3 so the created example group is a HelperExampleGroup.

As the boxed_content helper will not output text on its own I use eval_erb to execute it. This done in the method on lines 17-19. Putting it in a method makes the test code easier to read.

Ok. On to the spec_helper.rb where rspec and rspec-rails are set up:

The trick here is on line 5 where I create an ApplicationController class. This will load a bunch of stuff and allow us to continue with the rest of the require statements for rspec and rspec-rails. Lines 8-19 are copied from rspec-rails initialization.

Finally on line 21, my module containing the helpers i want to test, is loaded.

Resources

Rails , , ,

Using ActiveRecord with forms but without a table

November 24th, 2008

Introduction

From time to time I find myself implementing a story needing the nice validation features that
come with ActiveRecord and being able to display input errors in a simple way in the views.
I also want to implement the story in a RESTful way (even if this example should probably be
regarded as RESTlike).

But what I don’t want is a table in the database.

Implementing a User-Changes-Password-story not long ago was such an occasion and I decided to clean out the domain specifics and post the way it was done and also publish the active_record_tableless plugin I use.

There were a number of good reasons not to use the existing User model directly, but that’s long story. As this example is stripped of all the calls to legacy system it may seem like a strange way to do this but I hope you can follow along and understand how you can use active_record_tableless plugin in your own projects.

User story

User wanting to change password submits email address.

After email validation, the user enters passwords as listed below and submits to complete the change.

  • old password
  • new password
  • confirmation of the new password.

Implementation

Setup

You can download the complete source code from here, but I basically created a new rails app and generated a scaffold for a PasswordUpdate like this:

script/generate scaffold --skip-migration PasswordUpdate email:string \
          password:string new_password:string new_password_confirmation:string

To be able to use a model without backing it with a database table I use the active_record_tableless plugin:

script/plugin install git://github.com/robinsp/active_record_tableless.git

Controller

We wont need some of the actions created by the scaffold generator so index and destroy actions were deleted. The generated controller was also cleansed from code returning xml.

After implementing the things we need for the user story implementation the controller looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class PasswordUpdatesController < ApplicationController
 
  def new
    @password_update = PasswordUpdate.new
  end
 
  def edit
    @password_update = PasswordUpdate.find( params[:id] ) 
  end
 
  def update
    @password_update = PasswordUpdate.find(params[:id]) 
 
    if @password_update.update_attributes( params[:password_update] )
      redirect_to '/'
    else
      render :action => "edit" 
    end
  end
 
  def create
    @password_update = PasswordUpdate.new(
        params[:password_update].merge(:password_required => false ) )
 
    if @password_update.save
      @password_update.exists!( @password_update.email_as_id )
      redirect_to edit_password_update_url( @password_update )
    else
      render :action => "new" 
    end
  end
 
end

Fairly standard stuff going on in the new, edit and update actions (lines 3-19) but what’s going on in create?

Our user story states that a valid email address is entered into a form and submitted. Our new action renders this form with an single text_field and a button that submits to the create action. As we don’t care about the passwords in this stage, their validations are disabled by setting the :password_required attribute to false (line 23). We’ll look closer at this when discussing the model.

If there was something wrong with the submitted email addres the new action is rendered and the errors are displayed to the user (using the standard error_messages form helper).
However, if everything is ok and @password_update.save returns true (line 25) we want to behave well and redirect from the successful post.

As our model doesn’t have a persistent id (because it doesn’t have a database table) we fake this using the exists! method, provided by active_record_tableless plugin (line 26) and as our model object now has and an id, the redirect using a url helper on line 27 will work fine.

Model

PasswordUpdate model is implemented like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class PasswordUpdate < ActiveRecord::Base
  tableless :columns => [
      [:email, :string],
      [:password, :string],
      [:new_password, :string]
    ]
 
  attr_accessor :password_required  
 
  validates_presence_of     :email  
 
  validates_presence_of     :password, 
                            :new_password, 
                            :new_password_confirmation, 
                              :if => :password_required?
 
  validates_confirmation_of :new_password,
                              :if => :password_required?
 
  # Defaults to true
  def password_required?
    self.password_required.nil? ? true : self.password_required
  end
 
  def email_as_id
    raise "No email set" unless self.email
    PasswordUpdate.encode(self.email)
  end
 
  class << self 
    def find_by_id(id)
      raise "No id argument" unless id 
      p = PasswordUpdate.new(:email => decode(id))
      p.exists!(p.email_as_id)
      return p
    end
    alias_method :find, :find_by_id
 
    def encode( str )
      str.sub("\.", "-")
    end
 
    def decode( str )
      str.sub("-", ".")
    end
  end
 
end

Line 2-6 set up the attributes we need. To control whether validation should be done on passwords we need an attribute for this (line 9). We saw this in the create action earlier.

The validations declared on lines 10-18 are fairly straight forward except that they don’t check the passwords if our password_required attribute is false. (Rigorous validation of email format and such has been omitted in this example code.)

On lines 25-28 a util method is provided to convert the email address into a valid string to use in a url.

Lines 30-37 implements finders to fake that our model actually exists as a normal model would when returned from find. The bare minimum for an existing model is that it should have an id and that new_record? should return false. The active_record_tableless plugin takes care of this for us when we call the exists! method (line 34).

Resources

active_record_tableless plugin on github

The complete Rails 2.1 project that the example above describes is here.

About 2 years ago Jonathan Viney wrote ActiveRecord::Base Without Table. It doesn’t seem to be maintained and doesn’t support the exists! method described above. It also has a different way of integrating into the models.
Also, if you want to use “ActiveRecord::Base Without Table” with Rails 2.x you need to install it from activerecord-basewithouttable-for-rails-2 where Clinton R. Nixon has graciously been hosting it for some time.

Rails , , ,