Rails Plugin: acts_as_fu

March 31st, 2008

We’ve all seen the job listings. We know what they’re looking for. Rockstars. Code Monkeys. Rails Ninjas.

And you’re left thinking, “But I’m just a programmer.” Well, not anymore. Today, you become a Rails Kung-Fu Master!

“Not me,” you say. “I’ve already tried using all the acts_as_something plugins and all the something_fu plugins; and I’m still not a Rockstar or a Ninja. I’m barely a Code Simian!”

Sounds like you’ve tried the rest – now try the best! That’s right: it’s the new and improved ActsAsFu Rails plugin!

Three Easy Steps to Becoming a Rails Kung-Fu Master:

1. Stop web server

mongrel_rails stop

2. Install Plugin

./script/plugin install http://svn.offtheline.net/plugins/acts_as_fu

3. Start web server

mongrel_rails start

(note: if you’re using mongrel_cluster, thin, piston, or anything like that, these instructions don’t apply to you – you figure it out yourself, you Rockstar Ninja!)

Now you’re on your way to programming stardom! Catch ya on fame’s backside, my friend!

Bonus Round: If you work on a team, don’t forget to check this awesome plugin into your repository (or as the ninjas say, svn ci -m ‘added l33t skillz’). Your teammates will undoubtedly be very impressed by your jujitsu karate haxxing abilities. If you push it up to your production server, your boss might even give you a raise!

Has anyone noticed that the newer rake (as of 8.0) truncates task descriptions a lot shorter than the old rake (7.3)? Here’s what I mean:

jason@old-box:~/dev/some_app$ rake --version
rake, version 0.7.3
jason@old-box:~/dev/some_app$ rake -T rails
(in /home/jason/dev/some_app)
rake doc:clobber_rails         # Remove rdoc products
rake doc:rails                 # Build the rails HTML Files
rake doc:rerails               # Force a rebuild of the RDOC files
rake rails:freeze:edge         # Lock to latest Edge Rails or a specific revision with REVISION=X (ex: REVISION=4021) or a tag with TAG=Y (ex: TAG=rel_1-1-0)
(etc...)
jason@new-box:~/dev/some_app$ rake --version
rake, version 0.8.1
jason@new-box:~/dev/some_app$ rake -T rails
(in /home/jason/dev/some_app)
rake doc:clobber_rails         # Remove rdoc products
rake doc:rails                 # Build the rails HTML Files
rake doc:rerails               # Force a rebuild of the RDOC files
rake rails:freeze:edge         # Lock to latest Edge Rails or a specific re...
(etc...)

If you dig into the rake 8.0/8.1 gems, you might notice that the task list output is being truncated to 80 columns. This change was the result of a suggestion to make the task list more condensed, while at the same time adding a -D option for full descriptions (Rake 0.8.0 Released [rubyforge.org]):

jason@jason-4200:~/dev/phoenixmedia$ rake -D rails
(in /home/jason/dev/phoenixmedia)
rake doc:clobber_rails
    Remove rdoc products

rake doc:rails
    Build the rails HTML Files

rake doc:rerails
    Force a rebuild of the RDOC files

rake rails:freeze:edge
    Lock to latest Edge Rails or a specific revision with REVISION=X (ex: REVISION=4021) or a tag with TAG=Y (ex: TAG=rel_1-1-0)

(etc...)

...but as you can see, the full-description version puts a bunch of line breaks in the output.

I liked it better the old way! I don’t doubt the change was made for the best (line-wrapped descriptions in a small window are not very nice to look at), but I’d really like the option to set my own column width. For now, I’ve resorted to dropping this rake task into my Rails apps (you can save this as something.rake under my_rails_app_root/lib/tasks):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
NUMBER_OF_COLUMNS_TO_DISPLAY = 180

task :t, :p do |t, args|
  pattern = args.p || '.'
  displayable_tasks = Rake.application.tasks.select { |t|
    t.comment && t.name =~ /#{pattern}/
  }

  width = displayable_tasks.collect { |t| t.name_with_args.length }.max || 10
  cols = ENV['RAKE_DESC_COLS'] ? ENV['RAKE_DESC_COLS'].to_i : NUMBER_OF_COLUMNS_TO_DISPLAY
  max_column = cols - Rake.application.name.size - width - 7

  displayable_tasks.each do |t|
    printf "#{Rake.application.name} %-#{width}s  # %s\n",
      t.name_with_args, truncate(t.comment, max_column)
  end
