DRYing Models via Acts As

Posted over 7 years back at Revolution On Rails

ActsAs is an idiom familiar to every Rails developer, which makes it a good candidate for a shared functionality between models. Using it as early in the game as possible allows one to work on its functionality without a need to touch the code in multiple models. Let's look at a couple of examples.

Acts As Unique

I have some models that I want to have uniqueness across my application. I use some UUID mechanism (initially, a db call) to set a field (:token) after creation. Since I have multiple models, I decide to extract it the code for uniqueness setting to acts_as_unique. After refactoring, my model Fruit looks like:

# create_table :fruits do |t|
# t.column :name, :string
# t.column :token, :string
# end
class Fruit < ActiveRecord::Base
acts_as_unique
end

My acts_as_unique might look like:
module ActiveRecord; module Acts; end; end
module ActiveRecord::Acts::ActsAsUnique

def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def acts_as_unique(field = :token)
validates_uniqueness_of field
before_validation_on_create do |o|
o.send("#{ field }=", connection.select_one('SELECT UUID() AS UUID', "#{name} UUID generated")['UUID'])
end
end
end
end

ActiveRecord::Base.send(:include, ActiveRecord::Acts::ActsAsUnique)

Let's try it:
>> f = Fruit.create(:name => 'apple')
>> p f.token
"0a4d7c46-4df0-102a-a4b9-59b995bffdb7"

Now I can work on acts_as_unique to replace the DB call with a UUID gem or some other implementation without affecting the rest of the code.


Acts As Trackable

I have some models for which I want to keep track of when instances are created or updated. I have a polymorphic Event model for storage of such events. Since there are multiple models I want to track, I extract the functionality to acts_as_trackable. After refactoring, my models look like:
# create_table :fruits do |t|
# t.column :name, :string
# end
class Fruit < ActiveRecord::Base
acts_as_trackable
end

# create_table :events do |t|
# t.column "action", :string
# t.column "created_at", :datetime, :null => false
# t.column "trackable_type", :string
# t.column "trackable_id", :integer
# end
class Event < ActiveRecord::Base
belongs_to :trackable, :polymorphic => true
end

module ActiveRecord; module Acts; end; end 
module ActiveRecord::Acts::ActsTrackable

def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def acts_as_trackable
has_many :events, :as => :trackable, :dependent => :destroy
after_update { |o| o.events.create(:action => 'updated') }
after_create { |o| o.events.create(:action => 'created') }
end
end

end

ActiveRecord::Base.send(:include, ActiveRecord::Acts::ActsTrackable)

Let's see what we got:
>> f = Fruit.create(:name => 'apple')
>> p f.events.collect(&:action)
["created"]
>> f.name = 'passionfruit'
>> f.save!
>> p f.events.collect(&:action)
["created", "updated"]

The Event model is likely to evolve but it would be easier to support it since the only place where I need to reflect the changes is acts_as_trackable. The goal is achieved.

DRYing Models via Acts As

Posted over 7 years back at Revolution On Rails

ActsAs is an idiom familiar to every Rails developer, which makes it a good candidate for a shared functionality between models. Using it as early in the game as possible allows one to work on its functionality without a need to touch the code in multiple models. Let's look at a couple of examples.

Acts As Unique

I have some models that I want to have uniqueness across my application. I use some UUID mechanism (initially, a db call) to set a field (:token) after creation. Since I have multiple models, I decide to extract it the code for uniqueness setting to acts_as_unique. After refactoring, my model Fruit looks like:

# create_table :fruits do |t|
# t.column :name, :string
# t.column :token, :string
# end
class Fruit < ActiveRecord::Base
acts_as_unique
end

My acts_as_unique might look like:
module ActiveRecord; module Acts; end; end
module ActiveRecord::Acts::ActsAsUnique

def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def acts_as_unique(field = :token)
validates_uniqueness_of field
before_validation_on_create do |o|
o.send("#{ field }=", connection.select_one('SELECT UUID() AS UUID', "#{name} UUID generated")['UUID'])
end
end
end
end

ActiveRecord::Base.send(:include, ActiveRecord::Acts::ActsAsUnique)

Let's try it:
>> f = Fruit.create(:name => 'apple')
>> p f.token
"0a4d7c46-4df0-102a-a4b9-59b995bffdb7"

