Uploadify and Rails 2.3

Posted over 2 years back at RailsTips.org - Home

In which I show how to reach the promised land of multiple file uploads using Uploadify, a spot of rack middleware and Rails 2.3.

A few weeks back we (Steve and I) added multiple asset upload to Harmony using Uploadify. If you are thinking that sounds easy, you would be sorely mistaken. Uploadify uses flash to send the files to Rails. This isn’t a big deal except that we are using cookie sessions on Harmony and flash wasn’t sending the session information with the files, so to Rails the files appeared as unauthenticated.

We found multiple articles online showing how to get this working, but none of them worked as promised. At the time Harmony was running on Rails 2.2. Knowing that rack was probably the best way to solve our issue, we updated to 2.3, which was pretty painless, and started hacking. Be sure to check out a quick screencast of the finished product at some point as well.

Add Uploadify

First, we added the uploadify files and the following js to the assets/index view. We actually set many more options, but these are the ones pertinent to this article. Script is the url to post the files to. fileDataName is the name of the file field you would like to use. scriptData is any additional data you would like to post to the url.

<%- session_key_name = ActionController::Base.session_options[:key] -%>
<script type="text/javascript">
  $('#upload_files').fileUpload({  
      script          : '/admin/assets',
      fileDataName    : 'asset[file]',
      scriptData      : {
        '<%= session_key_name %>' : '<%= u cookies[session_key_name] %>',
        'authenticity_token'  : '<%= u form_authenticity_token if protect_against_forgery? %>'
      }
  });
</script>

As you can see, it adds the session key and the cookie value along with the authenticity token as data that gets sent with the file. We then use a piece of rack middleware to intercept the upload and properly set the Rails session cookie.

Add Some Middleware

We created an app/middleware directory and added it to the load path in environment.rb.

%w(observers sweepers mailers middleware).each do |dir|
  config.load_paths << "#{RAILS_ROOT}/app/#{dir}" 
end

Next, we dropped flash_session_cookie_middleware.rb in the app/middleware directory.

require 'rack/utils'

class FlashSessionCookieMiddleware
  def initialize(app, session_key = '_session_id')
    @app = app
    @session_key = session_key
  end

  def call(env)
    if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
      params = ::Rack::Utils.parse_query(env['QUERY_STRING'])

      unless params[@session_key].nil?
        env['HTTP_COOKIE'] = "#{@session_key}=#{params[@session_key]}".freeze
      end
    end

    @app.call(env)
  end
end

And, finally, we added the following to our session_store.rb initializer.

ActionController::Dispatcher.middleware.insert_before(
  ActionController::Session::CookieStore, 
  FlashSessionCookieMiddleware, 
  ActionController::Base.session_options[:key]
)

This inserts our middleware before ActionController’s CookieStore so that everything will just work as expected.

Assign the Content Type

The only other thing we needed to do was manually set the content type of the file. We were using paperclip (which is awesome) to do uploads, so something like this did the trick:

@asset.file_content_type = MIME::Types.type_for(@asset.original_filename).to_s

Be sure to add the mime type gem to your environment.rb file as well.

config.gem 'mime-types', :lib => 'mime/types'

But Why?

So why did we go through all this trouble to allow multiple uploads at once? Taking a quick look at the finished product might help. I didn’t record the entire screen in the video, as we haven’t actually released Harmony yet (ooooh secrets!), but I did capture enough that you can see the awesome uploads in action.

Harmony Multi-Uploading of Assets

Hope this spares some other poor soul attempting the same thing some time.

Uploadify and Rails 2.3

Posted over 2 years back at RailsTips.org - Home

A few weeks back we (Steve and I) added multiple asset upload to Harmony using Uploadify. If you are thinking that sounds easy, you would be sorely mistaken. Uploadify uses flash to send the files to Rails. This isn’t a big deal except that we are using cookie sessions on Harmony and flash wasn’t sending the session information with the files, so to Rails the files appeared as unauthenticated.

We found multiple articles online showing how to get this working, but none of them worked as promised. At the time Harmony was running on Rails 2.2. Knowing that rack was probably the best way to solve our issue, we updated to 2.3, which was pretty painless, and started hacking. Be sure to check out a quick screencast of the finished product at some point as well.

Add Uploadify