end

def truncate(string, width)
  return '' if string.nil?
  if string.length <= width
    string
  else
    string[0, width-3] + "..."
  end
end

Note that this uses the new rake 8.x feature of allowing for command-line arguments to get passed into a task, so we can pass in a ‘pattern’ to search on without using an environment variable. Now I can do this:

jason@new-box:~/dev/some_app$ rake t[rails]
(in /home/jason/dev/some_app)
rake doc:clobber_rails         # Remove rdoc products
rake doc:rails                 # Build the rails HTML Files
rake doc:rerails               # Force a rebuild of the RDOC files
rake rails:freeze:edge         # Lock to latest Edge Rails or a specific revision with REVISION=X (ex: REVISION=4021) or a tag with TAG=Y (ex: TAG=rel_1-1-0)
(etc...)

I set the default number of columns at 180 because that’s just right for a maximized terminal window with the Monospace font (default in Ubuntu) at 9pt on my 1280×1024 display. Of course, I left an option to easily change the number of columns via an environment variable. For example, on my EeePC, 110 is about right:

jason@eeePC:~/dev/some_app$ export RAKE_DESC_COLS=110
jason@eeePC:~/dev/some_app$ rake t[rails] 
(in /home/jason/dev/some_app)
(in /home/jason/dev/phoenixmedia)
rake doc:clobber_rails         # Remove rdoc products
rake doc:rails                 # Build the rails HTML Files
rake doc:rerails               # Force a rebuild of the RDOC files
rake rails:freeze:edge         # Lock to latest Edge Rails or a specific revision with REVISION=X (ex: RE...
(etc...)

Hey, I can actually read the descriptions again!

This week I finally broke down and decided to find out what this Unobtrusive Javascript thing is all about. First, I got really excited about trying out the UJS plugin for Rails. I’m not linking to it, because after a couple of hours of digging around, Googling for the errors I was getting and finally digging into the source to find out it was breaking the tag_options helper in Rails 2, I discovered it’s no longer a supported plugin. Dan Webb, the original author, came to the conclusion that the unobtrusive message was not getting through to the typical Rails developer who picked up the plugin for it’s shortcuts. He pulled the lowpro.js library out of the plugin and ditched the Rails bits. I think it was a good move (I just wish someone would update ujs4rails.com to say something to that effect – it’s still the same page it was over a year ago, and it really sells the plugin well…)

There’s not really a lot of documentation for Low Pro out there, but to be honest, once you start using it I think you find there really isn’t a lot to it. Built on prototype, the lowpro.js file is only 300 or so lines of code, including some helpful comments. There are some links on the lowpro wiki to a couple blog posts with examples. In particular I found Jarkko Laine’s article very helpful and of course Dan’s blog is worth a read. Unfortunately, due to the speed of technology, some of Dan’s examples are a little dated (thanks to improvements he’s made to the code).

So I figure, how’s about another example? One key concept behind unobtrusive javascripting is yanking those obnoxious mouse events out of your anchor tags; Onmouseover, onmouseout, etc – they’ve got to go. So how can we use Low Pro to add a rollover behavior to some images?

note: at the time of this writing, Low Pro is version 0.5 and requires prototype version 1.6.x

Step 1: Plain HTML

Let’s start with the HTML. Imagine a menu that’s just a column of images; no javascript yet, just plain ol images (and of course styled with CSS in another file):

1
2
3
4
5
<ul class="main_menu">
  <li><a href="/pages/home"><img src="home.gif" alt="Home"/></a></li>
  <li><a href="/pages/about"><img src="about.gif" alt="About"/></a></li>
  <li><a href="/pages/contact"><img src="contact.gif" alt="Contact"/></a></li>
</ul>

Very simple. Probably everyone knows the mechanics of a rollover by now: what we want is for the img src attribute to change to a different value when the mouse cursor is placed over the image. The effect is getting a different image to display in place of the current image. And of course, when the cursor leaves the area of the image, we want the src to switch back. So how do we do that without using our tried and true “onmouseover/onmouseout” HTML attributes?

Step 2: Create a Behavior