Now I can work on acts_as_unique to replace the DB call with a UUID gem or some other implementation without affecting the rest of the code.


Acts As Trackable

I have some models for which I want to keep track of when instances are created or updated. I have a polymorphic Event model for storage of such events. Since there are multiple models I want to track, I extract the functionality to acts_as_trackable. After refactoring, my models look like:
# create_table :fruits do |t|
# t.column :name, :string
# end
class Fruit < ActiveRecord::Base
acts_as_trackable
end

# create_table :events do |t|
# t.column "action", :string
# t.column "created_at", :datetime, :null => false
# t.column "trackable_type", :string
# t.column "trackable_id", :integer
# end
class Event < ActiveRecord::Base
belongs_to :trackable, :polymorphic => true
end

module ActiveRecord; module Acts; end; end 
module ActiveRecord::Acts::ActsTrackable

def self.included(base)
base.extend(ClassMethods)
end

module ClassMethods
def acts_as_trackable
has_many :events, :as => :trackable, :dependent => :destroy
after_update { |o| o.events.create(:action => 'updated') }
after_create { |o| o.events.create(:action => 'created') }
end
end

end

ActiveRecord::Base.send(:include, ActiveRecord::Acts::ActsTrackable)

Let's see what we got:
>> f = Fruit.create(:name => 'apple')
>> p f.events.collect(&:action)
["created"]
>> f.name = 'passionfruit'
>> f.save!
>> p f.events.collect(&:action)
["created", "updated"]

The Event model is likely to evolve but it would be easier to support it since the only place where I need to reflect the changes is acts_as_trackable. The goal is achieved.

Peter Cooper - Ruby on Rails Podcast

Posted over 7 years back at Ruby on Rails Podcast

The author of the popular RubyInside blog talks about his new book and about entrepreneurship.
Listen to this podcast for a chance to win a copy of Peter’s book, Beginning Ruby!
Also mentioned

Hobo != Scaffolding

Posted over 7 years back at The Hobo Blog

I was very pleased recently, to come across this remark in a post about Hobo:

don’t be mistaken that this is another scaffold, it’s not, far from it.

(from Funcoder - thanks!)

IMHO scaffolding is a very widely misunderstood concept and one that has caused a good deal of confusion. The only description of scaffolding I’ve heard that really holds water is from DHH: they’re a learning aid. Ironically, I think DHH unintentionally caused a lot of the initial confusion by featuring scaffolding so prominently in the early build-a-blog screencast. A lot of people saw that screencast and thought “Wow! Rails is building a big chunk of my app for me!”. That was a misconception.

The Hobo screencasts probably give people the same impression, and this time – they’re right! Hobo is trying to eliminate a lot of code that you would otherwise have to write yourself. The result is that many people are putting Hobo in the scaffolding category. Hobo does what people thought scaffolding would do.

If you add ActiveScaffold to the mix things get even more confusing. ActiveScaffold is an example of an “Admin Interface Builder”. From their website:

Most web applications have many more model objects exposed on the backend, or admin side, than they do on the front. Coding interfaces for all those models is redundant and a waste of resources when all you need is CRUD functionality that’s smart enough to handle all your ActiveRecord associations.

So they’re using the “scaffold” term very differently to DHH. But even if this use of scaffolding has become the de-facto meaning, it’s still different from Hobo.

To get some clarity, lets drop the buzz-words and take a look at what’s actually going on here. I can see three very different kinds of things:

Show me how to use the API. This, as I understand it, is the idea behind Rails scaffolds. I think it’s an awesome idea. A live, runnable code example using a model from my own app. Brilliant. It certainly helped me learn Rails. I do wonder though - if the usage of an API is so regular, doesn’t that mean the API is too low-level?

Make me an admin interface. This is ActiveScaffold, Streamlined, and from across the fence, Django (correct me if I’m wrong people). To be honest I can’t get too excited about this idea. Don’t get me wrong - these are all very well executed projects and seem to deliver the goods to a very high standard. It’s just not something I’ve ever felt a need for. There’s an implicit assumption here: “users” need a hand crafted UI, while “administrators” can use an auto-generated UI. I’ve never found myself in a situation where that assumption holds.

Build (part of) my app for me. In this category lies a large number of Rails plugins. Want tagging? acts_as_taggable. Want log-in and sign-up? acts_as_authenticated. Those things are mostly the same every time you need them, so why re-invent the wheel? This is where Hobo lies.