First, we added the uploadify files and the following js to the assets/index view. We actually set many more options, but these are the ones pertinent to this article. Script is the url to post the files to. fileDataName is the name of the file field you would like to use. scriptData is any additional data you would like to post to the url.

<%- session_key_name = ActionController::Base.session_options[:key] -%>
<script type="text/javascript">
  $('#upload_files').fileUpload({  
      script          : '/admin/assets',
      fileDataName    : 'asset[file]',
      scriptData      : {
        '<%= session_key_name %>' : '<%= u cookies[session_key_name] %>',
        'authenticity_token'  : '<%= u form_authenticity_token if protect_against_forgery? %>'
      }
  });
</script>

As you can see, it adds the session key and the cookie value along with the authenticity token as data that gets sent with the file. We then use a piece of rack middleware to intercept the upload and properly set the Rails session cookie.

Add Some Middleware

We created an app/middleware directory and added it to the load path in environment.rb.

%w(observers sweepers mailers middleware).each do |dir|
  config.load_paths << "#{RAILS_ROOT}/app/#{dir}"
end

Next, we dropped flash_session_cookie_middleware.rb in the app/middleware directory.

require 'rack/utils'

class FlashSessionCookieMiddleware
  def initialize(app, session_key = '_session_id')
    @app = app
    @session_key = session_key
  end

  def call(env)
    if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
      params = ::Rack::Utils.parse_query(env['QUERY_STRING'])
      
      unless params[@session_key].nil?
        env['HTTP_COOKIE'] = "#{@session_key}=#{params[@session_key]}".freeze
      end
    end
    
    @app.call(env)
  end
end

And, finally, we added the following to our session_store.rb initializer.

ActionController::Dispatcher.middleware.insert_before(
  ActionController::Session::CookieStore, 
  FlashSessionCookieMiddleware, 
  ActionController::Base.session_options[:key]
)

This inserts our middleware before ActionController’s CookieStore so that everything will just work as expected.

Assign the Content Type

The only other thing we needed to do was manually set the content type of the file. We were using paperclip (which is awesome) to do uploads, so something like this did the trick:

@asset.file_content_type = MIME::Types.type_for(@asset.original_filename).to_s

Be sure to add the mime type gem to your environment.rb file as well.

config.gem 'mime-types', :lib => 'mime/types'

But Why?

So why did we go through all this trouble to allow multiple uploads at once? Taking a quick look at the finished product might help. I didn’t record the entire screen in the video, as we haven’t actually released Harmony yet (ooooh secrets!), but I did capture enough that you can see the awesome uploads in action.

Harmony Multi-Uploading of Assets

Hope this spares some other poor soul attempting the same thing some time.

Uploadify and Rails 2.3

Posted over 2 years back at RailsTips.org - Home

In which I show how to reach the promised land of multiple file uploads using Uploadify, a spot of rack middleware and Rails 2.3.

A few weeks back we (Steve and I) added multiple asset upload to Harmony using Uploadify. If you are thinking that sounds easy, you would be sorely mistaken. Uploadify uses flash to send the files to Rails. This isn’t a big deal except that we are using cookie sessions on Harmony and flash wasn’t sending the session information with the files, so to Rails the files appeared as unauthenticated.

We found multiple articles online showing how to get this working, but none of them worked as promised. At the time Harmony was running on Rails 2.2. Knowing that rack was probably the best way to solve our issue, we updated to 2.3, which was pretty painless, and started hacking. Be sure to check out a quick screencast of the finished product at some point as well.

Add Uploadify

First, we added the uploadify files and the following js to the assets/index view. We actually set many more options, but these are the ones pertinent to this article. Script is the url to post the files to. fileDataName is the name of the file field you would like to use. scriptData is any additional data you would like to post to the url.

<%- session_key_name = ActionController::Base.session_options[:key] -%>
<script type="text/javascript">
  $('#upload_files').fileUpload({  
      script          : '/admin/assets',
      fileDataName    : 'asset[file]',
      scriptData      : {
        '<%= session_key_name %>' : '<%= u cookies[session_key_name] %>',
        'authenticity_token'  : '<%= u form_authenticity_token if protect_against_forgery? %>'
      }
  });
</script>

As you can see, it adds the session key and the cookie value along with the authenticity token as data that gets sent with the file. We then use a piece of rack middleware to intercept the upload and properly set the Rails session cookie.

Add Some Middleware

We created an app/middleware directory and added it to the load path in environment.rb.

