Factory Girl has been getting some hardcore internal refactorings over the past few months. Traits are a great example of something that’s started very bare-bones with a few caveats and been transformed to one of my favorite features of the Factory Girl syntax. It required a pretty decent chunk of refactoring and there were bugs for quite a while due to the way attributes in general were handled.
Internally, Factory Girl has four different types of attributes (Dynamic, Static,
Association, and Sequence); when compiling attributes, we were sorting
attributes by the order attributes were added, moving all static attributes
to the beginning of the list (with a
priority attribute). This worked for
99% of all cases but did have bugs (the most recent involved traits and
dynamic attributes). I
wanted to resolve this once and for all.
Factory Girl now creates a new class per factory (with some awesome usage of
defines methods on that class for each attribute declared in the factory
files. This means Factory Girl doesn’t have to worry about sorting attributes
anymore; every attribute is effectively lazily-evaluated so it’ll return the
correct value every time. Factory Girl also uses inheritance for these
anonymous classes, resulting in a pretty significant speedup of factory
interaction (namely because we were copying all parent attributes and
callbacks to each child to mimic inheritance before).
Factory Girl is now faster!
Here’s some benchmarks from Factory Girl 2.3.0:
user system total real build x10000 4.920000 0.010000 4.930000 ( 5.028088) trait build x10000 6.180000 0.010000 6.190000 ( 6.296030) inline trait build x10000 5.870000 0.010000 5.880000 ( 5.989512) deep build x10000 8.100000 0.020000 8.120000 ( 8.285039) attributes_for x10000 3.200000 0.010000 3.210000 ( 3.289043) create x500 1.010000 0.320000 1.330000 ( 4.186271)
And from Factory Girl 2.4.0:
user system total real build x10000 3.050000 0.000000 3.050000 ( 3.121359) trait build x10000 3.260000 0.010000 3.270000 ( 3.353590) inline trait build x10000 6.700000 0.040000 6.740000 ( 7.037460) deep build x10000 3.400000 0.010000 3.410000 ( 3.503984) attributes_for x10000 0.820000 0.010000 0.830000 ( 0.886641) create x500 0.710000 0.310000 1.020000 ( 3.980102)
Grab a copy of Factory Girl 2.4.0 and speed up your suite!
This February 6th we are launching a new workshop: Advanced Rails. The workshop draws content from the Scaling Rails and Rails Antipatterns workshops, replacing them and creating best-of-breed content that will take your skill to the next level in creating well-crafted Rails applications that scale.
One of the topics we touch on is profiling and benchmarking your app. There are a number of tools available to achieve this, one of which is baked into Rails itself. Although we do discuss all of the great ways you can perform caching in a Rails app, experience has shown us that caching should be your last resource in your scaling strategy. Remember the two hardest things in computer science: Cache invalidation, naming things and off-by-one errors.
On to benchmarking, say you have identified an expensive method in one of your models that needs to be tuned. One easy and straight-forward way to measure your refactored process is to use the built in benchmarker to run quick tests. To get set up, you need to add the
ruby-prof gem to your Gemfile, and have a properly patched ruby interpreter. I’m using the gcdata patch for MRI 1.9.2:
rvm install 1.9.2-p180 --patch gcdata --name gcdata
Now let’s assume the following expensive method in the Account class:
class Account def self.expensive_method sleep(1) end end
We can now run a quick benchmark on that method by running it 10 times and taking some benchmarking measurements:
bundle exec rails benchmarker --runs 10 'Account.expensive_method' Loaded suite script/rails Started BenchmarkerTest#test_10 (0 ms warmup) wall_time: 0 ms memory: 0 Bytes objects: 0 gc_runs: 0 gc_time: 0 ms BenchmarkerTest#test_user_expensive_method (1.10 sec warmup) wall_time: 1.00 sec memory: 0 Bytes objects: 0 gc_runs: 0 gc_time: 0 ms Finished in 24.933979 seconds.
You can even run a profiler with
rails profiler 'Account.expensive_method' 10 flat and get more information on what’s being called and which components in your system are taking longer.
With this quick benchmark, you can now create a second, hopefully optimized,
Account.expensive_method_fast and run them side-by-side, allowing you to quickly measure two implementations of the same behavior, and allowing you to quickly iterate to find the best solution.
This is just the tip of the iceberg. If you have some Rails experience and want to take it to the next level to grow your app into a well-factored and scalable system, check out our new Advanced Rails workshop.