Rails Plugin to Validate (X)HTML and CSS

Posted by Peter Donald Wed, 15 Mar 2006 08:18:00 GMT

Here is an enahnced version of Scott Raymond’s assert_valid_markup plugin that I use in my projects. Below are the directions for use;

HowTo Validate (X)HTML

# Calling the assertion with no parameters validates 
# whatever is in @request.body, which is automatically 
# set by the existing get/post/etc helpers. For example:

class FooControllerTest < Test::Unit::TestCase
  def test_bar_markup
    get :bar
    assert_valid_markup
  end
end

# Add a string parameter to the assertion to validate 
# any random fragment. For example:

class FooControllerTest < Test::Unit::TestCase
  def test_bar_markup
    assert_valid_markup "<div>Hello, world.</div>"
  end
end

# For the ultimate in convenience, use the class-level
# method to validate a number of actions in one line. 

class FooControllerTest < Test::Unit::TestCase
  assert_valid_markup :bar, :baz, :qux
end

HowTo Validate CSS

# Pass a string parameter to the assertion to validate 
# a css fragment. For example:

class FooControllerTest < Test::Unit::TestCase
  def test_bar_css
    filename = "#{RAILS_ROOT}/public/stylesheets/bar.css"
    assert_valid_css(File.open(filename ,'rb').read)
  end
end

# For the ultimate in convenience, use the class-level
# method to validate a bunch of css files in one line.
# Assumes that the CSS files are relative to 
# $RAILS_ROOT/public/stylesheets/ and end with '.css'. 
# The following example validates 
#  $RAILS_ROOT/public/stylesheets/layout.css, 
#  $RAILS_ROOT/public/stylesheets/standard.css and 
#  $RAILS_ROOT/public/stylesheets/theme.css

class FooControllerTest < Test::Unit::TestCase
  assert_valid_css_files 'layout', 'standard', 'theme'
end

Most of the credit for this plugin goes to Scott for the initial idea! The modifications that I made include;

  • Validation of CSS files.
  • Caching of fragments occurs in $RAILS_ROOT/tmp according to the name of the test class + test method. This avoids filling up the system temp folder with expired cache files.
  • Ability to turn off validation by setting the “NONET” environment variable to “true”.

You can grab it from subversion at;

http://assert-valid-asset.googlecode.com/svn/trunk/

Update 12th of May, 2008

Steve Sloan has also added a Git repo for the code which you can grab from:

http://github.com/CodeMonkeySteve/assert-valid-asset/tree/master

Posted in  | Tags , , , ,  | 2 comments | no trackbacks

Upload a file via POST with Net::HTTP

Posted by Peter Donald Thu, 02 Mar 2006 02:57:00 GMT

To upload a file to a website I needed to supply the data with a content type of “multipart/form-data”. The Net:HTTP API does not offer any such functionality, it just accepts raw content data. So I needed to roll my own.

The “multipart/form-data” content type consists of a number of secions separated by --BOUNDARY\r\n and terminated by BOUNDARY--\r\n where BOUNDARY is a string that does not appear in the content of any of the data transmitted to the server.

Each section represents a form field and contains a number of headers, a \r\n, the content and finishes with a \r\n. Normal form fields look like

Content-Disposition: form-data; name="mykey"

mydata

while file fields must include a few more headers.

Content-Disposition: form-data; name="mykey"; filename="filename"
Content-Transfer-Encoding: binary
Content-Type: text/plain

DATADATADATADATADATADATADATA...

To construct the parameters in ruby I use the following code;

def text_to_multipart(key,value)
  return "Content-Disposition: form-data; name=\"#{CGI::escape(key)}\"\r\n" + 
         "\r\n" + 
         "#{value}\r\n"
end

def file_to_multipart(key,filename,mime_type,content)
  return "Content-Disposition: form-data; name=\"#{CGI::escape(key)}\"; filename=\"#{filename}\"\r\n" +
         "Content-Transfer-Encoding: binary\r\n" +
         "Content-Type: #{mime_type}\r\n" + 
         "\r\n" + 
         "#{content}\r\n"
end

To put it all together you need to join the parameters with boundary separators between each section. This can be done via

boundary = '349832898984244898448024464570528145'
query = 
  params.collect {|p| '--' + boundary + "\r\n" + p}.join('') + "--" + boundary + "--\r\n"

The last thing that needs to be done is to make sure that you set the HTTP Header Content-type to multipart/form-data; boundary=BOUNDARY.