%w(observers sweepers mailers middleware).each do |dir|
  config.load_paths << "#{RAILS_ROOT}/app/#{dir}" 
end

Next, we dropped flash_session_cookie_middleware.rb in the app/middleware directory.

require 'rack/utils'

class FlashSessionCookieMiddleware
  def initialize(app, session_key = '_session_id')
    @app = app
    @session_key = session_key
  end

  def call(env)
    if env['HTTP_USER_AGENT'] =~ /^(Adobe|Shockwave) Flash/
      params = ::Rack::Utils.parse_query(env['QUERY_STRING'])

      unless params[@session_key].nil?
        env['HTTP_COOKIE'] = "#{@session_key}=#{params[@session_key]}".freeze
      end
    end

    @app.call(env)
  end
end

And, finally, we added the following to our session_store.rb initializer.

ActionController::Dispatcher.middleware.insert_before(
  ActionController::Session::CookieStore, 
  FlashSessionCookieMiddleware, 
  ActionController::Base.session_options[:key]
)

This inserts our middleware before ActionController’s CookieStore so that everything will just work as expected.

Assign the Content Type

The only other thing we needed to do was manually set the content type of the file. We were using paperclip (which is awesome) to do uploads, so something like this did the trick:

@asset.file_content_type = MIME::Types.type_for(@asset.original_filename).to_s

Be sure to add the mime type gem to your environment.rb file as well.

config.gem 'mime-types', :lib => 'mime/types'

But Why?

So why did we go through all this trouble to allow multiple uploads at once? Taking a quick look at the finished product might help. I didn’t record the entire screen in the video, as we haven’t actually released Harmony yet (ooooh secrets!), but I did capture enough that you can see the awesome uploads in action.

Harmony Multi-Uploading of Assets

Hope this spares some other poor soul attempting the same thing some time.

My Apprenticeship - Tuesday, July 20, 2004

Posted over 2 years back at Jake Scruggs

This summer I'm revisiting my short apprenticeship at Object Mentor. I'll be posting commentary on all my posts from the summer of 2004 exactly 5 years later to the day.

Tuesday 7-20-04

Second day of A.O.O.D. and I'm getting pretty good with UML. Today we discussed the various principles of software development: Single responsibility, Open/closed, Liskov, Interface segregation, and Dependency inversion (Which, all together, spells S.O.L.I.D. - you can learn things from XPAU mouse pads).

Paul an I spent an hour or so trying to design an system that could take in information about a bunch of different types of employees. Then, after we designed a system with enough interfaces and uses of inheritance to avoid violation of the principles, more user stories were added and we had to refactor. It was an initially frustrating, but ultimately fun project to take a system and make it do something new without, hopefully, coupling the classes together too much. I think we got it, but we'll find out tomorrow morning if we did.

Bob said he liked my blog, especially the bit where Micah came back after a week, looked at out project and asked us what we thought we were supposed to do. Apparently this is a pretty common experience in software -- after a few weeks the customer looks at what you've produced and either realizes that what you did isn't what s/he asked, or what they asked for isn't what they really want. One of the big points of XP is trying to make this conflict between intent and execution happen earlier rather than later. There's always going to be changes to the system, but big ones need to happen early (changing the look and feel of the GUI -- not so bad, changing a 2-D side scroller into a 3-D first person shooter? Little bit more difficult).

I still have one of those 'S.O.L.I.D.' mouse pads - I don't really need it for my optical mouse but I have it. Also nice to have an Uncle Bob (Martin) sighting. People often ask me about all the Object Mentor employees I must know, but most of them were off teaching classes at the client site or doing agile/xp coaching during the summer of 2004. I mostly hung around with David, Paul, and Micah that summer and had shorter interactions with the rest. The same thing happened at ThoughtWorks were people would assume I knew this, that, or the other person who worked there while I did. But often person 'X' was in Texas while I was in New Jersey and our interaction was limited to possible bumping into each other at a company function.

Ruby VM Roundup: Ruby 1.9.2 Preview 1, Ruby Versions Site

Posted over 2 years back at InfoQ Personalized Feed for unregistered user - Register to upgrade!

Ruby 1.9.2 Preview 1 is now available and brings API improvements such as Method#parameters, GC optimization for long lived objects, and more. Also: to keep up to speed with Ruby implementations, David A. Black announced ruby-versions.net which provides a long list of MRI versions as well as JRuby, Rubinius and REE installations. By Werner Schuster