We want to define a Behavior to describe the effect certain mouse events have on a particular image object. Low Pro makes this nice and clean, using the Class module baked into prototype to give us an object-oriented feel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var ImageRollover = Behavior.create({
  // define some attributes for this new Behavior (mmmm, OOP...)
  orig_name: null,    // the filename without extension
  image_ext: null,    // jpg, gif, etc, plus any ?123 nonsense at the end
  roll_suffix: null,  // whatever we want to stick on the end of the image name

  // one of these instances of ImageRollover will be created for each image 
  // when we set them up, we'll need to know the "suffix" of the rolled over image
  // for example, home.gif might be our image and home_on.gif might be our 
  // rollover, making '_on' our suffix
  initialize: function(roll_suffix) {
    this.roll_suffix = roll_suffix

    // here comes some lovely regex
    // image.src example: http://example.com/images/something.gif?1234567890
    // we need to chop that up into: 
    // 'http://example.com/images/something' and '.gif?1234567890' 
    matches = this.element.src.match(/(.*)((?:.gif|.jpg|.png).*)/);
    this.orig_name = matches[1]; 
    this.image_ext = matches[2];
  },

  // this function will get called on mouseover
  onmouseover: function() {
    // we're just stapling the pieces back together with the suffix between 
    // the file name and the file extension 
    this.element.src = this.orig_name + this.roll_suffix + this.image_ext
  },
  
  onmouseout: function() {
    // back to the original, no suffix
    this.element.src = this.orig_name + this.image_ext
  }
});

So there’s our behavior. It’s nice and generic – we could apply it to any image element. I’m not great with regex, but this will work for any image that ends with .gif, .jpg, or .png and optionally if it has an asset timestamp at the end following a question mark (Ruby on Rails likes to stick these on the end of assets to trick browsers into reloading cached image, css, and js files that might have changed when code has been updated). I recently came across this great testing tool for regex expressions: http://www.rubular.com/. Of course, the Firebug console is also great for this type of thing as well. For some reason my regular expression yields a third, unnecessary match (just the file extension with no timestamp) and I can’t quite nail it down. It doesn’t affect anything, but hey – let me know if you have a suggestion. Edit: Andy (in the comments) points out where the extra match comes from and how to ignore it. I updated the regex and it works.

Step 3: Assign Behavior

Finally, we can assign our ImageRollover behavior to some images. The magic of CSS selectors makes it easy to assign a behavior to a whole group.

1
2
3
Event.addBehavior({
  '.main_menu img': ImageRollover('_on')
});

The addBehavior method takes a hash where every key is a CSS selector and the value is a behavior. In this example, we can see that the CSS selector is saying “look for elements of the main_menu class and collect any of their children that have the img tag”. We also have to pass a value to ImageRollover that designates the rollover image suffix needed by the initialize method of the ImageRollover class.

Want proof that it works? See it in action (warning: extremely bad graphics that I cranked out in 5 minutes).

So there you have it. If anyone who knows Low Pro has any feedback on improving this example, I’d love to hear it.

receiving email with Rails

February 13th, 2008

Craig Ambrose writes about how he came up with a way to receive mail RESTfully in Rails. Basically, he makes the point that the method for processing email described in the Rails wiki is a little scary, as it can lead to the loading of the full Rails stack on every email you want to process. Craig speculates that this method could be problematic in terms of memory usage, and I tend to agree with him. He comes up with a pretty clever hack to basically redirect each incoming email at his actual running web application by grabbing it from postfix and posting it to his site via the use of Ruby’s net/http library. After that, it’s up to the Rails controller to handle the request.

While I like the cleverness of the solution, I can’t help but feel like it’s somehow asking for trouble. As some comments on the post point out, there doesn’t seem to be an elegant way to handle the mail if the web server happens to be down. Another concern is hammering the web server with too many requests in the case of multiple emails or text messages coming in, as each incoming email takes a up an http connection and occupies a mongrel server (or whatever) for however long it is needed (it’s no worse than submitting form data, but I think the concern is that there is potential for more volume).

I’ve been thinking about a way to handle incoming mail myself, and here’s the solution I’ve come up with. I have not had time to implement it, and maybe it’s been done before (please let me know if it has and has been documented).

  1. Postfix accepts incoming mail and stores the mail as a file somewhere on the server. A simple procmail recipe would probably be useful here to identify where to store incoming mail depending on it’s address, contents, day of the week, or whatever.
  2. A backgroundrb worker hangs around and processes the mail files on a periodic basis, by reading files in a specified directory one at a time, parsing each message with TMail, and doing any other Rails stuff you need it to do with each message. Remove each file after the message has been processed.

