I’ve had some fun toying with Rails views techniques. Russian translation is available.
Auto-loaded fixtures for RSpec: transactional madness
Catching constants was not as simple as I assumed. I’ve also tried to catch SQL queries (Adapter#execute) and load necessary fixtures immediately before SELECT/UPDATE/DELETE/INSERT. But it is also not that simple.
Transactional fixtures are inserted once per context. On every specify BEGIN..ROLLBACK transaction is performed, so there’s no need in fixture reloading.
If I catch SQL queries and load fixtures “in front of” them, they’d be wrapped into transaction and rolled back after specify is finished. Things will work, though, but much slower. Just like transactional fixtures were off.
With all that in mind, I’d like to evaluate one crazy thing to work transaction around.
The plan:
1. Add every SQL query in a queue.
If SQL requires fixture to be loaded:
2. Do ROLLBACK (loosing all the previous changes)
3. Load fixtures
4. Start transaction again (BEGIN)
5. Run previously saved SQL queries.
6. Proceed with SQL, add it to queue
If no fixtures needed, jump to 6 after 1.
It may have a drawback. In case of specify block evaluates too many SQL statements and needs some fixtures, we can get O(n*log(n)) execution time (n – number of queries), instead of O(n). But Good Boys make specify blocks short and focused, don’t they? ;-) After all, fixtures loaded in one specification are not reloaded in the other.
RSpec drives me crazy. Stay tuned.
Auto-loaded fixtures for RSpec: introduction
Have you ever forgotten to setup required fixtures in spec because of their quantity? Sometimes we have up to 10..20 tables to be filled before spec runs. In opposite, you may write many tiny contexts, so that you need short fixtures definitions in each of them. But the latter leads to very unmaintainable un-DRY code, especially when you add new entity to the project which touches many specs.
One may consider make all fixtures global, so that you don’t need to use fixtures method at all. But having 500 specify blocks and loading 30 tables per each isn’t that fast.
What we need is auto-loaded fixtures. Once model Person is mentioned in specify block, fixture people is loaded. That strategy guarantees three things:
1. Models always get required data in database
2. We don’t spend time on loading non-used fixtures
3. We don’t have to manage one more dependency, which is not that cool at all.
The main difficulty here is how catch the moment when model class appers in specify do .. end. Each constant gets catched and autoloaded by active_support/dependencies, but it is done only once, so it is not the right place to fixturize :) database.
One of the possible solutions is to hide ActiveRecord model constants before specify so to catch them in const_missing. Constants are collected within AR::inherited and loaded or revealed in const_missing.
The main problem now is to figure out which evaluation context asks for constant. This is needed for calling fixtures in the right place. You may ask: why don’t you just add const_missing to every context object to catch constant exactly where fixture is to be loaded. I tried that. RSpec uses blocks for specs, and ruby blocks, as everybody knows, are pure closures. Constant mentioned in block context have no way to be searched for in block caller context. Even using instance_eval or eval with binding (that’s nearly the same as instance_eval in that case).
Another problem is to catch fixture shortcuts: like people(:oleganza). Person class may not be even loaded before such call, so we need to do some simple heuristics to load class and fixtures altogether.
Of course, instantiated fixtures (@people) are not supported. They are slow and nobody uses them anyway.
I’m still in search for working solution. Hope it will be nice. Stay tuned, more to come.
JRuby: Unicode out-of-box
I’ve started playing with JRuby 0.9.8. I personally think that it has great potential for web development in Rails (they have 98% of rails tests passing already !).
What I was pleased to see is that (thanks to Java) unicode is working in JRuby just fine without any workarounds like String#chars :

That’s very, very good! Continuing to explore JRuby possibilities.
RSpec Generated Spec Name for "be"
I was talking about generated spec names few hours ago. Although, I’ve found that they simply don’t work properly with “be” matchers. I’ve tried to produce a quick workaround for this. That’s what I’ve created:
gem 'activesupport'
require 'active_support'
class Spec::Matchers::Be
def description
"be #{@comparison}#{@expected} #{@args.to_sentence}"
end
end
Now I can run following code:
context "Random number in 0..100 range" do
setup do
@random_number = rand(100)
end
specify do
@random_number.should be < 100
end
specify do
@random_number.should be >= 0
end
specify do
@random_number.should_not be_nil
end
specify do
@random_number.should be_between(0,100)
end
specify do
@random_number.should_not be_between(100,200)
end
end
and get this as output:
Random number in 0..100 range - should be < 100 - should be >= 0 - should not be nil - should be between 0 and 100 - should not be between 100 and 200
Update: This improvement was included into rspec trunk
RSpec Simple Specifications
What I also like in RSpec is that it constructs a name for simplistics specifications, like:
context "Random number in 0..100 range" do
setup do
@random_number = rand(100)
end
specify do
@random_number.should < 100
end
specify do
@random_number.should >= 0
end
specify do
@random_number.should_not be_nil
end
end
Random number in 0..100 range - should < 100 - should >= 0 - should not be nil
RSpec 0.8.1 is out. Out of any expectations!
class BeInZone
def initialize(expected)
@expected = expected
end
def matches?(actual)
@actual = actual
bob.current_zone.eql?(Zone.new(@expected))
end
def failure_message
"expected #{@actual.inspect} to be in Zone #{@expected}"
end
def negative_failure_message
"expected #{@actual.inspect} not to be in Zone #{@expected}"
end
end
def be_in_zone(expected)
BeInZone.new(expected)
end
and use it like this:
module CustomGameMatchers
class BeInZone
...
end
def be_in_zone(expected)
...
end
end
context "Player behaviour" do
include CustomGameMatchers
...
end
Given Ruby DSL power this may lead to some unpredictable addiction to BDD and Ruby in general.
Don't forget to run
script/generate rspec
as you're done with installation of Rails plugin from tag 0.8.1 cause it generates one more file for run preferences. I said preferences? Yes, like colored output. Passed specs are now green in Bash, messages are purple and failures are red. Really easy to recognize after months of black and white.
The same day Mauricio Fernandez posted announcement of RCov 0.8, code coverage tool for Ruby that works with RSpec. Not that bad...
And don't forget to read new documentation to RSpec, a lot of stuff changed there but everything is still easy to learn and specs look even closer to human written requirements text. What else could we ask BDD framework of?
New (experimental) Caches.rb
- was refactored a bit
- supports caching across rails models now