The Passionate Craftsman

Ruby, PHP, MySql, Software engineering and more.

Tuesday, 26 January 2010

Watir on Rails

Watir is a tool for user acceptance testing and in general can be used to control a browser to do automated task. Since I am using the standard Rails's testing framework, and I run my tests with rake test, I thought why not to use watir test in a similar way. There is a plugin, Watir on Rails, that integrates Ruby on Rails with Waitr. You only need to run rake test:watir to test your application with rake. The test can be written in a similar way of the other Rails' tests. For instance:



How to use the example:
  1. gem install watir # install watir
  2. script/plugin install git://github.com/chip/watir-on-rails.git
  3. put the file in test/watir or script/generate watir SuccessfulLogin to generate a test
  4. run: rake test:watir
  5. watch the browser doing your job ;-)
  6. Start learning from the cheat sheet:http://wiki.openqa.org/display/WTR/Ruby+Cheat+Sheet


Watir need your web server on, if you are testing a local application, so you should run:

mongrel_rails start -e test # if you are using mongrel

Links:

Watir on Rails - Watir on Rails web page
Watir on Rails at Github
Watir - the official web site

Labels: , ,

Wednesday, 20 January 2010

Rails deployment with Capistrano and SVN

I spent one day and half trying to deploy my first Rails application with Capistrano. Where I work, we use SVN as SCM. My PC runs Windows XP and the production server is Solaris 10. The server uses Apache, MySql and Passenger, aka mod_rails. I have used this recipe:


set :application, "apps"
set :user, 'my_username'
#default_run_options[:pty] = true
set :domain, "solaris10.ds.domain.com"
set :repository, "https://svn.domain.co.uk/svn/apps"
set :local_scm_command, "C:/applications/svn/svn"
set :scm_command, "/usr/local/bin/svn"
set :use_sudo, false
set :deploy_to, "/sites/rails-data/#{application}"
set :database_conf, "/sites/rails-data/database_conf"

role :app, domain
role :web, domain
role :db, domain, :primary => true

namespace :deploy do
task :start, :roles => :app do
run "touch #{current_release}/tmp/restart.txt"
end

task :stop, :roles => :app do
# Do nothing.
end

desc "Restart Application"
task :restart, :roles => :app do
run "touch #{current_release}/tmp/restart.txt"
end
end

desc "Echo environment vars"
namespace :env do
task :echo do
run "echo printing out cap info on remote server"
run "echo $PATH"
run "echo $LD_LIBRARY_PATH"
end
end

# optional task to reconfigure databases
after "deploy:update_code" , :configure_database
desc "copy database.yml into the current release path"
task :configure_database, :roles => :app do
db_config = "#{database_conf}/database_#{application}.yml"
run "cp #{db_config} #{release_path}/config/database.yml"
end


I have lost a huge amount of time trying to understand why Capistrano was not able to find the SVN client and some libraries. First tip, you must have an SVN client in your production server. When you run Capistrano, it connects to the production server with SSH. The main difference is that is an SSH session in non-interactive mode, so your user path is not loaded! Second tip, create a file in ~/.ssh name environment and add your PATH variable and your LD_LIBRARY_PATH, so the SVN client will be able to find the libraries that it needs. Third tip, do not use the export command in the environment file. Fourth tip, your sshd_config must have:

PermitUserEnvironment yes

Without this setting sshd will not load the environment file. In my Capistrano recipe there are three new tasks added to deploy, start, restart. The new two task are used to restart Passenger, so that tasks cannot be used if you have Mongrel. If you run 'cap env:echo' you will be able to see the PATH and LD_LIBRARY_PATH used by Capistrano during the SSH session.

If Capistrano is not able to set the PATH of the local and remote SVN clients you can use:

set :local_scm_command, "C:/applications/svn/svn" # set local svn client path
set :scm_command, "/usr/local/bin/svn" # set remote svn path

The standard path, for svn command, is typically /bin:/usr/bin:/usr/sbin.

You should not keep your database.yml in your SCM. I keep it in a directory and I tell to Capistrano to copy this file during the deployment, otherwise the migration will fail. Now what am I missing? Well a Continuos Integration (CI) solution. Yesterday I have evaluated CruiseControl.rb by ThoughtWorks. It seems very easy to install and use, but it still have not read the documentation completely and I still do not know if I can integrate CruiseControl.rb with Capistrano and if it is useful at all, but this will be another post.

After many releases, the release path will start to have many versions and if we want to clean the directory, this is the command to use:

cap deploy:cleanup

This tasks will leave only the last five releases. If you want to set the number of releases to keep in the production server;

