ActiveRecord Reflective Validation

Posted by Chris Thu, 05 Jun 2008 06:55:00 GMT

So, this is a pretty basic “for work” thing I ran into.

Managers like forms with little red asterisks next to required fields, and there doesn’t seem to be any straightforward way to query an ActiveRecord class to determine which fields are required.

Since AR already has to perform the validation, it seemed only right to lump it with the responsibility of keeping track of its own details.

So far, I’ve knocked up a small plugin within an existing rails app, which currently deals with presence and uniqueness validations, by providing the methods:

  • validates_presence_of?(field)
  • validates_uniqueness_of?(field)

to inspect the model classes.

Without further ado, here’s the prototype code:

module ActiveRecord
  module ReflectiveValidation
    def self.included(mod)
      mod.extend ClassMethods
      validation_methods.each do |method|
        arr_name = "#{method}_fields".to_sym
        mod.class_inheritable_array(arr_name)
        mod.write_inheritable_array(arr_name, [])
      end
    end

    def self.validation_methods
      [
        :validates_presence_of,
        :validates_uniqueness_of
      ]
    end
    module ClassMethods
      ActiveRecord::ReflectiveValidation::validation_methods.each do |method|
        module_eval(
          <<-eos
            def #{method}(*fields)
              write_inheritable_array(:#{method}_fields, fields)
              super(fields)
            end

            def #{method}?(field)
              #{method}_fields.include?(field)
            end
          eos
        )
      end
    end
  end
end

I’m looking at fleshing it out, handling more validations, and hopefully dealing with things like length requirements.

Oh, and actually hosting the plugin somewhere.

Posted in  | Tags , , ,  | no comments

Rails and shitty SQL generation

Posted by Chris Thu, 28 Feb 2008 02:57:00 GMT

Just a quick bitch about something I came across in setting up Redmine.

Removing members from a project in the admin interface was silently failing for me.

It turns out that Redmine, rather sensibly, updates the project so that no issues are automatically assigned to the user being removed from the project. This makes a call to ActiveRecord::Base’s update_all method.

This was the call that was failing, and it was all down to the fact that ActiveRecord was passing my postgres database an update query with an “ORDER BY” clause, which is apparently the sort of fucking retarded feature that mysql decided to implement, and which some useless dick chose to support in AR.

Really, what’s the point of ordering your update statement?

I don’t know who to be more pissed off at over this, but given past experiences with mysql I’ll pin it on them.

FUCK YOU AGAIN, MYSQL!

Posted in  | 1 comment

Setting up a Redmine site on Ubuntu

Posted by Chris Wed, 27 Feb 2008 16:37:00 GMT

After a variety of headaches setting up a redmine instance on an Ubuntu server, I thought I’d write a quick guide on how to do it quickly.

First step

Install Ubuntu. What flavour you go with is up to you.

Packages from apt

Install packages

as root, run:

aptitude install apache2 ruby rubygems subversion ruby-pkg-tools \
ruby1.8-dev build-essential postgresql libdbd-pg-perl libapache2-svn \
libapache-dbi-perl libapache2-mod-perl2 libdigest-sha1-perl

If you want to go with mysql rather than postgres, you can drop the postgresql and libdbd-pg-perl packages, and install the mysql equivalents, but you’re on your own in setting up the database.

Ruby gems

Install gems

as root, run:

gem install rails mongrel mongrel_cluster postgres-pr --include-dependencies

Add gems to $PATH

I did this by adding the line:

export PATH=/var/lib/gems/1.8/bin to the end of /etc/profile

Set up the database for Redmine to use

Switch to postgres user

as root, run:

su postgres

Create the redmine user

createuser redmine --no-superuser --no-createdb --no-createrole --login --pwprompt --encrypted

Create the redmine database

createdb --owner=redmine --encoding=utf-8 redmine

Switch back

exit

For the tutorial, I’ll be using the password “redmine” for the redmine user.

Redmine itself

For this setup I’ll be using the 0.6 stable branch of redmine via svn, and installing it under /var/www/rails_apps/redmine-0.6

Create the directory and checkout Redmine

mkdir /var/www/rails_apps
cd /var/www/rails_apps
svn co http://redmine.rubyforge.org/svn/branches/0.6-stable redmine-0.6

Configure database access

Create the file config/database.yml within the redmine directory with the following contents:

production:
  adapter: postgresql
  database: redmine
  host: localhost
  username: redmine
  password: redmine

Bootstrap the database

In the Redmine directory, run:

rake db:migrate RAILS_ENV="production"
rake redmine:load_default_data RAILS_ENV="production"

Configure mailing from Redmine

Setting up a mail account for redmine is your own business, then change this section of config/environment.rb:

# SMTP server configuration
config.action_mailer.smtp_settings = {
  :address => "127.0.0.1",
  :port => 25,
  :domain => "somenet.foo",
  :authentication => :login,
  :user_name => "redmine",
  :password => "redmine",
}

to this:

# SMTP server configuration
config.action_mailer.smtp_settings = {
  :address => "mail.yourdomain.com",
  :port => 25,
  :domain => "yourdomain.com",
  :authentication => :login,
  :user_name => "mail_user",
  :password => "mail_password"
}

or whatever’s appropriate for your setup.

Check that everything’s set up correctly

From the redmine directory run:

mongrel_rails start --environment=production

You should be able to view the redmine site at localhost:3000 and log in using the username “admin” and password “admin”.

Setting up subversion

Create the repository location

as root:

mkdir /var/svn
chmod 0750 /var/svn

Serve the repository path through Apache

Enable the necessary Apache modules

as root:

a2enmod dav
a2enmod dav_svn
a2enmod perl

Configure site and delegate authentication to Redmine

Copy the file Redmine.pm from the extra/svn directory in Redmine to /usr/lib/apache2.

Create a file under /etc/apache2/sites-available with an appropriate name (e.g. svn.yourdomain) with the following contents:

<VirtualHost *>
  ServerAdmin support@yourdomain
  ServerName svn.yourdomain

  PerlRequire /usr/lib/apache2/Redmine.pm

  <Location /svn>
    DAV svn
    SVNParentPath "/var/svn"

    AuthType Basic
    Authname "Redmine Project Tracking"
    Require valid-user

    PerlAccessHandler Apache::Authn::Redmine::access_handler
    PerlAuthenHandler Apache::Authn::Redmine::authen_handler
    PerlSetVar dsn DBI:Pg:dbname=redmine;host=localhost
    PerlSetVar db_user redmine
    PerlSetVar db_pass redmine
  </Location>

</VirtualHost>

Enable the site and restart apache:

ln -s /etc/apache2/sites-available/svn.yourdomain /etc/apache2/sites-enabled/svn.yourdomain
    /etc/init.d/apache2 restart

Check it’s up

Now, if you point your browser to http://svn.yourdomain/svn you should be prompted for a username and password. Don’t worry about authenticating yet, that’s for when you actually have a project repository to go poking around in.

Serve Redmine through apache and mongrel_cluster

Set up the cluster

Create the file config/mongrel_cluster.yml under the Redmine directory with the following contents:

user: root
cwd: /var/www/rails_apps/redmine-0.6
port: "9000"
environment: production
group: root
address: 0.0.0.0
pid_file: log/mongrel.pid
servers: 2

Check the cluster works

In the redmine directory, run:

mongrel_rails cluster::start

You should be able to get to Redmine on ports 9000 and 9001 of your server.

Have the cluster start at boot

As root:

mkdir /etc/mongrel_cluster
ln -s /var/www/rails_apps/redmine-0.6/config/mongrel_cluster.yml /etc/mongrel_cluster/redmine.yml
cp /var/lib/ruby/gems/1.8/gems/mongrel_cluster-1.0.5/resources/mongrel_cluster /etc/init.d
chmod +x /etc/init.d/mongrel_cluster
/usr/sbin/update-rc.d -f mongrel_cluster defaults

Install some more Apache modules

As root:

a2enmod proxy
a2enmod proxy_http
a2enmod proxy_balancer
a2enmod rewrite

Configure the redmine site through Apache

Create /etc/apache2/sites-available/redmine.yourdomain with the following contents:

<VirtualHost *>

  ServerAdmin support@yourdomain
  DocumentRoot /var/www/rails_apps/redmine-0.6
  ServerName redmine.yourdomain
  ErrorLog /var/www/rails_apps/redmine-0.6/log/error.log

  ProxyPass /images !
  ProxyPass /stylesheets !
  ProxyPass /javascripts !
  ProxyPass /favicon.ico !
  ProxyPass /static !
  ProxyPass /holding !
  ProxyPass /templates !
  ProxyPass / balancer://redmine_cluster
  ProxyPreserveHost On

  <Proxy balancer://redmine_cluster>
    BalancerMember http://127.0.0.1:9000
    BalancerMember http://127.0.0.1:9001
  </Proxy>

  RewriteEngine On
   # Redirect all non-static requests to cluster
  RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
  RewriteRule ^/(.*)$ balancer://redmine_cluster%{REQUEST_URI} [P,QSA,L]
</VirtualHost>

Enable the site and restart apache:

ln -s /etc/apache2/sites-available/redmine.yourdomain /etc/apache2/sites-enabled/redmine.yourdomain
/etc/init.d/apache2 restart

Have Redmine manage your repositories

Tell Redmine that it’s managing your repositories

In the Redmine site, go to Administration -> Settings. Tick the box labeled “Enable WS for repository management”

Set up automatic repository creation

Check that repository autocreation works

Add a project through Redmine with the identifier “test”

On your SVN server run:

ruby /var/www/rails_apps/redmine-0.6/extra/svn/reposman.rb --redmine redmine.theoru.com --svn-dir /var/svn --owner www-data --url http://svn.theoru.com/svn/

You should get the following message:

repository /var/svn/test registered in Redmine with url http://svn.theoru.com/svn/test
repository /var/svn/test created

Make the server do your dirty work for you

Add the following line to /etc/crontab:

10 * * * * root ruby /var/www/rails_apps/redmine-0.6/extra/svn/reposman.rb --redmine redmine.yourdomain --svn-dir /var/svn --owner www-data --url http://svn.yourdomain/svn/

Restrict access to Redmine’s web services’ API

Add the following section to /etc/apache2/sites-available/redmine.yourdomain:

<Location /sys>
  Order allow,deny
  Allow from ip.of.svn.yourdomain
</Location>

Restart apache:

/etc/init.d/apache2 restart

Relax, it’s done

Redmine and SVN should be happily up and running.

Once an hour, the cron job you set up will check for any newly registered projects and create an SVN repository for each new project at http://svn.yourdomain/svn/#{project_identifier} and register this location with the project.

To allow repository browsing, you will need to add appropriate credentials (e.g. project manager login) to the repository settings of the project.

Enjoy!

Posted in  | Tags , ,  | 6 comments

You should care what your code looks like

Posted by Chris Thu, 20 Dec 2007 00:03:00 GMT

The latest post over at Coding Horror, “Nobody Cares What Your Code Looks Like” is one of the strange posts that pop up occasionally where it seems that Jeff Atwood has missed the point entirely.

The elegance/ugliness of the code behind an application is immensely important in the maintenance and extension of any software, and particularly for open source software where it’s crucial that people want to work on your code.

The mind boggles, really. Well, at least I got through that without making a jab at Jeff for his VB use.

Posted in  | no comments

The Importance of Openness

Posted by Chris Sun, 11 Nov 2007 11:10:00 GMT

I’ve started on a new job hunt after being stuffed around and finally fired from a small, and dysfunctional web development company.

Looking around, I’ve realised that I should have been building up a public portfolio, of sorts, before now.

So far, the only thing I’ve released into the wild has been a small, capistrano module, for a very specific, and in my mind ill conceived, application. All the interesting stuff I’ve done has long been scrubbed from my hard drive, in compliance with long-winded NDAs from previous employers (including a rather nifty library for controlling mobile phones through their inbuilt modems to simulate and test network response/reliability).

I suppose my rambling boils down to the advice that you should, if possible, release your work to the public, and if you can’t, you damn well better be knocking up some personal projects to show off your skill.

Off to try to find something interesting to develop, I guess.

Posted in  | no comments

Disk free custom images on rails.

Posted by Chris Thu, 06 Sep 2007 01:38:00 GMT

So, I hit an odd problem in one of the apps I’m working on that I needed to render custom text onto images.

RMagick made that job pretty easy, but I didn’t want to clog up space on the server with a whole bunch of images that were only being viewed once.

The solution turned out to be fairly simple in the end, using send_data in the controller.

I’ll let the code speak for itself:

Controller (in StoreController):

def gift_preview
  require 'RMagick'

  text_params = {
    :width => 200,
    :height => 120,
    :xpos => 240,
    :ypos => 340,
    :colour => 'black'
  }

  # Get base image
  image = Magick::ImageList.new( File.join( "#{RAILS_ROOT}/public/images", 'gift_box.jpg' ) )

  # Set up text
  text = Magick::Draw.new
  text.font_family = 'times'
  text.pointsize = 15
  text.font_style = Magick::ItalicStyle

  # Add text to image
  text.annotate(
    image,
    text_params[:width],
    text_params[:height],
    text_params[:xpos],
    text_params[:ypos],
    params[:message] ) {
    self.fill = text_params[:colour]
  }
  # send the image data back
  send_data( image.to_blob, :type => 'image/jpeg' )
end

The important part of this is the last line that sends the image back as a binary blob with an appropriate content type (adjust for your image format).

In the template I have:

<%= link_to_function "Gift Preview",
  "$('gift_image').src='#{url_for( :action => 'gift_preview' )}?message=' + $F('order_message')" %>

“order_message” is a text area, and $F is a very handy prototype function that grabs it’s current contents, and “gift_image” is an image tag for displaying the custom image.

Clicking on this link sets the image source to the gift_preview action, passing the current message, then the controller returns the new custom image, never writing it to disk.

The code can do with some tidying, but for now, this is working for me (and it’s a prototype, so :P).

Enjoy! And for more RMagick detail, head here.

Posted in  | no comments

Fix the Apache trailing slash problem

Posted by Chris Sun, 17 Jun 2007 10:48:00 GMT

After a lot of stuffing about I finally found a solution to the problem that I was having with the URL for this blog.

The basic problem was that the link http://www.drinkingbird.net/blog (without the trailing slash) was being rejected with a 400 Bad Request error.

I spent ages trying to get this to work with awkward rewrite rules in the parent directory, when I should have been looking in the public directory of the rails app. The solution is well enough explained over at LavaFactory.

I just made one small refinement, which is to change:

RewriteRule .*/blog/(.*) http://www.drinkingbird.net/blog/$1 [L,R]

to

RewriteRule (.*)/blog/(.*) $1/blog/$2

to satisfy my relentless desire to avoid configuration tied to a particular domain.

Posted in ,  | no comments

Typo on Site5

Posted by Chris Sun, 10 Jun 2007 11:51:00 GMT

I thought I’d post a quick summary on what I went through to get Typo up and running on Site5, since it’s a bit more tricky now that the SiteAdmin interface no longer contains an automatic install script.

I decided to go the gem route on this, so that’s the only way I’m going to talk about. If you want to install from SVN trunk, you’re on your own.

First, you’ll want to set up your account with a local gem installation directory, as detailed in this forum thread.

Then, ssh into your account (if you’re not still there), and type:

gem install typo

There’s a good chance this will fail the first time, with an error on attempting to install the sqlite3-ruby gem. This can be solved by opening a ticket with Site5’s support, and asking to have the sqlite-devel package installed on your server, then trying again.

Once the gem is installed, you just need to run

typo <installation directory>
then manually kill the mongrel process it spaws immediately after installation (I’d lay money on site5’s servers doing this automatically if you don’t anyway). From here, it’s all fairly easy: Edit the environment.rb file in typo’s config directory, and uncomment the line:
ENV['RAILS_ENV'] = 'production'

and add the line:

ENV['GEM_PATH'] = '/home/<your_username>/<path_to_gems>:/usr/lib/ruby/gems/1.8'

Create the file public/.htaccess in the typo directory as:

# General Apache options
AddHandler fastcgi-script .fcgi
Options +FollowSymLinks +ExecCGI

RewriteEngine On

RewriteRule ^$ index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]