Backgroundrb has a great built-in scheduling system, so there’s no need to think cron in this setup. Backgroundrb will load the whole rails stack once. The workers sit in a separate process. Backgroundrb won’t run itself over: if you set the schedule to process mail every 2 minutes and the mail takes more than 2 minutes to process, backgroundrb will queue it’s jobs up. In this scenario, a worker is just looking in one directory, so once all the mail is processed, it gets deleted. If no new mail comes in by the next time the worker looks at the directory, it can see there’s nothing to do and go back to sleep. Knowing the expected volume of email coming in is important, but I’m willing to bet in most cases you could ask backgroundrb to run your mail-processing worker once a minute.

This method doesn’t get to claim the label RESTful, but to be honest I don’t think regurgitating incoming mail out to your web server is really in the spirit of “REST” either. If you stay true to the skinny controller, fat model mantra, handling email via model methods (all accessible via backgroundrb) or even module/other non-model class methods in a backgroundrb worker should be as easy as (if not easier than) doing so from within a controller.

NetBeans 6.0 Cheat Sheet

December 11th, 2007

I stitched together a cheat sheet for NetBeans 6.0, boiling it down to what I find are the most useful shortcuts for Ruby and Rails stuff.

NetBeans 6.0 Cheat Sheets:
PC (Linux/Windows/etc) [PDF] [ODT]
Mac (OS X) [PDF] [ODT]

I’ve been using NB6 for a couple of weeks and I’m starting to feel good about it. A couple of tweaks after the initial install (of the Ruby flavor of NB6) that I have to make before I’m really happy:

  • Go to Tools, Plugins, and install Extra Ruby Color Themes, Ruby Extra Hints, and Ruby RSpec Support
  • Go to Options, Fonts & Colors, change “Profile” to “Ruby Dark Pastels” (this is the TextMate-like color scheme, I guess)
  • A couple of things bug me about the color scheme – most notably, under Syntax Highlighting, Ruby, I have to change Mark Occurrences to the Inherited background (otherwise, whenever your cursor is in a variable, it and all it’s occurrences will light up on the page, which I find very distracting). I also like to change the color of the highlighted row under the Highlighting tab on that page (the default background is too bright for me).
  • AutoComplete drives me crazy. I’ve always hated it – I feel like it slows my computer down and sometimes it even seems to derail me in the middle of typing something because the window comes up and somehow snags focus. I turn it off under Options, Editor, General (uncheck Auto Popup Completion Window).

Most of the TextMate snippets have been imported into NetBeans 6. I didn’t squeeze all of these into the cheat sheet because there are so many, and personally, I don’t use ‘em (there’s probably some irony in the fact that I’m too lazy to learn a shortcut). You can peep the list of default ruby snippets and rhtml snippets or just go search for a TextMate cheat sheet.

click thumbnail for larger image

For a no-cost, cross-platform IDE, NetBeans 6 may be hard to beat. I still feel like there are some minor stability issues, but for the most part it hums right along on my Ubuntu desktop and laptop (both Gutsy-AMD64 installs) and even works fairly well on my ASUS EeePC (running the pre-installed custom build of Xandros).

really breaking up an array

December 6th, 2007

Last time I tried to break up an array, I learned about enumerable’s partition method. This time, I’m taking it one step further and returning multiple arrays rather than just two:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Array
  def group_by(attribute = nil)
    arrays = {}
    for i in 0...size
      object = self[i]
      value = attribute ? object.send(attribute.to_sym) : yield(object)
      if arrays[value.to_s]
        arrays[value.to_s] << object
      else
        arrays[value.to_s] = [object]
      end
    end
    return arrays.values
  end
end

And, the examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>> # you can group by attribute name
>> my_array = ["cats", "dogs", "people", "things", "elephants", "computers", "cars", "1001"]
=> ["cats", "dogs", "people", "things", "elephants", "computers", "cars", "1001"]
>> my_array.group_by "size"
=> [["people", "things"], ["elephants", "computers"], ["cats", "dogs", "cars", "1001"]]

>> # also can take a block - equal evaluation of the block determines grouping
>> my_array.group_by { |a| a[0] }
=> [["things"], ["cats", "computers", "cars"], ["1001"], ["dogs"], ["elephants"], ["people"]]

>> [4, "foo", 500, "bar", 4.5].group_by { |a| a.is_a? Integer }
=> [[4, 500], ["foo", "bar", 4.5]]

