Good to Great (rules to live by)

Posted over 7 years back at work.rowanhick.com

What makes a great developer ? Outside of obligatory technical and problem solving skills which go without saying, the differentiator between good developers and great developers I think simply boils down to understanding, empathy and respect for your client. Having a cross functional developer that can write code and talk to a client in plain english is an amazingly huge (and seemingly rare) asset to any team or studio. As the world slowly moves away from waterfall development I think this is going to be increasingly important. I've worked with the uber algorithm generator russion rocket scientists, the fresh out of university bumbling junior guy, the know it all with an ego the size of africa, the one that wants to debate every minute detail until they forgot why they were debating it, the one that will fight to the death to not change the feature they just implemented. Every time the one developer that's stood out, and had clients want to work with them, were those that sat down and understood the business problem at hand and were empathetic to their clients needs. If you spend those hours/days up front and actually listen to your client. Put yourselves in their shoes, and understand how they do business and want to do it better. You are going to go a lot further than those who tell the customer how to do business. Here's my rules to live by: 1. Mr/Ms Pleasant. It doesn't matter what business you are dealing with, people unless they like being antagonised, will actively avoid dealing with an unpleasant person. I have a bike store by my house, it's got everything, it's convenient, but if I have to deal with the mechanic once more.... so I use the bike store close to work. Inconvenient, pain in the proverbial, the kids are way less experienced but I'd much rather deal with an 18yr old kid who's eager to please, than the 40 yr arrogant sod who has no time of day for you (I have no time of day giving him my hard earned cash). I'm sure we've all had those experiences no ? Now multiply that $50 purchase in the bike store a good 200-2000 times into a big scale web project. Who's the customer going to pick, Mr/Ms Pleasant or Mr/Ms Arrogant. 2. Mr/Ms Understanding. Understand the business problem at hand, and make an effort to understand the wider context of the business. Don't try and get the full story in the nth degree on the first meeting - you'll be there for days, however everytime you meet again, always try to get a bit more business knowledge in the wider context of the business. The more domain knowledge you have about the customer's business the easier it makes your job, the more valuable you become. 3. Mr/Ms Talker. Talk with your customer.... just because you and everyone around you uses email for everything, it's not necessarily a good thing. You get so much more out of talking and more importantly listening than you ever will on email - the hesitant pauses (.. maybe not a good idea to work on this functionality, they're unsure), the excited yes please (.. get it delivered straight away they want it!). I tend to talk or meet face to face, then follow up with an email restating the items to be actioned, priorities etc. Everytime I've relied solely on email it's been a disaster. And with any important business dealings (ie you get the contract, you've been paid, major milestone etc). Talk on the phone then follow up with an email - it shows respect. 4. Mr/Ms Restater. As part of number 3, to show your understanding of a problem, always restate it, multiple times if need be. It's like learning your ABC's. It a) shows you know and understand it, b) gives the customer a chance to rethink it through, see if it makes sense. Use role play "Okay, so I'm your business store owner, at the start of the day I go to my computer, fire up the browser, go to... I'm the region manager, at the end of the day I do..". You've got to have personal confidence of knowing the problem and your customer has to feel happy you can go away and put together a solution. Nothing's worse than giving a task to someone who has that semi nervous 'yeah.. sure, okay, i'll be back in touch'. I rarely need to refer to notes as I make an effort to understand the problem. 5. Mr/Ms Light. Don't go dark. No matter what.. don't go dark. It's the standing joke of feeding the pizza under the door and waiting for something to come out the other end (and waiting.. and waiting). The customer gets worried, you go off on a tangent it all turns to custard. Keep in touch. Even if it's just an email 'Just to touch base I'm working on aspect x. I expect to have it sorted in 3 days, I'll send you a screenshot then'. It also keeps you top of their mind, and them top of your mind. 6. Mr/Ms Nicely No. Say no carefully. If something's a bad idea, no with a smile and say why you're saying no. Remember Mr/Ms Pleasant ? ... The customer is paying the bills so be careful with this one. The customer is always right can be rephrased as: The customer is nearly always right in terms of what they want to acheive - but doesn't have experience in execution so often need to be guided. 7. Mr/Ms Goal Oriented Understand the stated goals and objectives. Extract these from the client before you run down the path of looking at the functional elements of your task. If you don't know what you're trying to acheive how can you write the steps to get there ? The customer often will express what they want by how they want to acheive it e.g. "add this button here, make it go to this page, then ask the user for this" vs "We want the user to give us their address information before entering the site" 8. Mr/Ms Humble Know when you're wrong, face up admit it and move forward. Unless you're facing a law suit then talk to your lawer don't listen to me! It seems obvious but it's the hardest thing to do, especially the later in the game it gets. Finally... Remember you're an expert in your field, they're an expert in theirs. Respect, understanding and an empathetic relationship will take you a lot further with your dealings, and will help to garner you more business and referrals. The cool toys like Flex, Rails, Ajax, etc all just help you along the way. Be the dude/dudette that either a client, or a project manager says "This person is fantastic, you should work with them"

Managing database.yml with Capistrano 2.0

Posted over 7 years back at Shane's Brain Extension

Jeremy Voorhis posted a really great Capistrano recipe for managing database.yml which dynamically creates a database.yml file in your shared directory on setup, and symlinks your app’s database.yml once it’s deployed. This is great if you don’t version control your database.yml file for security reasons or working with multiple developers.

changes the syntax for task callbacks and gets rid of the useful render method.  However, using ERb, Ruby's built-in templating system, isn't much more difficult than using the old render method.  Here is Jeremy's script updated for Capistrano 2.0 using ERb and the new namespaced callback syntax.
require 'erb'

before "deploy:setup", :db
after "deploy:update_code", "db:symlink" 

namespace :db do
  desc "Create database yaml in shared path" 
  task :default do
    db_config = ERB.new <<-eof base:><<:><<:><<:>

Until I get better syntax highlighting for this blog, check out the Pastie for the color version. For more info on whats new in Capistrano 2.0, check out Jamis’ preview and Geoff’s post. Also, props to Jamis for suggesting I use ERb directly.

Update: Updated code to use its own :db namespace instead of the default one. The database yaml file will be created by the default :db task, and the symlink will be created by the db:symlink task. Note how namespaces in Cap 2.0 allows us to have two symlink tasks, one in the deploy namespace and the other in db.

Managing database.yml with Capistrano 2.0

Posted over 7 years back at Shane's Brain Extension

Jeremy Voorhis posted a really great Capistrano recipe for managing database.yml which dynamically creates a database.yml file in your shared directory on setup, and symlinks your app’s database.yml once it’s deployed. This is great if you don’t version control your database.yml file for security reasons or working with multiple developers.

changes the syntax for task callbacks and gets rid of the useful render method.  However, using ERb, Ruby's built-in templating system, isn't much more difficult than using the old render method.  Here is Jeremy's script updated for Capistrano 2.0 using ERb and the new namespaced callback syntax.
require 'erb'

before "deploy:setup", :db
after "deploy:update_code", "db:symlink" 

namespace :db do
  desc "Create database yaml in shared path" 
  task :default do
    db_config = ERB.new <<-eof base:><<:><<:><<:>

Until I get better syntax highlighting for this blog, check out the Pastie for the color version. For more info on whats new in Capistrano 2.0, check out Jamis’ preview and Geoff’s post. Also, props to Jamis for suggesting I use ERb directly.

Update: Updated code to use its own :db namespace instead of the default one. The database yaml file will be created by the default :db task, and the symlink will be created by the db:symlink task. Note how namespaces in Cap 2.0 allows us to have two symlink tasks, one in the deploy namespace and the other in db.

Episode 38: Multibutton Form

Posted over 7 years back at Railscasts

If you have a form with multiple buttons, you can detect which button was clicked by checking the passed parameters. Learn how in this episode.

ActiveRecord Delegation Pitfalls

Posted over 7 years back at zerosum dirt(nap) - Home

Delegation is a powerful concept. And it’s a useful Ruby mixin, too. You can use the delegation mixin to easily expose related objects’ methods as your own, which makes for much cleaner code than defining those methods by hand. However, there are some pitfalls to be aware of when dealing with delegation and the ActiveRecord lifecycle.

Here’s a simple example of delegation in action:

class User < ActiveRecord::Base
  has_one :homepage, :class_name => "Article"
  delegate :url, :to => :homepage
end

my_user.url => url of the home page article for this user

Yes, we could just call my_user.homepage.url here too. But there are definitely some situations where you’d prefer not to do this. I won’t attempt to relate my present situation to yours, as it’s outside the scope of this discussion. Anyway, the point is that delegation can be used to make your life easier.

Here’s where we get into trouble:

new_user = User.new
new_user.url

NoMethodError: You have a nil object when you didn't expect it!
The error occurred while evaluating nil.url

O NOES! What happened?

In a nutshell, when we use it in our model, the delegate class method defines a bunch of new instance methods on our User object. These methods simply forward their queries onto the target (:to) object. If the target object, at the present stage in the AR model lifecycle, happens to be nil, then we’re trying to call a method on nil. The nil object doesn’t have a url method, so we’re toast.

def #{method}(*args, &block)
  #{to}.__send__(#{method.inspect}, *args, &block)
end

How can we get around this? Well, court3nay opened a ticket to address this a while back. It was recently closed due to inactivity. I just reopened it, and added a small patch. Here’s the difference in the way the mixin creates the delegated methods:

-  #{to}.__send__(#{method.inspect}, *args, &block)
+  #{to}.__send__("nil?") ? nil : #{to}.__send__(#{method.inspect}, *args, &block)

Pretty simple really. We just test the delegation target to see if it’s nil first. If it is, we return nil instead of trying to send it a message. Otherwise, we call the method on the target object and let the receiver worry about it.

Huzzah, we’ve successfully delegated the task of dealing with nil delegation targets! Way special, eh?

Ruby-esque JMX -- Part 2

Posted over 7 years back at Revolution On Rails

Below is the code from the JMX tinkering. I snake-ized the keys to be more Ruby-esque, per Ed's suggestion.

require 'java'
include Java

# Obviously stolen from Rails ActiveSupport for underscoring strings
class String
def underscore
self.to_s.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
end

module JMX
class MBean
include_class 'javax.management.ObjectName'
include_package "java.lang.management"
include_package "javax.management"
include_package "javax.management.remote"
include_class "java.util.HashMap"
attr_accessor :name
def initialize(object_name)
@object_name = object_name
@name = @object_name.to_s
end

def attributes
attrs = MBean.connection.getMBeanInfo(@object_name).attributes rescue []
attrs.inject({}) do |list, a|
list[a.name.underscore] = MBean.connection.getAttribute(@object_name, "#{a.name}") rescue "Unknown"
list
end
end

def self.find_all_by_name(name)
object_name = ObjectName.new(name)
beans = MBean.connection.queryMBeans(object_name,nil )
beans.collect {|bean| MBean.new(bean.get_object_name)}
end

def self.find_by_name(name)
#obviously inefficient
find_all_by_name(name).first
end

def self.connection
@@mbsc ||= begin
#load from some config file later maybe
url = "service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"
connector = JMXConnectorFactory::connect(JMXServiceURL.new(url), HashMap.new)
connector.getMBeanServerConnection
end
end
protected

def method_missing(method, *args, &block)
attributes.keys.include?(method.to_s) ? attributes[method.to_s] : super
end
end
end

#Find all the MBeans matching some object name
mbeans = JMX::MBean.find_all_by_name("java.lang:*")
puts "Found #{mbeans.size} beans"

#Find a bean, and get its attribute
bean = JMX::MBean.find_by_name("java.lang:type=ClassLoading")
puts "There are #{bean.loaded_class_count} classes loaded in that VM"

Ruby-esque JMX -- Part 2

Posted over 7 years back at Revolution On Rails

Below is the code from the JMX tinkering. I snake-ized the keys to be more Ruby-esque, per Ed's suggestion.

require 'java'
include Java

# Obviously stolen from Rails ActiveSupport for underscoring strings
class String
def underscore
self.to_s.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
end

module JMX
class MBean
include_class 'javax.management.ObjectName'
include_package "java.lang.management"
include_package "javax.management"
include_package "javax.management.remote"
include_class "java.util.HashMap"
attr_accessor :name
def initialize(object_name)
@object_name = object_name
@name = @object_name.to_s
end

def attributes
attrs = MBean.connection.getMBeanInfo(@object_name).attributes rescue []
attrs.inject({}) do |list, a|
list[a.name.underscore] = MBean.connection.getAttribute(@object_name, "#{a.name}") rescue "Unknown"
list
end
end

def self.find_all_by_name(name)
object_name = ObjectName.new(name)
beans = MBean.connection.queryMBeans(object_name,nil )
beans.collect {|bean| MBean.new(bean.get_object_name)}
end

def self.find_by_name(name)
#obviously inefficient
find_all_by_name(name).first
end

def self.connection
@@mbsc ||= begin
#load from some config file later maybe
url = "service:jmx:rmi:///jndi/rmi://localhost:1099/jmxrmi"
connector = JMXConnectorFactory::connect(JMXServiceURL.new(url), HashMap.new)
connector.getMBeanServerConnection
end
end
protected

def method_missing(method, *args, &block)
attributes.keys.include?(method.to_s) ? attributes[method.to_s] : super
end
end
end

#Find all the MBeans matching some object name
mbeans = JMX::MBean.find_all_by_name("java.lang:*")
puts "Found #{mbeans.size} beans"

#Find a bean, and get its attribute
bean = JMX::MBean.find_by_name("java.lang:type=ClassLoading")
puts "There are #{bean.loaded_class_count} classes loaded in that VM"

Using MySQL reserved words as model names

Posted over 7 years back at Spejman On Rails

The generation of a model with a migration in a ruby on rails application lets to the creation of a database table with the pluralization form of the desired model name. In MySQL, a migration will generate a sql statement like:


CREATE TABLE model_name_pluralized (`id` int(11) DEFAULT NULL auto_increment PRIMARY KEY,
`created_on` date DEFAULT NULL, `name` varchar(255) DEFAULT NULL) ENGINE=InnoDB

As you can notice column names are quoted but table name doesn't. If you use as a model name a sigularized form of a MySQL reserved word, the migration that creates this model will generate a statement that will lead to an error like:

Mysql::Error: You have an error in your SQL syntax; check the manual that corresponds to your
MySQL server version for the right syntax to use near 'databases (`id` int(11) DEFAULT NULL
auto_increment PRIMARY KEY, `created_on` da' at line 1: CREATE TABLE databases (`id` int(11)
DEFAULT NULL auto_increment PRIMARY KEY, `created_on` date DEFAULT NULL, `name` varchar(255)
DEFAULT NULL) ENGINE=InnoDB