Make any Ruby object Rack-friendly with Rackable

Posted over 2 years back at Ruby Inside

rackFrançois Vaux has recently published a Ruby module called Rackable which allows you to make any Ruby object Rack-friendly, providing it with a REST-like interface.

What does this mean? Well, a Rack application is essentially a Ruby object that responds to call(). Rackable just gives your object a call method which uses the Rack environment to dispatch to a method.

So, you just need to include Rackable in your class and implement methods for the the appropriate REST verbs. This means you can create a hello_world.ru file like this:

require 'rackable'

class HelloWorld
  include Rackable

  def get()
    "Hello, world!"
  end

end

run HelloWorld.new

... start it with rackup, and then use something like curl (or even your browser) to call the methods.

Thanks to Alex Young for putting me on to this.

devver-icon.gifAlso.. Got a slow Test::Unit or RSpec suite? Run them up to three times faster on Devver's cloud! Setup is simple and requires no code changes. Request a beta invite today!

Rails 2.3.3 Released: Nothing Big, But Not To Be Sneezed At

Posted over 2 years back at Rails Inside

David Heinemeier Hansson, creator of Rails, has just announced the release of Rails 2.3.3.

Rails 2.3.3 is primarily a bug fixing release, but a few new features have creeped in, including decoding backends for faster JSON and YAML libraries, the touch method for "touching" a record's timestamp (but nothing else), and a :primary_key option for belongs_to associations to allow support for more legacy schemas.

Other than that, not a particularly exciting release, but the main focus is now on Rails 3.0.

devver-icon.gifAlso.. Got a slow Test::Unit or RSpec suite? Run them up to three times faster on Devver's cloud! Setup is simple and requires no code changes. Request a beta invite today!

Rails 2.3.3: Touching, faster JSON, bug fixes

Posted over 2 years back at Riding Rails - home

We’ve released Ruby on Rails version 2.3.3. This release fixes a lot of bugs and introduces a handful of new features.

Active Record

  • touch is a convenient method to update a record’s timestamp and nothing else. This is extracted from apps whose models “touch” others when they change, such as a comment updating the parent.replies_changed_at timestamp after save and destroy. Timestamping an entire has_many association makes it easy to build a key for fragment caching that covers changes to the parent object and any of its children. This pattern is wrapped up as belongs_to :parent, :touch => :replies_changed_at. When the child changes, parent.replies_changed_at is touched. :touch => true is defaults to :touch => :updated_at.
  • :primary_key option for belongs_to for broader support of legacy schemas and those using a separate UUID primary key: belongs_to :employee, :primary_key => 'SSN', :foreign_key => 'EMPID' changeset

JSON

  • decoding backends for the json and yajl libraries. Both are significantly faster than the default YAML backend. To get started, install the json gem and set ActiveSupport::JSON.backend = 'JSONGem'.
* leaner user-facing encoding API. Since a JSON libraries implement to_json with varying compatibility, safely overriding it is difficult. Most custom to_json looks like

def to_json(*encoder_specific_args)
  { :some => "json representation" }.to_json(*encoder_specific_args)
end
so we DRYed the user-facing API down to a more natural

def as_json(options = {})
  { :some => "json representation" }
end
without the ugly internal state exposed by overloading to_json as both public-facing and internal builder API. Rails 3 splits the API explicitly, so prepare now by switching from to_json to as_json.

Other Features

  • Add :concat option to asset tag helpers to force concatenation. changeset
  • Restore backwards compatibility for AR::Base#to_xml. changeset
  • Move from BlueCloth to Markdown for the markdown helper. Users using BlueCloth to provide their markdown functionality should upgrade to version 1.0.1 or 2.0.5 in order to restore compatibility.

Notable Bug Fixes

  • Fix errors caused by class-reloading with streaming responses in development mode.
  • Several fixes to the gem bundling, unpacking and installing system.
  • Make text_area_tag escape contents by default.
  • Make filter_parameters work correctly with array parameters.
  • Thread-safety fixes for postgresql string quoting.
  • Performance fixes for large response bodies.

Rails 2.3.3: Touching, faster JSON, bug fixes

Posted over 2 years back at Riding Rails - home

We’ve released Ruby on Rails version 2.3.3. This release fixes a lot of bugs and introduces a handful of new features.

