Tagged with "coding - Coderkitty"

Picking up the breadcrumbs

Its always easy to skip going through the details of what your stacked up knowledge and just go for trying out new stuff. This time, I take it one step back and skim through pages of books I have always ignored and pick some sections that are worth looking into and post them here.

I’ve been trying to cut down on paper materials at home. Mostly, trying to read all those books and photocopied materials and just jot down notes from them. I decided to post them instead. So here goes..

From a photocopy of a ruby/rails book (no title gathered):

Some string extensions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string = "Now is the time"
puts string.at(2)  #=> "w"
puts string.from(8)  #=> "he time"
puts string.to(8)  #=> "Now is th"
puts string.first  #=> "N"
puts string.first(3)  #=> "Now"
puts string.last  #=> "e"
puts string.last(4)  #=> "time"
 
puts string.starts_with?("No")  #=> true
puts string.ends_with?("ME")  #=> false
 
count = Hash.new(0)
string.each_char { |ch | count[ch] += 1 }  
#=> {" " => 3, "w" => 1, "N" => 1, "o" => 1, "e" => 2, "h" => 1, "s" => 1, "t" => 2, "i" => 2 }

Customizing inflections:

1
2
3
4
5
Inflector.inflections do |inflect|
  inflect.plural(/-in-law$/, "s-in-law")
  inflect.singular(/s-in-law$/, "-in-law")
  inflect.uncountable("air", "information", "water")  #no singular form, no plural form
end

Extensions to numbers:

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
puts 3.ordinalize  #=> "3rd"
puts 321.ordinalize  #=> "321st"
 
puts 20.byes  #=> 20
puts 20.kilobytes  #=> 20480
puts 20.megabytes  #=> 20971520
puts 20.gigabytes  #=> 21474836480
puts 20.terabytes  #=> 21990232555520
puts 20.petabytes  #=> 22517998136852480
puts 1.exabyte  #=> 1152921504606846976
 
puts 20.seconds  #=> 20
puts 20.minutes  #=> 1200
puts 20.hours  #=> 72000
puts 20.days  #=> 1728000
puts 20.weeks  #=> 12096000
puts 20.fortnights  #=> 24192000
puts 20.months  #=> 51840000
puts 20.years  #=> 630720000
 
puts Time.now  #=> Tue May 18 22:03:21 +0800 2010
puts 20.minutes.ago  #=> Tue May 18 21:43:53 +0800 2010
puts 20.hours.from_now  #=> Wed May 19 18:04:11 +0800 2010
puts 20.weeks.from_now  #=> Tue Oct 05 22:04:22 +0800 2010
puts 20.months.ago  #=> Thu Sep 18 22:04:34 +0800 2008
puts 20.minutes.until("2010-12-26 12:00:00".to_time)  #=> Sun Dec 26 11:40:00 UTC 2010
puts 20.minutes.since("2006-11-08 01:15:00".to_time)  #=> Wed Nov 08 01:35:00 UTC 2006

Time and Date Extensions:

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
now = Time.now  
puts now  #=>  Tue May 18 22:09:17 0800 2010
puts now.to_date  #=> 2010-05-18
puts now.to_s  #=> Tue May 18 22:09:17 +0800 2010
 
puts now.to_s(:short)  #=> 18 May 22:09
puts now.to_s(:long)  #=> May 18, 2010 22:09
puts now.to_s(:db)  #=> 2010-05-18 22:09:17
puts now.to_s(:rfc822)  #=> Tue, 18 May 2010 22:09:17 +0900
puts now.ago(3600)  #=> Tue May 18 21:09:17 +0800 2010
puts now.at_beginning_of_day  #=> Tue May 18 00:00:00 +0800 2010
 
puts now.at_beginning_of_month  #=> Sat May 01 00:00:00 +0800 2010
puts now.at_beginning_of_week  #=> Mon May 17 00:00:00 +0800 2010
puts now.beginning_of_quarter  #=> Thu Apr 01 00:00:00 +0800 2010
puts now.at_beginning_of_year  #=> Fri Jan 01 00:00:00 +0800 2010
puts now.at_midnight  #=> Tue May 18 00:00:00 +0800 2010
 