Last week I read the Josh Susser Laying Tracks slides who
encourages me to write a patch for this issue.

Before writing anything I tried to find if someone has made something related and I found some tickets related in Rails trac:


The most interesting of this tickets is #4905 which fix all MySQL statements to prevent reserved words crash, but I don't know why isn't included in the code because it's last history is from 05/25/2006. #7850 is closed as duplicated because of #4905. And #3631 history finishes with "don't use reserved words" what in my opinion isn't the best solution.

In brief, the problem exists (I can't name my models with names like "database", "exist", ...) and the patch too (#4905). Then, what should we do to fix this problem?

Episode 37: Simple Search Form

Posted over 7 years back at Railscasts

A search form is quite different than other forms, this is because it does not deal with model's attributes. See a good way to add a simple search form in this episode.

Syncing up external Filemaker data with Rails

Posted over 7 years back at work.rowanhick.com

One of the web apps I work on leverages a Filemaker database. My co worker Brent the expert Filemaker dude, and I had a serious problem to tackle - how to get Rails and Filemaker talking to each other. Now Filemaker has iffy SQL standard support and we didn't necessarily want our web app to run live from the Filemaker database. We spent some time looking at making an ActiveRecord adapter for FM but really at the end of the day it was too much like hard work. There are 3rd party solutions out there that can do this stuff for you, but if your needs dictate that they won't work - our experience may help you. I'm going to write up the theory behind this which has been live in production for a good 6 months + and in the coming weeks will release some code acts_as_syncable or similar... First off we wanted it occassionaly connected, one way synchronising. We had an installed instance of Filemaker Web Publishing engine so we could expose our data as xml views. We used a background rails (backgroundrb) process to query Filemaker every 5-10mins and ask for changed data, pull that data using REXML, and populate/update on the Rails side using ActiveRecord. Here's the mechanics... 1. Each object, needs a last_modified timestamp, whenever an update to the object that affects the data being synced, this last_modified timestamp needs to be updated (for all intents and purposes create an updated_at column on the filemaker side). 2. Next on the Filemaker side create a layout that exposes that data when queried via Web Publishing, as an xml result. We're going to do two different queries so your layout must support these - as per note ii. 3. On the Rails side we need to add two models, and extend the models we want synced. First I created a SyncLog model which will track when syncing occurs (just for informational purposes), then a ClassSyncLog, which will track the models synced, when they were synced, the last timestamp and id of each model synced. 4. For each model on the rails app side, we need a number of properties: - The corresponding id field name from Filemaker - A hash listing each rails field and it's corresponding name on the Filemaker side (we're assuming that these will be different, in our case they were wildly different) - Some call backs, pre process sync, and post process sync if you need to do stuff before/after syncing. 5. We need a model that will actually do the work of syncing the objects. This is the involved part of the process that does the grunt work of syncing so pay careful attention. 5.1 Pass in a list of Class names, for each class name do the following 5.2 Query filemaker using Net::HTTP from the last updated_at timestamp and see if any records have been updated since then, if so move on to 5.3 or go to the next class (well... almost there's a trick here **) 5.3 For that returned result set from Filemaker (from the HTTP Raw data returned), fill an REXML object from the returned xml file. For each of the records in the result set do the following 5.3.1 Find or create the rails side object from the corresponding id in the resultset, check if you can sync it per the pre process rules if you have any. 5.3.2 Iterate over each of the fields in the result set, see if it matches a field in the field mapping on the rails class you're syncing (4). If it does update the current object from the result set. 5.3.3 Finally save the current object and move onto the next record 5.4 Now before moving onto the next class you need to save a ClassSyncLog record, with the last timestamp of the last updated record, and the last id. So next time you have a starting point to retrieve the records. 5.5 Move onto the next class 5.6 After completing all classes write out a log entry to say it's been done. 6. Wait 5mins... and repeat. Of course there's some caveats. i. First is if you're dealing with 100'000's of records you do not want to grab just one resultset back - you're going to run out of memory and/or have problems with getting the full stream of data. So we get the result set query into groups of ~ 2000. ii. Related to this, lets say you have 100'000 records updated at 10:10:01 AM. The first request is going to be for all records greater than the last updated time, for example 9:30AM. So you'd get 2000 in on your 100'000 records, the next time in you ask for > 10:10:01 am, you're going to skip 98'000 records, so you want to do two queries first: a. Give me all records time = last_updated_time AND id > last_updated.id, which will catch the next 2000 records - repeat this one until you get no more results then: b. Give me all records time > last_updated_time - then go back to qry #a for the next repeat. iii. You will have performance issues, this isn't quick by any stretch of the imagination so be a little patient to work through the issues. We're running massive datasets so we often see updates that take hours. Not great but it's ok. iv. Watch your memory, this can be a long running process so throw in 'GC.start' to clean up memory along the way. (eg every resultset retrieved) v. To avoid ID collissions it's strongly recommended you use the MySQL master-master auto-increment-increment + offset scheme, so you stagger your ID inserts between your Filemaker DB(s) and MySQL DB(s). Again look for code in the coming weeks.. I'm wrapping our custom stuff up into an acts_as module for the (limited) audience this may help...

Loading all Rails test fixtures with fixtures :all

Posted over 7 years back at Cody Fauser

Are you as tired as we were of loading 20+ different fixtures in each of your Rails test classes? We were, and we even added a method all_fixtures() to test_helper.rb to do the loading of all our fixtures for us.

Thankfully though, we don't need our own helper method anymore, as the Rails fixtures() method will now accept a symbol :all, which will instruct the test helper to load all of your fixtures automatically.

1
2
3
4
5
6
7
8

require File.dirname(__FILE__) + '/../test_helper'

class ShopTest < Test::Unit::TestCase
  fixtures :all

  # Your tests here
end

As of Rails 1.2.3 this feature has not yet been merged from the trunk. This means that you'll either need to run Edge Rails from Subversion, or install the beta Rails gems as follows:


sudo gem install -s http://gems.rubyonrails.org rails -y

Happy testing!

Sometimes It's The Little Things, pt 2

Posted over 7 years back at zerosum dirt(nap) - Home

So my first patch to Rails core was accepted yesterday. It’s a tiny, tiny patch. All it does is add a :method parameter to the auto_complete_field helper so you can do RESTful autocompletion (the filter query should be submitted with a GET, not a POST, if you want to follow the REST conventions).

No big deal really, but it feels good to finally be able to “give back” to the community in a way other than blogging and IRC help. You know, with like, actual code that benefits people other than just me. I’ve contributed to a handful of Java and PHP-based OSS projects over the years, but this is officially my first contribution to a Ruby-based project, and that certainly feels like a step in the right direction.

Ruby-esque JMX

Posted over 7 years back at Revolution On Rails

The topic of JMX on JRuby came up recently and I decided to play around. I found a great starter on Jeff Mesnil's blog, but I decided I hated the syntax.

Ruby has spoiled me. ActiveRecord has spoiled me.

So I cooked up this little (fully working) example:

#Find all the MBeans matching some object name
mbeans = JMX::MBean.find_all_by_name("cacheStatistics:*")

mbeans.each do |bean|
puts "#{bean.name} "

#Either use methods on the bean object
puts " - CacheHits: #{bean.CacheHits}"

#Or access the attributes hash.
puts " - CacheMisses: #{bean.attributes["CacheHits"]}"
end


The code is ~50lines which I'll post at some point.

I never thought working with java objects could be made to "feel" nice.

I was also chatting with "headius" on #jruby, and he mentioned that Rob Harrop, of Spring fame, had a talk at JavaOne about about something similar called MScript. I'd love to get my hands on those slides.

Ruby-esque JMX

Posted over 7 years back at Revolution On Rails

The topic of JMX on JRuby came up recently and I decided to play around. I found a great starter on Jeff Mesnil's blog, but I decided I hated the syntax.

Ruby has spoiled me. ActiveRecord has spoiled me.

So I cooked up this little (fully working) example:

#Find all the MBeans matching some object name
mbeans = JMX::MBean.find_all_by_name("cacheStatistics:*")

mbeans.each do |bean|
puts "#{bean.name} "

#Either use methods on the bean object
puts " - CacheHits: #{bean.CacheHits}"

#Or access the attributes hash.
puts " - CacheMisses: #{bean.attributes["CacheHits"]}"
end


The code is ~50lines which I'll post at some point.

I never thought working with java objects could be made to "feel" nice.

I was also chatting with "headius" on #jruby, and he mentioned that Rob Harrop, of Spring fame, had a talk at JavaOne about about something similar called MScript. I'd love to get my hands on those slides.

Acts As Fast But Very Inaccurate Counter

Posted over 7 years back at Revolution On Rails

Introduction

If you have chosen the InnoDB MySQL engine over MyISAM for its support of transactions, foreign keys and other niceties, you might be aware of its limitations, like much slower count(*). Our DBAs are in a constant lookout for slow queries in production and the ways to keep DBs happy so they recommended that we should try to fix count(). They suggested to check SHOW TABLE STATUS for an approximate count of rows in a table. This morning I wrote acts_as_fast_counter which proved that the speed is indeed improved but the accuracy might be not acceptable. The rest of the post just records details of the exercise.

The approach

I created a model per engine and seeded each with 100K records. Then I run count on each model for a thousand times and measured the results.

The code:

module ActiveRecord; module Acts; end; end 

module ActiveRecord::Acts::ActsAsFastCounter

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

module ClassMethods

def acts_as_fast_counter
self.extend(FastCounterOverrides)
end

module FastCounterOverrides

def count(*args)
if args.empty?
connection.select_one("SHOW TABLE STATUS LIKE '#{ table_name }'")['Rows'].to_i
else
super(*args)
end
end

end

end

end

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

# create_table :myisams, :options => 'engine=MyISAM'  do |t|
# t.column :name, :string
# end
# 100_000.times { Myisam.create(:name => Time.now.to_s) }
#
# create_table :innodbs, :options => 'engine=InnoDB' do |t|
# t.column :name, :string
# end
# 100_000.times { Innodb.create(:name => Time.now.to_s) }

class Bench

require 'benchmark'
require 'acts_as_fast_counter'

def self.run
measure
show_count
convert_to_fast_counter
show_count
add_records
show_count
destroy_records
show_count
measure
end

def self.measure
puts "* Benchhmarks:"
n = 1_000
Benchmark.bm(12) do |x|
x.report('MyISAM') { n.times { Myisam.count } }
x.report('InnoDB') { n.times { Innodb.count } }
end
end

def self.convert_to_fast_counter
Innodb.send(:acts_as_fast_counter)
puts "* Converted Innodb to fast counter"
end

def self.add_records
@myisam = Myisam.create(:name => 'One more')
@innodb = Innodb.create(:name => 'One more')
puts "* Added records"
end

def self.destroy_records
@myisam.destroy
@innodb.destroy
puts "* Destroyed records"
end

def self.show_count
puts "* Record count:"
puts " MyISAM: #{ Myisam.count }"
puts " InnoDB: #{ Innodb.count }"
end

end


The results:
* Benchhmarks:
user system total real
MyISAM 0.180000 0.040000 0.220000 ( 0.289983)
InnoDB 0.430000 0.070000 0.500000 ( 35.102496)
* Record count:
MyISAM: 100000
InnoDB: 100000
* Converted Innodb to fast counter
* Record count:
MyISAM: 100000
InnoDB: 100345
* Added records
* Record count:
MyISAM: 100001
InnoDB: 100345
* Destroyed records
* Record count:
MyISAM: 100000
InnoDB: 100345
* Benchhmarks:
user system total real
MyISAM 0.250000 0.030000 0.280000 ( 0.350673)
InnoDB 0.250000 0.040000 0.290000 ( 0.977711)


Final thoughts

The MySQL manual has a clear warning about inaccuracy of the amount of rows in the SHOW TABLE STATUS results:

Rows - The number of rows. Some storage engines, such as MyISAM, store the exact count. For other storage engines, such as InnoDB, this value is an approximation, and may vary from the actual value by as much as 40 to 50%. In such cases, use SELECT COUNT(*) to obtain an accurate count.


The test confirms it by showing 345 more records then expected thus making it not very useful but for some edge cases. If you know a way to improve the speed of count() on InnoDB with some other approach beyond using a counter table, please share.