Active Record

  • touch is a convenient method to update a record’s timestamp and nothing else. This is extracted from apps whose models “touch” others when they change, such as a comment updating the parent.replies_changed_at timestamp after save and destroy. Timestamping an entire has_many association makes it easy to build a key for fragment caching that covers changes to the parent object and any of its children. This pattern is wrapped up as belongs_to :parent, :touch => :replies_changed_at. When the child changes, parent.replies_changed_at is touched. :touch => true is defaults to :touch => :updated_at.
  • :primary_key option for belongs_to for broader support of legacy schemas and those using a separate UUID primary key: belongs_to :employee, :primary_key => 'SSN', :foreign_key => 'EMPID' changeset

JSON

  • decoding backends for the json and yajl libraries. Both are significantly faster than the default YAML backend. To get started, install the json gem and set ActiveSupport::JSON.backend = 'JSONGem'.
  • leaner user-facing encoding API. Since a JSON libraries implement to_json with varying compatibility, safely overriding it is difficult. Most custom to_json looks like
    
    def to_json(*encoder_specific_args)
      { :some => "json representation" }.to_json(*encoder_specific_args)
    end
    so we DRYed the user-facing API down to a more natural
    
    def as_json(options = {})
      { :some => "json representation" }
    end
    without the ugly internal state exposed by overloading to_json as both public-facing and internal builder API. Rails 3 splits the API explicitly, so prepare now by switching from to_json to as_json.

Other Features

  • Add :concat option to asset tag helpers to force concatenation. changeset
  • Restore backwards compatibility for AR::Base#to_xml. changeset
  • Move from BlueCloth to Markdown for the markdown helper. Users using BlueCloth to provide their markdown functionality should upgrade to version 1.0.1 or 2.0.5 in order to restore compatibility.

Notable Bug Fixes

  • Fix errors caused by class-reloading with streaming responses in development mode.
  • Several fixes to the gem bundling, unpacking and installing system.
  • Make text_area_tag escape contents by default.
  • Make filter_parameters work correctly with array parameters.
  • Thread-safety fixes for postgresql string quoting.
  • Performance fixes for large response bodies.

Ordered Hashes for Ruby 1.8

Posted over 2 years back at coderrr


For those of you who want a more declarative way to define ordered hashes in ruby 1.8 (perhaps for a DSL or something) here’s something I came up with:


class Object
  def >>(v)
    [self, v]
  end
end

def H(*pairs)
  pairs.inject(OrderedHash.new) {|oh, (k, v)| oh[k] = v; oh }
end

my_hash = H :some_key >> 'some value', 'nother key' >> another_value

Of course this won’t work for Fixnums as keys.

Call Native Code From Your Android Applications

Posted over 2 years back at InfoQ Personalized Feed for unregistered user - Register to upgrade!

Responding to a call from developers, the Android Native Developer Kit (NDK) now supports calling native code in the Dalvik virtual machine. CPU-intensive operations that don't allocate much memory may benefit from increased performance and the ability to reuse existing code. Some example applications are signal processing, intensive physics simulations, and some kinds of data processing. By Nicholas Nezis

RVideo for video processing and inspection

Posted over 2 years back at WyeWorks Blog - Home

At WyeWorks headquarters, every once in a while, we come across some project that needs a media edition/transcoding solution to build into. This was the case of our latest project in which we built a pretty simple interface with Brightcove, a powerful video platform on which we may write something about it in our forthcoming posts, but it’s not the point right now.

Turns out to be that Brightcove recommends that files should be encoded using either H.264 or VP6. As usual, we ask ffmpeg for salvation when we need to transcode media files and this was not the exception. But we didn’t want to transcode just every file nor make the choice based on the file’s extension. We wanted a way to check the current file encoding.

Searches made at that time lead us to think that the usual way to get a media file encoding is by running:

ffmpeg -i 

which i must say that it’s pretty ugly for me since that command it’s supposed to be used for conversion and as far as i know it doesn’t offer some flag to get only the file information. In fact, that command returns an error (but still prints the information we need):

