acts_as_confirmable rails plugin

June 11, 2008 Comments Off

acts_as_confirmable is a Ruby on Rails plugin that is useful when you want to know who ticked a check box and when they did so. It can be found at http://github.com/kumo/acts_as_confirmable/tree/master.

This plugin treats a datetime attribute and an integer attribute as a boolean. This boolean can then be used as normal attribute in a check box (or in any part of the program) and when it is checked, the datetime records the moment and the integer records the id of the current user.

It is assumed that there is a Users table and that the current user can be found at User.current_user. If the current user cannot be accessed then 1 is used as the id.

h3. Installation

$ cd vendor/plugins
$ git checkout git://github.com/kumo/acts_as_confirmable/tree

h3. Usage

* On models where you want to be able to confirm X add a X_confirmed_at datetime and X_confirmed_by integer.
* In the model put acts_as_confirmed :X
* In the views add check boxes (check_box :X)

h3. Example

h4. create a new rails app

$ rails confirm
$ cd confirm

h4. create users table and items table with 3 confirmable fields

$ script/generate scaffold user name:string
$ script/generate scaffold item name:string \
      started_confirmed_by:integer started_confirmed_at:datetime \
      finished_confirmed_by:integer finished_confirmed_at:datetime \
      ready_confirmed_by:integer ready_confirmed_at:datetime

h4. add the plugin to the model

class Item < ActiveRecord::Base
  acts_as_confirmable :started, :finished, :ready
end

h4. add 3 check boxes in items/new.html.erb and items/edit.html.erb



  <%= f.label :started %>
  <%= f.check_box :started %>



  <%= f.label :finished %>
  <%= f.check_box :finished %>



  <%= f.label :ready %>
  <%= f.check_box :ready %>

h4. show the confirmation info in items/list.html.erb


  <%=h item.started? %>
  <%=h item.finished_confirmer.name if @item.finished? -%>

h4. example of assigning a user to current_user

class User < ActiveRecord::Base
  cattr_accessor :current_user
end

class ItemsController < ApplicationController
  before_filter :load_user

  def load_user
    User.current_user = User.find(:first)
  end
end

h3. Available fields

If a model has the attributes fields started_confirmed_at and started_confirmed_by, then the plugin provides:

*

started?

-- true if it has been confirmed by someone on a specific date
*

started

-- same as above but is normally used by a check box tag
*

started=

-- the assignment method that is used by the check box when posting
*

started_confirmer

-- the user that confirmed it
*

started_at

-- when it was confirmed

The perils in trusting syntax highlighting

February 14, 2008 Comments Off

Today after upgrading RSpec I ran a user story that I had just written and received a wonderful error: You have a nil object when you didn't expect it! (NoMethodError). The error occurred while evaluating nil.add_scenario. I didn’t even think to check my user story because the syntax highlighting looked fine and it made sense to me:

  Scenario: areas have different types
    As an administrator
    I want to assign different types to the areas
    So that I can structure the home page

    Scenario: footer shows page links
      Given an area
      And area has 5 pages
      And area is of type footer
      When I view the home page
      Then I should see the home page
      And I should see the footer section
      And the list section should contain 0 areas
      And the footer section should contain 5 links

Of course, if I actually bothered to read it properly then I would have seen that the first line should have read:

  Story: areas have different types

Moral of the story: even if the code that you have to write is small, always assume that you are capable of making silly mistakes!

an assert_difference that works with arrays

November 23, 2006 Comments Off

I have started using the better assert_difference that was posted at blog.caboo.se to improve my rails tests. This allowed me to write tests such as:

  assert_difference Item, :count do
    Item.create(:name => "Monkey magic")
  end

This replaces:

  item_count = Item.count
  Item.create(:name => "Monkey magic")
  assert_difference, item_count + 1, Item.count

but I also wanted to be able to test if emails were sent by writing:

  assert_difference ActionMailer::Base.deliveries, :size do
    SystemNotifier.deliver_important_message
  end

That didn’t work and it was noted on that website that the code didn’t work properly with arrays so I have modified the assert_difference method to check if the objects respond to the method (e.g. size) and act accordingly:

  def assert_difference(objects, method = nil, difference = 1)
    initial_values = []
    if objects.respond_to? method
      initial_values = objects.send(method)
    else
      objects = [objects].flatten
      initial_values = objects.inject([]) { |sum,obj| sum << obj.send(method) }
    end

    yield

    if difference.nil?
      objects.each_with_index { |obj,i|
        assert_not_equal initial_values[i], obj.send(method) #, "#{obj}##{method}"
      }
    else
      if objects.respond_to? method
        assert_equal initial_values + difference, objects.send(method) #, "#{objects}##{method}"
      else
        objects.each_with_index { |obj,i|
          assert_equal initial_values[i] + difference, obj.send(method) #, "#{obj}##{method}"
        }
      end
    end
  end

I have tested it for an array such as ActionMailer::Base.deliveries but I haven't tested it in other situations so hopefully it won't create any problems.


The rspec version of this is:

  def assert_difference(objects, method = nil, difference = 1)
    initial_values = []
    if objects.respond_to? method
      initial_values = objects.send(method)
    else
      objects = [objects].flatten
      initial_values = objects.inject([]) { |sum,obj| sum << obj.send(method) }
    end

    yield

    if difference.nil?
      objects.each_with_index { |obj,i|
        initial_values[i].should_not_equal obj.send(method) #, "#{obj}##{method}"
      }
    else
      if objects.respond_to? method
        (initial_values + difference).should_eql objects.send(method) #, "#{objects}##{method}"
      else
        objects.each_with_index { |obj,i|
          (initial_values[i] + difference).should_eql obj.send(method) #, "#{obj}##{method}"
        }
      end
    end
  end

rubyonrails – beware of similarly named relationships

April 29, 2006 5 comments

I recently developed a “rubyonrails”:http://www.rubyonrails.org application in which a user had a province (as part of their address) and also a collection of selected provinces.

class Users
  belongs_to :province
  has_and_belongs_to_many :selected_provinces, :class_name => "Province"
end

I could very easily find out which provinces a user had selected and also which users had selected a specific province. However I didn’t realise that if I accessed a user via the province then the province_id which previously pointed the user’s address province was replaced by the id of the selected province.

province = Province.find_first
province.users.each do |user|
  # do something
  # however user.province_id now points to province
end

I assume the province_id is added by the habtm join so that we can understand which selected_province object this user object comes from however in my case it meant that I lost the original province_id and so if I saved that user then I would end up changing the address province.

This is probably not a bug as such but something that should be watched out for as it took me a while to discover this problem with my application.



Flags…