set :keep_releases, 3 # where 3 is the number of releases

I have a directory of videos, named uploads, in Rails root directory. Every relaease will move your files with the old code. So I removed uploads directory from SVN (ignore list), then I configured an additional task in deploy.rb:

deploy.task :symlinks do
run "ln -nfs #{shared_path}/uploads #{release_path}/uploads"
end

This task create a symlink to the shared path of Capistrano. The shared path is there to be used by any release. With this technique you can keep your files outside your code, all you need is a symlink!

I see Capistrano as a very useful tool, very flexible and it solves most of my deployment needs. Everytime I think it is time to update the production environment, I commit all the modified files with SVN, than I run 'cap deploy', enter the password to access the server and... I have my fresh code updated. Of course before updating, all the tests should be executed successfully.

Links:
pretheory - Capistrano path and environment issues
Capistrano - From the Beginning
The Pragmatic Bookself - Agile Web Development with Rails, Third Edition

Labels: ,

Saturday, 16 January 2010

Qcubed/Qcodo Oracle adapter on Github

I will stop using Qcodo and Qcubed as web framework and moving my current project, a corporate intranet, to Ruby on Rails. However since I was the first to complete the Oracle adapter, and I have developed few applications working with Oracle (for Acquedotto del Fiora), I decided to put the adapter in a Github project, after fixing a serious bug.

http://github.com/rtacconi/Qcodo-Qcubed-Oracle-adapter

Thursday, 14 January 2010

Happy Birthday Blog!

I started this blog last year, the 5th of January 2009. So, happy bithday blog, your one year older. I hope that this blog might be interesting so someone. I am using it not only to publish articles for the Internet readers, but even for myself. I write technical things that I need to use it not every day, but sometimes. This blog has been helpful for refresh my mind on how to create a trigge on Mysql, referetial integrity and more.

Friday, 8 January 2010

Migration from Qcodo to Rails

I am re-developing an intranet in Rails. It is currently written in PHP with Qcodo as framework. It is very new intranet, so why I should re-develop it with Rails? Well, I do not like to program with PHP anymore and I have found many problems in Qcodo and I realized that the way to develop with that framework is not the way I like it.

This is a list of aspects that I do not like in PHP:

  • Every variable has to use the dollar character, the semicolon in the end of a statement, curly braces for blocks.
  • Namespaces in PHP 5.3 has an horrible syntax, it is similar to a DOS path (same slash).
  • PHP 5.3 has the infamous goto! Is that a way to improve a language? Crazy!
  • PHP is loosely-typed, which means that converts values behind the scenes and you need to have a good knowledge of the language to understand what it might happen to your data.
  • Variables are case sensitive, functions are not. There is not a real naming convention in the primitives.
  • Every function must be declared with the 'function' keyword, how redundant!
  • PHP attracts bad programmers (although there are good programmes that use PHP).
  • Many famous programs written in PHP are not really OOP, they do not use design patterns, or something to similar to a clean code, see Drupal, Joomla & co.
  • There are other reasons, but I want to talk about moving from PHP to Rails.
Qcodo problems are:

  • QFroms, the form used by Qcodo are dependent form JavaScript, without it, your web site/application will not work.
  • The JavaScript used is bugged, does not use a mainstream JS framework.
  • QForm controls, such as text boxes, select lists, sometimes loose the status, so the data have nasty Ajax errors that block the page (it becomes non-responsive).
  • Ajax error just make the QForm dead. This is not acceptable.
  • It is an MVC framework, but you do not have a front controller, so forget to use a nice URL out of the box; you have to build a front controller by your own and use mod_rewrite.
  • It is event-driven too, which is not bad. But, you define all your controls as you do in a desktop-like application, like in VB (the first version was made in Visual Basic!). Lots of HTML is embedded in objects, you do not get the feeling of making a template but you just write just $object->render(). HTML templates should have HTML or tags, as in Java, Pythons or Rails frameworks.
  • You cannot add JavaScript in your tags, because you do not has tags, but just $object->render() (well you can do it but it is awkward). Instead you have to create a custom object to put your Javascript (official way to do it).
  • Because of the previous point, to write JavaScript in Qcodo is a pain in the a...
  • Qcodo is a dead project because the creator, Mike Ho, has blocked the commits to the source code for a lot of time. Now the project is on Github, but it has not changed a lot, for me it is too late and I cannot see anything positive in the roadmap, if there is one.
  • Qcodo has been forked by some guys because of the problem stated above. The fork is Qcubed. I left that community becouse of their mentality and the ignorance of that people, of course not all of them, but most of the core developers. BDD and DDD for them is something trendy, to make some bucks with consulting. They do not know the framework properly, they are not as good as Mike Ho, they are going in nowhere. Symfony is a much better alternative, but is still PHP.
  • Again, there are other reasons but I want to talk about how to migrate to Rails.