With that description, Hobo sounds very different from the other two categories, so why the confusion? Well, Hobo does so much for you, that, as you’ve seen in the first screencast, it will create a working app with no programming at all outside the model layer. If you use this app as is, you’re effectively using Hobo as a “make me an admin interface” tool. That’s fine, but it’s also kinda missing the point.

The point being, that this “generated” app is customisable. Easily customisable. Completely and totally customisable. No less so that a raw Rails app. The second and third screencasts are trying to point in this direction. So design your app exactly the way you want it. Where your design is very “standard”, Hobo will provide those bits for you. Where your design is unique and specially tailored, you’ll build those bits yourself.

I think the word “scaffolding” captures the point really well. Scaffolding is sometimes a temporary fix - holding things in place while things are under construction. It’s sometimes used more permanently – around the back, for the people that don’t mind the odd rough edge. Either way it’s a perfectly valid and very useful tool. Hobo has features that can be used like this, but it’s definitely not what Hobo is about.

Hobo != Scaffolding

Posted over 7 years back at The Hobo Blog

I was very pleased recently, to come across this remark in a post about Hobo:

don’t be mistaken that this is another scaffold, it’s not, far from it.

(from Funcoder - thanks!)

IMHO scaffolding is a very widely misunderstood concept and one that has caused a good deal of confusion. The only description of scaffolding I’ve heard that really holds water is from DHH: they’re a learning aid. Ironically, I think DHH unintentionally caused a lot of the initial confusion by featuring scaffolding so prominently in the early build-a-blog screencast. A lot of people saw that screencast and thought “Wow! Rails is building a big chunk of my app for me!”. That was a misconception.

The Hobo screencasts probably give people the same impression, and this time – they’re right! Hobo is trying to eliminate a lot of code that you would otherwise have to write yourself. The result is that many people are putting Hobo in the scaffolding category. Hobo does what people thought scaffolding would do.

If you add ActiveScaffold to the mix things get even more confusing. ActiveScaffold is an example of an “Admin Interface Builder”. From their website:

Most web applications have many more model objects exposed on the backend, or admin side, than they do on the front. Coding interfaces for all those models is redundant and a waste of resources when all you need is CRUD functionality that’s smart enough to handle all your ActiveRecord associations.

So they’re using the “scaffold” term very differently to DHH. But even if this use of scaffolding has become the de-facto meaning, it’s still different from Hobo.

To get some clarity, lets drop the buzz-words and take a look at what’s actually going on here. I can see three very different kinds of things:

Show me how to use the API. This, as I understand it, is the idea behind Rails scaffolds. I think it’s an awesome idea. A live, runnable code example using a model from my own app. Brilliant. It certainly helped me learn Rails. I do wonder though - if the usage of an API is so regular, doesn’t that mean the API is too low-level?

Make me an admin interface. This is ActiveScaffold, Streamlined, and from across the fence, Django (correct me if I’m wrong people). To be honest I can’t get too excited about this idea. Don’t get me wrong - these are all very well executed projects and seem to deliver the goods to a very high standard. It’s just not something I’ve ever felt a need for. There’s an implicit assumption here: “users” need a hand crafted UI, while “administrators” can use an auto-generated UI. I’ve never found myself in a situation where that assumption holds.

Build (part of) my app for me. In this category lies a large number of Rails plugins. Want tagging? acts_as_taggable. Want log-in and sign-up? acts_as_authenticated. Those things are mostly the same every time you need them, so why re-invent the wheel? This is where Hobo lies.

With that description, Hobo sounds very different from the other two categories, so why the confusion? Well, Hobo does so much for you, that, as you’ve seen in the first screencast, it will create a working app with no programming at all outside the model layer. If you use this app as is, you’re effectively using Hobo as a “make me an admin interface” tool. That’s fine, but it’s also kinda missing the point.

The point being, that this “generated” app is customisable. Easily customisable. Completely and totally customisable. No less so that a raw Rails app. The second and third screencasts are trying to point in this direction. So design your app exactly the way you want it. Where your design is very “standard”, Hobo will provide those bits for you. Where your design is unique and specially tailored, you’ll build those bits yourself.