jose:~$ ffmpeg -i barsandtone.flv 
FFmpeg version 0.5-svn17737+3:0.svn20090303-1ubuntu6, Copyright (c) 2000-2009 Fabrice Bellard, et al.
  configuration: --enable-gpl --enable-postproc --enable-swscale --enable-x11grab --extra-version=svn17737+3:0.svn20090303-1ubuntu6 --prefix=/usr --enable-avfilter --enable-avfilter-lavf --enable-libgsm --enable-libschroedinger --enable-libspeex --enable-libtheora --enable-libvorbis --enable-pthreads --disable-stripping --disable-vhook --enable-libdc1394 --disable-armv5te --disable-armv6 --disable-armv6t2 --disable-armvfp --disable-neon --disable-altivec --disable-vis --enable-shared --disable-static
  libavutil     49.15. 0 / 49.15. 0
  libavcodec    52.20. 0 / 52.20. 0
  libavformat   52.31. 0 / 52.31. 0
  libavdevice   52. 1. 0 / 52. 1. 0
  libavfilter    0. 4. 0 /  0. 4. 0
  libswscale     0. 7. 1 /  0. 7. 1
  libpostproc   51. 2. 0 / 51. 2. 0
  built on Apr 10 2009 23:18:41, gcc: 4.3.3
Input #0, flv, from 'barsandtone.flv':
  Duration: 00:00:06.00, start: 0.000000, bitrate: 505 kb/s
    Stream #0.0: Video: vp6f, yuv420p, 360x288, 409 kb/s, 1k tbr, 1k tbn, 1k tbc
    Stream #0.1: Audio: mp3, 44100 Hz, stereo, s16, 96 kb/s
  Must supply at least one output file

The information we need is the video codec (in this case vp6f) to determine if we need to transcode it.

Another thing to mention is that nothing that you see as the output there outputs to the stdout. No rocket science needed but i was kind of lazy to parse this from scratch. Lucky for me, there was this not so new rvideo gem (still unknown for me) that made me save some precious time.

RVideo still relies in ffmpeg command and also it’s internal work to get the information we need involves that same output parsing, you just don’t have to do it yourself. After installation (not covered here, just check rvideo readme file), you can do things as shown below:

inspector = RVideo::Inspector.new(:file => file.path)
inspector.video_codec
=> "vp6f" 
inspector.duration
=> 6000
inspector.audio_codec
=> "mp3"

We’ve just made a tiny introduction to inspection. I’m not covering video processing. Check rvideo for more details! Enjoy!

Double Shot #499

Posted over 2 years back at A Fresh Cup


Another productive weekend – may have a new project to announce shortly.

  • What do we need to get on Ruby 1.9? – Wycats inquires. For me, the fact that I can’t run a Rails app with a database using mainstream gems is a showstopper. Not interested in science projects when I’m trying to do paying work.
  • Memcached 1.4.0 – Now released, including dead-simple Amazon EC2 AMIs for those who want to cache in the cloud.
  • Nmap 5.0.0 released – I used port scanners a lot more when I was on Windows, but I still keep some eye at what’s going on.
  • Kii – A new entrant in the open-source Rails wiki sweepstakes.
  • Test::Unit 2.0.3 – Huh, I hadn’t realized T::U was still under active development.
  • The Lazy developer: Tweak your MySQL – Elad offers some memory tuning suggestions.
  • Big Old Rails Template – I’ve done some updating to this template, which I talked about here.

Episode 171: Delayed Job

Posted over 2 years back at Railscasts

Is there a long running task which should be handled in the background? One of the best ways is using the delayed_job plugin like I show in this episode.

validates_length_of byte counting gotcha

Posted over 2 years back at Rail Spikes

Watch out for validates_length_of if you need to make sure a string is a certain number of bytes long. For example, SMS messages can be no longer than 160 bytes in length. I recently got bit by this because some unicode “curly” quotes slipped into a reply message, but they weren’t detected by the validation.

Here’s the problem.

Consider this string:

str = "€"

It is 3 bytes long:

str.size => 3

However, ActiveRecord’s validates_length_of records this as only one character, because it uses str.split(//).size to measure the size.

If you NEED to be certain that a string is less than a certain number of bytes, you’ll need to override the default behavior of validate_length_of.

Fortunately, you can supply your own tokenizer, which makes this easy. The tokenizer is called, and size is called on its return value to find out how many tokens there are. Since String responds to size which returns the number of bytes, you can simply return the attribute value itself as the tokenizer, like this:

validates_length_of :message, :maximum => 160, :tokenizer => lambda { |str| str }

Will this still work in Ruby 1.9? I’m not sure. I now have a test case which will warn me if it doesn’t…