ErrorDocument 500 /500.html # You may want to edit this file

Setup a mysql database for the blog’s production db, and configure it in config/database.yml. Example config:

production:
  adapter: mysql
  database: username_blog
  username: username_blog
  password: secret
  host: localhost

Then run

rake db:migrate RAILS_ENV=production

to load the schema.

If you installed your blog anywhere other than under your public_html directory, you’ll need to create a symlink to the blog, e.g.

ln -s /path/to/typo/public public_html/blog

Hopefully this ends up helping someone. If not, well at least it gave me an excuse to get to grips with some of typo’s editing syntax. If I’ve missed anything, please bitch at me, so I can fix it.

Posted in ,  | 2 comments

New blog christening!

Posted by Chris Sun, 10 Jun 2007 11:17:00 GMT

Well, here goes. After years of steadfast resolve I’ve given in, and started up my own little, self-centred part of the web.

There was a bit of fiddling about to get this thing up and running, but now that’s done I’ve just got to figure out what to write here :).

Hopefully there’ll be some useful tech stuff, eventually, but knowing my general approach to unmoderated speech, it might get a bit buried in pointless rambling and poorly directed vitriolic outbursts.

Oh well, maybe I’ll try to keep this thing under wraps for a while until I can write something without sounding like an angsty teen with a large vocabulary.

Posted in