In the end after evaluating Java, Groovy and Ruby, JRuby, I chose Ruby. Of course I am using the most famous framework, Rails. But how start to use Rails in a corporate intranet near to be released? I decided to write new modules with Rails and leave the rest with PHP, at least for now.

I started by cleaning the CSS and HTML template. When the template was ready I have adopted the same template for Rails and PHP, so the user could not spot the difference that the intranet is using two different environments and languages. Well, before doing that I had installed Pushion Passenger and Ruby Enterprise Edition on the testing and production server (well, the server's admin installed Passenger there).

Then I integrated the sessions by using the PHPSESSID cookie. The user first logs in the PHP intranet, using LDAP, than I store the PHPSESSID value in the database with a corresponding email address, which is unique and it is the user name too. In the application controller (Rails) I get PHP session id (PHPSESSID) from the cookie and than I query an intranet's web service, passing the sessoin id and getting back the corresponding email address. With the email of the user I get the identifier that I need to integrate PHP with Rails, at least the user's credentials. But, why not performing a query directly to the database instead of using the web service. I already have a standalone application that use this solution, so I just cut and pasted the code, with very little modifications, otherwise I just could query the database table.

The tables of the database (MySql) do not follow Rails naming conventions. The first idea was to use another database, but I realized that I could use the same database and Rails' migrations could managed everything, at least the tables used by Rails. I have found several way to use a legacy database with PHP and Rails. Here are some notes during my braindumps:
  • use ActiveRecord's table_name and primary_key methods to cope with the old table names.
  • to move functionality and data from PHP-land to Rails-land, use Tim Riley's acts_as_importable plugin
  • Use views: CREATE VIEW departments as select deptID as id,
    `honcho_userID` as head,
    `parent_deptID` as parent_id,
    `ordinal` as position,
    `deptName` as name,
    `code`,
    `description`,
    `contact_us`,
    `display`
    FROM dept
  • Use a table prefix to differentiate Rails table from legacy tables: in your config/environment.rb you would add: config.active_record.table_name_prefix = 'rails_'
  • How to import a legacy data: http://openmonkey.com/articles/2009/05/importing-legacy-data-in-rails
  • How to protected legacy database access from Rails: http://github.com/jaz303/read_only_model
The above list was used by myself over the last month. It is confusing but makes sense to me. I decided to use views because is an easy way to use Rails naming conventions and helped my to refactor a little bit the database, since I have inherited it from another developer and has lots of ugly names and errors that brakes referential integrity. If you decide to prefix your Rails tables, remenber that there is a but in the testing framework and a table like this:

_users

will become:

__users

I was using an underscore as prefix, but when running 'rake test' the tables where created with two times the prefix. there is a fix for that but it is not included in new versions of Rails and it will never be, since the ticket in Trac has been closed. So I do not use and prefix, old tables use the singular notation, Rails one are plural, that is my way to separate Qcodo's tables form Rails' tables.

I decided to use Rails migrations for any database change, since Rails migrations are capable to do what I want so I get the real benefit to take advantage to version my database changed. I you need to do something that Rails migrations are not capable, just use the execute command and put your SQL as argument.

That's all for now but I will post other interesting things that I will discover in the future.

Labels: , ,

Monday, 4 January 2010

Create MySql Trigger before deleting a field

I asked some help from StackOverFlow and I had some partial help. I have a table with departments and the table user is referencing the department table, so every user belongs to one department (one to many). When I have to delete an old deparment it happens that there are some users associated to that department. Since a business rule is to have each user associated to one department or at least to department #1, which is the company it self, I want to set all users' department to 1, if the referenced department is deleted. This can be done with your programming language code or with a trigger, I decided to have a trigger (althogh I usually prefer to use the programming language and MySql as persistent layer, but this is another story).

I have used this code:

CREATE TRIGGER dept_set_user_to_wuk BEFORE DELETE ON dept
FOR EACH ROW UPDATE user SET deptid = 1 WHERE deptid = OLD.deptid;


I have created a trigger named dept_set_user_to_wuk saying that before deleting a row in dept (BEFORE DELETE ON dept), it should update each user's department to the default ID #1. It is worth to mention that the where clause is very important, since I used the OLD keyword. OLD keyword reference the field of the table specified after ON, in this case is the 'dept' table.

Labels: ,