>> [4, "foo", 500, "bar", 4.5].group_by { |a| a.to_s.size }
=> [[4], ["foo", 500, "bar", 4.5]]

Again, I find this method useful for grouping ActiveRecord sets based on different attributes. It's especially handy when constructing views where records need to be grouped under different headings or in different tables.

breaking up an array

October 13th, 2007

There’s probably a better way to name this, but whattayagonna do?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Array
  # split an array based on block
  def divorce
    mom = []
    dad = []
    for i in 0...size
      value = self[i]
      if yield(value)
        mom << value
      else
        dad << value
      end
    end
    return [mom, dad]
  end
end

Examples:

1
2
3
4
5
6
7
8
9
10
>> strings, non_strings = [5,6,"aaa", 7, "ccc"].divorce { |element| element.is_a? String }
=> [["aaa", "ccc"], [5, 6, 7]]

>> longs, shorts = ["Hello.", "Hi, how are you today?", "Great!"].divorce { |st| st.size > 10 }
=> [["Hi, how are you today?"], ["Hello.", "Great!"]]

>> mults_of_3, non_mults_of_3 = (1..10).to_a.divorce { |n| n % 3 == 0 }
=> [[3, 6, 9], [1, 2, 4, 5, 7, 8, 10]]
>> mults_of_2, non_mults_of_2_or_3 = non_mults_of_3.divorce { |n| n % 2 == 0 }
=> [[2, 4, 8, 10], [1, 5, 7]]

I use it mostly for breaking up collections of ActiveRecord objects, where I want to process them differently depending on some attribute, but don't want to make multiple database/find calls or select/detect calls.

Ruby for Batching

September 16th, 2007

The other day I wanted to resize a bunch of images using ImageMagick’s convert command-line utility. I wanted to both resize and change their names from image_name.png to image_name-sm.png, but I couldn’t figure out how to guide ‘convert’ to use part of the original name in the new name. The next obvious tactic would be to use some common shell utilities like ‘for’ and ‘do’. My shell-fu is rusty however, and I still couldn’t get things quite the way I wanted them. Then I think to myself, this would be much easier to do with Ruby.

First, I use ‘puts’ to see if the syntax will come out right:

1
2
3
4
5
6
irb(main):017:0> Dir['*.png'].each { |f| puts "convert #{f} -resize 400x400 #{f[0..-5]}-sm.png" }
convert character_creation.png -resize 400x400 character_creation-sm.png
convert battle-mountain_pass.png -resize 400x400 battle-mountain_pass-sm.png
convert loot-drag_and_drop.png -resize 400x400 loot-drag_and_drop-sm.png
convert market.png -resize 400x400 market-sm.png
...(etc)...

Then I just run the same line with system instead of puts:


Dir['*.png'].each { |f| system "convert #{f} -resize 400x400 #{f[0..-5]}-sm.png" }

With a little practice, this can be done on the command line - who needs shell scripting?

1
2
# run from the shell prompt:
ruby -e 'Dir["*.png"].each { |f| system "convert #{f} -resize 400x400 #{f[0..-5]}-sm.png" }'


Handling IE6 Incompatibilities

August 30th, 2007

While developing my browser-based game, I’m always using Firefox and periodically testing in Internet Explorer 7. A few months ago, I set up a wiki for the game and asked players to contribute to the documentation. One helpful player posted some screenshots of the game in Internet Explorer 6 and I was horrified. I knew there were transparency issues with png graphics in IE6, as well as some CSS incompatibilities, and I gave a disclaimer to say so, but coming face to face with what the site actually looked like in IE6 was quite revealing. I checked out my Google Analytics page and I could see that my visitors are split almost half and half between Firefox and IE. When I looked at the IE breakdown, I saw that it’s pretty close to half IE7 and half IE6. This means 25% of my visitors are still using IE6. I decided I didn’t want to rely 100% on a disclaimer to push IE6 out of mind; I needed to do do some fixin’.

My first problem was alpha transparency in png graphics. As many web designers know, these graphics end up with solid background colors in IE6. In most cases, it ends up looking like crap. I initially thought, am I going to have to change all my PNG graphics to the inferior GIF format just so it won’t look so bad in IE6? There are javascript hacks out there that will force transparencies to mostly work on IE6. I decided instead I wanted to create a copy of every png image as a gif and serve up the gifs to IE6 users and give pngs to everyone else. This way I wouldn’t have to worry about any problems in IE6, and my disclaimer still applies – the graphics look slightly better in Firefox, Opera, IE7, or Safari than they do in IE6.