puts now.change(:hour => 13)  #=> Tue May 18 13:00:00 +0800 2010
puts now.last_month  #=> Sun Apr 18 22:09:17 +0800 2010
puts now.last_year  #=> Mon May 18 22:09:17 +0800 2009
puts now.midnight  #=> Tue May 18 00:00:00 +0800 2010
puts now.monday  #=> Mon May 17 00:00:00 +0800 2010
 
puts now.months_ago(2)  #=> Thu Mar 18 22:09:17 +0800 2010
puts now.months_since(2)  #=> Sun Jul 18 22:09:17 +0800 2010
puts now.next_week  #=> Mon May 24 00:00:00 +0800 2010
puts now.next_year  #=> Wed May 18 22:09:17 +0800 2011
puts now.seconds_since_midnight  #=> 79757.033881
 
puts now.since(7200)  #=> Wed May 19 00:09:17 +0800 2010
puts now.tomorrow  #=> Wed May 19 22:09:17 +0800 2010
puts now.years_ago(2)  #=> Sun May 18 22:09:17 +0800 2008
puts now.years_since(2)  #=> Fri May 18 22:09:17 +0800 2012
puts now.yesterday  #=> Mon May 17 22:09:17 +0800 2010
 
puts now.advance(:days => 30)  #=> Thu Jun 17 22:09:17 +0800 2010
puts Time.days_in_month(2)  #=> 28
puts Time.days_in_month(2, 2000)  #=> 29

Extension to Ruby Symbols:

1
2
3
4
5
groups = posts.group_by { |post| post.author_id }
 
or
 
groups = post.group_by(&:author_id)

Forcing urls to use https

I found this extremely useful:

1
2
3
def redirect_to_ssl
    redirect_to url_for params.merge({:protocol => 'https://'}) unless request.ssl?
end

Put this in your application controller and call

1
before_filter :redirect_to_ssl

on every controller that would require ssl. You can pick which actions would only require this by extending your before_filter declaration as such:

1
before_filter :redirect_to_ssl, :only => [:new, :purchase]

or even by exclusion, whichever is more convenient for your case:

1
before_filter :redirect_to_ssl, :except => [:show]

PS: There are other resources too, such as this one (that could be of help): http://fuadcse.blogspot.com/2009/01/redirecting-http-request-to-https-in.html

Aug 2, 2009 - Coderkitty, PHP, Programming    No Comments

Free mini store search app in PHP and AJAX – for dummies

My last PHP project was almost a year and a half ago. I started working in PHP eversince college. Moved on to Java, Python and now, Ruby. I must say, PHP was the longest scripting language I used in my entire professional programming career. Maybe because it was the easiest language to learn (and abuse).

So many resources on the Web have fluttered around for so long, even after frameworks were born. There came CakePHP, Zend, Prado, Symfony, etc. There were other tools too to help in making PHP a truly object orientd scripting language. Some of my few favorites were: Smarty and AdoDB.

I’ve quit on PHP, but I do not abhor it. I still understand the old businesses still running their cash-cow apps in PHP and is not yet looking forward to trusting Ruby on Rails or even other alternatives. The business is always driven by finances and resources. Sometimes, the best technological decisions elude the managers’ table during meetings.. hence, the prevalence of a lot of legacy PHP applications. With this understanding, I still believe that these businesses would very much benefit upgrading their systems and even the skill sets of their own developers. Even if they wanted to remain with PHP, there are a lot of options out there that do not involve complete development of applications from the ground up. Some would even prefer built systems that allow plugins for upgrade–like Drupal, Joomla, and even WordPress.

There are cons of using such built systems (dependency on plugin upgrades, general hacking, and a becoming a prey to overly customizing them), and so, I myself would not recommend using them if you won’t be able to protect your site from these downside.

