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
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!
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
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.