I think the word “scaffolding” captures the point really well. Scaffolding is sometimes a temporary fix - holding things in place while things are under construction. It’s sometimes used more permanently – around the back, for the people that don’t mind the odd rough edge. Either way it’s a perfectly valid and very useful tool. Hobo has features that can be used like this, but it’s definitely not what Hobo is about.

Episode 28: in_groups_of

Posted over 7 years back at Railscasts

Have you ever wanted to visually line up items in rows and columns? The in_groups_of method makes this a cinch. Just watch out for the gotcha.

Bad Spammers! Go Away!

Posted over 7 years back at Sporkmonger

I finally got annoyed enough by all the comment spam. (Probably upwards of 100 comment spams a day.) To absolutely no one’s surprise, my IP blacklist wasn’t helping much. I’ve now customized the comment submission system in Mephisto to include a CAPTCHA of sorts. Well, really more like a test for an actual Javascript-capable browser. Sadly, the technique requires Javascript to be on, and I was a little too lazy to accommodate people who have it off. If you have Javascript off, and you really, truly can’t turn it on, well, I suppose you can always email me? The rest of site works perfectly fine with Javascript off, and I apologize in advance for letting you become a casualty of the War on Spam. If anyone has trouble commenting, email me, I haven’t tested this thing very well—it was a five minute change.

Update:

I rather like how well this has worked out thus far. Went from 100+ spam comments per day to zero since the code was deployed, with effectively zero chance of false positives. I can live with that.

[RELEASE] Plugems packaging

Posted over 7 years back at Revolution On Rails

The Plugems runtime is enough to justify their existence, but it does not stop there. Since we already defined our dependencies in a gem-like fashion, it is only a small step to start using them for packaging as well. All that is needed for this is the plugems_deploy gem that can be installed from rubyforge. After the gem is installed, a new plugem command comes up. It piggy-backs to capistrano to provide some plugems-related recipes. The recipe packaged with the initial version is build. This action allows you to package your project as a gem.

Let's take for example our sample application's config:

:version: [1, 0]
:name: "cool_application"
:description: "My First Plugemified Application"
:dependencies:
- ['some_gem', '~> 1.0']
- ['other_gem', '> 2.0']
- ['one_more', '2.0.1']

When we run the plugem build on the top of the application, we get a gem built:

$ plugem build
* executing task plugem_build
rm -rf pkg
mkdir -p pkg
Successfully built RubyGem
Name: cool_application
Version: 1.0.0
File: cool_application-1.0.0.gem
mv cool_application-1.0.0.gem pkg/cool_application-1.0.0.gem

If you looked inside, you would find that the attributes and dependencies from the manifest file were translated to corresponding gem attributes and dependencies.

Where did the build (micro) revision come from?

You might noticed that the manifest defined only major and minor revisions but there was a micro added during the packaging time. The plugem packaging process follows the Rubygem's rational versioning policy giving you full control over the build revision. The full version can be defined in the manifest file a-la-Rakefile (i.e. :version: [1, 0, 1]). You can derive it dynamically from a source like svn revision. You just set the capistrano gem_micro_revision variable in a deployment recipe (like config/deploy.rb). An svn based example is:
set :gem_micro_revision, `svn info`.grep(/^Revision:/).first[/(\d+)/][$1]

And you can always overwrite the full version via the '--version' flag of plugem: plugem build --version 3.2.1

The choice is yours.

You might wonder why would you package your rails application as a gem. The rationale is that it allows you to utilize the only ruby-native packaging and distribution system to distribute and deploy your application. The next plugem_deploy releases and this series installments would provide the tools and guidance how to do that.

But it is not just for packaging...

Since you have you dependencies clearly defined, it is really easy to update them to the latest version. Just run plugem update from the project directory:

$ plugem up
* executing task plugem_update
Updating some_gem (~> 1.0)
* executing task plugem_install
Bulk updating Gem source index for: http://gems.rubyforge.org
Installing [ some_gem, 1.2.2 ]

You also have a full control which gem servers to use. Set the variable in your deployment recipe:
set :gem_servers, [ 'http://gems.mycompany.com:8808', 'http://gems.rubyforge.org' ]

and see the difference:

$ plugem up
* executing task plugem_update
Updating some_gem (~> 1.0)
* executing task plugem_install
Bulk updating Gem source index for: http://gems.mycompany.com:8808
Bulk updating Gem source index for: http://gems.rubyforge.org