To accomplish this task in Rails, I created an ‘images_ie6’ directory alongside my ‘images’ directory in ‘public’. I used ImageMagick’s ‘convert’ command-line tool to convert all the pngs into gifs and placed them in the same folder structure, but in the base ‘images_ie6’ directory. Then I rewrote image_tag to detect the user agent and change the images path to ‘images_ie6’, and change .png to .gif if it’s IE6.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# in app/helpers/application_helper.rb 
# override image tag to point IE6 users to a different directory with GIFs instead of PNGs
module ActionView::Helpers::AssetTagHelper
  alias_method(:orig_image_tag, :image_tag) unless method_defined?(:orig_image_tag) 
  
  def image_tag(path, options = {})
    if request.env['HTTP_USER_AGENT'] and request.env['HTTP_USER_AGENT'].include? "MSIE 6.0" 
      path.gsub! ".png", ".gif"
      path.gsub! "/images/", ""
      path = "/images_ie6/#{path}"
    end
    orig_image_tag(path, options)
  end
end

Notice that I need to check to see if the environment variable for HTTP_USER_AGENT is set before checking to see if it contains “MSIE 6.0”. For some reason, the test environment sets some variables in request.env, but leaves out the user agent string. The extra check keeps tests from failing.

Aside from the image_tag hack, I had to deal with the problems that some images were referenced from CSS (background properties for example), and there were other CSS-related issues (IE6 has trouble with the min-height and min-width properties). So to handle those problems, I’m loading an extra ie6fixes.css file if the user agent indicates IE6. These styles overwrite only the necessary elements defined in the normal stylesheets.

1
2
# in app/views/layouts/application.html  
stylesheet_link_tag 'ie6fixes' if request.env['HTTP_USER_AGENT'] and request.env['HTTP_USER_AGENT'].include? "MSIE 6.0" 

Standard disclaimer about trusting user agent strings applies. Frankly, I'm of the opinion (like many) that if a visitor messes with their user agent string, they're on their own.

I have been using Err’s acts_as_textiled plugin for making certain fields of an ActiveRecord model always display with textile formatting. It’s smart enough to know that when used in a input field, the data should be displayed unformatted. The nice thing about textiling a field is not worrying about bad HTML polluting your page or attempts at XSS.

Being that the acts_as_textiled plugin saves me from accidentally displaying a field without HTML stripping (like when I forget to use the handy <%=h erb shortcut), I started using it on fields all over the place. Sometimes, where not really appropriate.

For example, let’s say you have a User model and one of the attributes is “display_name”. Now if someone picks a name like “I*am*the*greatest*ever!”, I don’t care that they are using non-alpha-numeric characters. I just don’t want it to show up like “Iamthegreatestever!”

The problem was, I had been relying on acts_as_textiled to save me from bad HTML in that field, so now if I turn it off, someone can make their display_name “Run <script blah blah blah > or something” and I have all these places in my views where I left out the h in <%=h so I’m screwed. I needed another way to strip HTML without using textiled, but I wanted to be able to auto-strip where appropriate by setting up a model the same way acts_as_textiled does.

Enter: acts_as_stripped

From the README:

Strips HTML out of an attribute whenever it's displayed - even if it's in a form 
input box/textarea (for the purposes of this plugin, I'm considering HTML evil 
in the specified fields, no matter what). 
NOTE: no stripping happens when the attribute is written, only read.
NOTE: value is converted to string; so for example if you errantly list an integer 
attribute in the attribute list it's going to come back as a string.

Inspired by Err's acts_as_textiled, but I needed some attributes to be displayed 
without textile messing with underscores and asterisks. 

And I don't trust myself to sanitize HTML in views 100% of the time. 

Use like so: 

class SomeModel < ActiveRecord::Base
  acts_as_stripped :name, :description

    # ...
end

If you need to get the unstripped value, you can always use: 
your_model.attributes["att_name"]

Install with:

./script/plugin install http://svn.offtheline.net/plugins/acts_as_stripped/

(or just use svn or piston to import into your vendor/plugins directory)

Any feedback? Questions? Criticism? All welcome and appreciated.

reversing svn externals

May 23rd, 2007