Though its been quite a while since I last worked on PHP, it became a challenge for me to just do this mini store application just for fun of measuring how long I could work on such a request given that I have been out of touch from the PHP world, and that the requirement could also involve AJAX.. and it would be from the ground up!

This would be a very simple application though needing a database and javascript support for AJAX. Let me now guide you to how it was done.

First, create the database with the following schema:

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
CREATE TABLE areas (
  id int(11) NOT NULL auto_increment,
  country_id int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY  (id),
  KEY country_id_index (country_id)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
 
CREATE TABLE countries (
  id int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL,
  PRIMARY KEY  (id)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;
 
CREATE TABLE stores (
  id int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL,
  contact_person varchar(255) NOT NULL,
  contact_info varchar(255) NOT NULL,
  address varchar(255) NOT NULL,
  email varchar(40) NOT NULL,
  website varchar(255) NOT NULL,
  area_id int(11) NOT NULL,
  PRIMARY KEY  (id),
  KEY name_index (`name`),
  KEY area_id_index (area_id)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

Next, let’s prepare the main file. We are going to need a stores.php, getareas.php, getstores.php, search.php, ajax.js and include files for connecting to the database and query functions.

Stores.php

The most important parts of this file are the section handlers. We’ll need handles for: (1) search results, (2) areas list, (3) stores list. These should have distinct ids that you’ll have to note.

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
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Mini store app</title>
    <script type="text/javascript" src="js/ajax.js"></script>
    <script src="js/prototype.js" type="text/javascript"></script>
    <script src="js/scriptaculous.js" type="text/javascript"></script>    
  </head>
 
  <body>
 
    <form id="searchForm">
      <input type="text" name="search">
      <input type="submit" onclick="showSearchResults(Form.findFirstElement(document.forms[0]).value);$('searchBox').setStyle('display: block');new Effect.SlideDown($('searchBox'));return false;" value="Search for stores">
    </form>
 
    <div id="searchBox" style="display: none;"></div>
 
    <div style="float: left;clear: both;">
      <?php
        $results = Site::getCountries();
 
        echo "<ul class='item' style='overflow:auto;height: 500px;'>";  
        foreach($results as $country){
            echo "<li>";
            echo "<a href='' onclick=\"javascript:showAreas('" . $country['id'] . "');$('areaHint').setStyle('display: none;');$('areaHint').undoPositioned();new Effect.Move($('areaHint'), { x: 0, y: 0, mode: 'absolute' });$('areaHint').setStyle('display: block;');new Effect.Move($('areaHint'), { x: 60, y: 0 });return false;\">" . $country['name'] . "</a>";
            echo "</li>";
        }
        echo "</ul>";
      ?>
    </div>
 
    <div id='storeHint' style="border: 0px solid blue; margin-top: 15px; overflow: auto; float: right; width: 300px; height: 500px; display: block;">
    </div>
    <div id='areaHint' style="border: 0px solid red; margin-top: 30px; overflow: auto; width: 300px; height: 500px; display: block;"></div>
  </body>
</html>

Getareas.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
  $results = Site::getAreasOfCountry($_GET["q"]);
 
  if($results){
    echo "<ul class='item'>";  
    foreach($results as $area){
        echo "<li>";
        echo "<a href='' onclick=\"javascript:showStores('" . $area['id'] . "');$('storeHint').setStyle('display: none;');$('storeHint').setStyle('display: block;');$('storeHint').undoPositioned();new Effect.Move($('storeHint'), { x: 0, y: 0, mode: 'absolute' });$('storeHint').setStyle('display: block;');new Effect.Move($('storeHint'), { x: -60, y: 0 });return false;\">" . $area['name'] . "</a>";
        echo "</li>";
    }
    echo "</ul>";   
  }else{
    echo "<p style='text-align: center;'>Whoops, no areas found.</p>";
  }
?>

Getstores.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
  $results = Site::getStoresOfArea($_GET["q"]);
 
  if($results){
    echo "<ul class='item'>";  
    foreach($results as $row){
      echo "<li>";
      echo "<a href='' onclick=\"javascript:showStoreDetails('" . $row['id'] . "');return false;\">" . $row['name'] . "</a>";
      echo "<div id='storeDetail" . $row['id'] . "' style='display: none;'>";
        echo "<table>";
        echo "<tr><td>Contact Person</td><td>" . $row['contact_person'] . "</td></tr>";
        echo "<tr><td>Contact Info</td><td>" . $row['contact_info'] . "</td></tr>";
        echo "<tr><td>Address</td><td>" . $row['address'] . "</td></tr>";
        echo "<tr><td>Email</td><td>" . $row['email'] . "</td></tr>";
        echo "<tr><td>Website</td><td>" . $row['website'] . "</td></tr>";
        echo "</table>";
      echo "</div>";
      echo "</li>";
    }
    echo "</ul>";   
  }else{
    echo "<p style='text-align: center;'>Whoops, no stores found.</p>";
  }
?>

Search.php

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
<?php
  $results = Site::getSearch($_GET["search"]);
 
  if($results){
?>
 
    <a href="#" style="float: right;padding-right: 15px;" onclick="$('searchBox').setStyle('display: none;');">CLOSE</a>
 
<?php    
    echo "<table border='1' style='margin: 10px; background-color: rgb(83, 131, 219); width: 90%;'>";
    echo "<tr><th>Name</th><th>Contact Person</th><th>Contact Info</th><th>Address</th><th>Email</th><th>Website</th><th>Country</th><th>Area</th></tr>";
 
    foreach($results as $row){
      echo "<tr>";
      echo "<td>" . $row['name'] . "</td><td>" . $row['contact_person'] . "</td><td>" . $row['contact_info'] . "</td><td>" . $row['address'] . "</td><td>" . $row['email'] . "</td><td>" . $row['website'] . "</td><td>";
      echo "<a href='' onclick=\"javascript:showAreas('" . $row['country_id'] . "');$('searchBox').setStyle('display: none;');$('areaHint').setStyle('display: none;');$('areaHint').undoPositioned();new Effect.Move($('areaHint'), { x: 0, y: 0, mode: 'absolute' });$('areaHint').setStyle('display: block;');new Effect.Move($('areaHint'), { x: 60, y: 0 }); return false;\">" . $row['country_name'] . "</a>";
      echo "</td><td>";
      echo "<a href='' onclick=\"javascript:showStores('" . $row['area_id'] . "');$('searchBox').setStyle('display: none;');$('storeHint').setStyle('display: none;');$('storeHint').undoPositioned();$('storeHint').setStyle('display: block;');new Effect.BlindDown('storeHint'); return false;\">" . $row['area_name'] . "</a>";
      echo "</td>";
      echo "</tr>";
    }
    echo "</table>";
  }else{
    echo "Whooops! No store matched your search criteria.";
  }
?>

Ajax.js

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
var xmlhttp;
var areaHandle;
var storeHandle;
 
function showSearchResults(str)
{
xmlhttp=GetXmlHttpObject();
if (xmlhttp==null){  
  alert ("Browser does not support HTTP Request");  
  return;
}
 
var url="search.php";
url=url+"?search="+str;
url=url+"&sid="+Math.random();
xmlhttp.onreadystatechange=stateChangedSearch;
xmlhttp.open("GET",url,true);
xmlhttp.send(null);
}
 
function showAreas(str)
{
xmlhttp=GetXmlHttpObject();
if (xmlhttp==null){  
  alert ("Browser does not support HTTP Request");  
  return;
}
 
areaHandle = str;
var url="getareas.php";
url=url+"?q="+str;
url=url+"&sid="+Math.random();
xmlhttp.onreadystatechange=stateChangedAreas;
xmlhttp.open("GET",url,true);
xmlhttp.send(null);
}
 
function showStores(str)
{
xmlhttp=GetXmlHttpObject();
if (xmlhttp==null){  
  alert ("Browser does not support HTTP Request");  
  return;
}
 
storeHandle = str;  
var url="getstores.php";
url=url+"?q="+str;
url=url+"&sid="+Math.random();
xmlhttp.onreadystatechange=stateChangedStores;
xmlhttp.open("GET",url,true);
xmlhttp.send(null);
}
 
function stateChangedSearch()
{
  switch(xmlhttp.readyState){
    case 4:
      document.getElementById('searchBox').innerHTML=xmlhttp.responseText;
      break;
 
    case 1:
      document.getElementById("searchBox").innerHTML="Loading...&nbsp;<img src='ajax-loader.gif' alt='loading...' />";
      break;
  }  
}
 
function stateChangedAreas()
{
  switch(xmlhttp.readyState){
    case 4:
      handleId = "areaHint";
      document.getElementById(handleId).innerHTML=xmlhttp.responseText;
      break;
 
    case 1:
      document.getElementById("areaHint").innerHTML="Loading...&nbsp;<img src='ajax-loader.gif' alt='loading...' />";
      break;
  }  
}
 
function stateChangedStores()
{
  switch(xmlhttp.readyState){
    case 4:
      handleId = "storeHint";
      document.getElementById(handleId).innerHTML=xmlhttp.responseText;
      break;
 
    case 1:
      document.getElementById("storeHint").innerHTML="Loading...&nbsp;<img src='ajax-loader.gif' alt='loading...' />";
      break;
  } 
}
 
function showStoreDetails(storeId){
  storeHandle = "storeDetail" + storeId;
 
  if(document.getElementById(storeHandle).style.display=='block'){
    document.getElementById(storeHandle).style.display = 'none';
  }else{
    document.getElementById(storeHandle).style.display = 'block';
  }
}
 
function GetXmlHttpObject()
{
if (window.XMLHttpRequest)
  {
  // code for IE7+, Firefox, Chrome, Opera, Safari
  return new XMLHttpRequest();
  }
if (window.ActiveXObject)
  {
  // code for IE6, IE5
  return new ActiveXObject("Microsoft.XMLHTTP");
  }
return null;
}

Of course, it will be totally up to you how you will implement your query functions and other includes for this, but here is what I have:

includes/site.inc.php

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
function getCountries(){
    $db = $GLOBALS['db'];
    $sql = "select * from countries order by name asc";
    $a = $db->CacheGetAll(3600,$sql);
    return $a;
  }
 
  function getSearch($key){
    $db = $GLOBALS['db'];
    $sql = "select stores.name, stores.contact_person, stores.contact_info, stores.address, countries.name as country_name, countries.id as country_id, areas.name as area_name, areas.id as area_id from stores, countries, areas where (stores.name like '%{$key}%' or stores.address like '%{$key}%' or stores.contact_person like '%{$key}%' or stores.contact_info like '%{$key}%' or stores.email like '%{$key}%' or stores.website like '%{$key}%') and stores.area_id = areas.id and areas.country_id = countries.id order by stores.name, stores.address asc";
    $a = $db->GetAll($sql);
    return $a;
  }
 
  function getAreasOfCountry($countryId){
    $db = $GLOBALS['db'];
    $sql = "select areas.*, countries.name as country_name from areas, countries where areas.country_id = countries.id and areas.country_id = $countryId order by areas.name asc";
    $a = $db->CacheGetAll(3600,$sql);
    return $a;
  }
 
  function getStoresOfArea($areaId){
    $db = $GLOBALS['db'];
    $sql = "select * from stores where area_id=$areaId order by name asc";
    $a = $db->CacheGetAll(3600,$sql);
    return $a;
  }

To briefly explain, the stores.php page displays a search form at the top and lists all available countries at the left side. For better display, the list of countries, areas and stores are clipped and set overflow to automatic only. Each country is displayed a link which then triggers and ajax call to get the areas for this specific country. This link points to a javascript method that invokes AJAX and returns the results of the invoked file to the handle specified in our stores.php page.

screenshot of mini store app here

The list of areas are displayed in the next pane. The AJAX method displays a loading message while waiting for the results. Added effects were used from the use of prototype and scriptaculous for eye candy only. If you don’t want to implement prototype/scriptaculous, you can remove the following files from being loaded in the head section of your stores.php:

1
2
 <script src="js/prototype.js" type="text/javascript"></script>
 <script src="js/scriptaculous.js" type="text/javascript"></script>

and, use these lines to display the names of the country/area:

in stores.php:

1
echo "<a href='' onclick=\"javascript:showAreas('" . $country['id'] . "');return false;\">" . $country['name'] . "</a>";

in getareas.php:

1
echo "<a href='' onclick=\"javascript:showStores('" . $area['id'] . "');return false;\">" . $area['name'] . "</a>";

You’re officially good to go. There are other tips you can use too. If you’re interested with personalizing your ajax-loader.gif, you can visit this site and create your own. And if however, you have a huge amount of information to be stored, make sure you have the correct indices for your database.

1
2
3
ALTER TABLE `areas` ADD INDEX `country_id_index` ( `country_id` );
ALTER TABLE `stores` ADD INDEX `store_name_index` ( `name` );
ALTER TABLE `stores` ADD INDEX `store_area_id_index` ( `area_id` );

This mini store app plus a CMS to maintain it (both from scratch) took me a whole deal of 5hours’ work. I must admit, it was a bit hard for me to go back to working in PHP when I was so overjoyed with Ruby. I knew I could have done this all in Ruby on Rails in a little less than 5hours’ work and with much lesser code. I still think that for an application to be neat, less cluttered and specifically working in DRY, it would take so much effort not like in Rails. And that’s the reason why I don’t want to go back to PHP again.

But do help yourself, I saved an archive of this code for your consumption. Download here or here. I’m pretty sure you’ll put better evolution to this short but sweet app. Its very crude, and most styles where just hard coded for the benefit of fulfilling a demo for the client‘s request. You may want to visit the store playground here.

Thanks and Enjoy!

Dec 9, 2008 - Coderkitty, Testing    No Comments

Testing is a Remarkable thing!

I never knew testing could really change the way I code. I know I’m a good programmer because I love implementing standards and doing the “right things” the “first time”.. and yet testing has made me realize something..

I never got to know how to properly test my own code. I’ve only used a lot of selenium, trying to dig into making it work for simple end user testing and usability of the application, but framework testing that involves the unit tests, integration tests and even performance tests are now putting themselves in the limelight for my programming career.

Looking at a huge challenge of writing tests for an old yet immature application (that I know of), I felt that this would be a very beautiful yet painstaking challenge. It can help me grow as a programmer and still save the project and the sanity of my colleagues. I have closely inspected the use of certain frameworks.

The following have been my choices: RSpec, Shoulda, Cucumber, Rails Unit Test. I have asked around and most of my network responded with their love of RSpec. I know that RSpec is already gained widespread use and popularity amongst Rails developers, but as I was also looking into using Shoulda, I found that there truly were things that I did not like from RSpec that I very much found in Shoulda. Some of these are:

  1. I am having difficulty of testing nested resources in RSpec.
  2. RSpec’s it-something-blank syntax doesn’t really amuse me (on a personal note, thought it fascinated me way back at the time I discovered it).
  3. I don’t like the way messages are displayed by RSpec when a test case fails.

Shoulda on the other hand looked cleaner for me. I also like the straightforwardness of the should-have-this methods for testing (especially in ActiveRecords).

Now, what I was missing in between was using the strength of RSpec in all other areas, and the readability and macros of Shoulda. Here is when I stumbled across Remarkable; and truly I must agree it is remarkable!

I have immediately jumped into writing a simple test for one of my models as follows:

class Feedback &gt; ActiveRecord::Base
  acts_as_audited
  validates_presence_of :name, :message =&gt; "..hey you can't be anonymous!", :on =&gt; :create
  validates_presence_of :email, :message =&gt; "..I need your contact details.", :on =&gt; :create
  validates_presence_of :topic, :message =&gt; "..what are you writing to me about?", :on =&gt; :create
  validates_presence_of :message, :message =&gt; "..so you were saying?", :on =&gt; :create
 
  validates_format_of :email, :with =&gt; /^([^@\s]{1}+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i, :message =&gt; "..it should look something like yourname@something.com", :on =&gt; :create
 
  validates_length_of :message, :minimum =&gt; 5, :message =&gt; "..uhm, can you be more specific?"
  validates_length_of :name, :minimum =&gt; 3, :message =&gt; "..uhm, can you be more specific?"
  validates_length_of :topic, :minimum =&gt; 5, :message =&gt; "..uhm, can you be more specific?"
end

I know there are only few resources in the net that show you the exact counterpart of code being tested and test codes, but here’s mine. Maybe this can help.

require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
 
describe Feedback, 'A Feedback instance' do
 
    VALID = {
      :name =&gt; "firstname lastname",
      :email =&gt; "email.email@email.com",
      :topic =&gt; "some topic",
      :message =&gt; "some message"
    }
 
  it 'should accept VALID attributes' do
    assert f = Feedback.new(VALID)
    assert f.valid?
  end
 
  should_require_attributes(:name, :message =&gt; "..hey you can't be anonymous!")
  should_require_attributes(:email, :message =&gt; "..I need your contact details.")
  should_require_attributes(:topic, :message =&gt; "..what are you writing to me about?")
  should_require_attributes(:message, :message =&gt; "..so you were saying?")
 
  should_ensure_length_at_least(:name, 3, :short_message =&gt; "..uhm, can you be more specific?")
  should_ensure_length_at_least(:message, 5, :short_message =&gt; "..uhm, can you be more specific?")
  should_ensure_length_at_least(:topic, 5, :short_message =&gt; "..uhm, can you be more specific?")
 
  should_allow_values_for(:name, "maricris", "ace", "greg", "john paul", "mary jesus joseph")
  should_not_allow_values_for(:name, "aa", "at", "jp", "1", "s", :message =&gt; "..uhm, can you be more specific?")
 
  should_allow_values_for(:email, "email.email@email.com", "email_email@yahoo.com", "123@yahoo.com")
  should_not_allow_values_for(:email, "123@.com", "testing!@!yahoocom", "3@11234.#8com", :message =&gt; "..it should look something like yourname@something.com")
end

There. I know its not the best and the prettiest, but its a work in progress. Writing these wee lines of code to test a small, almost bare model was truly a worthwhile experience. What I really liked about doing this was the fact that at some point, while I was working backward (not the usual TDD), when I arbitrarily took that my model was initially written meekly but completely, I was wrong.

In the process of enumerating the cases where the validation should fail, I really had to go back to my model and change something. Writing tests can make you more precise, and even make you feel that you can bring out your code to production and feel confident.

I can now see why Gregg said he learned to love testing too. Its not like I am forcing you to, but you should have a taste of this cake and be your own judge. Will you like it or not? ..its totally up to you. This will bring me not just sanity, but also confidence and a lot more freedom to rewrite and refactor in the future. Just great! smile

Trust nothing but tests

Where have all the tests gone? What tests???

I hate it that I have to manually exert so much effort into checking everything one by one to see that nothing is broken. In the end, what you have painstakingly put into order always gets blamed, and you don’t have any proof whatsoever that its not broken.

I “shoulda”, I coulda.. but can’t

I have often exercised optimism and tried to work on my own tests for my own modules. Yeah, but no yeah. I end up using up all of my time for coding because the time allotted for the whole thing isn’t even enough for just coding the feature!

Now, when time comes that you want to put some improvements, you become doubtful.. very unconfident. When a bug ticket comes up, you just don’t know where to look. It could’ve been elegant, hackish to just run rcov or your tests and find out where and what went wrong.. but no.

My energy gets wasted

What more can I say.. I just can’t seem to trust anything anymore without tests. And yes, my energy gets wasted.

  • Facebook
  • LinkedIn
  • FriendFeed
  • Twitter
  • Tumblr
  • Flickr
  • YouTube
  • Vimeo