To Be Continued ...

[RELEASE] Plugems packaging

Posted over 7 years back at Revolution On Rails

The Plugems runtime is enough to justify their existence, but it does not stop there. Since we already defined our dependencies in a gem-like fashion, it is only a small step to start using them for packaging as well. All that is needed for this is the plugems_deploy gem that can be installed from rubyforge. After the gem is installed, a new plugem command comes up. It piggy-backs to capistrano to provide some plugems-related recipes. The recipe packaged with the initial version is build. This action allows you to package your project as a gem.

Let's take for example our sample application's config:

:version: [1, 0]
:name: "cool_application"
:description: "My First Plugemified Application"
:dependencies:
- ['some_gem', '~> 1.0']
- ['other_gem', '> 2.0']
- ['one_more', '2.0.1']

When we run the plugem build on the top of the application, we get a gem built:

$ plugem build
* executing task plugem_build
rm -rf pkg
mkdir -p pkg
Successfully built RubyGem
Name: cool_application
Version: 1.0.0
File: cool_application-1.0.0.gem
mv cool_application-1.0.0.gem pkg/cool_application-1.0.0.gem

If you looked inside, you would find that the attributes and dependencies from the manifest file were translated to corresponding gem attributes and dependencies.

Where did the build (micro) revision come from?

You might noticed that the manifest defined only major and minor revisions but there was a micro added during the packaging time. The plugem packaging process follows the Rubygem's rational versioning policy giving you full control over the build revision. The full version can be defined in the manifest file a-la-Rakefile (i.e. :version: [1, 0, 1]). You can derive it dynamically from a source like svn revision. You just set the capistrano gem_micro_revision variable in a deployment recipe (like config/deploy.rb). An svn based example is:
set :gem_micro_revision, `svn info`.grep(/^Revision:/).first[/(\d+)/][$1]

And you can always overwrite the full version via the '--version' flag of plugem: plugem build --version 3.2.1

The choice is yours.

You might wonder why would you package your rails application as a gem. The rationale is that it allows you to utilize the only ruby-native packaging and distribution system to distribute and deploy your application. The next plugem_deploy releases and this series installments would provide the tools and guidance how to do that.

But it is not just for packaging...

Since you have you dependencies clearly defined, it is really easy to update them to the latest version. Just run plugem update from the project directory:

$ plugem up
* executing task plugem_update
Updating some_gem (~> 1.0)
* executing task plugem_install
Bulk updating Gem source index for: http://gems.rubyforge.org
Installing [ some_gem, 1.2.2 ]

You also have a full control which gem servers to use. Set the variable in your deployment recipe:
set :gem_servers, [ 'http://gems.mycompany.com:8808', 'http://gems.rubyforge.org' ]

and see the difference:

$ plugem up
* executing task plugem_update
Updating some_gem (~> 1.0)
* executing task plugem_install
Bulk updating Gem source index for: http://gems.mycompany.com:8808
Bulk updating Gem source index for: http://gems.rubyforge.org


To Be Continued ...

Hobo Recipe Ideas

Posted over 7 years back at The Hobo Blog

I’m thinking more and more that a great way to document Hobo is with a collection of “Hobo Recipes”. So I’ve created a wiki page where we can capture ideas for recipes. There’s only two there at the moment but I’m sure it will grow fast.

Hobo Recipe Ideas

Posted over 7 years back at The Hobo Blog

I’m thinking more and more that a great way to document Hobo is with a collection of “Hobo Recipes”. So I’ve created a wiki page where we can capture ideas for recipes. There’s only two there at the moment but I’m sure it will grow fast.

Episode 27: Cross Site Scripting

Posted over 7 years back at Railscasts

Another common security issue is cross site scripting. In this episode you will see why it is so important to escape any HTML a user may submit.

Focussing on documentation

Posted over 7 years back at The Hobo Blog

The forums are getting really busy, which is a great sign that Hobo is starting to gain some mind-share. It’s so busy that it’s getting harder for me to keep up. If you look at a few posts, it quickly becomes clear that the main issue is lack of documentation. There’s an obvious conclusion here - writing docs is a better way for me to help you all than answering lots of individual questions.

Up until now I’ve had a kind of “no unanswered questions” attitude to the forum, but I don’t think it’s sensible to carry on like that. I’ve set aside a chunk of time each day to keep the Hobo community ticking along, and for now I’m going to try and focus a bit more on documentation and a bit less on the forums.