Generally when you install a plugin in a Rails app that is under version control with subversion, it’s recommended you use the –x switch to export the plugin to your directory. The –x just uses the svn export command the svn externals definition. I’ve happily installed many a plugin this way. It’s a great way to stay up to date – if the plugin ever gets updated, the changes get pulled in with svn update.

Recently, I found myself wanting to do some cleanup on an app however, and I had a couple plugins in there that I tried out at some point but I wasn’t using any more. I tried to use svn rm vendor/plugins/plugin_x , and it appeared to work (showed those files as marked with a D) but it didn’t actually remove them. Or rather, it had removed them, but they came right back when I did svn update.

Now I love subversion, but I didn’t even know where to look to figure out how to unhook these external plugins from my app. Eventually I figured out I need to check on the svn properties of the plugin directory.

jason@jwl-dev:~/dev/rails_app$ svn proplist vendor/plugins/
Properties on 'vendor/plugins':
  svn:externals

Ah, there we go. Let’s see what’s actually set in the svn:externals property.

jason@jwl-dev:~/dev/rails_app$ svn propget svn:externals vendor/plugins/
acts_as_versioned       http://svn.techno-weenie.net/projects/plugins/acts_as_versioned
unwanted_plugin         http://blahblahblah/plugins/unwanted_plugin

In order to remove the unwanted plugin, I just needed to use svn propedit svn:externals vendor/plugins and delete that line. After that, the unwanted plugin was no longer under version control and I could just delete the whole ‘unwanted_plugin’ directory (didn’t even have to use svn rm).

Observer is Deprecated

April 30th, 2007

So if you’ve seen deprecation warnings in your tests, you’ve probably been to http://www.rubyonrails.org/deprecation – for months, this page was just blank. I noticed today that it was updated, but it’s still pretty flimsy, sadly.

In one of my apps, I was using acts_as_authenticated from way back, and I’d been ignoring the error:

DEPRECATION WARNING: observer is deprecated and will be removed from Rails 2.0 See http://www.rubyonrails.org/deprecation for details. (called from /home/jason/dev/myapp/config/../app/controllers/account_controller.rb:2)

DEPRECATION WARNING: depend_on is deprecated and will be removed from Rails 2.0 See http://www.rubyonrails.org/deprecation for details. (called from observer_without_deprecation at /usr/lib/ruby/gems/1.8/gems/actionpack-1.13.3/lib/action_controller/deprecated_dependencies.rb:29)

This particular deprecation is not yet mentioned on the deprecation page, but I managed to track it down. What’s deprecated is calling ‘observer’ from a controller. Instead, you need to set it in your config/environment.rb file:


        config.active_record.observers = :user_observer

That’s it.

err had a great article on the many uses of inject the other day. Here’s a bit of code I was working on today. Similar to the popular “sum” example, I’m using inject to perform multiple logical “or’s”.

1
2
3
4
5
6
7
8
9
10
# An example of or'ing tests on everything in an array
# asking the question "is our letter equal to anything in this array?"
>> arrrr = ["a", "b", "c"]
=> ["a", "b", "c"]
>> arrrr.inject(false) { |n,m| n or (m=="b") }
=> true
>> arrrr.inject(false) { |n,m| n or (m=="a") }
=> true
>> arrrr.inject(false) { |n,m| n or (m=="x") }
=> false

What an over-simplified example – in fact, we could have just used include? to do the work of that example. Let’s try something a little more relevant. I have a couple ActiveRecord models called User and UserGroup. They are related by “has and belongs to many”. Peep this code, I use to check if a user is in any one of the groups I give in a list:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# somewhere inside class User

  # takes one group name and returns true if the user is in it
  def is_in_group?(group_name)
    self.user_groups.include? UserGroup.find_by_name(group_name)
  end

  # takes an array of groups and returns true if the user is in at least one of those groups
  # ex: user.is_in_any_groups?(['administrators', 'support staff']) == true
  def is_in_any_groups?(group_list)
    group_list.inject(false) { |n,m| n or self.is_in_group?(m) }
  end

  # takes an array of groups and returns true if the user is in at least one of those groups
  def is_in_any_groups?(group_list)
    group_list.inject(true) { |n,m| n and self.is_in_group?(m) }
  end

Notice the base value passed to inject is "false" for a series of or operations, but you have to start with "true" if you want to perform a series of and operations.

Rails 1.2.1 and Serialize

January 21st, 2007