A complete example that I extracted from code that uploads a css file to the w3c validator service is as follows.

params = [ 
  file_to_multipart('file','file.css','text/css',data),
  text_to_multipart('warning','1'),
  text_to_multipart('profile','css2'),
  text_to_multipart('usermedium','all') ]

boundary = '349832898984244898448024464570528145'
query = 
  params.collect {|p| '--' + boundary + "\r\n" + p}.join('') + "--" + boundary + "--\r\n"

response = http.start('jigsaw.w3.org').
  post2("/css-validator/validator",
        query,
        "Content-type" => "multipart/form-data; boundary=" + boundary)

It was a little bit painful to figure out “multipart/form-data” via Ethereal but relatively easy to implement. Hope this helps!

Update 3rd of October, 2006:

Slight correction supplied by Andrew Willis so that last boundary is '--' + boundary + '--' rather than just boundary + '--'.

Posted in  | Tags ,  | no comments | no trackbacks

Removing Stale Rails Sessions

Posted by Peter Donald Wed, 01 Mar 2006 00:53:00 GMT

By default rails does not clear out stale sessions from the session store. To implement this feature I added the following small snippet of code;

class SessionCleaner
  def self.remove_stale_sessions
    CGI::Session::ActiveRecordStore::Session.
      destroy_all( ['updated_on <?', 20.minutes.ago] ) 
  end
end

And then invoke the remove_stale_sessions method every 10 minutes via;

*/10 * * * * ruby /full/path/to/script/runner 
   -e production "SessionCleaner.remove_stale_sessions"

Posted in  | Tags ,  | no comments | no trackbacks

Flexible Application Configuration in Rails

Posted by Peter Donald Tue, 28 Feb 2006 10:37:00 GMT

Most of my rails application have required some form of application specific configuration. When the configuration varies depending on where I deploy it I prefer to externalize the configuration in a YAML file similar to the way database configuration is handled.

I remember reading a blog somewhere that suggested the use of OpenStruct and have since started using the following code placed at the bottom of my config/environment.rb file.

require 'ostruct'
require 'yaml'

ConfigFile = "#{RAILS_ROOT}/config/config.yml"
if File.exist?(ConfigFile)
 ::ApplicationConfig = OpenStruct.new(YAML.load_file(ConfigFile))
end

Consider the scenario where config/config.yml contains;

proxy_config:
  host: proxy.cs.latrobe.edu.au
  port: 8080

I could then access the the configuration data using

>> ApplicationConfig.proxy_config['host'] 
=> "proxy.cs.latrobe.edu.au"
>> ApplicationConfig.proxy_config['port']
=> 8080

To make your code more robust in the scenario where either the proxy configuration is missing or the whole configuration file is missing I generally add in extra guards. Below is an example of the code I use to instantiate a Net:HTTP object. If the configuration file is present and it contains a proxy_config section then the code will attempt to instantiate a Net::HTTP::Proxy object. Otherwise the code will instantiate a plain old Net::HTTP that will not attempt to go via a proxy server.

def http
  if Module.constants.include?("ApplicationConfig") && 
      ApplicationConfig.respond_to?(:proxy_config)
    host = ApplicationConfig.proxy_config['host']
    port = ApplicationConfig.proxy_config['port']
    Net::HTTP::Proxy(host, port)
  else
    Net::HTTP
  end
end

Posted in  | Tags , ,  | no comments | no trackbacks

Uncacheable web pages in Rails

Posted by Peter Donald Thu, 15 Dec 2005 03:36:00 GMT

In an attempt to ensure that a page is always fetched from the server I needed to add the following code to my base controller. Seems to be a bit long-winded but does the job.

class ApplicationController < ActionController::Base
  before_filter :force_no_cache

private
  def force_no_cache
    # set modify date to current timestamp
    response.headers["Last-Modified"] = CGI::rfc1123_date(Time.now)

    # set expiry to back in the past 
    # (makes us a bad candidate for caching)
    response.headers["Expires"] = 0

    # HTTP 1.0 (disable caching)
    response.headers["Pragma"] = "no-cache"

    # HTTP 1.1 (disable caching of any kind)
    # HTTP 1.1 'pre-check=0, post-check=0' (IE specific)
    response.headers["Cache-Control"] = 
      "no-cache, no-store, must-revalidate, pre-check=0, post-check=0"

  end

Posted in  | no comments | no trackbacks

Older posts: 1 2 3 4