I’ll still follow the forums closely of course, and reply to the more challenging questions and bug reports, so you’ll still see lots of posts from me. Those of you that have dived into the source and learnt Hobo the hard way – it would be great if you could help out in the forums with some of the more basic questions.

Focussing on documentation

Posted over 7 years back at The Hobo Blog

The forums are getting really busy, which is a great sign that Hobo is starting to gain some mind-share. It’s so busy that it’s getting harder for me to keep up. If you look at a few posts, it quickly becomes clear that the main issue is lack of documentation. There’s an obvious conclusion here - writing docs is a better way for me to help you all than answering lots of individual questions.

Up until now I’ve had a kind of “no unanswered questions” attitude to the forum, but I don’t think it’s sensible to carry on like that. I’ve set aside a chunk of time each day to keep the Hobo community ticking along, and for now I’m going to try and focus a bit more on documentation and a bit less on the forums.

I’ll still follow the forums closely of course, and reply to the more challenging questions and bug reports, so you’ll still see lots of posts from me. Those of you that have dived into the source and learnt Hobo the hard way – it would be great if you could help out in the forums with some of the more basic questions.

CSS: CSS Browser Selector

Posted over 7 years back at Revolution On Rails


Each browser has its quirks. Having a clear way of organizing the work arounds
for those quirks is a challenge. Using CSS selectors is not a new idea, but I thought it might be helpful to others to give an example of the technique and how we've been able to successfully deploy it for revolutionhealth.com.



First, for IE (the browser that usually requires a hack), we can rely on conditional comments. This is good because it means we don't need to depend on Javascript. For other browsers we'll have to rely on a document.write solution. For Safari, Opera, and Firefox, we rely on the script from http://rafael.adm.br/css_browser_selector/ and for IE, conditional comments.


Here's what we include at the top of our document. (The browser_detect_start partial.)



<!--[if lt IE 7.]>
<div class='ie ie6'>
<![endif]-->
<!--[if IE 7]>
<div class='ie ie7'>
<![endif]-->
<script type="text/javascript">//<![CDATA[
var d = browserCSSDetection();
if( d.browser != "ie" ){ document.write( "<div class='" + d.browser + " " + d.os + "'>" ); }
//]]></script>

And here's what we do for the end of the document. (The browser_detect_end partial.)

<!--[if IE ]>
</div>
<![endif]-->
<script type="text/javascript">//<![CDATA[
var d = browserCSSDetection();
if( d.browser != "ie" ){ document.write( "</div>" ); }
//]]></script>


The browser detection in Javascript. This could be enhanced further, but for us this allowed us to get the site working relatively easy in Konqueror. As well it enabled us to fix our menu's so that they float over flash in Linux using this techinque.



function browserCSSDetection()
{
// see: http://rafael.adm.br/css_browser_selector/
var ua = navigator.userAgent.toLowerCase();
var is = function(t){ return ua.indexOf(t) != -1; };
var b = (!(/opera|webtv/i.test(ua))&&/msie (\d)/.test(ua)) ?
('ie ie'+RegExp.$1) :
is('gecko/') ? 'gecko' :
is('opera/9') ? 'opera opera9' :
/opera (\d)/.test(ua) ? 'opera opera'+RegExp.$1 :
is('konqueror')?'konqueror' :
is('applewebkit/') ? 'webkit safari':
is('mozilla/')?'gecko':'';
// see: http://www.mozilla.org/docs/web-developer/sniffer/browser_type.html
var os = (is('x11')||is('linux'))?' linux':is('mac')?' mac':is('win')?' win':'';
var css = {browser:b,os:os};
return css;
}


Finally, to make all this fit nicely into a layout:

<head>
<%= javascript_include_tag 'browser_detect' %>
</head>
<body>
<%= render :partial => "browser_detect_start" %>
<%= @content_for_layout %>
<%= render :partial => "browser_detect_end" %>
</body>


Update:
Here's a simple example of what this enables. The advantage of this over a conditionally included file is it keeps everything about the login box isolated to one place. You don't need to worry about openning up a separate file to make your IE fixes. We do use iefix specific CSS for our site, but only for very large features like menus



#login {
padding:0px;
}
.ie6 #login {
padding: 2px;
}