Nov 28 2007

Cleaning up Rails Session Records

etienne @ 8:07 pm

Similar:
  • None Found
  • In the Rails Cookbook there is an article describing how to clean up stale session records periodically, i.e. 13.14 Cleaning Up Residual Session Records. When I attempted to set this up as described in the book there was a couple of problems.

    1. I have a few additional require statements in my config/environment.rb which includes some of my custom classes which I’m serializing.

    require ‘app/models/survey_item’
    require ‘app/models/survey_group’

    The problem was that the cron job was failing as it could not find these files so I had to modify the cron job from the suggested

    */10 * * * * ruby /srv/www/htdocs/apps/<appname>/script/runner \
    script/runner -e production SessionCleanup.nuke_old_db_session

    to

    */10 * * * * cd /srv/www/htdocs/apps/<appname> && ruby script/runner \
    -e production SessionCleanup.nuke_old_db_sessions

    2. Now my cron job was running but if was deleting all my sessions, it was as if it was comparing the wrong time so the condition was always true. I use UTC time in my application and realised I had to make a slight modification to the function in the environment.rb that deletes the session records to take this into consideration, so I changed the function from:

    # Clean up session
    class SessionCleanup
      def self.nuke_old_db_sessions
        CGI::Session::ActiveRecordStore::Session.destroy_all(
          [’updated_at < ?’, 20.minutes.ago]
        )
      end
    end

    to

    # Clean up session
    class SessionCleanup
      def self.nuke_old_db_sessions
        CGI::Session::ActiveRecordStore::Session.destroy_all(
          [’updated_at < ?’, 20.minutes.ago.utc]
        )
      end
    end
     

    By adding the .utc to 20.minutes.ago fixed the problem.

     

    Tag: cron, Rails, Ruby

    Nov 22 2007

    Rails Deployment and Installation - Nginx + Mongrel Cluster + Subversion + Capistrano

    etienne @ 7:17 pm

    Ok so I’ve finished my new Rails application and it’s now time to deploy, which has turned out to be somewhat more challenging than I thought. I’m deploying to a dedicated host running SuSe 9.3.

    Installation check list:

    1. Ruby
    2. RubyGems
    3. Rails
    4. Mongrel + Mongrel Cluster
    5. Nginx
    6. Subversion
    7. Capistrano

    So roll up your sleeves and lets get into it.

    1. Ruby

    We will build Ruby using the latest source that we will download from the Ruby web site. Before we can do this we need to do some initial setup which includes making sure the required packages are installed that will allow us to build Ruby from source.

    1. Start YAST, Software, Install and Remove Software

    2. Check if Ruby is already installed, remove it if it is

    3. Install the following packages if they have not already been installed

    • zlib-devel
    • make
    • gcc 
    • openssl
    • openssl-devel

    We then need to download the latest version (1.8.6-p110 at time of writing) of the Ruby source which can be downloaded from the official Ruby web site. Once you have downloaded the source we can start the build process.

    Create a temporary directory where we can save the source and do all of our builds:
    mkdir /tmp/build

    Save the Ruby source file to the new directory and extract all of the files using 
    tar zxvf ruby-1.8.6-p110.tar.gz

    Change into the new source directory created by the extraction process 
    cd ruby-1.8.6-p110

    Setup our make files
    ./configure –prefix=/usr –with–openssl

    Build Ruby
    make

    Install 
    sudo make install

    Check installation worked
    ruby -v
    which should return the Ruby version, e.g. ruby 1.8.6 (2007-09-23 patchlevel 110) [i686-linux]

    Remove the source files
    rm -rf  ruby-1.8.6-p110

    2. RubyGems

    As we do not want to run Rails without installing RubyGems we need to download the source code and build it. Go to RubyForge and download the latest source code (0.9.4 at time of writing).

    Save the source code in the temporary directory we created for Ruby above, i.e.
    /tmp/build

    Extract the source code using
    tar zxvf rubygems-0.9.4.tgz

    Change to the source directory created by the extraction process
    cd rubygems-0.9.4

    Perform the installation
    sudo ruby ./setup.rb 

    Remove source files
    rm -rf rubygems-0.9.4

    3. Rails

    We can now install Rails which is something you may already have done in your development environment. Use the include-dependencies option to make sure all required files are installed.

    Install Rails
    sudo gem install rails –include-dependencies 

    Verify the installation worked
    rails -v
    which returns the Rails version number for example Rails 1.2.5

    4. Mongrel + Mongrel Cluster

    To install Mongrel you will need to have Ruby 1.8.4 (or later) and RubyGems installed.

    Install Mongrel
    sudo gem install mongrel –include-dependencies

    We will be using a cluster of three mongrel processes, you need to decide how many will be suitable for your application. To run a cluster of mongrel processes we need to install the mongrel_cluster gem, for details on this gem see the Mongrel web site.

    Install the Mongrel Cluster gem
    sudo gem install mongrel_cluster

    Now that we have Mongrel and Mongrel Cluster installed we can continue with the configuration. I created a test Rails application to test my setup and make sure everything is working correctly. Later when I install my application I will make the required changes to these configs to work correctly with my "real" application.

    Create the Rails application we will use for testing, I’m placing it in the apache root directory under a new apps directory. To find the document root go to /etc/apache2 and do a grep on documentroot, i.e. grep -i documentroot
    cd /srv/www/htdocs/apps/
    rails testapp

    Create a user that will be used by the cluster
    useradd -system mongrel

    Create a cluster configuration file with three processes using ports 8001, 8002, and 8003. This will create a file in the config directory called mongrel_cluster.yml
    cd /srv/www/htdocs/apps/testapp
    mongrel_rails cluster::configure -p 8001 -e production -a 127.0.0.1 -N 3

    I then modified the default configuration file to look like this:

    user: mongrel
    cwd: /srv/www/htdocs/apps/testapp
    log_file: /var/log/mongrel.log
    port: "8001"
    environment: production
    group: www
    address: 127.0.0.1
    pid_file: tmp/pids/mongrel.pid
    servers: 3

    Change permissions and owner of the Rails application. I am using the same group as used by my Apache system (see /etc/apache2/uid.conf) in my case it’s www
    chown -R mongrel:www /srv/www/htdocs/apps/testapp

    That takes care of the installation and configuration. We can test to make sure the cluster is working by starting it and connecting to it via a browser.

    Start the cluster
    cd /srv/www/htdocs/apps/testapp
    mongrel_rails cluster::start

    Check status of the cluster
    cd /srv/www/htdocs/apps/testapp
    mongrel_rails cluster::status

    Run browser and connect to mongrel ports, you should see the standard Rails startup page
    127.0.0.1:8001
    127.0.0.1:8002
    127.0.0.1:8003

    To stop the cluster
    cd /srv/www/htdocs/apps/testapp
    mongrel_rails cluster::stop

    To restart after a code change for example
    cd /srv/www/htdocs/apps/testapp
    mongrel_rails cluster::restart 

    We need to setup the cluster so that it will be restarted when the server is rebooted. 

    Create a new directory and create a new link to the mongrel cluster configuration file we created in the previous step
    mkdir /etc/mongrel_cluster
    ln -s /srv/www/htdocs/apps/testapp/config/mongrel_cluster.yml \
    /etc/mongrel_cluster/testapp.yml

    Copy the shell script supplied when the mongrel cluster gem was installed to the init.d directory. The shell script allows us to control the cluster including start, stop, status, and restart.
    cp /usr/lib/ruby/gems/1.8/gems/mongrel_cluster_1.0.5/resources/mongrel_cluster \
    /etc/init.d/
    chmod +x /etc/init.d/mongrel_cluster
    Usage: /etc/init.d/mongrel_cluster <status|start|stop|restart>

    Add the mongrel cluster as a service
    Add the service:
    chkconfig mongrel_cluster 35
    To check the service:
    chkconfig –list mongrel_cluster
    To delete the service: chkconfig –del mongrel_cluster

    There was one modification I had to make to the mongrel_cluster script to get it to work without any errors: 
    Edit: /etc/init.d/mongrel_cluster
    Change:
    chown $USER:$USER $PID_DIR
    To: chown $USER:$GROUP $PID_DIR 
     

    5. Nginx

    Now that we have a working mongrel cluster running three mongrel processes how are we going to do the load balancing for these processes? I initially looked at using Apache 2.2 + mod_proxy_balancer but as SuSe 9.3 does not have any pre-built packages for 2.2 and I did not want to face the daunting task of building Apache myself I had to look at some alternatives.

    I initially considered Pen and Pound but then came across Nginx which seems to have a lot of positive feedback from users. From what I have read it seems that it only requires a small amount memory and is as fast if not faster than mod_proxy_balancer. So I decided to give it a try and so far I’ve been very happy with it.

    Before we can install nginx we need to install some packages required during the build process.

    1. Start YAST, Software, Install and Remove Software

    2. Install the following packages if they have not already been installed

    • pcre
    • pcre-devel
    • pcre++-devel
    • openssl
    • openssl-devel

    I found that these packages where not available on my installation CD so I used packages I found using rpmfind. I connected to this site from the SuSe machine using Konqueror, which when I clicked on the required package, the package information window was displayed allowing installation via YAST.

    We can now build and install nginx, one point to note is that I’m using the latest snapshot which includes the upstream_fair changes as discussed in Ezra Zygmuntowicz article A Fair Balancer for Nginx and Mongrel 

    Download snapshot from the link below, save the file in the /tmp/build directory
    http://git.localdomain.pl/?p=nginx.git;a=shortlog;h=upstream_fair

    Extract all files 
    tar xzvf nginx-x.x.xx.tar.gz

    Change to the extracted source directory
    cd nginx-x.x.xx 

    Setup our make files
    ./configure –with-http_ssl_module –prefix=/usr

    Build nginx
    make

    Install
    sudo make install

    Check installation worked
    nginx -v
    which should return the nginx version, e.g. nginx version: nginx/0.5.32

    Remove the source files
    rm -rf  nginx-x.x.xx

    Now we need to configure nginx to work with our Rails application, to do this I used a modified version of  Ezra Zygmuntowicz configuration file as described in the Nginx, my new favorite front end for mongrel cluster article. The other change I made to the configuration file was to include the performance improvement tip described in another of Ezra’s articles New Nginx conf with optimization.

    Create the nginx.conf file in the test Rails application config directory

    I changed the ownership and rights of this file to be the same as the other config files, i.e.
    chmod 664 nginx.conf
    chown mongrel:www nginx.conf

    Test nginx config file
    cd /srv/www/htdocs/apps/testapp
    nginx -t -c config/nginx.conf 

    Start nginx using the new config file
    cd /srv/www/htdocs/apps/testapp
    nginx -c config/nginx.conf

    Stop nginx
    ps aux | grep nginx
    kill -15 <pid>
    where <pid> is the pid of the master process

    Test nginx is working correctly, start a browser and connect to nginx using the port you specified in the config file listen directive, i.e. listen 80;. You should see the standard Rails welcome page.

    We now need to setup nginx to run as a service so that it will be restarted when the server is rebooted. To do this I modified a script that I found in a slicehost article Ubuntu LTS - adding an ngnix init script, here is my modified version which I’m sure can be improved on but for now it does what I need it to do. Create this script in the /etc/init.d directory in a file called nginx.

    #! /bin/sh

    PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
    DAEMON=/usr/sbin/nginx
    NAME=nginx
    DESC=nginx
    PIDFILE=/usr/logs/$NAME.pid
    DAEMON_CONFIG=/etc/nginx/nginx.conf

    test -x $DAEMON || exit 0

    set -e

    case "$1" in
      start)
            echo -n "Starting $DESC: "       
            start-stop-daemon –start –quiet –pidfile $PIDFILE \
                    –exec $DAEMON — -c $DAEMON_CONFIG
            echo " started"
            ;;
      stop)
            echo -n "Stopping $DESC: "
     if [ -f $PIDFILE ]; then
                    kill -15 `cat $PIDFILE 2>/dev/null`
            fi
            echo " stopped"
            ;;
      restart|force-reload)
            echo -n "Restarting $DESC: "
     if [ -f $PIDFILE ]; then
                    kill -15 `cat $PIDFILE 2>/dev/null`
            fi
            sleep 1
            start-stop-daemon –start –quiet –pidfile $PIDFILE \
                    –exec $DAEMON — -c $DAEMON_CONFIG
            echo " restarted"
            ;;
      status)
            echo "Status $DESC: "
            ps aux | grep -v grep | grep -v /bin/sh | grep $NAME
            ;;
      *)
            N=/etc/init.d/$NAME
            echo "Usage: $N {start|stop|status|restart}" >&2
            exit 1
            ;;
    esac

    exit 0

    Steps to configure nginx as a service.

    Create a new directory
    mkdir /etc/nginx

    Create a link to the nginx.conf file in the test Rails application config directory
    ln -s /srv/www/htdocs/apps/testapp/config/nginx.conf \
    /etc/nginx/nginx.conf

    Make the script executable
    chmod +x /etc/init.d/nginx
    Usage: /etc/init.d/nginx <status|start|stop|restart>

    Add it as a service
    Add the service: chkconfig nginx 35
    To check the service: chkconfig –list nginx 
    To delete the service: chkconfig –del nginx

     

     


    Nov 11 2007

    Naming Conventions - Ruby and Ruby on Rails

    etienne @ 10:02 pm

    In  an attempt to see if I can get my "Ruby and Rails Naming Conventions" article back on Google I’ve created this entry to see if it gets listed.

    Click on the link to access the actual article Ruby and Rails Naming Conventions

    Sorry for the inconvenience.

     


    Nov 09 2007

    Ruby and Rails Naming Conventions Article Missing from Search Engines

    admin @ 7:44 am

    Over the past week or two I’ve noticed traffic to my Blog have dropped dramatically. Upon further investigation I have found that traffic to one of the most popular articles "Ruby and Rails Naming Conventions" have almost dropped to zero.

    I’ve checked Google and the other search engines and this article does not appear at all when using the following search terms "rails naming conventions", it use to be one of the top five for quite some time.

    I have searched the Internet to try and figure out why my article is no longer listed, but as yet I’ve not found anything that may explain why this has happened. If anyone has any ideas it would be much appreciated if you could let me know. I’m not sure if I can approach Google about this as this seems to be the same for all search engines.

    Click on this link to access the actual article Ruby and Rails Naming Conventions 


    Oct 28 2007

    Ruby and Rails Naming Conventions

    etienne @ 10:15 am

    I’ve been looking for a consolidated list of all Ruby and Rails naming conventions without too much luck so I’ve started my own. I find I always forget the naming convention especially as I move between projects that use different languages.

    Please let me know of any others that I have missed. 

    Naming Conventions

    Ruby Naming Convention

    Ruby uses the first character of the name to help it determine it’s intended use.

    Local Variables
    Lowercase letter followed by other characters, naming convention states that it is better to use underscores rather than camelBack for multiple word names, e.g. mileage, variable_xyz

    Instance Variables
    Instance variables are defined using the single "at" sign (@) followed by a name. It is suggested that a lowercase letter should be used after the @, e.g. @colour 

    Instance Methods
    Method names should start with a lowercase letter, and may be followed by digits, underscores, and letters, e.g. paint, close_the_door

    Class Variables
    Class variable names start with a double "at" sign (@@) and may be followed by digits, underscores, and letters, e.g. @@colour

    Constant
    Constant names start with an uppercase letter followed by other characters. Constant objects are by convention named using all uppercase letters and underscores between words, e.g. THIS_IS_A_CONSTANT

    Class and Module
    Class and module names starts with an uppercase letter, by convention they are named using MixedCase, e.g. module Encryption, class MixedCase

    Global Variables
    Starts with a dollar ($) sign followed by other characters, e.g. $global

    Rails Naming Convention

    Rails use the same naming convention as Ruby with some additions:

    Variable
    Variables are named where all letters are lowercase and words are separated by underscores, e.g. order_amount, total

    Class and Module
    Classes and modules use MixedCase and have no underscores, each word starts with a uppercase letter, e.g. InvoiceItem

    Database Table
    Table names have all lowercase letters and underscores between words, also all table names need to be plural, e.g. invoice_items, orders

    Model 
    The model is named using the class naming convention of unbroken MixedCase and is always the singular of the table name, e.g. table name might be orders, the model name would be Order. Rails will then look for the class definition in a file called order.rb in the /app/models directory. If the model class name has multiple capitalised words, the table name is assumed to have underscores between these words.

    Controller
    Controller class names are pluralized, such that OrdersController would be the controller class for the orders table.  Rails will then look for the class definition in a file called orders_controller.rb in the /app/controllers directory.

    Files, Directories and other pluralization
    Files are named using lowercase and underscores. Assuming we have an Orders controller then the following other conventions will apply:

    • That there is a helper module named OrdersHelper in the orders_helper.rb found in the app/helpers directory
    • Rails will look for view template files for the controller in the app/views/orders directory
    • Output from this view will then be used in the layout defined in the orders.html.erb in the app/views/layouts directory
    • Test files including order_test.rb will be created in the /test/unit directory, a file will be created in the /test/fixtures directory called orders.yml and finally a file called orders_controller_test.rb will be created in the /test/functional directory

    Primary Key
    The primary key of a table is assumed to be named id.

    Foreign Key
    The foreign key is named with the singular version of the target table name with _id appended to it, e.g. order_id in the items table where we have items linked to the orders table.

    Many to Many Link Tables
    Tables used to join two tables in a many to many relationship is named using the table names they link, with the table names in alphabetical order, for example items_orders.

    Automated Record Timestamps
    You can get ActiveRecord to automatically update the create and update times of records in a database table. To do this create two specially named columns created_at and updated_at to your table, i.e. t.datetime :created_at and t.datetime :updated_at. If you only want to store the date rather than a date and time, use :created_on and :updated_on.

    Naming Convention Summary 

    Model Naming Convention

    Table: orders
    Class: Order
    File: /app/models/order.rb
    Primary Key: id
    Foreign Key: customer_id
    Link Tables: items_orders

    Controller Naming Convention

    Class: OrdersController
    File: /app/controllers/orders_controller.rb
    Layout: /app/layouts/orders.html.erb

    View Naming Convention

    Helper: /app/helpers/orders_helper.rb
    Helper Module: OrdersHelper
    Views: /app/views/orders/… (list.html.erb for example)

    Tests Naming Convention

    Unit: /test/unit/order_test.rb
    Functional: /test/functional/orders_controller_test.rb
    Fixtures: /test/fixtures/orders.yml

      


    Oct 24 2007

    Preventing Text Wrapping Around Images

    etienne @ 9:03 pm

    As I was writing the Survey builder article I wanted to prevent text from wrapping around my images. I simply wanted to display the image and have the text start below it rather than having it flow around it. I found that you can do this using HTML or CSS code.

    HTML

    <br clear=left|right|all>

    CSS

    clear:  none | left | right | both

     

    Tag: images, HTML, CSS

    Oct 21 2007

    Ruby on Rails Survey Builder

    etienne @ 7:17 pm

     

    Update 9 January 2008: SMERF Released!

    Update 2 January 2008: Announcing SMERF - Simple MEta Rails Form plugin

    Over the past few weeks I have been developing a general purpose survey/questionnaire builder that makes it easy to create a new survey, present it to the user, record the user’s responses in a database, and allow the user to recall and update the responses. It’s still in it’s infancy and I will add more functionality as required, for now it’s functional enough to meet all the requirements of the current project.

    To create a new survey we define the content of the survey using a YAML file. I decided to use YAML as it seems to be the defacto standard for Rails, it’s simple and has a nice hierarchical structure which suits the way surveys are structured.

    Survey Structure

    Each survey is made up of any number of question groups, within each group there are any number of questions. Questions can accept free form text or present a list of options to the user. In some cases a particular answer may require additional information, this can be done using subquestions. Subquestions can be nested to any depth, i.e. a subquestion can present a set of answers, which in turn can present another subquestion with another set of answers and so on. The diagram below shows the structure of a survey:

      Survey Structure

     

    Defining the Survey

    When setting up a new survey the first thing we do is define some settings for the survey as a whole. Currently the following items can be defined for the survey:

    • name: Name of the survey (mandatory)
    • welcome: Message displayed at the start of the survey (optional)
    • thank_you: Message displayed at the end of the survey (optional)
    • group_sort_order_field: Nominates which group field to use when sorting groups for display (mandatory)
    • groups: Defines the question groups within the survey (mandatory)

    Here is the definition for our demo survey:


    survey:
      name: Demo

      welcome: |
        <b>Welcome:</b><br>
        Thank you for taking part in our demo survey we appreciate your
        input.<br><br>

        <b>PRIVACY STATEMENT</b><br>
        We will keep all the information you provide private and not share
        it with anyone else….<br>

      thank_you: |
        <b>Thank you for your input.</b><br><br>

        Should you wish to discuss this survey please contact<br>
        Joe Bloggs<br>
        Tel. 12 345 678<br>
        e-mail <A HREF=\"
    mailto:jbloggs@xyz.com.au\">Joe’s email</A><br><br>
     
        February 2007

      group_sort_order_field: code-

      groups:
      …

     As you can see from the example you are able to embed any HTML code within the text.

    Defining Groups

    Each survey is divided up into groups of questions, you must have at least one group per survey. Here are the fields that are currently available when defining a group:

    • code: This code must be unique for all groups within the survey as it is used to identify each group (mandatory)
    • name: The name of the group, this is displayed as the group heading (mandatory)
    • description: Give a more detailed description/instructions for the group (optional)
    • questions: Defines all the questions contained within this group (mandatory)

    Here is the definition for the Personal Details group of the Demo survey:

      groups:
        personal_details:
          code: 1
          name: Personal Details
          description: | A brief description of this group…
          questions:
          …

    Group example

     

    Defining Questions 

    A group can contain any number of questions, there must be at least one question per group. When defining a question you must specify the question type, the type determines the type of form field that will be created. There are currently four types that can be used, this will be expanded as needed. The current question types are:

    • multiplechoice: Allows the user to select all of the answers that apply from a list of possible choices, check boxes are used for this question type as multiple selections can be made
    • singlechoice: Allows the user to select one answer from a list of possible choices, radio buttons are used for the question type as only a single answer can be selected
    • textbox: Allows the user to enter a large amount of free text, the size of the text box can be specified
    • textfield: Allows the user to enter a small amount of free form text, the size of the text field can be specified

    Question types

    The following fields can be used to define a question:

    • code: Unique code that will identify the question, the code must be unique within a survey (mandatory)
    • type: Specifies the type of field that should be constructed on the form for this question, see above list for current types (mandatory)
    • question: The text of the question, this field is optional as subquestions do not have to have question text
    • textbox_size: Specifies the size of the text box to construct, rows x cols, defaults to 30×5 (optional)
    • textfield_size: Specified the size of the text field that should be constructed, specified in the number of visible characters, default to 30 (optional)
    • header: Specifies a separate heading for the question. The text will be displayed above the question allowing questions to be broken up into subsections (optional)
    • required: Specifies that an answer is required for this question
    • sort_order: Specifies the sort order for the question
    • help: Help text that will be displayed below the question
    • answers: Defines the answers to the question if the question type displays a list of possibilities to the user

     Below is an example question definition:

          questions:
            which_industry:
              code: g2q1
              type: multiplechoice
              sort_order: 1
              question: | Which industries have you worked in 
              help: | Mark <b>all</b> that apply                
              answers:
              …

    Question example

     

    Defining Answers

    If the question presents a list of possible answers to the user then we can easily define these answers by using the following fields:

    • code: Code to uniquely identify the answer, code needs to be unique for each question (mandatory)
    • answer: The text that will be displayed to the user (mandatory)
    • default: If set to Y then this answer will be selected by default (optional)
    • sort_order: The sort order for this answer (mandatory)
    • subquestions: Some answers may need additional information, another question can be defined to obtain this information. To define a subquestions the same fields that define a normal question is used (optional)

    Here is an example answer definition:

               answers:
                it:
                  code: 1
                  answer: | Information Technology
                  sort_order: 1
                  default: N
                  subquestions:
                  …

    Defining Subquestions 

    Additional questions can be defined for an answer if more information is required if the user selects the answer. For example you may have an answer ‘Other’ that required additional information from the user. A subquestion can be defined for the ‘Other’ answer that takes an additional input from the user. To define a subquestion you use the same fields as for a normal question (Refer to the Defining Question section above).

    Below is an example subquestion definition:

                  subquestions:
                    other_industries:
                        code: g2q1a4s1
                        type: textbox
                        sort_order: 1
                        question:
                        help: | Please specify
                        textbox_size: 30×3
     

    Subquestion example

     

     

    Data Store

    There are three tables used by the survey builder, a surveys table that stores the survey details, a surveys_users table that stores a users answers for a survey and finally the users table.

    When a user selects to view a survey the system checks the survey definition file to see if any changes have been made since the last time the survey was loaded, if it has the survey definition file is processed and validated. A set of classes and objects are created that describes the survey including SurveyFile, SurveyGroup, SurveyQuestion, SurveyAnswer, these are serialized to YAML format and stored in a single database field within the surveys table. If no changes have been made to the definition file then the survey is simply read from the surveys table and serialized from YAML with all classes and objects reconstructed.

    User survey answers are stored  in the surveys_users table in a single field within the table. The answers are serialized to YAML and stored in the database field, the classes and objects are reconstructed when the record is retrieved.

    Validation and Errors

    The only validation currently performed  is to check that mandatory questions have been answered. In the near future I will be expanding validations by adding the ability to use regular expressions to validate the format of answers. I’m also thinking about allowing Ruby code to be specified to construct validation code that can be executed to check answers.

    When an error is detected, a summary of the errors are displayed at the top of the form, additionally an error message is displayed for each question that has an error. This makes it very easy for the user to see which question they need to fix.

    Error example

     

     Complete Example

    Here is the complete survey definition fie for the Demo survey:


    survey:
      name: Demo
      welcome: |
        <b>Welcome:</b><br>
        Thank you for taking part in our demo survey we appreciate your
        input.<br><br>

        <b>PRIVACY STATEMENT</b><br>
        We will keep all the information you provide private and not share
        it with anyone else….<br>

      thank_you: |
        <b>Thank you for your input.</b><br><br>

        Should you wish to discuss this survey please contact<br>
        Joe Bloggs<br>
        Tel. 12 345 678<br>
        e-mail <A HREF=\"
    mailto:jbloggs@xyz.com.au\">Joe’s email</A><br><br>
     
        February 2007
      group_sort_order_field: code

      groups:
       
    personal_details:
          code: 1
          name: Personal Details
          description: | A brief description of this group…
          questions:
           
    specify_your_age:
              code: g1q1
              type: singlechoice
              sort_order: 1
              question: | Specify your age 
              help: | Select the <b>one</b> that apply
              required: Y
              answers:
                1_20:
                  code: 1
                  answer: | 1-20
                  sort_order: 1
                  default: N
               
    21_40:
                  code: 2
                  answer: | 21_40
                  sort_order: 2
                  default: N
                >40:
                  code: 3
                  answer: | > 40
                  sort_order: 3
                  default: N
                  subquestions:
                   
    >40:
                      code: g1q1a3s1
                      type: singlechoice
                      sort_order: 1
                      question: | Are you aged over 40
                      help: | Select the <b>one</b> that apply
                      answers:
                        41_50:
                          code: 1
                          answer: | 41-50
                          sort_order: 1
                          default: N
                        51_60:
                          code: 2
                          answer: | 51_60
                          sort_order: 2
                          default: N
                        >61:
                          code: 3
                          answer: | > 61
                          sort_order: 3
                          default: N

            are_you_married:
              code: g1q2
              type: singlechoice
              sort_order: 2
              question: | Are you married 
              help:
              required: N
              answers:
                no:
                  code: 1
                  answer: | No
                  sort_order: 1
                  default: Y
                yes:
                  code: 2
                  answer: | Yes
                  sort_order: 2
                  default: N

        employment_details:
          code: 2
          name: | EMPLOYMENT DETAILS
          description: | Brief description of the employment details group 
          question_sort_order_field: sort_order
          questions:
            which_industry:
              code: g2q1
              type: multiplechoice
              sort_order: 1
              question: | Which industries have you worked in 
              help: | Mark <b>all</b> that apply               
              answers:
                it:
                  code: 1
                  answer: | Information Technology
                  sort_order: 1
                  default: N
                accounting:
                  code: 2
                  answer: | Accounting
                  sort_order: 2
                  default: N
                finance:
                  code: 3
                  answer: | Finance
                  sort_order: 3
                  default: N
                other:
                  code: 4
                  answer: | Other
                  sort_order: 4
                  default: N
                  subquestions:
                    other_industries:
                        code: g2q1a4s1
                        type: textbox
                        sort_order: 1
                        question:
                        help: | Please specify
                        textbox_size: 30×3

            how_many_years:
              code: g2q3
              type: textfield
              sort_order: 3
              header:
              question: | How many years have you worked in the industry
              textfield_size: 10
              help:

    Complete example 

     


    Sep 02 2007

    Views and nil Database Objects

    etienne @ 6:04 pm

    When a database field in a table is NULL and this field is then used in a view a nil object exception will be raised. So for example if I had the following code to display the name of the user in my view and the login field was NULL an error would be generated as login object would be nil. 

    Signed in as <%= current_user.login %>

    To overcome this you would need to use something like the code below:

    Signed in as <%= current_user.login unless current_user.login.blank? %>

    Which if you have many field to deal with becomes a major pain. As I was reading about this I realised that nil in Ruby is an actual object that has methods including to_s which will return an empty string when called. So instead I can do the following:

    Signed in as <%= current_user.login.to_s %>

    When the login field is NULL a nil object will be returned and the to_s message sent to this object which in turn will return an empty string, no error messages will be generted and everything works nicely.


    Aug 26 2007

    Ruby on Rails Class Serialize Problem

    etienne @ 6:23 pm

    I’ve been chasing my tail for the past day trying to track down a problem I was having in serializing a hash that contained two of my custom classes. I have two custom classes:

    class Menu

    end

    class Permissions

    end

    I then have an ActiveRecord class that stores instances of these two classes in a hash which is then saved to my database.

    class Role < ActiveRecord::Base

      serialize :credentials
      …
      self.credentials[:permissions] = Permissions.new(self)
      self.credentials[:menu] = Menu.new(self)
      …
    end

    After saving the record to the database I could inspect the field and all the YAML code was present and accounted for, the problem was that when I went to retrieve the serialized field, instead of getting back a hash with my two custom classes (i.e. Menu and Permissions) I was getting both objects of class YAML::Object.

    After pulling my hair out for some time, I traced into the Rails code and saw that YAML::Load(string) was being called to unserialize the data. I then went to the YAML site and read more about the library which I admit I have limited knowledge about. I figured out that for some reason YAML was not finding my custom class definitions which meant it used YAML::Object. So now it was a matter of finding out how to tell YAML about my classes.

    After doing more searching on the net I came across these articles which gave me the information I needed:

    http://yaml4r.sourceforge.net/doc/page/type_families.htm
    http://dev.rubyonrails.org/ticket/7537

    I was able to add the following code to my environment.rb which will ensure any ActiveRecord derived classes will be correctly serialized. I also added two require statements to environment.rb to make sure my custom (non ActiveRecord derived) classes where also correctly serialized.

    require ‘permissions’
    require ‘menu’

    YAML.add_domain_type("ActiveRecord,2007", "") do |type, val|
      klass = type.split(’:').last.constantize
      YAML.object_maker(klass, val)
    end

    class ActiveRecord::Base
      def to_yaml_type
        "!ActiveRecord,2007/#{self.class}"
      end
    end

    class ActiveRecord::Base
      def to_yaml_properties
        [’@attributes’]
      end
    end

    I restarted my server and everything worked !!!

     


    Aug 19 2007

    CSS ID and Class usage

    etienne @ 8:24 pm

    I’ve always been slightly confused about when to use an ID and when to use a Class in my CSS. I’ve done some research which clarified my understanding.

    The key thing to understand is that ID’s must be unique on a page and identifies a specific element, so you can only use a specified ID once per page. Classes on the other hand can be used multiple times on a page, if you are going to use a style that will be applied to multiple elements then use a Class.

    I start by looking at all the sections and subsections on the page and how they work together. I identify all the unique sections on the page and define these with DIV’s and ID’s. Normally a page would consist of the following main sections:

    #container { … }

    #header { … }

    #content { … }

    #left { … }

    #right { … }

    #footer { … }

    I then define all the child elements within each of these sections, so for example if I want the links in the header to be different to those in the content section I can easily do so. One of the main benefits of this approach is that when you look at the CSS file you can easily see why and where each style is being used.

    #header a:link, #header a:visited {
    color: red;
    text-decoration: none;
    font-weight: bold;
    }

    #content a:link, #content a:visited {
    color: blue;
    text-decoration: none;
    font-weight: normal;
    }

    I define any shared look and feel styles using classes which I then use in different sections, In the case of the sidebar I have a left as well as a right sidebar, they have many look and feel things in common so I create a class called "sidebar" which defines the common styles. I share this common class between the left and right sidebar elements.

    CSS:

    .sidebar {
    width: 170px;
    margin: 0;
    padding: 0;
    }

    .sidebar p {
    font-size: 0.6em;
    color: #555;
    padding-left: 5px;
    padding-top: 3px;}

    #left {
    float: left;
    }

    #right {
    float: right;
    }

    HTML:

    <div id="left" class="sidebar">
       <p>This is the left sidebar</p>
    </div> <!– left –>

    <div id="right" class="sidebar">
       <p>This is the right sidebar</p>
    </div>

    Another interesting point is that You are able to apply multiple classes to an element by space separating the classes, this allows an element to inherit multiple classes.

    <DIV id="left" class="class1 class2 class3">Hello World!</DIV>

     

    Tag: CSS, classes

    « Previous PageNext Page »