I just updated to Rails 1.2.1 and tried it out on one of my applications. I’m getting some test failures because one of my models has an attribute that’s serialized as another class. In some cases, this attribute may be nil, which used to work fine, but now causes a SerializationTypeMismatch error, because when unserialize_attribute gets called it sees an object of type NilClass instead of the serialize target class.

I looked at the source and found unserialize_attribute in active_record/base and it’s unchanged from Rails 1.1.6, so best I can tell is in Rails 1.1.6 it was testing for nil before attempting to unserialize an attribute. I had a method that checks for this particular attribute being nil so I could change output depending on whether that attribute was there or not. So something definitely changed…

My workaround for now is to overwrite the unserialize_attribute method for that particular model. I copied the method out of activerecord/base and added one little thing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Returns the unserialized object of the attribute.
def unserialize_attribute(attr_name)
  unserialized_object = object_from_yaml(@attributes[attr_name])

  if unserialized_object.is_a?(self.class.serialized_attributes[attr_name])
    @attributes[attr_name] = unserialized_object
  # added to allow for a nil object
  elsif unserialized_object.nil?
    nil
  else
    raise SerializationTypeMismatch,
      "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}"
  end
end

This seems to work, so I get behavior I had previously in Rails 1.1.6. I was planning on refactoring this model anyway, because I didn’t like using serialize for these particular attributes – I had set it up that way several months ago and now things have changed, so this gives me a reason to go ahead and get my re-factoring underway.

mongrel and "NOT FOUND"

January 19th, 2007

I just got my slice at Slicehost and I went with the Ubuntu Dapper install, following this quick guide to getting apache, mongrel, and rails installed. It worked out ok for the most part – my only problem with these “cut and paste” type guides is it doesn’t explain what you’re actually installing. Thankfully, the author Chris links to the three different articles he collected the steps from, and for someone who’s already aware of the concepts, this how-to makes going through the motions that much easier. I got a newer version of apache than is shown – he has 2.2.2 and 2.2.4 is the newest stable release.

Once I got dapper set up with rails, mongrel and apache, I started to use the deprec gem following the guide posted on Slicehost’s wiki. Once I started going through the steps, I realized that these deprec recipes were way more automated than I wanted right now, and I had to cancel halfway through. I was expecting a per-app kind of automation, but I was in copy-and-paste mode and ran the “cap install_rails_stack” line and realized it was trying to do all the stuff I had just set up. The author of the deprec gem warns that it’s really a set up scripts set up to work for him, and use at your own risk, blah blah blah. It may work for some, but like I said, I found it a little too automated.

I went back and grabbed the deploy.rb I’d used before from Slingshot’s Capistrano support page. This script has worked pretty good for me in the past, but I had to make a few alterations. The script, called apache_2_2_mongrel_deploy.rb, had been updated since the last time I tried it, but it’s still a tad buggy.

One problem I had right off the bat was the reference to a variable called “crontab_email”. This is for a special crontab-related task that I don’t need right at the moment, but since this variable was referenced and not set, it caused an error.

The next major problem is that there’s a place to set the database user and password – so I assumed that the database.yml file that gets generated in setup would be using that user and password. Oddly enough, it actually only uses the username, not the password. Took me a few minutes to realize my initial migrations were bombing because there was no password referenced in database.yml.

Thirdly, the “mongrel_conf” variable that gets set for the mongrel recipes to use is pointing to the wrong directory. I changed the line to: set :mongrel_conf, ”/etc/mongrel_cluster/#{application}.yml”

Lastly, and this is the big one – when trying to start the mongrel cluster for the first time, I got an error from mongrel about the “mongrel_prefix” needing to start with a slash (/) but not end with one. Since the line was: set :mongrel_prefix, ”/usr/local/bin/” I simply took the last slash off the line and my error message went away. Happy as can be, I went ahead and got apache going and fired up my mongrel servers, pointed my web browser at my new server and got:

NOT FOUND

This happened to me the last time I used an earlier version of this script, and for the life of me, I couldn’t remember what the problem was. The apache logs were sparse. The mongrel log offered nothing but telling me when the servers were started or stopped. The production log was blank. After an hour of poking and prodding, I finally remembered what it was – the mongrel config file that had mongrel_prefix set to /usr/local/bin. My mongrel bin directory should have been /usr/bin (actually, I just blanked it out and that worked just fine).

A very frustrating experience. Mongrel is great and you can’t go wrong with this stack for Rails, but man – a better error message would be nice. Something in the log. Anything….