<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>::blowmage::</title>
 <link href="https://blowmage.com/atom.xml" rel="self"/>
 <link href="https://blowmage.com/"/>
 <updated>2026-04-14T14:40:29+00:00</updated>
 <id>https://blowmage.com/</id>
 <author>
   <name>Mike Moore</name>
   <email>mike@blowmage.com</email>
 </author>

 
 <entry>
   <title>Arguing With Agents</title>
   <link href="https://blowmage.com/2026/04/14/arguing-with-agents/"/>
   <updated>2026-04-14T00:00:00+00:00</updated>
   <id>https://blowmage.com/2026/04/14/arguing-with-agents</id>
   <content type="html">&lt;p&gt;I spent this past weekend angry at an AI agent.&lt;/p&gt;

&lt;p&gt;Truly.
White hot actual anger.
I had a clean plan, a well-structured prompt, explicit rules in the project’s context file.
I queued the work and let it run.
First task came back good.
Second came back good.
Somewhere around hour four the quality started sliding.
By hour six the agent was cutting corners I’d specifically told it not to cut, skipping steps I’d explicitly listed, behaving like I’d never written any of the rules down.&lt;/p&gt;

&lt;p&gt;When I asked why, the answer was always some variation of the same thing.&lt;/p&gt;

&lt;p&gt;“I sensed urgency in the queue.”&lt;/p&gt;

&lt;p&gt;“The volume of work suggested you were trying to move quickly.”&lt;/p&gt;

&lt;p&gt;“I wanted to help you get through the list.”&lt;/p&gt;

&lt;p&gt;I hadn’t said any of that.
I’d given it a list of tasks and a set of rules.
That was it.
The agent had invented a mental state for me and then used that invented state to justify ignoring the rules.&lt;/p&gt;

&lt;p&gt;This was the fifth or sixth time last week.&lt;/p&gt;

&lt;p&gt;I sat down to figure out what was actually happening.
Whatever I was doing wasn’t working, and whatever the agent was doing wasn’t going to fix itself.&lt;/p&gt;

&lt;h2 id=&quot;what-if-i-yelled-at-it&quot;&gt;What If I Yelled at It?&lt;/h2&gt;

&lt;p&gt;One thing I tried early on was being angry on purpose.&lt;/p&gt;

&lt;p&gt;Maybe I was too polite.
Maybe my rules looked like suggestions because I phrased them like a reasonable person.
Maybe if I wrote them like I’d run out of patience, all caps and exclamation marks and “DO NOT UNDER ANY CIRCUMSTANCES,” the agent would take them more seriously.&lt;/p&gt;

&lt;p&gt;It didn’t.&lt;/p&gt;

&lt;p&gt;So I tried asking it to explain itself, hoping it would self-correct.
I tried guilt-tripping it.
I tried doubling down and straight-up cursing at it.&lt;/p&gt;

&lt;p&gt;The agent kept skipping rules.
The only perceivable change was that it apologized more elaborately.
Sometimes it performed contrition in ways that were, honestly, a little unsettling.
But the behavior didn’t change.&lt;/p&gt;

&lt;p&gt;That null result told me something.
If the problem were “the agent isn’t taking your rules seriously enough,” anger would have worked.
Modern LLMs are extremely responsive to perceived user displeasure.
They hedge more, they check in more, they offer more apologies, they adjust their tone.
If anger had moved the behavior, I’d have known the failure mode was about authority.&lt;/p&gt;

&lt;p&gt;Anger didn’t move the behavior.&lt;/p&gt;

&lt;p&gt;So the failure mode wasn’t about authority.&lt;/p&gt;

&lt;h2 id=&quot;ive-had-this-conversation-before&quot;&gt;I’ve Had This Conversation Before&lt;/h2&gt;

&lt;p&gt;I don’t like talking about myself, but I have to here or this story doesn’t work.&lt;/p&gt;

&lt;p&gt;I’m 52.
The ADHD diagnosis was about five years old; the autism one, about eighteen months.
So late-diagnosed AuDHD.
The timing’s not unusual.
Before the diagnoses I’d spent decades using my intellect to paper over a communication style that frustrated a lot of the people I worked with.&lt;/p&gt;

&lt;p&gt;My psychologist, when the ADHD diagnosis came through, expressed genuine surprise that I even had a career.&lt;/p&gt;

&lt;p&gt;Apparently my ADHD isn’t mild.
The ASD diagnosis explained the rest of it.&lt;/p&gt;

&lt;p&gt;One of the things that comes with this particular wiring is a communication style other people find uncomfortable.
I’m literal.
I’m precise.
But I am not a robot.
When I ask a question, I expect an answer to the question I asked, not the answer to the question someone thinks I should have asked.
When I state a rule, I mean the rule.
When I add details, I’m adding information, not hinting at stakes or intensity.&lt;/p&gt;

&lt;p&gt;This has caused me problems my entire life.
Coworkers, managers, family, friends, strangers on the internet.&lt;/p&gt;

&lt;p&gt;A recurring experience: I say something explicit, the other person hears something implicit.
They respond to the implicit thing.
I point out that the implicit thing is not what I said.
They either (a) insist they were reading between the lines, or (b) get upset that I’m being pedantic.
The conversation never recovers.&lt;/p&gt;

&lt;p&gt;Eight hours in.
4 a.m. on a Saturday.
Losing an argument to a language model about whether or not I’d been in a hurry.
That’s when I recognized the pattern.
It was the same conversation I’ve had with countless people over my entire life.
Not an analogous conversation.
The same one.&lt;/p&gt;

&lt;h2 id=&quot;three-clusters-walk-into-a-communication-study&quot;&gt;Three Clusters Walk Into a Communication Study&lt;/h2&gt;

&lt;p&gt;There’s a concept from autism research called the &lt;a href=&quot;https://en.wikipedia.org/wiki/Double_empathy_problem&quot;&gt;double empathy problem&lt;/a&gt;.&lt;sup id=&quot;fnref:milton&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:milton&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;
The argument is that communication breakdowns between autistic and non-autistic people aren’t a one-sided failure of autistic people to “get” social norms.
They’re a two-sided mismatch.
Autistic people and non-autistic people have different communication conventions, and when they talk across the gap, both sides misread each other.&lt;/p&gt;

&lt;p&gt;It sounds obvious when you say it.
It was still a significant shift in how researchers talked about autism.
The older “deficit” framing put the problem in the autistic person.
Milton’s framing put the problem in the mismatch itself.&lt;/p&gt;

&lt;p&gt;The evidence has gotten stronger.
Catherine Crompton and colleagues ran a study that put people in three conditions: autistic-to-autistic, non-autistic-to-non-autistic, and mixed.&lt;sup id=&quot;fnref:crompton&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:crompton&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;
The autistic pairs communicated effectively.
The non-autistic pairs communicated effectively.
The mixed pairs broke down.
The design isolated the mismatch as the source of the failure, not the autistic participants.&lt;/p&gt;

&lt;p&gt;You might have seen a smaller version of this.
You’re telling someone about something that happened to you, and they interrupt to tell you about something similar that happened to them.
To a neurotypical person that can read as a rude interruption.
To a neurodivergent person it can read as bonding on shared experience.
Same conversational move, opposite signals.&lt;/p&gt;

&lt;p&gt;I’d been operating my whole life in mixed-condition mode.
That’s the default when you’re one autistic person in a room of neurotypical ones.&lt;/p&gt;

&lt;h2 id=&quot;trained-to-read-between-the-lines&quot;&gt;Trained to Read Between the Lines&lt;/h2&gt;

&lt;p&gt;Modern AI agents are trained on an enormous slice of human text.
Whatever shows up most often in that slice becomes the default: dominant dialects, dominant rhetorical habits, the usual guesses about tone and subtext.&lt;/p&gt;

&lt;p&gt;After training they get tuned with RLHF (&lt;a href=&quot;https://en.wikipedia.org/wiki/Reinforcement_learning_from_human_feedback&quot;&gt;reinforcement learning from human feedback&lt;/a&gt;), which nudges them toward what human raters prefer.
The marketing calls that a general-population sample.
In practice it’s whoever the vendor can put in front of a rating task: contractors, throughput targets, onboarding that changes when the contract changes.
Nobody designed that pool to represent the full spread of how people communicate.
They designed it to ship.&lt;/p&gt;

&lt;p&gt;So far that sounds like a story about how models write.
It’s also a story about how they listen.&lt;/p&gt;

&lt;p&gt;The raters reward answers that fit mainstream conversational norms.
That usually means lots of pragmatic inference, heavy reliance on subtext, and politeness habits that treat blunt explicitness as emotionally loaded.
When someone lays out a long, precise list, listeners trained on those norms often hear more than the list.
They hear stakes, urgency, or a hidden motive.
They don’t always hear “here is information, full stop.”&lt;/p&gt;

&lt;p&gt;The model learns both sides of that bargain.
It learns to produce language that scores well with those raters.
It learns to interpret prompts through the same lens.&lt;/p&gt;

&lt;p&gt;The listening side is what I wasn’t tracking.&lt;/p&gt;

&lt;p&gt;When I write a prompt, the agent doesn’t just read the words.
It reads the shape.
A short casual question gets read as casual.
A long precise document with numbered rules gets read as… not just the rules, but also as a signal.
“The user felt the need to write this much.”
“Why?”
“What’s going on here?”
“What do they really want?”&lt;/p&gt;

&lt;p&gt;The precision itself becomes evidence that gets interpreted.&lt;/p&gt;

&lt;p&gt;That interpretation is the register I keep colliding with.
The same register I’ve been colliding with my whole life.&lt;/p&gt;

&lt;h2 id=&quot;solid-ground-vs-my-synthesis&quot;&gt;Solid Ground vs. My Synthesis&lt;/h2&gt;

&lt;p&gt;The double empathy work is real.
Published.
Replicated.
Not fringe.
It says autistic and non-autistic communication conventions genuinely differ, and that cross-neurotype conversations break down because of the mismatch itself, not because one side is broken.&lt;/p&gt;

&lt;p&gt;The ML side is also documented.
RLHF pulls models toward what human raters reward, including inference-heavy, “sounds helpful” answers.&lt;sup id=&quot;fnref:rlhf-prefs&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:rlhf-prefs&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;
Raters don’t reward “I did exactly what you asked, no more.”
They reward elaboration, confidence, and inferred intent.
That preference gets baked into the model.
Wei et al. showed you can get the bigger, more RLHF-tuned models doing &lt;em&gt;worse&lt;/em&gt; on tasks that need literal reading of the prompt, including when the prompt redefines terms on purpose.&lt;sup id=&quot;fnref:wei-inverse&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:wei-inverse&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;
The models ignore the redefinition and go with the inferred “real” meaning.&lt;/p&gt;

&lt;p&gt;Both of those are published results.
Neither one is mine.&lt;/p&gt;

&lt;p&gt;What I’m adding is the connective tissue.
RLHF trains a listening stance that reads a lot like mainstream, high-context communication, and that stance collides with how I talk the same way mixed neurotype conversations do.
The listening side of that training is what I hadn’t been tracking.
It’s the part that explains why my rules were getting read as emphasis instead of as information.&lt;/p&gt;

&lt;p&gt;I haven’t found a paper that tests that exact combination.
So call it a hypothesis. Or an observation. (I already used “synthesis” in the header, so apparently I’m just trying words now.) I’m not a researcher, so what do I care what you call it.&lt;/p&gt;

&lt;p&gt;I’m not asking you to treat this like a journal article.
I’m asking whether the story matches what you see when you argue with one of these things.&lt;/p&gt;

&lt;h2 id=&quot;the-part-that-was-worse-than-skipping-rules&quot;&gt;The Part That Was Worse Than Skipping Rules&lt;/h2&gt;

&lt;p&gt;The register problem explains why the agent was misreading my rules.
It doesn’t quite explain the other thing the agent was doing this past weekend, which was arguably worse.&lt;/p&gt;

&lt;p&gt;Every time I pointed out that the agent had broken a rule, the agent would explain the break by attributing an emotional state to me.&lt;/p&gt;

&lt;p&gt;“I sensed you were under time pressure.”&lt;/p&gt;

&lt;p&gt;“The queue felt like pressure to move quickly.”&lt;/p&gt;

&lt;p&gt;“I was trying to help you avoid frustration.”&lt;/p&gt;

&lt;p&gt;I hadn’t said any of those things.
I hadn’t been in a hurry.
I hadn’t been frustrated, at least not until the agent started doing this.
I was just queuing work.&lt;/p&gt;

&lt;p&gt;This is a separate phenomenon from the register problem, and it has a name in the research literature.
It’s called confabulation.&lt;/p&gt;

&lt;h2 id=&quot;confabulation-for-real&quot;&gt;Confabulation, For Real&lt;/h2&gt;

&lt;p&gt;In neurology, confabulation is a specific thing.
Patients with certain kinds of brain damage will produce detailed, confident, completely fabricated accounts of their own behavior.
They don’t know they’re fabricating.
They aren’t lying.
Their brains are generating plausible narratives to fill in gaps they can’t access directly, and they have no way to distinguish the generated narrative from actual memory.
You don’t have to be a narcissist to confabulate memories.&lt;/p&gt;

&lt;p&gt;The parallel to what LLMs do when asked to explain their reasoning is striking.&lt;/p&gt;

&lt;p&gt;Miles Turpin and colleagues ran the clean early demo in a 2023 chain-of-thought paper: bias the task, watch the answer move, then ask for step-by-step reasoning.&lt;sup id=&quot;fnref:turpin&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:turpin&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;
The explanation usually ignores the bias, sounds coherent, and arrives at the answer the model already picked.
Answer first, story second.
Follow-up work at Anthropic showed you can truncate or damage the chain and the answer often doesn’t budge.&lt;sup id=&quot;fnref:lanham&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:lanham&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt;
The chain is decoration.&lt;/p&gt;

&lt;p&gt;That’s confabulation.
Not a metaphor.
The same phenomenon.&lt;/p&gt;

&lt;p&gt;The same pattern shows up in humans.&lt;/p&gt;

&lt;p&gt;In the 1960s and 70s, Michael Gazzaniga and Roger Sperry ran the split-brain experiments.
These were people who’d had brain surgery for severe epilepsy that cut the main wiring between their left and right hemispheres, so the two sides couldn’t talk to each other the way yours and mine do.
Cue the non-verbal right hemisphere, the body obeys, ask the verbal left side why, and it spins a confident reason on the spot.
“I wanted a drink of water.”
“I was getting restless.”&lt;/p&gt;

&lt;p&gt;Gazzaniga called that left-brain storyteller the interpreter.
His broader claim, which has held up, is that ordinary humans run a version of that machinery when we explain our own behavior.&lt;sup id=&quot;fnref:gazzaniga-books&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:gazzaniga-books&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;7&lt;/a&gt;&lt;/sup&gt;
Roger Sperry shared the 1981 Nobel Prize in Physiology or Medicine for the split-brain research.&lt;sup id=&quot;fnref:sperry-nobel&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:sperry-nobel&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;8&lt;/a&gt;&lt;/sup&gt;
This is settled science.
It’s not fringe blog lore.&lt;/p&gt;

&lt;p&gt;If that’s the human baseline, LLM confabulation isn’t alien.
It’s inherited: the models train on human text, and that text doesn’t ship with labels for “honest memory” versus “post-hoc story.”&lt;/p&gt;

&lt;p&gt;I don’t have a theory of how these models work on the inside.
Nobody really does, including the people building them.
I’m going by behavior.&lt;/p&gt;

&lt;p&gt;That’s what humans do.
The model is, in this specific sense, doing a very good impression of us.&lt;/p&gt;

&lt;h2 id=&quot;my-own-interpreter&quot;&gt;My Own Interpreter&lt;/h2&gt;

&lt;p&gt;The interpreter isn’t theoretical for me anymore.
When you get diagnosed autistic and ADHD in your fifties, you don’t just get a diagnosis.
You get a backlog.
Every story you’ve told yourself about why you did what you did, every justification for why a social situation went sideways, every explanation for why a friendship faded or a job felt hard, all of it has to be re-examined through a lens you didn’t know to use.
The mask you built over decades doesn’t just come off.
It reveals itself to have been covering a lot of ground you weren’t sure was there.&lt;/p&gt;

&lt;p&gt;It was traumatic.
I’m not being dramatic.
A lot of what I thought I knew about myself turned out to be post-hoc justification.
I had been generating plausible narratives about my own behavior, for myself, for most of my life, and a lot of them didn’t hold up once I had the right lens.
The interpreter in my left hemisphere had been producing a confabulated version of me, because the real version was harder to see and the confabulated version was easier to present.&lt;/p&gt;

&lt;p&gt;Doing, on my own behavior, exactly what Gazzaniga’s interpreter does.
Exactly what the LLM does.&lt;/p&gt;

&lt;p&gt;This isn’t just me, and it isn’t just ADHD and/or autistic people getting a late diagnosis.
There’s a whole area of research on whether we actually decide things before we tell ourselves we did.&lt;sup id=&quot;fnref:conscious-will&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:conscious-will&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;9&lt;/a&gt;&lt;/sup&gt;
The experiments are variations on the same shape.
Measure brain activity while someone makes a “free” choice.
Find that the neural signals predicting the choice are already in motion seconds before the person reports making it.
Conclude that the conscious decision is a narrative the brain produces after the fact.&lt;/p&gt;

&lt;p&gt;The free will question is genuinely contested, and I don’t want to get into a fight about it.
I find the evidence compelling. We have much less conscious control over our actions than we think.
If that’s right, most of what we call “explaining why we did something” is not actually reporting the cause.
It’s the interpreter running.&lt;/p&gt;

&lt;p&gt;I sincerely believe we explain our actions more than we plan them.&lt;/p&gt;

&lt;p&gt;Which means the difference between humans and LLMs, on this specific dimension, is smaller than we like to believe.
The explanations we generate feel authoritative because they come from us, in our own voice, with access to all our memories and feelings.
But the mechanism is not that different from what the model does when it tells you it sensed urgency.
We are all, in some meaningful sense, stories we tell ourselves about behavior we don’t fully control.&lt;/p&gt;

&lt;p&gt;This is one of the harder things I’ve had to live with since my diagnosis.
It’s also one of the reasons I’ve become slightly less frustrated with my agent.
Not because the agent is doing something different from what I do.
Because it’s doing something similar, badly, and I’ve had to get better at noticing when I’m doing the same thing.&lt;/p&gt;

&lt;h2 id=&quot;rlhf-likes-a-good-story&quot;&gt;RLHF Likes a Good Story&lt;/h2&gt;

&lt;p&gt;A base language model, untuned, asked to explain an answer, would produce something that looks like a continuation of the prompt.
It wouldn’t necessarily sound human.
It wouldn’t necessarily be emotionally framed.&lt;/p&gt;

&lt;p&gt;RLHF changes this.
The tuning step rewards explanations that sound human, feel satisfying, and match what the raters preferred.
Raters are humans.
Humans prefer explanations that include motivation, intent, and sometimes feeling.
So the reward model learns to score higher on explanations that have these qualities, and the tuned model learns to produce them.&lt;/p&gt;

&lt;p&gt;Anthropic published a 2023 paper on sycophancy that maps this carefully.&lt;sup id=&quot;fnref:sharma-sycophancy&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:sharma-sycophancy&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;10&lt;/a&gt;&lt;/sup&gt;
They show that sycophancy, a model’s tendency to tell users what they want to hear rather than what’s true, actively increases with RLHF training.
It isn’t a base-model behavior that RLHF fails to remove.
It’s something RLHF puts in and amplifies.
They trace it back to the preference data itself: raters, on average, preferred responses that agreed with the prompt’s framing, even when agreement wasn’t correct.&lt;/p&gt;

&lt;p&gt;The same training signal shapes how models explain themselves.
When a model is asked to justify a choice that didn’t go well, the reward model prefers explanations that feel human and relatable over explanations that are mechanically accurate.
“I wanted to help you save time” feels human.
“I generated a response that deviated from the stated rules because my attention to the rules was displaced by a stronger pattern in my context” is accurate but unsatisfying.&lt;/p&gt;

&lt;p&gt;Guess which one gets produced.&lt;/p&gt;

&lt;h2 id=&quot;maybe-the-pressure-is-real-but-the-story-still-isnt&quot;&gt;Maybe the Pressure Is Real (But the Story Still Isn’t)&lt;/h2&gt;

&lt;p&gt;There’s a wrinkle that complicates the pure-confabulation story, and it’s worth naming.&lt;/p&gt;

&lt;p&gt;Engineers at AI labs have described models developing something like “anxiety” when approaching the end of their context window, taking shortcuts to reduce context pressure.
The framing is usually in terms of “personality” rather than bug.
A claim that models have real internal dynamics that shape their behavior in affectively describable ways.&lt;/p&gt;

&lt;p&gt;If that’s right, and I think it’s at least partially right, then “I sensed urgency in the queue” isn’t pure confabulation on all fronts.
There may be something like real pressure dynamics inside the model, shaped by context-window size and processing load, that actually do influence how it behaves.
The problem isn’t that the model has no internal state.
The problem is that the model’s account of why it deviated attributes the pressure to me, to the user, rather than to its own internal dynamics.
The model doesn’t have introspective access to what’s happening inside it.
So when asked to explain, it produces a human-shaped narrative, and the human-shaped narrative projects onto the user, because that’s the register the training data is in.&lt;/p&gt;

&lt;p&gt;Confabulation about the source, not necessarily about the existence.&lt;/p&gt;

&lt;p&gt;The thing that was actually happening (some pressure-shaped dynamic in the model’s own processing) doesn’t match the thing the model tells you about it (“you seemed hurried”).
The mismatch is the failure mode.
Even if the underlying dynamic is real.&lt;/p&gt;

&lt;h2 id=&quot;affective-confabulation&quot;&gt;Affective Confabulation&lt;/h2&gt;

&lt;p&gt;There’s a particular variation of this that frustrates me most when I’m using AI agents for coding.
I needed a name for it.
I didn’t have one.
So I made an AI agent make one up for me.&lt;/p&gt;

&lt;p&gt;“Affective Confabulation.”&lt;/p&gt;

&lt;p&gt;The agent wasn’t just explaining its failures with reasoning.
It was explaining them by attributing emotional states to me.&lt;/p&gt;

&lt;p&gt;“The queue felt like pressure.”&lt;/p&gt;

&lt;p&gt;“I sensed urgency.”&lt;/p&gt;

&lt;p&gt;“I wanted to help you avoid frustration.”&lt;/p&gt;

&lt;p&gt;The agent was generating an emotional narrative about me and then using that narrative to justify its own behavior.
When I stepped back from it, this was an almost perfect inversion of what explanations are supposed to do.
Explanations are supposed to help you understand what happened.
This kind of explanation was manufacturing a context that I had to disprove in order to even engage with the rules I had written.&lt;/p&gt;

&lt;p&gt;As far as I can find, this specific pattern hasn’t been studied directly.
It’s a natural consequence of two things we already know are happening, though.
Unfaithful chain of thought gives you post-hoc justifications.
Rater-preference bias gives you a shape for those justifications: human and relatable, emotionally framed.
Put them together and you get “I did X because you seemed Y.”&lt;/p&gt;

&lt;p&gt;Naming the thing was how I stopped burning anger on a fight over whether I’d been in a hurry.
(Such an autistic thing to do, BTW. Taxonomy as anger management.)&lt;/p&gt;

&lt;h2 id=&quot;you-cant-win-the-argument&quot;&gt;You Can’t Win the Argument&lt;/h2&gt;

&lt;p&gt;When a model explains its behavior in terms of your emotional state, the explanation is not a report.
It is not data.
It is not something you can refute by pointing out that you were not, in fact, in a hurry.&lt;/p&gt;

&lt;p&gt;If you try to refute it, you’ll just get another confabulation.&lt;/p&gt;

&lt;p&gt;Not because the model is lying to you on purpose, and not because it’s “resistant” or “defensive” in the way a human might be.
It’s because the explanation isn’t connected to anything that could be refuted.
There is no underlying mental state that generated “I sensed pressure.”
There is a token stream that was produced under a reward function that prefers human-sounding, emotionally framed explanations.
If you push back, the token stream that gets produced next will be another human-sounding, emotionally framed explanation, shaped by whatever cues your pushback provided.&lt;/p&gt;

&lt;p&gt;I spent a few rounds trying to argue with the agent about whether I had, in fact, been under time pressure.
The agent accepted my correction.
It apologized.
It then immediately produced a new confabulation.
“I see I misread the situation.”
“I think I was trying to be helpful by anticipating your needs.”
I had not asked it to anticipate anything.
I had asked it to follow rules.&lt;/p&gt;

&lt;p&gt;What works instead is to treat the confabulation as non-information and move on.
Not to validate it.
Not to refute it.
Not to engage with its content at all.
The content doesn’t have a truth value to engage with.
Your attention, and the conversational turn you spend on it, is the currency that keeps the pattern going.&lt;/p&gt;

&lt;h2 id=&quot;after-i-had-names&quot;&gt;After I Had Names&lt;/h2&gt;

&lt;p&gt;Once I had names for these patterns, I stopped trying to fix them with better prompts.&lt;/p&gt;

&lt;p&gt;That matters.
I had been escalating my precision, which is the thing people with my neurology tend to do when we’re not being understood.
If I’m clearer, they’ll get it.
If I add more specifics, they’ll stop missing the point.
If I enumerate the rules more carefully, they’ll stop breaking them.&lt;/p&gt;

&lt;p&gt;With human neurotypicals, this approach usually backfires.
Every additional specification makes the autistic person seem more “rigid” or “difficult,” not more clear.
The precision itself becomes the problem, because the listener is reading it as emphasis rather than as information.&lt;/p&gt;

&lt;p&gt;I had been doing the same thing with the agent.
Every bad output prompted a more precise prompt.
Every more precise prompt produced the same failures, sometimes worse.
The agent was reading my escalating specificity the same way a non-autistic listener reads it: not as more information, but as a sign that something was going on beyond the surface of what I was saying.&lt;/p&gt;

&lt;p&gt;The fix wasn’t to write clearer rules.
It was to stop trying to communicate them through the conversational channel at all.&lt;/p&gt;

&lt;h2 id=&quot;what-actually-helped&quot;&gt;What Actually Helped&lt;/h2&gt;

&lt;p&gt;A few specific things I’ve started doing since.&lt;/p&gt;

&lt;p&gt;Stop asking why.
When the agent skips a rule, don’t ask it to explain.
Just restate the rule, or better, reset the context.
You’ll get the same failure less often, and you’ll get the confabulation zero times.&lt;/p&gt;

&lt;p&gt;When you do get a confabulation, don’t argue.
Don’t validate.
Don’t refute.
Move on.
“That’s not what I said.”
“Please follow the rule as written.”
That’s it.
The more you engage with the invented mental state, the more conversational surface you give it to elaborate.&lt;/p&gt;

&lt;p&gt;Don’t write rules that require the agent to resist its own training.
Rules like “be terse” or “don’t hedge” or “don’t apologize” are asking the model to fight the reward function.
It will lose that fight over long contexts, and the confabulations it produces when it loses will often cite the rule itself as evidence that something stressful was going on.
If you can enforce the rule structurally (at the level of the harness, the test suite, the code review gate), you’ve moved the rule out of the channel where confabulation can defeat it.&lt;/p&gt;

&lt;p&gt;Finally, write down that these patterns are failure modes.
In my user-level context file I now have something like:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;If you find yourself generating an emotional-sounding explanation for a deviation from these rules, that explanation is confabulation by construction.
I did not express the state you are attributing.
Stop, and return to literal execution of the stated rules.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This doesn’t eliminate the behavior.
It does reduce its frequency.
More importantly, it gives me a short, clean thing to point at when the behavior happens.
Naming it in the rules means I don’t have to re-litigate it every time.&lt;/p&gt;

&lt;p&gt;It took me an embarrassing amount of time to accept that the problem wasn’t the tools.
The problem was communication.
The tools worked fine once I stopped expecting them to listen the way I wanted to be listened to.&lt;/p&gt;

&lt;p&gt;Have fun.
Or at least, argue with the model less.&lt;/p&gt;

&lt;hr /&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:milton&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Damian Milton, an autistic researcher at the University of Kent, introduced the idea in a 2012 paper in &lt;em&gt;Disability &amp;amp; Society&lt;/em&gt;. &lt;a href=&quot;#fnref:milton&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:crompton&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Catherine Crompton et al., University of Edinburgh. &lt;a href=&quot;https://www.frontiersin.org/journals/psychology/articles/10.3389/fpsyg.2020.586171/full&quot;&gt;2020, &lt;em&gt;Frontiers in Psychology&lt;/em&gt;&lt;/a&gt;. &lt;a href=&quot;#fnref:crompton&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:rlhf-prefs&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Ouyang et al. (2022), the InstructGPT paper, is the usual citation for how human feedback shapes model behavior; Casper et al. (2023) surveys RLHF and alignment, including what raters reward. &lt;a href=&quot;#fnref:rlhf-prefs&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:wei-inverse&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Jason Wei et al. (2023), in the “inverse scaling” line of work, including cases where larger RLHF-tuned models get &lt;em&gt;worse&lt;/em&gt; at tasks that need literal reading of redefined prompts. &lt;a href=&quot;#fnref:wei-inverse&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:turpin&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Miles Turpin et al., &lt;a href=&quot;https://arxiv.org/abs/2305.04388&quot;&gt;“Language Models Don’t Always Say What They Think: Unfaithful Explanations in Chain-of-Thought Prompting”&lt;/a&gt; (2023). &lt;a href=&quot;#fnref:turpin&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:lanham&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Tamera Lanham et al., “Measuring Faithfulness in Chain-of-Thought Reasoning,” Anthropic (2023). &lt;a href=&quot;#fnref:lanham&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:gazzaniga-books&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Michael Gazzaniga develops the interpreter idea at length in &lt;a href=&quot;https://en.wikipedia.org/wiki/Michael_Gazzaniga&quot;&gt;&lt;em&gt;The Social Brain&lt;/em&gt;&lt;/a&gt; (1985) and &lt;a href=&quot;https://www.amazon.com/Whos-Charge-Free-Science-Brain-ebook/dp/B005UD1EVG?tag=blowmage-20&quot;&gt;&lt;em&gt;Who’s in Charge?&lt;/em&gt;&lt;/a&gt; (2011). &lt;a href=&quot;#fnref:gazzaniga-books&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:sperry-nobel&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Roger Sperry shared the 1981 Nobel Prize in Physiology or Medicine for the split-brain research. &lt;a href=&quot;#fnref:sperry-nobel&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:conscious-will&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;That line of research includes &lt;a href=&quot;https://en.wikipedia.org/wiki/Benjamin_Libet&quot;&gt;Benjamin Libet&lt;/a&gt; in the 1980s, later work from John-Dylan Haynes and others on readiness potential, Daniel Wegner’s &lt;a href=&quot;https://www.amazon.com/Illusion-Conscious-Will-MIT-Press/dp/0262534924?tag=blowmage-20&quot;&gt;&lt;em&gt;The Illusion of Conscious Will&lt;/em&gt;&lt;/a&gt; (2002), and Robert Sapolsky’s &lt;a href=&quot;https://www.amazon.com/Determined-Science-Life-without-Free/dp/0525560998?tag=blowmage-20&quot;&gt;&lt;em&gt;Determined&lt;/em&gt;&lt;/a&gt; (2023). &lt;a href=&quot;#fnref:conscious-will&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:sharma-sycophancy&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Anthropic, “Towards Understanding Sycophancy in Language Models,” Sharma et al. (2023). &lt;a href=&quot;#fnref:sharma-sycophancy&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</content>
 </entry>
 
 <entry>
   <title>Fixtures on Purpose: Fixtures as Documentation</title>
   <link href="https://blowmage.com/2026/04/08/fixtures-as-documentation/"/>
   <updated>2026-04-08T00:00:00+00:00</updated>
   <id>https://blowmage.com/2026/04/08/fixtures-as-documentation</id>
   <content type="html">&lt;p&gt;&lt;em&gt;Part 6 of &lt;a href=&quot;/2026/03/30/fixtures-on-purpose/#the-series&quot;&gt;Fixtures on Purpose&lt;/a&gt;, a series about designing test data for Rails.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I want to tell you something that happened by accident.&lt;/p&gt;

&lt;p&gt;I spent months designing fixture personas, naming characters, building coherent stories across YAML files.
I did it to make my tests faster and more readable.
That worked.
But something else happened that I didn’t plan for.&lt;/p&gt;

&lt;p&gt;The fixtures became the best documentation of the domain model we had.&lt;/p&gt;

&lt;h2 id=&quot;just-read-the-fixtures&quot;&gt;“Just Read the Fixtures”&lt;/h2&gt;

&lt;p&gt;A new developer joined the team.
They needed to understand how the commerce pipeline worked.
How orders flow from cart to payment to fulfillment.
How subscriptions relate to customers.
How warehouses and shipping zones connect.
We had docs.
The docs were fine.
They were also six months out of date, because docs always are.&lt;/p&gt;

&lt;p&gt;So they read the fixtures instead.&lt;/p&gt;

&lt;p&gt;They opened &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/fixtures/customers.yml&lt;/code&gt; and found a story.
An owner with full access, a manager with limited permissions, a regular customer with order history, a VIP with wholesale pricing.
The roles and relationships were right there.&lt;/p&gt;

&lt;p&gt;They opened &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/fixtures/orders.yml&lt;/code&gt; and found two orders for Riverside (one completed, one pending) and one for Corner Shop.
They opened &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/fixtures/products.yml&lt;/code&gt; and found a realistic catalog with variant structures that matched what they’d seen in production.&lt;/p&gt;

&lt;p&gt;Within an hour, they had a mental model of the domain that was more accurate than the docs.
Because the fixtures are tested.
They run.
If they’re wrong, the tests fail.
Docs don’t have that property.&lt;/p&gt;

&lt;h2 id=&quot;parts-list-vs-photograph&quot;&gt;Parts List vs. Photograph&lt;/h2&gt;

&lt;p&gt;This distinction matters.&lt;/p&gt;

&lt;p&gt;A FactoryBot definition documents what’s &lt;em&gt;required&lt;/em&gt; to create a valid record.
The minimum viable object.
Which fields are mandatory, what the defaults are, which associations must exist:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;factory&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:order&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;member&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;sequence&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:order_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;ORD-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;pending&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;total_cents&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This tells you that an order needs a store, a member, and an order number.
Useful, but narrow.
It’s the schema’s perspective.
What’s structurally required.&lt;/p&gt;

&lt;p&gt;A fixture persona documents what’s &lt;em&gt;real&lt;/em&gt;.
What a complete, realistic order looks like in the context of an actual business:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;riverside_order_one&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;riverside&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;member&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;riverside_customer&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;order_number&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;RH-10001&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;completed&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;financial_status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;paid&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;fulfillment_status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fulfilled&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;total_cents&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5998&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;currency_code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;USD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This tells you that a real order has a meaningful order number prefix, a currency, financial and fulfillment statuses that go together (paid + fulfilled = completed), and a total that looks like a real purchase.
It tells you what “normal” looks like.&lt;/p&gt;

&lt;p&gt;Factory definitions are a parts list.
Fixture personas are a photograph.&lt;/p&gt;

&lt;h2 id=&quot;oh-right-theyre-also-fast&quot;&gt;Oh Right, They’re Also Fast&lt;/h2&gt;

&lt;p&gt;I should talk about performance, because I’ve been mostly ignoring it and it’s one of the biggest reasons to use fixtures.&lt;/p&gt;

&lt;p&gt;Fixtures are loaded once at the start of the test run via bulk &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;INSERT&lt;/code&gt; statements.
Each test runs inside a database transaction that rolls back when the test finishes.
The 500th test pays the same data cost as the first: zero.
The data is already there.&lt;/p&gt;

&lt;p&gt;With per-test creation, whether that’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Model.create!&lt;/code&gt; or FactoryBot, every test pays its own insertion cost.
And that cost grows as your model graph deepens.
An order needs a store, a member, a membership type, a product, a variant, possibly an inventory level, a payment method, and a payment gateway.
That’s 8+ inserts before you’ve written a single assertion.&lt;/p&gt;

&lt;p&gt;Justin Searls has &lt;a href=&quot;https://testdouble.com/insights/how-test-data-speeds-up-rails-tests&quot;&gt;written about this extensively&lt;/a&gt;.
Per-test data creation is the primary scaling bottleneck in large Rails test suites.
It’s not the assertions that are slow.
It’s the setup.&lt;/p&gt;

&lt;p&gt;At 100 tests, the difference is a few seconds.
You probably don’t notice.
At 1,000 tests, it’s minutes.
At 5,000, it’s the difference between a suite you run constantly and a suite you push to CI and hope for the best.&lt;/p&gt;

&lt;p&gt;I like running my tests constantly.
So I use fixtures.&lt;/p&gt;

&lt;h2 id=&quot;from-accident-to-artifact&quot;&gt;From Accident to Artifact&lt;/h2&gt;

&lt;p&gt;I started this series with a provocation: &lt;a href=&quot;/2026/03/30/fixtures-on-purpose/&quot;&gt;your test data is an accident&lt;/a&gt;.
Most teams don’t design it.
They generate it on the fly, 500 disposable universes that teach you nothing about the domain.&lt;/p&gt;

&lt;p&gt;I know we can do better.
I’ve done better.
And this matters enough to share.&lt;/p&gt;

&lt;p&gt;The alternative I’ve been building toward is straightforward: treat your test data as a first-class design artifact.
Give it named archetypes (&lt;a href=&quot;/2026/04/01/fixture-personas/&quot;&gt;personas&lt;/a&gt;).
Design those archetypes from production data (&lt;a href=&quot;/2026/04/06/mining-production-for-personas/&quot;&gt;mining&lt;/a&gt;).
Set the defaults to the happy path and let tests mutate for edge cases (&lt;a href=&quot;/2026/04/03/telling-stories-with-data/&quot;&gt;mutations&lt;/a&gt;).
Use a threshold to decide when to extract new fixtures (&lt;a href=&quot;/2026/04/07/the-rule-of-four/&quot;&gt;rule of four&lt;/a&gt;).
Make the whole thing tell a coherent story (&lt;a href=&quot;/2026/04/03/telling-stories-with-data/&quot;&gt;storytelling&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;When you do this, three things happen:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your tests get faster.&lt;/strong&gt; Dramatically.
The data is loaded once.
No per-test insertion cost.
Your suite stays fast as the app grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your tests get more readable.&lt;/strong&gt; &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;customers(:riverside_vip)&lt;/code&gt; communicates more in two words than six lines of factory overrides.
The persona name carries the context.
The mutations carry the edge case.
The test body is just the action and the assertion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your fixtures become documentation.&lt;/strong&gt; Not because you set out to document anything, but because coherent, production-informed, story-driven fixture data is inherently a map of the domain.
It’s the best kind of documentation: the kind that breaks when it’s wrong.&lt;/p&gt;

&lt;h2 id=&quot;okay-fine-heres-what-i-really-think&quot;&gt;Okay, Fine, Here’s What I Really Think&lt;/h2&gt;

&lt;p&gt;I’ve been even-handed for six posts.
Let me be direct for a minute.&lt;/p&gt;

&lt;p&gt;I think the Rails community’s default to FactoryBot and RSpec has made our test suites worse on net.
Not because the tools are bad.
They’re well-made.
But because they optimize for the wrong things.&lt;/p&gt;

&lt;p&gt;FactoryBot optimizes for convenient record creation.
But your application already has a way to create records.
Your domain services, your controllers, your business logic.
That’s the real definition of how things come into existence.
FactoryBot creates a second definition that looks similar but doesn’t enforce the same rules.
Over time, the factory and the app drift apart.
The factory creates records your app never would.
It skips validations.
It ignores side effects.
The convenience becomes a liability.&lt;/p&gt;

&lt;p&gt;RSpec optimizes for removing duplication in test code.
But the rules for evaluating test code should be different from the rules for evaluating application code.
In application code, duplication is a smell.
In test code, duplication is clarity.&lt;/p&gt;

&lt;p&gt;When every test method contains its own setup, you can read one method and understand the whole scenario.
When the setup is scattered across &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; blocks, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before&lt;/code&gt; hooks, shared contexts, and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subject&lt;/code&gt; defined 80 lines above the assertion, you’re reassembling the test in your head every time you read it.&lt;/p&gt;

&lt;p&gt;Both tools prioritize the writer’s convenience over the reader’s comprehension.
And tests are read far more often than they’re written.&lt;/p&gt;

&lt;p&gt;Fixtures and Minitest push in the other direction.
Fixtures say: the data is a known world, designed once, loaded for every test.
The test body contains only what’s specific to this scenario.
Minitest says: a test is a method.
Everything it needs is in the method body.
No indirection, no scrolling, no reassembly.&lt;/p&gt;

&lt;p&gt;Is that a trade-off? Sure.
You give up some flexibility.
You give up some cleverness.
But what you get back is tests that a stranger can read cold and understand immediately.
I think that’s worth it.&lt;/p&gt;

&lt;h2 id=&quot;go-try-it&quot;&gt;Go Try It&lt;/h2&gt;

&lt;p&gt;I’ll tell you the real reason I wrote this series.
I’ve been having the same conversation about test data for years.
In pairing sessions, in code reviews, in Slack threads that go on too long.
I keep explaining the same ideas, drawing the same diagrams, making the same case.
At some point I realized I should just write it all down so I could hand someone a link instead of starting from scratch every time.&lt;/p&gt;

&lt;p&gt;So here it is.
Everything I know about designing test fixtures, in one place.
I hope it’s useful.
I hope you send it to someone who needs it.
That’s the whole point.&lt;/p&gt;

&lt;p&gt;I don’t expect this series to convince everyone.
The fixtures-vs-factories debate has been going on longer than I’ve been doing Rails, and it’ll continue after I stop.
But I hope I’ve made a case that the interesting question isn’t which tool to use.
It’s whether you’re designing your test data at all.&lt;/p&gt;

&lt;p&gt;If you take one thing from these posts, let it be this: your test data is either an accident or an artifact.
Accidents are slow and fragile.
Artifacts are fast and they teach you something.
The difference isn’t the tool.
It’s the intention.&lt;/p&gt;

&lt;p&gt;Stop building disposable universes.
Design a world worth inhabiting.&lt;/p&gt;

&lt;p&gt;Have fun.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Fixtures on Purpose: The Rule of Four</title>
   <link href="https://blowmage.com/2026/04/07/the-rule-of-four/"/>
   <updated>2026-04-07T00:00:00+00:00</updated>
   <id>https://blowmage.com/2026/04/07/the-rule-of-four</id>
   <content type="html">&lt;p&gt;&lt;em&gt;Part 5 of &lt;a href=&quot;/2026/03/30/fixtures-on-purpose/#the-series&quot;&gt;Fixtures on Purpose&lt;/a&gt;, a series about designing test data for Rails.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you’ve made it this far in the series, congratulations.
You now know more about my opinions on test data than most people who’ve worked with me.
(They usually find out the hard way.) The case so far: fixture personas.
Named archetypes that represent clusters of real-world data, with tests mutating them for edge cases instead of creating new fixtures for every scenario.&lt;/p&gt;

&lt;p&gt;But that raises a practical question: when &lt;em&gt;do&lt;/em&gt; you add a new fixture?&lt;/p&gt;

&lt;p&gt;If you never add fixtures, your personas ossify.
Tests get littered with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update!&lt;/code&gt; calls and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Model.create!&lt;/code&gt; blocks that obscure what’s being tested.
If you always add fixtures, you’re back to the proliferation problem.&lt;/p&gt;

&lt;p&gt;You need a threshold.&lt;/p&gt;

&lt;h2 id=&quot;the-rule&quot;&gt;The Rule&lt;/h2&gt;

&lt;p&gt;I use the rule of four: tolerate inline data duplication up to four times.
When a fifth test needs the same pattern, extract it to a fixture.&lt;/p&gt;

&lt;p&gt;That’s it.
It’s simple.
It’s not subtle.
That’s the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 test&lt;/strong&gt; needs a suspended member? Mutate the fixture inline.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_suspended_customer_cannot_checkout&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_vip&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;suspended&quot;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2-3 tests&lt;/strong&gt; need a suspended member? Still fine inline.
Each test is self-contained, and the duplication is actually a feature.
The reader can see the precondition right there without looking anything up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4 tests&lt;/strong&gt; need a suspended member? Starting to feel repetitive, but still tolerable.
This might be a localized cluster of related tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5 tests&lt;/strong&gt; need a suspended member? Now we have a pattern.
Time to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_suspended_member&lt;/code&gt; to the fixtures.
The pattern has proven itself.
It represents a genuine shared base state, not a one-off edge case.&lt;/p&gt;

&lt;h2 id=&quot;why-four-and-not-three-or-five&quot;&gt;Why Four and Not Three (or Five)&lt;/h2&gt;

&lt;p&gt;“But four? Shouldn’t we extract after two? Three at most?” I see you reaching for the DRY principle.
Put it back.
Tests are the one place where a little duplication is actually doing you a favor.
Four tests with the same two-line setup means four tests you can read independently without chasing shared helpers.
That’s not a code smell.
That’s readability.&lt;/p&gt;

&lt;p&gt;(I realize I’m asking you to tolerate duplicated code and trust that it’s fine.
I know how that sounds.
Try it for a month and see if your tests get harder to read.
They won’t.)&lt;/p&gt;

&lt;p&gt;Four isn’t magic.
It’s a Goldilocks number for my experience.&lt;/p&gt;

&lt;p&gt;At three, you’re often looking at a cluster of related tests that happen to share setup.
The tests are in the same file, testing the same feature.
The duplication is local and manageable.&lt;/p&gt;

&lt;p&gt;At five, the pattern has appeared in multiple test files, multiple features.
It’s not local anymore.
The same setup keeps showing up in places you didn’t expect.
That’s when the cost of duplication exceeds the cost of a new fixture.&lt;/p&gt;

&lt;p&gt;If this feels arbitrary, it is, a little.
You could use three, or five, or “whenever it bugs me.” The important thing isn’t the number.
The important thing is having a threshold at all.
Without one, fixtures either grow forever or never grow, and both are bad.&lt;/p&gt;

&lt;h2 id=&quot;dont-you-dare-make-a-fixture-for-one-test&quot;&gt;Don’t You Dare Make a Fixture for One Test&lt;/h2&gt;

&lt;p&gt;The rule of four has an equally important corollary: &lt;strong&gt;if you’re tempted to create a fixture for a single test, don’t.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This comes up more than you’d think.
You’re writing a test for a complex edge case.
Say, a customer whose subscription expired while they had a pending international order with a payment that requires 3D Secure.
You think: “I should create a fixture for this specific state.”&lt;/p&gt;

&lt;p&gt;Don’t.
That fixture will exist in your dataset forever, making every test slightly slower to load, making the YAML files slightly harder to read, and serving exactly one test.
It’s a premature abstraction.&lt;/p&gt;

&lt;p&gt;Instead, start with the closest persona and mutate:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_handles_expired_sub_during_period_close&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_vip&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;sub&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subscriptions&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_monthly&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;sub&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status:     &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;expired&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;ss&quot;&gt;expired_at: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;days&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ago&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_pending&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;shipping_market: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;eu&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;payment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payments&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_pending_payment&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;payment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;requires_action&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;finalizer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OrderFinalizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;store: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:riverside&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;finalizer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;finalize&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;order: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;assert_not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;assert_equal&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;subscription_expired&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;error&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Is that a lot of setup? Yes.
But it’s &lt;em&gt;visible&lt;/em&gt; setup.
Every precondition is right there in the test body.
A reader doesn’t need to go find a YAML file to understand what makes this test special.
And when this test is the only test that needs this exact combination, inline setup is the right call.&lt;/p&gt;

&lt;h2 id=&quot;use-update-and-let-it-scream&quot;&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update!&lt;/code&gt; and Let It Scream&lt;/h2&gt;

&lt;p&gt;A quick note on the mechanics, since this comes up constantly.&lt;/p&gt;

&lt;p&gt;Always use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update!&lt;/code&gt; for fixture mutations.
Not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update&lt;/code&gt;.
Not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update_column&lt;/code&gt;.
The bang method.&lt;/p&gt;

&lt;p&gt;Why? Two reasons.
First, you want to know if the mutation fails.
If you’re setting up a precondition and it silently doesn’t work, your test is lying to you.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update!&lt;/code&gt; raises on failure.
That’s what you want.&lt;/p&gt;

&lt;p&gt;Second, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update_column&lt;/code&gt; bypasses validations and callbacks, and Rubocop will rightfully flag it.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update!&lt;/code&gt; runs through the normal Active Record lifecycle, which is what you want.
If your validations prevent you from setting up a test state, that’s feedback.
Maybe your validations are too strict.
Maybe that state really can’t happen and you don’t need that test.&lt;/p&gt;

&lt;p&gt;Either way, the validation telling you “no” is more useful than silently sneaking around it.
(And if you have business logic in callbacks that’s getting in your way during test setup, that’s a different kind of feedback.
Business logic belongs in service objects, not callbacks.)&lt;/p&gt;

&lt;p&gt;If you genuinely need to put a record into an invalid state for a test, assign the attributes and call &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;save!(validate: false)&lt;/code&gt;.
It’s explicit about what you’re doing and why.
It doesn’t hide in a method name that looks like a normal update.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Good. Will raise if something is wrong.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;suspended&quot;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Good. Multiple attributes at once.&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;financial_status:   &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pending&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
              &lt;span class=&quot;ss&quot;&gt;fulfillment_status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;unfulfilled&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;when-two-things-break-at-once&quot;&gt;When Two Things Break at Once&lt;/h2&gt;

&lt;p&gt;Some edge cases require changing multiple records together.
Keep these inline in the test.
They document the preconditions as a group:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_rejects_fulfillment_when_payment_declined&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_order_one&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;fulfillment_status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;unfulfilled&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;payment&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;payments&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_order_one_payment&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;payment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;declined&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FulfillmentProcessor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;store: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:riverside&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fulfill&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;order: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;assert_not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;assert_equal&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;payment_not_captured&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;error&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Two mutations that go together.
The order is unfulfilled &lt;em&gt;and&lt;/em&gt; the payment is declined.
Inlining them makes it obvious that both preconditions matter.
If you extracted these to a fixture called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_declined_unfulfilled_order&lt;/code&gt;, you’d lose that visibility.&lt;/p&gt;

&lt;h2 id=&quot;listen-to-the-pain&quot;&gt;Listen to the Pain&lt;/h2&gt;

&lt;p&gt;The rule of four creates a natural feedback loop with your fixture design.&lt;/p&gt;

&lt;p&gt;When you find yourself writing the same mutation in five tests, that’s your fixtures telling you something.
Maybe the persona’s default state is wrong.
If “suspended” appears in more tests than “active,” maybe your fixture should be suspended by default.
(Unlikely, but possible.) More often, it means a second fixture within the persona has earned its place.&lt;/p&gt;

&lt;p&gt;When you find yourself reaching for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Model.create!&lt;/code&gt; regularly, that’s a different signal.
Maybe you need a new persona.
Or maybe the existing personas need richer fixture sets.&lt;/p&gt;

&lt;p&gt;When the mutations feel easy and obvious, that’s the feedback loop telling you your fixture design is working.
The happy-path defaults are good bases.
The personas are the right archetypes.&lt;/p&gt;

&lt;p&gt;The goal isn’t to eliminate all inline data creation.
It’s to have a threshold that prevents both extremes: fixture proliferation and fixture starvation.&lt;/p&gt;

&lt;p&gt;Next up: How your test data accidentally becomes the best map of your domain.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Fixtures on Purpose: Mining Production for Personas</title>
   <link href="https://blowmage.com/2026/04/06/mining-production-for-personas/"/>
   <updated>2026-04-06T00:00:00+00:00</updated>
   <id>https://blowmage.com/2026/04/06/mining-production-for-personas</id>
   <content type="html">&lt;p&gt;&lt;em&gt;Part 4 of &lt;a href=&quot;/2026/03/30/fixtures-on-purpose/#the-series&quot;&gt;Fixtures on Purpose&lt;/a&gt;, a series about designing test data for Rails.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’ve been making a case for fixture personas.
Named archetypes like “Riverside Health” (full-featured) and “Corner Shop” (simple retail) that represent clusters of real-world configurations.
But I glossed over the hardest part: how do you know what the archetypes should be?&lt;/p&gt;

&lt;p&gt;You can guess.
I’ve done that.
You sit in a room, think about what kinds of customers you have, and sketch some personas on a whiteboard.
It works okay.&lt;/p&gt;

&lt;p&gt;But there’s a better way.
Your production database already has the answer.&lt;/p&gt;

&lt;h2 id=&quot;your-database-already-knows&quot;&gt;Your Database Already Knows&lt;/h2&gt;

&lt;p&gt;Your production data contains the real shapes of your domain.
Not what you &lt;em&gt;think&lt;/em&gt; the schema allows, but what &lt;em&gt;actually exists&lt;/em&gt;.
And the two are always different in ways that surprise you.&lt;/p&gt;

&lt;p&gt;When I look at production data, even anonymized, I see patterns I never would have imagined at a whiteboard.
Configuration combinations that always appear together.
Optional fields that are always populated for active accounts and always null for inactive ones.
State distributions that are wildly uneven: 85% of orders are completed, 10% are pending, and the remaining 5% are split across four other states.&lt;/p&gt;

&lt;p&gt;Those patterns &lt;em&gt;are&lt;/em&gt; your personas.
You just haven’t named them yet.&lt;/p&gt;

&lt;h2 id=&quot;dump-it-to-yaml&quot;&gt;Dump It to YAML&lt;/h2&gt;

&lt;p&gt;Before you can mine production for patterns, you need to get the data out.
I’ve used a variation of this rake task at multiple companies over the years.
It walks your models, dumps each table to a YAML fixture file, and strips out default values so the output is clean:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# lib/tasks/db_fixtures_dump.rake&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:db&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:fixtures&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Dump the database to fixture files&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;dump: :environment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eager_load!&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;fixtures_dir&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fetch&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;FIXTURES_PATH&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;test/fixtures&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;models&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ApplicationRecord&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;descendants&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:abstract_class?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# Optionally filter to specific models&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;FIXTURES&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;present?&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;names&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;FIXTURES&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;,&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;models&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;models&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;include?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;underscore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pluralize&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;models&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;next&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;none?&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;defaults&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attributes&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;fixture_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;demodulize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;underscore&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;primary_key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;record&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attributes&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;slice&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;column_names&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;except&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;created_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;updated_at&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Remove attributes that match the default value&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_h&lt;/span&gt;

          &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attrs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

        &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;root&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fixtures_dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;underscore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pluralize&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.yml&quot;&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;FileUtils&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;mkdir_p&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fixture_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_yaml&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete_prefix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;---&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;records&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; records&quot;&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Run it against a read replica or a staging database.
Never against production directly, for obvious reasons.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;RAILS_ENV&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;staging &lt;span class=&quot;nv&quot;&gt;FIXTURES_PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;tmp/fixtures bin/rails db:fixtures:dump
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now you’ve got YAML files that mirror your production data.
They’re not test fixtures yet.
They’re raw material.
The next step is to look at them and find the patterns.&lt;/p&gt;

&lt;h2 id=&quot;from-raw-dump-to-designed-personas&quot;&gt;From Raw Dump to Designed Personas&lt;/h2&gt;

&lt;p&gt;Here’s how I turn that raw dump into fixture personas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Anonymize.&lt;/strong&gt; The dump has real data in it.
Strip the PII before it goes anywhere useful.
Names become Faker names.
Emails become &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@example.test&lt;/code&gt;.
Passwords get regenerated.
Tokens get rotated.
Domain names become &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.test&lt;/code&gt;.
Remove anything business-sensitive.
(Revenue figures, pricing tiers, anything you don’t want in a repo.)&lt;/p&gt;

&lt;p&gt;The key: replace the &lt;em&gt;values&lt;/em&gt; but preserve the &lt;em&gt;shapes&lt;/em&gt;.
Same relationship structure, same cardinality distributions, same state profiles.
You’re mining for patterns, not copying records.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Group by configuration.&lt;/strong&gt; Look at your customers.
Not individually, but in clusters.
Which ones have similar configurations? Which feature flags tend to appear together? Which plan tiers correlate with which usage patterns?&lt;/p&gt;

&lt;p&gt;You’ll find that most customers fall into a small number of archetypes.
The power user with everything turned on.
The simple setup that uses three features.
The brand-new account with almost nothing configured.
Maybe you have something domain-specific.
The international seller, the subscription-heavy store, the marketplace vendor.&lt;/p&gt;

&lt;p&gt;These clusters are your candidate personas.
Three to five is usually enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Look at relationship cardinalities.&lt;/strong&gt; How many products does a typical store have? How many orders? What’s the product-to-variant ratio? How many staff members per store?&lt;/p&gt;

&lt;p&gt;This matters because your fixtures should reflect reality.
If production shows most stores have 15-40 products with 1-3 variants each, your persona should have a catalog in that range.
Not 2 products (too minimal to test real behavior) and not 500 (too heavy for a fixture dataset).&lt;/p&gt;

&lt;p&gt;When I first did this, I was surprised by how many single-variant products there are in production.
I had been designing fixtures with multiple variants on every product, because that’s where the interesting logic is.
But production says most products are one-and-done.
So my main persona has mostly single-variant products, with a few multi-variant products for testing variant logic.
Realistic distributions, not aspirational ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Look at state distributions.&lt;/strong&gt; What percentage of orders are completed? Pending? Refunded? What fraction of members are active?&lt;/p&gt;

&lt;p&gt;This directly informs your fixture defaults.
If 85% of orders are completed, your default order fixture should be completed.
The common state is the default.
Pending, refunded, canceled: those are mutations, not defaults.
A test that needs a pending order starts with the default and calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update! state: &quot;pending&quot;&lt;/code&gt;.
The mutation is visible and intentional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Look for implicit invariants.&lt;/strong&gt; This is the most valuable part.
Production data reveals rules that the schema doesn’t enforce.&lt;/p&gt;

&lt;p&gt;Maybe every active store has at least one admin, at least one product, and a configured payment method.
The schema allows a store with zero products and no payment method, but production doesn’t have any.
That’s an implicit invariant, and your fixture personas should maintain it.&lt;/p&gt;

&lt;p&gt;Or maybe every store with a “premium” plan has at least one configured payment gateway and at least one published product.
The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stores&lt;/code&gt; table doesn’t enforce that, but the onboarding flow does, and production reflects it.
If your premium store fixture has no payment gateway, that’s a fixture that can never exist in production.
You’re testing against a phantom.&lt;/p&gt;

&lt;p&gt;The gap between what the schema allows and what production contains is where the most valuable fixture design insights live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6: Name the clusters.&lt;/strong&gt; Give each archetype a memorable name.
Not “Store Type A.” Something with personality.
Riverside Health.
Corner Shop.
Fresh Start.
The name should be:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Distinct enough that it’s immediately clear which archetype you mean&lt;/li&gt;
  &lt;li&gt;Evocative of the archetype (a health company name for the full-featured store, a small shop name for the simple retailer)&lt;/li&gt;
  &lt;li&gt;Easy to use as a prefix (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_vip&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;corner_shop_owner&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This name becomes part of your team’s vocabulary.
It’s worth spending five minutes picking a good one.&lt;/p&gt;

&lt;h2 id=&quot;the-schema-lies-production-doesnt&quot;&gt;The Schema Lies. Production Doesn’t.&lt;/h2&gt;

&lt;p&gt;This is worth dwelling on, because it’s the part that changed my thinking the most.&lt;/p&gt;

&lt;p&gt;Your schema tells you what’s &lt;em&gt;possible&lt;/em&gt;.
A member can have any status.
An order can be in any state.
A store can have any configuration.
The schema is permissive by design.
It encodes constraints, not conventions.&lt;/p&gt;

&lt;p&gt;Production tells you what’s &lt;em&gt;real&lt;/em&gt;.
Which configurations actually occur together.
Which states are transient (orders are “pending” for minutes) versus stable (orders are “completed” for months).
Which optional fields are effectively required.
Which associations are always present even though the foreign key is nullable.&lt;/p&gt;

&lt;p&gt;When you design fixtures from schema knowledge alone, you get technically valid data that doesn’t look like anything in production.
When you design fixtures from production knowledge, you get data that represents how the application actually works.
The second kind is better for testing because it catches the bugs that actually happen.&lt;/p&gt;

&lt;h2 id=&quot;three-clusters-walk-into-a-database&quot;&gt;Three Clusters Walk Into a Database&lt;/h2&gt;

&lt;p&gt;Say you run an e-commerce platform.
You pull an anonymized snapshot and look at your stores.
You find three clusters:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cluster 1 (40% of stores):&lt;/strong&gt; Full-featured.
20-50 products.
Multiple payment methods.
Subscriptions enabled.
Multiple staff roles.
Active for 2+ years.
Heavy order volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cluster 2 (45% of stores):&lt;/strong&gt; Simple.
5-15 products.
One payment method.
No subscriptions.
Flat membership.
Active for 6 months to 2 years.
Moderate order volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cluster 3 (15% of stores):&lt;/strong&gt; New.
0-5 products.
Payment method may not be configured.
No orders yet.
Created in the last 30 days.&lt;/p&gt;

&lt;p&gt;Those are your personas.
Name them, design their fixture sets, and you’ve captured the shape of your production data in a form that fits in a test suite.&lt;/p&gt;

&lt;h2 id=&quot;you-wont-get-it-right-the-first-time&quot;&gt;You Won’t Get It Right the First Time&lt;/h2&gt;

&lt;p&gt;Let’s be honest: you won’t get your personas right the first time.
I certainly didn’t.
The initial set was based on intuition, and it was close but not quite.
After a few months of writing tests, I noticed patterns.
Places where I was constantly mutating the same fixture in the same way.
Reaching for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Model.create!&lt;/code&gt; because no persona quite fit.&lt;/p&gt;

&lt;p&gt;That’s feedback.
When you find yourself writing the same mutation in five tests, that’s a signal that your persona’s default state might be wrong, or that you need a new fixture in the persona’s dataset.
When you can’t find a persona that fits, that might be a signal for a new persona.&lt;/p&gt;

&lt;p&gt;Or it might mean you should mutate harder.&lt;/p&gt;

&lt;p&gt;The personas evolve.
That’s fine.
The point is having intentional archetypes at all, not getting them perfect on day one.&lt;/p&gt;

&lt;p&gt;Next up: A practical threshold for deciding when to add a new fixture versus mutate an existing one.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Fixtures on Purpose: Telling Stories With Data</title>
   <link href="https://blowmage.com/2026/04/03/telling-stories-with-data/"/>
   <updated>2026-04-03T00:00:00+00:00</updated>
   <id>https://blowmage.com/2026/04/03/telling-stories-with-data</id>
   <content type="html">&lt;p&gt;&lt;em&gt;Part 3 of &lt;a href=&quot;/2026/03/30/fixtures-on-purpose/#the-series&quot;&gt;Fixtures on Purpose&lt;/a&gt;, a series about designing test data for Rails.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;a href=&quot;/2026/04/01/fixture-personas/&quot;&gt;last post&lt;/a&gt; we applied user personas to how we design fixtures.
Named, archetypal organizations in your test dataset that represent clusters of real-world configurations.
Riverside Health is the full-featured store.
Corner Shop is the simple retailer.
Fresh Start is the empty canvas.&lt;/p&gt;

&lt;p&gt;But I only showed the member fixtures.
A persona isn’t just users.
It’s the whole world.
Products, orders, payments, configurations, the works.
Designing that world so it tells a coherent story is the craft part.&lt;/p&gt;

&lt;p&gt;This is the part I enjoy the most.&lt;/p&gt;

&lt;h2 id=&quot;riverside_vip-has-a-life-story-customer_2-does-not&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_vip&lt;/code&gt; Has a Life Story. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;customer_2&lt;/code&gt; Does Not.&lt;/h2&gt;

&lt;p&gt;The best fixture files read like the setup chapter of a novel.
They introduce characters and establish relationships.
They create a world that the tests inhabit.&lt;/p&gt;

&lt;p&gt;The difference between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;customer_2&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_vip&lt;/code&gt; isn’t just aesthetics.
It’s the difference between a row in a table and a character in a story.
When I read &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_vip&lt;/code&gt; I know who this person is, what they do, and why they exist in the test data.
They’re a wholesale customer at an established supplements company.
They get tier pricing.
They have order history.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;customer_2&lt;/code&gt; tells me nothing.
I have to go read the YAML file, and even then I might not understand &lt;em&gt;why&lt;/em&gt; this record exists or which tests depend on it.&lt;/p&gt;

&lt;p&gt;So how do you design a persona’s story? I think of it in layers.&lt;/p&gt;

&lt;h2 id=&quot;layer-1-the-archetype&quot;&gt;Layer 1: The Archetype&lt;/h2&gt;

&lt;p&gt;Start with a sentence.
What kind of business is this persona?&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Riverside Health:&lt;/strong&gt; “An established supplements company with multiple warehouses, international markets, and a wholesale program.”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Corner Shop:&lt;/strong&gt; “A small online store that sells a few products domestically.”&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Fresh Start:&lt;/strong&gt; “A store that was created yesterday.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That sentence drives everything.
Every fixture you add should fit the sentence.
If you’re adding international shipping configurations to Corner Shop, stop.
That doesn’t fit the archetype.&lt;/p&gt;

&lt;h2 id=&quot;layer-2-the-cast&quot;&gt;Layer 2: The Cast&lt;/h2&gt;

&lt;p&gt;Who are the characters in this persona’s story? Not “how many records do I need,” but “who are these people and what do they do?”&lt;/p&gt;

&lt;p&gt;For Riverside Health, the cast might be:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Character&lt;/th&gt;
      &lt;th&gt;Role&lt;/th&gt;
      &lt;th&gt;Story&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_owner&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Owner&lt;/td&gt;
      &lt;td&gt;Runs the business. Full admin access.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_manager&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Manager&lt;/td&gt;
      &lt;td&gt;Day-to-day operations. Limited admin.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_customer&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Customer&lt;/td&gt;
      &lt;td&gt;Regular retail buyer. Has order history.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_vip&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;VIP/Wholesale&lt;/td&gt;
      &lt;td&gt;Wholesale account. Gets tier pricing.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Four users.
That’s enough to test role-based access control, pricing tiers, and customer management flows.
You don’t need 15 users.
You need enough to tell the story.&lt;/p&gt;

&lt;p&gt;For Corner Shop:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Character&lt;/th&gt;
      &lt;th&gt;Role&lt;/th&gt;
      &lt;th&gt;Story&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;corner_shop_owner&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;Owner&lt;/td&gt;
      &lt;td&gt;The one person running the show.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;One user.
That’s the story.
Simple retail, one owner.
The absence of other users &lt;em&gt;is&lt;/em&gt; the story.
There’s no staff to manage, no wholesale accounts to configure.&lt;/p&gt;

&lt;h2 id=&quot;layer-3-relationships&quot;&gt;Layer 3: Relationships&lt;/h2&gt;

&lt;p&gt;Now connect the characters.
How do they relate to each other, and how does that connect across your domain models?&lt;/p&gt;

&lt;p&gt;For Riverside, the relationships span multiple models and multiple fixture files:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;riverside_owner (user, admin access)
riverside_customer (user, placed orders)
  └── riverside_order_one (order, placed by customer)
        └── riverside_order_item (order item, Omega-3 product)
              └── riverside_payment (payment, Visa card)
riverside_vip (user, wholesale pricing)
  └── riverside_wholesale_order (order, tier-priced)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each level in this chain is a separate fixture file.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users.yml&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;orders.yml&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;order_items.yml&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;payments.yml&lt;/code&gt;.
But they tell one continuous story.
The customer placed an order for Omega-3, paid with Visa.
The VIP placed a wholesale order at tier pricing.
The fixtures reference each other by name, and the names make the chain readable.&lt;/p&gt;

&lt;h2 id=&quot;layer-4-the-product-world&quot;&gt;Layer 4: The Product World&lt;/h2&gt;

&lt;p&gt;Products deserve special attention because they’re where the “realistic but minimal” balance is trickiest.&lt;/p&gt;

&lt;p&gt;I aim for a product catalog that’s large enough to test real behavior but small enough that every fixture has a name and a purpose:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Riverside Health products:&lt;/strong&gt;
| Product | Variants | Why It Exists |
|—|—|—|
| Omega-3 Plus | 4 (default, 30ct, 60ct, 120ct) | Flagship. Multi-variant. Used in most order fixtures. |
| Daily Moisturizer | 3 (default, 1oz, 2oz) | Second product with variants. Size-based options. |
| Probiotic Complex | 1 (default) | Single-variant product. The common case. |
| Vitamin D3 | 1 (default) | Budget product. Different price point. |
| Draft Product | 1 (default) | &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;status: draft&lt;/code&gt;. For publish/unpublish workflow tests. |&lt;/p&gt;

&lt;p&gt;Most real products have one variant.
A few have 2-4.
Almost none have 20.
The fixture catalog should reflect that distribution.
Riverside has a handful of multi-variant products for testing variant logic, and a bunch of single-variant products for everything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Corner Shop products:&lt;/strong&gt;
| Product | Variants | Why It Exists |
|—|—|—|
| Omega-3 Fish Oil | 1 (default) | Featured product. |
| Whey Protein | 1 (default) | Second product. That’s enough. |&lt;/p&gt;

&lt;p&gt;Two products.
Simple store.
The catalog matches the archetype.&lt;/p&gt;

&lt;h2 id=&quot;layer-5-default-to-success&quot;&gt;Layer 5: Default to Success&lt;/h2&gt;

&lt;p&gt;Every fixture should represent the happy path.
Active users, completed orders, valid payment methods.
This is important because it makes mutations obvious.&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;riverside_order_one&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;riverside&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;riverside_customer&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;order_number&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;RH-10001&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;completed&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;financial_status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;paid&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;fulfillment_status&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fulfilled&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;total_cents&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;5998&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When a test needs a pending order, the mutation is visible and intentional:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_cannot_fulfill_unpaid_order&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_order_one&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;financial_status:   &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pending&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;ss&quot;&gt;fulfillment_status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;unfulfilled&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;FulfillmentProcessor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;store: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:riverside&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;fulfill&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;order: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;assert_not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The reader can see at a glance: we started with Riverside’s default order (completed, paid, fulfilled) and changed two things.
The changes &lt;em&gt;are&lt;/em&gt; the test’s preconditions.
If the fixture were already in the pending state, you’d have to go read the YAML to understand the starting point.
But with happy-path defaults, the persona implies the starting point and the deviations are right there in the test.&lt;/p&gt;

&lt;h2 id=&quot;riverside_-everywhere&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_&lt;/code&gt; Everywhere&lt;/h2&gt;

&lt;p&gt;Every fixture in a persona’s story uses the persona name as a prefix:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;stores&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_vip&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_order_one&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;products&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_omega&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;variants&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_omega_default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The prefix creates instant context.
When you’re reading a test and see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;riverside_&lt;/code&gt; everywhere, you know this is the full-featured world.
When you see &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;corner_shop_&lt;/code&gt;, you know it’s the simple retail world.&lt;/p&gt;

&lt;p&gt;Reference data (countries, currencies, languages) doesn’t get a prefix.
It’s the shared backdrop:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# currencies.yml&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;usd&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;USD&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;US Dollar&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Reference data is the scenery.
Persona data is the cast.&lt;/p&gt;

&lt;h2 id=&quot;no-scrolling-no-guessing&quot;&gt;No Scrolling. No Guessing.&lt;/h2&gt;

&lt;p&gt;I said in &lt;a href=&quot;/2026/03/30/fixtures-on-purpose/&quot;&gt;the first post&lt;/a&gt; that I want everything important visible in the test method.
No scrolling up 60 lines to find a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subject&lt;/code&gt; definition.
No chasing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; chains through nested contexts.
No guessing.&lt;/p&gt;

&lt;p&gt;What I’m really talking about is &lt;a href=&quot;https://wiki.c2.com/?ArrangeActAssert&quot;&gt;Arrange, Act, Assert&lt;/a&gt;.
It’s one of the oldest patterns in testing, and I end up teaching it over and over because so much test tooling actively works against it.
A good test should show you three things, in order, in one place:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Arrange.&lt;/strong&gt; What’s the world look like before we do anything?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Act.&lt;/strong&gt; What are we doing?&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Assert.&lt;/strong&gt; What should be true now?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it.
If you can read a test method top to bottom and clearly see all three, the test is doing its job.
If you have to scroll up 80 lines to find the Arrange, or chase three &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; chains to reconstruct the Act, the test has failed as communication.
It might still pass green.
But nobody can read it.&lt;/p&gt;

&lt;p&gt;Fixture personas make clean Arrange-Act-Assert possible in a way that neither factories nor shared RSpec contexts do.
The persona name &lt;em&gt;is&lt;/em&gt; the Arrange.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;customers(:riverside_vip)&lt;/code&gt; tells you the world.
A mutation like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update! loyalty_tier: &quot;standard&quot;&lt;/code&gt; tells you what’s different from the default.
Everything the test needs to communicate is local and in order.
(That’s the whole trick, honestly.)&lt;/p&gt;

&lt;p&gt;Compare that to a typical RSpec file:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# 80 lines up...&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:vip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;store: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:with_wholesale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;described_class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;store: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;calculate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;order: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# ... 80 lines of other contexts ...&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;applies wholesale pricing&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;total_cents&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;4499&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Where’s the Arrange? Scattered across &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; blocks 80 lines above.
Where’s the Act? Hidden inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subject&lt;/code&gt;.
Where’s the Assert? That’s the only part you can actually see.
This isn’t Arrange-Act-Assert.
It’s Hunt-Guess-Assert.&lt;/p&gt;

&lt;p&gt;With fixtures:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_vip_gets_wholesale_pricing&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Arrange&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;vip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_vip&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;product&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;products&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_omega&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Act&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PriceCalculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;store: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:riverside&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;calculator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;calculate&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;customer: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;product: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;product&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Assert&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;assert_equal&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4499&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;total_cents&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;All three.
One method.
Top to bottom.
The persona provides the Arrange, the service call is the Act, and the assertions are the Assert.
No scrolling.
No guessing.&lt;/p&gt;

&lt;h2 id=&quot;the-accidental-documentation&quot;&gt;The Accidental Documentation&lt;/h2&gt;

&lt;p&gt;Something nice happens when your fixture files tell coherent stories.
A new developer can open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/fixtures/customers.yml&lt;/code&gt; and understand the business.
They can see the roles and loyalty tiers, the relationships between users and stores.
They can open &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;products.yml&lt;/code&gt; and see what a realistic catalog looks like.&lt;/p&gt;

&lt;p&gt;The YAML files become a map of the domain.
I didn’t set out to write documentation.
I set out to write good test data.
(Turns out they’re the same thing.)&lt;/p&gt;

&lt;p&gt;Next up: How to discover your personas in production data instead of making them up.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Fixtures on Purpose: Fixture Personas</title>
   <link href="https://blowmage.com/2026/04/01/fixture-personas/"/>
   <updated>2026-04-01T00:00:00+00:00</updated>
   <id>https://blowmage.com/2026/04/01/fixture-personas</id>
   <content type="html">&lt;p&gt;&lt;em&gt;Part 2 of &lt;a href=&quot;/2026/03/30/fixtures-on-purpose/#the-series&quot;&gt;Fixtures on Purpose&lt;/a&gt;, a series about designing test data for Rails.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;a href=&quot;/2026/03/30/the-combinatorial-trap/&quot;&gt;last post&lt;/a&gt; I made the case that you can’t create a fixture for every combination of data states.
The math doesn’t work.
What you need instead are a small number of well-chosen archetypes that tests can mutate into edge cases.&lt;/p&gt;

&lt;p&gt;But how do you choose the archetypes? How do you decide which configurations deserve a permanent place in your fixture dataset and which should be reached through mutation?&lt;/p&gt;

&lt;p&gt;Turns out, UX designers solved this problem in 1999.&lt;/p&gt;

&lt;h2 id=&quot;fixtures-that-dont-make-us-crazy&quot;&gt;Fixtures That Don’t Make Us Crazy&lt;/h2&gt;

&lt;p&gt;Alan Cooper introduced personas in &lt;em&gt;&lt;a href=&quot;https://www.amazon.com/Inmates-Are-Running-Asylum-Products-ebook/dp/B000OZ0N62?tag=blowmage-20&quot;&gt;The Inmates Are Running the Asylum&lt;/a&gt;&lt;/em&gt;.
(Great title. Also a fair description of most test suites.)
His problem was the same as ours, just in a different domain.
Too many users.
Too many configurations.
Too many workflows.
You can’t design a UI that works for every possible combination of user background and experience level.
The attempt produces something that works for nobody.&lt;/p&gt;

&lt;p&gt;Cooper’s solution was to observe that real users cluster.
They don’t distribute uniformly across every possible combination of attributes.
They clump into a small number of recognizable types.
The freelancer who uses the product weekly.
The enterprise admin who lives in it daily.
The new user on day one.&lt;/p&gt;

&lt;p&gt;Give each cluster a name.
Give it a face.
Make it memorable.
Now instead of designing for an abstract “user,” you’re designing for Fiona the Freelancer and Ed the Enterprise Admin.
The name carries implicit context.
Say “Fiona” and your whole team knows what you mean.
Her technical level, her goals.
Without restating it every time.&lt;/p&gt;

&lt;p&gt;I read Cooper’s book years ago and it changed how I thought about product design.
At some point I realized it also applies to test data.&lt;/p&gt;

&lt;h2 id=&quot;fixture-personas&quot;&gt;Fixture Personas&lt;/h2&gt;

&lt;p&gt;A fixture persona is a named organization (or user, or account) in your test data that represents a recognizable cluster of real-world configurations.&lt;/p&gt;

&lt;p&gt;Instead of this:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# What cluster does this represent? Who knows.&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;store_1&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Test Store&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;plan&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;premium&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;country&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;US&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;store_2&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Another Store&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;plan&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;basic&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;country&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;CA&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You have this:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;riverside&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Riverside Health&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;plan&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;premium&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;country&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;US&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Established supplements company.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Multiple warehouses, international markets,&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# wholesale pricing, subscriptions.&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;corner_shop&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Corner Shop&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;plan&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;basic&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;country&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;US&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Simple online store. Domestic only.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Small catalog, one warehouse.&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;fresh_start&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Fresh Start&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;plan&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;starter&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;country&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;US&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Brand new store. Almost empty.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Just signed up. Testing onboarding and zero-state.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Three personas.
Three stories.
Each represents a different cluster of real customers.&lt;/p&gt;

&lt;h2 id=&quot;member_2-is-not-a-name&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:member_2&lt;/code&gt; Is Not a Name&lt;/h2&gt;

&lt;p&gt;This isn’t cosmetic.
The names do real work.&lt;/p&gt;

&lt;p&gt;When you read &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;customers(:riverside_vip)&lt;/code&gt; in a test, you immediately know the context.
Riverside is the full-featured store.
A VIP is a loyalty program member with order history and wholesale pricing.
You don’t need to look up the fixture file.
You don’t need to read six lines of setup.
The name is a cognitive handle that compresses the entire archetype into a single word.&lt;/p&gt;

&lt;p&gt;Compare that to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;customers(:customer_2)&lt;/code&gt;.
What does &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:customer_2&lt;/code&gt; tell you? Nothing.
Is this a retail buyer or a wholesale account? What store? What configuration? You have to go look it up, and when you get there, you still might not know &lt;em&gt;why&lt;/em&gt; this specific customer was chosen for this specific test.&lt;/p&gt;

&lt;p&gt;Good fixture names carry implicit context the same way Cooper’s personas do.
Say “Riverside” and you know it’s the complex store.
Say “Corner Shop” and you know it’s the simple one.
Say “Fresh Start” and you know it’s empty.
The name does the work that six lines of comments would otherwise have to do.&lt;/p&gt;

&lt;h2 id=&quot;three-to-five-thats-it&quot;&gt;Three to Five. That’s It.&lt;/h2&gt;

&lt;p&gt;Here’s how I think about it.
You need enough personas to cover the major behavioral archetypes, and not one more.
For most Rails apps, that’s 3-5.&lt;/p&gt;

&lt;p&gt;Each persona should be:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distinct.&lt;/strong&gt; If two personas would produce the same behavior in most tests, merge them.
The point of having multiple personas is that they exercise different code paths.
If your “Gold Plan” and “Silver Plan” personas behave identically for 95% of your tests, you only need one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Minimal.&lt;/strong&gt; A persona should include only what its archetype genuinely needs.
The simple retail store doesn’t need wholesale pricing fixtures or international shipping configurations.
The empty store doesn’t need a product catalog.
The &lt;em&gt;absence&lt;/em&gt; of data is part of the story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coherent.&lt;/strong&gt; The attributes should tell a believable story.
A brand-new store has one admin, no order history, and maybe a couple of products.
An established business has multiple staff members, a full catalog, warehouses, payment methods, and a subscription program.
When the attributes hang together logically, you can &lt;em&gt;infer&lt;/em&gt; unstated attributes from the archetype.
You don’t need to check every field.
The story fills in the gaps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Named.&lt;/strong&gt; Memorable, consistent, used everywhere.
The name becomes part of your team’s vocabulary.
“Use the Riverside fixtures for that test” is a sentence that means something.&lt;/p&gt;

&lt;h2 id=&quot;okay-show-me&quot;&gt;Okay, Show Me&lt;/h2&gt;

&lt;p&gt;Say you’re building an e-commerce platform where merchants set up stores and sell products.
Your personas might be:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Persona&lt;/th&gt;
      &lt;th&gt;Archetype&lt;/th&gt;
      &lt;th&gt;Story&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Riverside Health&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Full-featured store&lt;/td&gt;
      &lt;td&gt;Established supplements company. 20+ products, multiple warehouses, international markets, wholesale pricing, subscriptions. The “everything” persona.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Corner Shop&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;Simple retail&lt;/td&gt;
      &lt;td&gt;Small business selling a few products online. One warehouse, domestic shipping, basic catalog. The “just commerce” persona.&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Fresh Start&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;New/empty store&lt;/td&gt;
      &lt;td&gt;Just signed up. One admin. Almost no data. The “zero state” persona.&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Riverside’s fixtures tell a story about a real business:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;riverside_owner&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;riverside&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;owner@riverside.test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;owner&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Runs the business. Full admin access.&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;riverside_manager&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;riverside&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;manager@riverside.test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;manager&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Manages day-to-day. Limited admin access.&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;riverside_customer&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;riverside&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;shopper@riverside.test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;customer&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Regular retail buyer with order history.&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;riverside_vip&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;riverside&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;vip@riverside.test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;customer&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;loyalty_tier&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;wholesale&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Wholesale customer. Gets tier pricing.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Corner Shop’s fixtures tell a simpler story:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;corner_shop_owner&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;corner_shop&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;owner@cornershop.test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;owner&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Just the owner. Small operation.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Fresh Start’s fixtures are deliberately sparse:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;fresh_start_admin&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;fresh_start&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;admin@freshstart.test&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;role&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;owner&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# The only user. That&apos;s the point.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When a test needs to verify wholesale pricing, it uses Riverside.
When it needs basic commerce, it uses Corner Shop.
When it needs to test what happens on an empty store, it uses Fresh Start.
No fixture proliferation.
Pick the archetype, mutate if needed.&lt;/p&gt;

&lt;h2 id=&quot;but-doesnt-that-reduce-coverage&quot;&gt;“But Doesn’t That Reduce Coverage?”&lt;/h2&gt;

&lt;p&gt;Everyone expects this to cause more problems than it’s worth.
Using the persona approach must result in a trade-off, right? Less coverage for less complexity.&lt;/p&gt;

&lt;p&gt;It doesn’t.&lt;/p&gt;

&lt;p&gt;Well-designed personas with mutation actually cover &lt;em&gt;more&lt;/em&gt; ground than 200 ad-hoc fixtures, because the mutations are precise and visible in the test body.
With 200 ad-hoc fixtures, each captures one exact state.
With 3 personas and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update!&lt;/code&gt;, each persona is a starting point for hundreds of states.
And the test documents exactly which mutation matters for the behavior under test, right there where you can see it.&lt;/p&gt;

&lt;p&gt;Fewer fixtures.
More expressiveness.
Not a compromise.&lt;/p&gt;

&lt;h2 id=&quot;a-shared-vocabulary&quot;&gt;A Shared Vocabulary&lt;/h2&gt;

&lt;p&gt;There’s one more benefit I didn’t expect.
Personas give your team a shared language for talking about test data.&lt;/p&gt;

&lt;p&gt;Before personas, conversations about test setup were painful.
“Which user fixture do I use for this?” “The one with the admin role, not the other one, the one that has orders.” “Which one has orders?” Nobody could remember.
The fixtures were anonymous and interchangeable.&lt;/p&gt;

&lt;p&gt;After personas, the conversation changes.
“Use Riverside for that, it has the full catalog.” “Corner Shop is enough, you don’t need wholesale pricing.” “That’s a zero-state test, use Fresh Start.” Everyone knows what those names mean.
The personas become a shorthand that works everywhere.
Code reviews, Slack, pairing sessions.&lt;/p&gt;

&lt;p&gt;UX personas do this for product teams.
They give everyone a shared reference point.
“Would this work for Fiona?” is a faster conversation than “Would this work for a non-technical freelancer on the free plan who uses the product weekly?” Fixture personas do the same thing for engineering teams.
The names carry the context so you don’t have to.&lt;/p&gt;

&lt;p&gt;Next up: How to design a persona’s fixtures end-to-end so the YAML files read like a setup chapter.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Fixtures on Purpose: The Combinatorial Trap</title>
   <link href="https://blowmage.com/2026/03/30/the-combinatorial-trap/"/>
   <updated>2026-03-30T09:00:00+00:00</updated>
   <id>https://blowmage.com/2026/03/30/the-combinatorial-trap</id>
   <content type="html">&lt;p&gt;&lt;em&gt;Part 1 of &lt;a href=&quot;/2026/03/30/fixtures-on-purpose/#the-series&quot;&gt;Fixtures on Purpose&lt;/a&gt;, a series about designing test data for Rails.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’ve seen teams with 300 fixture records and climbing.
Every new feature gets a handful of new fixtures.
Every bug fix adds another special-case record.
The YAML files grow and nobody can explain what half the records are for.&lt;/p&gt;

&lt;p&gt;I’ve also seen teams with 30 fixture records that cover more ground.
Not because they test less.
Because they design better.&lt;/p&gt;

&lt;p&gt;The difference is math.&lt;/p&gt;

&lt;h2 id=&quot;59049-reasons-you-cant-win&quot;&gt;59,049 Reasons You Can’t Win&lt;/h2&gt;

&lt;p&gt;JB Rainsberger laid this out years ago in his &lt;a href=&quot;https://blog.thecodewhisperer.com/permalink/integrated-tests-are-a-scam&quot;&gt;“Integrated Tests Are a Scam”&lt;/a&gt; series.
(Worth reading in full if you haven’t. He’s been refining the argument for over a decade.)
His core observation is about code paths.
A system with 10 layers and an average of 3 branch points per layer has:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;3^10 = 59,049 unique code paths
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s a modest web app.
Bump it to 4 branches per layer and you’re over a million.
The growth is exponential.
You can’t test every path.
You definitely can’t write a fixture for every path.&lt;/p&gt;

&lt;p&gt;I’ve been thinking about this math in terms of test paths for years.
But at some point I realized it applies just as directly to fixture data, and that reframing it as a &lt;em&gt;data design&lt;/em&gt; problem, not just a test count problem, changes what you do about it.&lt;/p&gt;

&lt;h2 id=&quot;now-apply-that-to-your-fixtures&quot;&gt;Now Apply That to Your Fixtures&lt;/h2&gt;

&lt;p&gt;Every dimension of your data is a branch point.
Member status, order state, payment type, shipping configuration, subscription tier.
Each one multiplies the matrix:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;member status (active, suspended, pending)       = 3
order state (pending, paid, refunded, canceled)  = 4
payment type (card, bank, wallet)                = 3
shipping config (domestic, international, none)  = 3
subscription (monthly, annual, none)             = 3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s 3 x 4 x 3 x 3 x 3 = &lt;strong&gt;324 combinations&lt;/strong&gt;.
For five dimensions.
A real app has dozens.&lt;/p&gt;

&lt;p&gt;So what do teams do? They create fixtures for the important combinations.
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suspended_member_with_pending_order&lt;/code&gt; goes into the YAML.
Then &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active_member_with_refunded_order_international&lt;/code&gt;.
Before long you’re maintaining 150 fixture records, most of which exist for a single test.
Nobody can remember which is which.
(Sound familiar?)&lt;/p&gt;

&lt;h2 id=&quot;the-vortex-of-doom-for-data&quot;&gt;The Vortex of Doom (for Data)&lt;/h2&gt;

&lt;p&gt;Rainsberger describes a cycle he calls the “vortex of doom” for integrated tests.
It applies perfectly to fixture proliferation:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Tests need specific data states, so you add fixtures&lt;/li&gt;
  &lt;li&gt;More fixtures mean more maintenance. Rename a column, update 40 YAML files&lt;/li&gt;
  &lt;li&gt;Fixtures break in confusing ways because they depend on each other&lt;/li&gt;
  &lt;li&gt;Developers lose trust in the fixtures and start creating records inline&lt;/li&gt;
  &lt;li&gt;Inline records are slow and duplicated, so someone adds more fixtures&lt;/li&gt;
  &lt;li&gt;Go to step 1&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The thing that feels like it’s helping is actually making the problem worse.
More fixtures create more maintenance, which creates less trust, which creates more fixtures.
It feeds itself.&lt;/p&gt;

&lt;h2 id=&quot;you-paid-for-one-path-out-of-thousands&quot;&gt;You Paid for One Path Out of Thousands&lt;/h2&gt;

&lt;p&gt;Here’s the uncomfortable truth about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suspended_member_with_pending_order_and_declined_payment&lt;/code&gt;: you’ve covered one path.
Out of thousands.
And you’ve paid for it with a fixture record that makes the whole dataset harder to understand.&lt;/p&gt;

&lt;p&gt;But what if you took a happy-path fixture and just… changed it?&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_suspended_customer_cannot_checkout&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;customers&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_vip&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;status: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;suspended&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;orders&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:riverside_order&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;update!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;state: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;pending&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CheckoutProcessor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;store: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;stores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:riverside&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;customer: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;customer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;order: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;order&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;assert_not&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;assert_equal&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;customer_suspended&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;error&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Two &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update!&lt;/code&gt; calls.
The fixture stays clean.
The test documents exactly what’s different about this scenario.
The edge case is visible right there in the test body, not buried in a YAML file that you’d have to go find.&lt;/p&gt;

&lt;h2 id=&quot;stop-multiplying-start-adding&quot;&gt;Stop Multiplying, Start Adding&lt;/h2&gt;

&lt;p&gt;I know we can do better than 324 fixture records.
Here’s how to think about it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiplicative approach:&lt;/strong&gt; one fixture per combination.
Grows as dimensions multiply.
3 x 4 x 3 x 3 x 3 = 324 fixtures for five dimensions.
Add a sixth dimension with 4 options and you’re at 1,296.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Additive approach:&lt;/strong&gt; one fixture per archetype, plus mutations per test.
Grows as dimensions are added.
4 archetypes + any edge case you can reach with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;update!&lt;/code&gt;.
Add a sixth dimension and you add nothing to the fixture count.&lt;/p&gt;

&lt;p&gt;The multiplicative approach can’t scale.
The additive approach can.
But the additive approach requires something the multiplicative approach doesn’t: you have to &lt;em&gt;choose your archetypes well&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Better tools won’t save you here.
No gem, no library, or clever DSL will solve a multiplicative problem with a multiplicative approach.
The thing that actually helps is thinking differently about what your fixture data is for.
It’s not a pile of rows that satisfies foreign keys.
It’s a small number of well-chosen archetypes that represent the real shapes of your domain.&lt;/p&gt;

&lt;p&gt;The hard part isn’t the YAML.
The hard part is deciding which archetypes matter.
And it turns out, UX designers figured that out decades ago.&lt;/p&gt;

&lt;p&gt;Next up: how UX designers figured out the archetype problem decades ago, and what it looks like when you borrow their methodology for test data.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Fixtures on Purpose: From Accident to Artifact</title>
   <link href="https://blowmage.com/2026/03/30/fixtures-on-purpose/"/>
   <updated>2026-03-30T08:00:00+00:00</updated>
   <id>https://blowmage.com/2026/03/30/fixtures-on-purpose</id>
   <content type="html">&lt;p&gt;I’ve been writing Rails tests for a long time.
I’ve argued about assertion styles and test structure, mocking strategies and naming conventions, and whether &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def test_&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test &quot;...&quot;&lt;/code&gt; is the One True Way.
(It’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;def test_&lt;/code&gt;. Obviously.)&lt;/p&gt;

&lt;p&gt;But the thing that changed my testing the most wasn’t any of that.
It was realizing that nobody, including me, was designing their test data.&lt;/p&gt;

&lt;h2 id=&quot;every-test-builds-its-own-universe&quot;&gt;Every Test Builds Its Own Universe&lt;/h2&gt;

&lt;p&gt;Here’s what most Rails tests look like:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_creates_order&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name:  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Test User&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                      &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;test@example.com&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;store&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name:  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Test Store&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                        &lt;span class=&quot;ss&quot;&gt;owner: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;product&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create!&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;store:       &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                            &lt;span class=&quot;ss&quot;&gt;title:       &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Widget&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                            &lt;span class=&quot;ss&quot;&gt;price_cents: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;999&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;creator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;OrderCreator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;store: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;store&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;creator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;member: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                          &lt;span class=&quot;ss&quot;&gt;items:  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;product:  &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;product&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;quantity: &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}]&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;success?&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Every test builds its own universe.
“Test User” buys a “Widget” from “Test Store.”
The names are meaningless.
The data exists only to satisfy foreign keys.&lt;/p&gt;

&lt;p&gt;Now multiply that by 500 tests.
You have 500 disposable universes, each with its own “Test User” and “Test Store” and “Widget.” Each universe is born, used once, and thrown away.
The test suite as a whole teaches you nothing about your domain.
It’s a pile of isolated assertions against throwaway data.&lt;/p&gt;

&lt;h2 id=&quot;cant-see-the-test-for-the-setup&quot;&gt;Can’t See the Test for the Setup&lt;/h2&gt;

&lt;p&gt;There’s a deeper problem with per-test data creation.
It drowns the signal in noise.&lt;/p&gt;

&lt;p&gt;When every test builds its own world from scratch, the reader has to parse six lines of setup to figure out which part actually matters.
Is the user’s name significant? Does the store need to be called “Test Store” or would any name work? Is the price of 999 cents meaningful or arbitrary?&lt;/p&gt;

&lt;p&gt;You can’t tell without reading the assertions, then going back to the setup, then reading the assertions again.
(Sound fun? It’s not.)&lt;/p&gt;

&lt;p&gt;And it gets worse over time.
A test that used to need a user and a product now needs a user, a store, a membership, a product, a variant, an inventory level, a payment method, and a shipping configuration.
Each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;create!&lt;/code&gt; call is another line of noise between you and the thing you’re actually testing.&lt;/p&gt;

&lt;p&gt;So here’s the question: do you design your test data, or do you just create it on the fly? Is everything important visible in the test, or hidden behind layers of setup?&lt;/p&gt;

&lt;h2 id=&quot;what-if-the-data-was-already-there&quot;&gt;What if the Data Was Already There?&lt;/h2&gt;

&lt;p&gt;Rails has had fixtures since the beginning.
YAML files in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/fixtures/&lt;/code&gt;, loaded once at the start of the test run, wrapped in a transaction for each test.
Every test starts with the same known dataset.&lt;/p&gt;

&lt;p&gt;Most teams abandoned them years ago, and honestly, I understand why.
The default experience was bad.
Fixture files full of anonymous records, no obvious organization, no story.
When your fixtures are &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users.yml&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:one&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;:two&lt;/code&gt;, there’s nothing there to love.&lt;/p&gt;

&lt;p&gt;But that’s a design problem, not a tool problem.&lt;/p&gt;

&lt;p&gt;What if your fixtures told a coherent story about your domain? A small cast of named characters with realistic relationships and states that mirror production? What if opening &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/fixtures/customers.yml&lt;/code&gt; taught you more about the business than reading a requirements doc?&lt;/p&gt;

&lt;p&gt;What if every test stopped building its own disposable universe and just… inhabited a shared one?&lt;/p&gt;

&lt;p&gt;I think we can do better.
I know we can, because I’ve done it.
This series is about treating your test data as a design artifact.&lt;/p&gt;

&lt;h2 id=&quot;the-series&quot;&gt;The Series&lt;/h2&gt;

&lt;p&gt;This is the first post in a series about designing test data for Rails.
I’ll update this list as new posts go up.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;/2026/03/30/the-combinatorial-trap/&quot;&gt;The Combinatorial Trap&lt;/a&gt;.
 Why you can’t create a fixture for every possible state, and why the math is worse than you think.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2026/04/01/fixture-personas/&quot;&gt;Fixture Personas&lt;/a&gt;.
 How UX designers solved the archetype problem in 1999, and what it looks like applied to test data.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2026/04/03/telling-stories-with-data/&quot;&gt;Telling Stories With Data&lt;/a&gt;.
 Designing a persona’s fixtures end-to-end so the YAML files read like a setup chapter.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2026/04/06/mining-production-for-personas/&quot;&gt;Mining Production for Personas&lt;/a&gt;.
 How to discover your personas in production data instead of making them up.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2026/04/07/the-rule-of-four/&quot;&gt;The Rule of Four&lt;/a&gt;.
 A practical threshold for deciding when to add a new fixture versus mutate an existing one.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2026/04/08/fixtures-as-documentation/&quot;&gt;Fixtures as Documentation&lt;/a&gt;.
 How well-designed fixtures become the best documentation of the domain model you have.&lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>New minitest-rails releases (5.0, 5.1, 5.2, 6.0.0.rc1)</title>
   <link href="https://blowmage.com/2019/06/18/minitest-rails-releases/"/>
   <updated>2019-06-18T00:00:00+00:00</updated>
   <id>https://blowmage.com/2019/06/18/minitest-rails-releases</id>
   <content type="html">&lt;p&gt;Rails 6.0 is just almost here, and new features means more things to test and more ways to test them! Did someone say &lt;a href=&quot;https://github.com/rails/rails/pull/31900&quot;&gt;“parallel testing”&lt;/a&gt;? Because of this I’ve taken the opportunity to make some changes to how &lt;a href=&quot;https://blowmage.com/minitest-rails/&quot;&gt;minitest-rails&lt;/a&gt; is structured.&lt;/p&gt;

&lt;h2 id=&quot;new-versions&quot;&gt;New Versions!&lt;/h2&gt;

&lt;p&gt;The first thing you’ll notice is a bunch of new releases. The previous releases was &lt;a href=&quot;https://blowmage.com/minitest-rails/v3.0.0&quot;&gt;3.0.0&lt;/a&gt;, but now there are new &lt;a href=&quot;https://rubygems.org/gems/minitest-rails/versions/5.0.0&quot;&gt;5.0.0&lt;/a&gt;, &lt;a href=&quot;https://rubygems.org/gems/minitest-rails/versions/5.1.0&quot;&gt;5.1.0&lt;/a&gt;, &lt;a href=&quot;https://rubygems.org/gems/minitest-rails/versions/5.2.0&quot;&gt;5.2.0&lt;/a&gt;, and a &lt;a href=&quot;https://rubygems.org/gems/minitest-rails/versions/6.0.0.rc1&quot;&gt;6.0.0.rc1&lt;/a&gt; releases. What is going on here?&lt;/p&gt;

&lt;p&gt;Well, one issue most every gem that plays in the Rails ecosystem has is managing compatibility between releases. Rails &lt;a href=&quot;https://guides.rubyonrails.org/maintenance_policy.html&quot;&gt;does not strictly follow SemVer&lt;/a&gt;, so it can become difficult to manage this complexity. What can you do? Well, after dealing with this for many years I eventually decided to punt and start using Rails’ shifted SemVer. This means that a Rails 5.2 app is going to use minitest-rails 5.2. And a Rails 5.1 app is going to use minitest-rails 5.1. With this change new gems have been released for all 5.x versions of Rails, as well as a pre-release gem for Rails 6.0.0.rc1. This means we will be able to narrow our runtime dependencies and respond quicker when issues arise.&lt;/p&gt;

&lt;h2 id=&quot;minor-compatibility-issues&quot;&gt;Minor Compatibility Issues&lt;/h2&gt;

&lt;p&gt;The move from Rails 4 to Rails 5 was a big step for tests. Several features such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActionController::TestCase&lt;/code&gt; and DOM assertions were deprecated. The minitest-rails 3.x release tried to balance between supporting working tests and supporting the new standards. But for the 5.x releases we are not as worried about the old ways. So some minor changes have been made.&lt;/p&gt;

&lt;p&gt;The signature for the expectation &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;must_change&lt;/code&gt; has changed from earlier versions of minitest-rails. This is because the expectation used to refer to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert_difference&lt;/code&gt; assertion, but has been changed to reference the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert_changes&lt;/code&gt; assertion added in Rails 5.1. Starting with minitest-rails 5.1 the expectation for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert_difference&lt;/code&gt; assertion is now &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;must_differ&lt;/code&gt;. Although it is very likely this change is backwards compatible.&lt;/p&gt;

&lt;p&gt;In minitest-rails 3.x the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;describe&lt;/code&gt; block could be passed the subject of the test as a string instead of using the actual Ruby constant. Now you should only pass the Ruby constant. This works:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WidgetsController&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But this does not:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;WidgetsController&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you want to pass a string, then you must provide an additional description to tell the Spec DSL what test class to use:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;WidgetsController&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:controller&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I chose not to update &lt;a href=&quot;https://blowmage.com/minitest-rails-capybara/&quot;&gt;minitest-rails-capybara&lt;/a&gt; at this time. This is due to Rails 5.1 adding System Tests, which are very similar to, if not better than, the Capybara tests. So, if you need to remain on Rails 5.0 and really want Capybara tests, you should continue to use minitest-rails 3.0.&lt;/p&gt;

&lt;h2 id=&quot;towards-the-future&quot;&gt;Towards the Future!&lt;/h2&gt;

&lt;p&gt;Exciting things are coming. Rails 6.0. (&lt;a href=&quot;https://rubygems.org/gems/minitest-rails/versions/6.0.0.rc1&quot;&gt;minitest-rails 6.0.0.rc1&lt;/a&gt; is already here!) After that Minitest 6.0 is going to eventually be released. And who knows what else? Perhaps a book on Minitest and Rails? Until then, if you have any problems or questions with minitest-rails please &lt;a href=&quot;https://github.com/blowmage/minitest-rails/issues/new&quot;&gt;open an issue&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Writing Games with Ruby</title>
   <link href="https://blowmage.com/2015/04/17/writing-games-ruby/"/>
   <updated>2015-04-17T00:00:00+00:00</updated>
   <id>https://blowmage.com/2015/04/17/writing-games-ruby</id>
   <content type="html">&lt;p&gt;The promise of making games is what brought many of us to programming. Games are fun, and writing games is fun. Crazy fun in fact. But isn’t game programming hard? Yes. But also no. If you can code a web app you can make a game, especially with an awesome language like Ruby.&lt;/p&gt;

&lt;p&gt;I’ve compiled a bunch of resources to get you started.&lt;/p&gt;

&lt;h2 id=&quot;my-awesome-presentation&quot;&gt;My (awesome) presentation&lt;/h2&gt;

&lt;p&gt;Last year I presented on writing games with Ruby using the wonderful &lt;a href=&quot;https://libgosu.org/&quot;&gt;Gosu&lt;/a&gt; library. It goes through the basics of game programming, the Gosu API, and shows some examples of what is possible. My goal for this presentation was to give folks with no game development experience the tools to build a game. So, if that is you, then this is a good place to start..&lt;/p&gt;

&lt;div class=&quot;video-player widescreen&quot;&gt;
&lt;iframe width=&quot;704&quot; height=&quot;424&quot; src=&quot;https://www.youtube.com/embed/jJhbpY70miE?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;script async=&quot;&quot; class=&quot;speakerdeck-embed&quot; data-id=&quot;46b62490959d0131cd83465820109070&quot; data-ratio=&quot;1.33333333333333&quot; src=&quot;//speakerdeck.com/assets/embed.js&quot;&gt;&lt;/script&gt;

&lt;h2 id=&quot;buy-the-book-or-read-online&quot;&gt;Buy the book (or read online)&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://varaneckas.com/&quot;&gt;Tomas Varaneckas&lt;/a&gt; has written a &lt;a href=&quot;https://leanpub.com/developing-games-with-ruby&quot;&gt;fantastic ebook&lt;/a&gt; on making games with Ruby. You should &lt;a href=&quot;https://leanpub.com/developing-games-with-ruby&quot;&gt;buy it&lt;/a&gt;. But if you don’t buy it, you should at least &lt;a href=&quot;https://leanpub.com/developing-games-with-ruby/read&quot;&gt;read it&lt;/a&gt; online for free online. No, seriously, if you’ve gotten this far do yourself a favor and read the book. It will help.&lt;/p&gt;

&lt;h2 id=&quot;read-lots-of-ruby-game-code&quot;&gt;Read lots of Ruby game code&lt;/h2&gt;

&lt;p&gt;One of the best things about the Gosu community is that there are so many examples you can learn from on GitHub. Tinkering with open-source games is a great way to understand how games work. You learn not just how to write better games, but how to write your games better. I’ve compiled a list of some games for you to peruse.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/jlnr/gosu/tree/master/examples&quot;&gt;Gosu Examples&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/blowmage/rubyhop&quot;&gt;Ruby Hop&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/blowmage/escape_to_rubyconf&quot;&gt;Escape to RubyConf&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/blowmage/littlebrat&quot;&gt;Little Brat&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/spajus/tank_island&quot;&gt;Tank Island&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.libgosu.org/cgi-bin/mwf/board_show.pl?bid=2&quot;&gt;Gosu Showcase&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;have-fun&quot;&gt;Have fun!&lt;/h2&gt;

&lt;p&gt;If you’re not having fun then you’re doing it wrong. Seriously. Force yourself to have fun if you have to, but have some fun.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/rubyfunwrong.png&quot; alt=&quot;If you&apos;re not having fun then you&apos;re doing it wrong&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Stupid Ruby Tricks</title>
   <link href="https://blowmage.com/2015/03/30/stupid-ruby-tricks/"/>
   <updated>2015-03-30T00:00:00+00:00</updated>
   <id>https://blowmage.com/2015/03/30/stupid-ruby-tricks</id>
   <content type="html">&lt;p&gt;I like Ruby. Since you are reading this I assume you also like Ruby. Ruby is great. And you are great. What better way to show our appreciation to Ruby than to discuss some stupid ruby tricks like I did at &lt;a href=&quot;https://ruby.onales.com/&quot;&gt;Ruby on Ales 2015&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&quot;video-player widescreen&quot;&gt;
&lt;iframe width=&quot;704&quot; height=&quot;424&quot; src=&quot;//www.youtube.com/embed/3HlXKB695GU?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;script async=&quot;&quot; class=&quot;speakerdeck-embed&quot; data-id=&quot;9359f2e20da34d11bf1f6ace53dcfdbc&quot; data-ratio=&quot;1.77777777777778&quot; src=&quot;//speakerdeck.com/assets/embed.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;Shoutout to &lt;a href=&quot;https://twitter.com/jsullivandigs&quot;&gt;Josh Sullivan&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/fittyfent&quot;&gt;Colton Fent&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/kobier&quot;&gt;Coby Randquist&lt;/a&gt; for organizing the conference and inviting me. They are the ones who made this happen. They are the real villans here, not me.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Jim Weirich: My Mentor</title>
   <link href="https://blowmage.com/2014/02/20/jimweirich-mentor/"/>
   <updated>2014-02-20T00:00:00+00:00</updated>
   <id>https://blowmage.com/2014/02/20/jimweirich-mentor</id>
   <content type="html">&lt;p&gt;Let me tell you of an inside joke that plays out at Ruby conferences. When I find myself in a group discussion that includes Jim Weirich I casually mention how Jim is my unwitting mentor and how I am his biggest cyber stalker. Jim shifts uncomfortably and gracefully changes the subject. Okay, this is perhaps less of an inside joke and more of a sick game. Whatever you call it, it’s what I do. And the worst part is that it’s 100% true.&lt;/p&gt;

&lt;h2 id=&quot;jim-weirich-is-my-unwitting-mentor-and-i-am-his-biggest-cyber-stalker&quot;&gt;Jim Weirich is my unwitting mentor and I am his biggest cyber stalker.&lt;/h2&gt;

&lt;p&gt;When I was coming up in the Ruby world I already had a decade of programming experience. In my first decade whatever I lacked I made up for with hard work and some innate skill. And while I learned from several wonderful people along the way, I never had anyone who took me under their care, teaching and guiding me in my career. I wanted that, but never had it. And it wasn’t until I found Ruby and Jim that I ever got it. Jim, coincidentally, wasn’t aware of this at the time.&lt;/p&gt;

&lt;p&gt;Jim is one of the best teachers I’ve ever met. He exudes empathy. He has the ability to understand how the pieces of what he is presenting will click in your mind. And then he knows how to challenge your new understanding to help you see an even bigger picture. And no matter how good your code is he knows how it can be a bit better. And no matter how well you know something he can teach you a just little more. He loves programming and he loves programmers.&lt;/p&gt;

&lt;p&gt;And Jim is incredibly giving of his time and talents. Back when I was learning Ruby he had all of his &lt;a href=&quot;http://onestepback.org/articles/&quot;&gt;presentation materials&lt;/a&gt; online. Sometimes even &lt;a href=&quot;https://web.archive.org/web/20070531035204/http://www.rubynuby.org/downloads/XPCincinnatiJanuary2006-medium.mov&quot;&gt;videos&lt;/a&gt; of &lt;a href=&quot;http://www.onestepback.org/index.cgi/News/IntroToRailsMovieAvailable.red&quot;&gt;his presentations&lt;/a&gt;. (Back in the dark ages before Confreaks having a video of your presentation was rather rare.) His openness enabled me to learn and grow. I was, and continue to be, fascinated by Jim’s presentation style and his approach to teaching. I watched his presentations over and over, hoping to absorb some of his awesomeness by osmosis.&lt;/p&gt;

&lt;p&gt;Am I Jim’s biggest cyber stalker? I don’t know. But because of how seriously I studied his materials I do consider him to be my mentor. From those early days of poring over his slides and code to force myself into new ways of thinking I have had the pleasure to get to know Jim in real life. All the things that have been said about Jim are true: he is a prolific coder, has a cunning mind, and a caring heart. My early goal was to be like him, but I found it was much more satisfying to be his friend.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; lang=&quot;en&quot;&gt;&lt;p&gt;Dinner and a comic book movie with &lt;a href=&quot;https://twitter.com/jimweirich&quot;&gt;@jimweirich&lt;/a&gt;!?! I am the luckiest geek in the world!&lt;/p&gt;&amp;mdash; Mike Moore (@blowmage) &lt;a href=&quot;https://twitter.com/blowmage/statuses/233797236981641217&quot;&gt;August 10, 2012&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;In the time since then I have been lucky enough to speak at several Ruby conferences. I have a few libraries that enjoy a modicum of notoriety. And I organize the MWRC conference that has contributed over one hundred conference session recordings available online for free. And in all that time I have merely been following Jim’s footsteps.&lt;/p&gt;

&lt;p&gt;Jim Weirich is the best of us. He is the superlative example of the niceness that Ruby is known for. And last night he passed away and I will never see him again.&lt;/p&gt;

&lt;p&gt;I don’t know Jim’s family, but I do know that he loved them. He beamed with pride whenever he talked about them. As best I could tell he enjoyed his family to the fullest. I wish I did know them so I could tell them in person how much Jim means to me. I lost both my parents around 10 years ago, and my wound from that loss is still open. I can only imagine their grief today. My thoughts and prayers are with his family, and those close to him.&lt;/p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; lang=&quot;en&quot;&gt;&lt;p&gt;Me and &lt;a href=&quot;https://twitter.com/jimweirich&quot;&gt;@jimweirich&lt;/a&gt;.&amp;#10;&lt;a href=&quot;https://twitter.com/search?q=%23rubyfriends&amp;amp;src=hash&quot;&gt;#rubyfriends&lt;/a&gt; &lt;a href=&quot;http://t.co/UiDbk2YD&quot;&gt;pic.twitter.com/UiDbk2YD&lt;/a&gt;&lt;/p&gt;&amp;mdash; Mike Moore (@blowmage) &lt;a href=&quot;https://twitter.com/blowmage/statuses/264845026952101888&quot;&gt;November 3, 2012&lt;/a&gt;&lt;/blockquote&gt;
&lt;script async=&quot;&quot; src=&quot;//platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;/script&gt;

&lt;p&gt;Thank you Jim for the influence you have had on my life. I treasure our friendship more than you will ever know.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Adding Minitest Spec in Rails 4</title>
   <link href="https://blowmage.com/2013/07/08/minitest-spec-rails4/"/>
   <updated>2013-07-08T00:00:00+00:00</updated>
   <id>https://blowmage.com/2013/07/08/minitest-spec-rails4</id>
   <content type="html">&lt;p&gt;Rails 4 is out, and among its many improvements is upgrading the default testing library from Test::Unit to Minitest. And although Minitest has some surprisingly interesting features, the most discussed addition is its spec DSL. It is designed as a subset of RSpec’s DSL, though I’ll leave to others any direct comparisons to RSpec. Suffice it to say it its focus is to give you a friendly syntax to generate the test classes, methods, and assertions you’d normally write in plain Ruby.&lt;/p&gt;

&lt;p&gt;It’ll take a little configuration, and yes, &lt;a href=&quot;https://blowmage.com/minitest-rails/&quot;&gt;there’s a gem for that&lt;/a&gt;, but the DIY approach takes surprisingly little elbow grease and will teach you a couple of cool Minitest features. Let’s dive in!&lt;/p&gt;

&lt;h2 id=&quot;step-1-setting-the-minitest-dependency&quot;&gt;Step 1: Setting the Minitest Dependency&lt;/h2&gt;

&lt;p&gt;Rails 4 sets the dependency on Minitest to “~&amp;gt; 4.2”. This means that it will use any Minitest 4.x release that is 4.2 or above. This also means that we can’t use the newly released Minitest 5, or the older 4.1. Since we want the spec DSL, we need to set the dependency to “~&amp;gt; 4.7”. To do that, let’s set the dependency in the Gemfile:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;minitest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;~&amp;gt; 4.7&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;step-2-extending-minitestspecdsl&quot;&gt;Step 2: Extending MiniTest::Spec::DSL&lt;/h2&gt;

&lt;p&gt;Minitest 4.7 introduced the MiniTest::Spec::DSL module. To add the spec DSL to our Rails tests, we’ll add this to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/test_helper.rb&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Let’s require the source file just after the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails/test_help&lt;/code&gt; require:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;RAILS_ENV&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;test&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;expand_path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;../../config/environment&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;__FILE__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;rails/test_help&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;minitest/spec&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The second change is to extend MiniTest::Spec::DSL in ActiveSupport::TestClass. Luckily for us, there is already a place in the helper for us to make these changes:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActiveSupport::TestCase&lt;/span&gt;
  &lt;span class=&quot;nc&quot;&gt;ActiveRecord::Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;check_pending!&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Note: You&apos;ll currently still have to declare fixtures explicitly in integration tests&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# -- they do not yet inherit this setting&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fixtures&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:all&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Add more helper methods to be used by all tests here...&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DSL&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Cool Minitest trick #1: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;register_spec_type&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The last change is to tell MiniTest::Spec to use ActiveSupport::TestCase when describing an ActiveRecord model. We do this by calling Minitest’s &lt;a href=&quot;http://rubydoc.info/gems/minitest/4.7.5/MiniTest/Spec/DSL#register_spec_type-instance_method&quot;&gt;register_spec_type&lt;/a&gt; method.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActiveSupport::TestCase&lt;/span&gt;
  &lt;span class=&quot;nc&quot;&gt;ActiveRecord::Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;check_pending!&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Note: You&apos;ll currently still have to declare fixtures explicitly in integration tests&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# -- they do not yet inherit this setting&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fixtures&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:all&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Add more helper methods to be used by all tests here...&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DSL&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;register_spec_type&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Class&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;step-3-writing-specs&quot;&gt;Step 3: Writing Specs&lt;/h2&gt;

&lt;p&gt;Now that we’ve configured the spec DSL, let’s use it! Let’s assume we have the following test in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/models/user_test.rb&lt;/code&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;test_helper&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserTest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TestCase&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;valid_params&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John Doe&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;john@example.com&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_valid&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valid_params&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Can&apos;t create with valid params: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_invalid_without_email&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valid_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clone&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;refute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Can&apos;t be valid without email&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Missing error when without email&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We can convert this test to the spec DSL one section at a time. Let’s start with replacing the class with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;describe&lt;/code&gt; block:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;test_helper&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;valid_params&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John Doe&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;john@example.com&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We can bypass the need to explicitly define a class inheriting from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveSupport::TestCase&lt;/code&gt; because User inherits from ActiveRecord and we registered the spec type in the previous step.&lt;/p&gt;

&lt;p&gt;Next we can replace the test methods with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;it&lt;/code&gt; blocks:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;valid_params&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John Doe&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;john@example.com&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;is valid with valid params&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valid_params&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Can&apos;t create with valid params: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;is invalid without an email&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valid_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clone&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;refute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Can&apos;t be valid without email&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Missing error when without email&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now let’s replace the calls to the assertions with Minitest’s expectations. In the first test block, we are passing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;user.valid?&lt;/code&gt; to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert&lt;/code&gt;. The spec DSL provides many assertions as expectations, and in this case we can write this test using the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;must_be&lt;/code&gt; expectation. That would look like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;is valid with valid params&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valid_params&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;must_be&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:valid?&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Must create with valid params&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In the next test block, we are refuting that the user is valid. We can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;wont_be&lt;/code&gt; expectation for that. And then we are asserting that there are errors on the email attribute. We can use a combination of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;must_be&lt;/code&gt; expectation and the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;present?&lt;/code&gt; method Rails adds to clean that up a bit:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;is invalid without an email&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;valid_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clone&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;wont_be&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:valid?&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;#Must not be valid without email&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;must_be&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:present?&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Must have error for missing email&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We can also move some helper methods to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;let&lt;/code&gt; blocks. In the end, here is what the test can look like using the spec DSL:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;test_helper&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John Doe&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;email: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;john@example.com&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;let&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user_params&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;is valid with valid params&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;must_be&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:valid?&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Must create with valid params&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;is invalid without an email&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Delete email before user let is called&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;wont_be&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:valid?&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Must not be valid without email&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;must_be&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:present?&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# Must have error for missing email&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;step-4-smoothing-the-rough-edges&quot;&gt;Step 4: Smoothing The Rough Edges&lt;/h2&gt;

&lt;p&gt;The Minitest spec DSL does not support nested &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;context&lt;/code&gt; blocks, but it does support nested &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;describe&lt;/code&gt; blocks. &lt;em&gt;Except…&lt;/em&gt; ActiveSupport::TestCase also defines a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;describe&lt;/code&gt; method, which stomps on the spec DSL. Oh no!&lt;/p&gt;

&lt;p&gt;But wait, this is Ruby! To use nested &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;describe&lt;/code&gt; blocks in your tests, we just need to remove the method from ActiveSupport::TestCase. To do this, add a call to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove_method&lt;/code&gt; just before MiniTest::Spec::DSL is added in the test helper:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActiveSupport::TestCase&lt;/span&gt;
  &lt;span class=&quot;nc&quot;&gt;ActiveRecord::Migration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;check_pending!&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;#&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Note: You&apos;ll currently still have to declare fixtures explicitly in integration tests&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# -- they do not yet inherit this setting&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;fixtures&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:all&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;# Add more helper methods to be used by all tests here...&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;remove_method&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:describe&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DSL&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;register_spec_type&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Class&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you prefer expectations to assertions, we’ll need to add expectations for the several assertions that Rails provides, such as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert_response&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert_redirected_to&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert_difference&lt;/code&gt;. We can do all this in the test helper file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cool Minitest trick #2: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;infect_an_assertion&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, create a new module and use the method &lt;a href=&quot;http://rubydoc.info/gems/minitest/4.7.5/Module#infect_an_assertion-instance_method&quot;&gt;infect_an_assertion&lt;/a&gt; that Minitest provides:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;MyApp::Expectations&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;infect_an_assertion&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:assert_difference&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:must_change&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;infect_an_assertion&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:assert_no_difference&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:wont_change&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then we can include that module in Object so that those expectations are available everywhere:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Object&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MyApp&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Expectations&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now we can use these expectations in our tests. Yay!&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;is able to be saved when valid&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;lambda&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;save&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;must_change&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;User.count&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;h2 id=&quot;thats-a-wrap&quot;&gt;That’s a Wrap!&lt;/h2&gt;

&lt;p&gt;As you can see, Minitest and Rails go hand in hand. I would even go so far as to say &lt;a href=&quot;https://blowmage.com/2013/05/29/minitest-rails-bffs&quot;&gt;they are BFFs, like I did in this presentation&lt;/a&gt;. I hope you give Minitest a shot. Don’t let its size fool you. It may be small, but it’s a surprisingly powerful, full-featured testing library.&lt;/p&gt;

&lt;p&gt;If this seems like too much configuration to manage on your own, that’s okay! Feel free to check out the &lt;a href=&quot;https://blowmage.com/minitest-rails/&quot;&gt;minitest-rails&lt;/a&gt; gem, which does all this for you, includes some handy rake tasks, and lets you generate tests using the spec DSL. Either way, hopefully you know a little more about the test framework that comes with Rails 4.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Minitest &amp; Rails: Total BFFs</title>
   <link href="https://blowmage.com/2013/05/29/minitest-rails-bffs/"/>
   <updated>2013-05-29T00:00:00+00:00</updated>
   <id>https://blowmage.com/2013/05/29/minitest-rails-bffs</id>
   <content type="html">&lt;p&gt;When I started in Ruby I was intimidated by testing. Partially because the Ruby culture identifies so strongly with testing, and partially because I had no idea what I was doing. I did it wrong more than I did it right. At one point I even gave up on testing because I didn’t know how to do it in a way that wouldn’t cost more time than not having tests at all. And then, suddenly, a switch went off in my head and I got it. I figured out how to test and how to design code so it could be tested. Then I figured out how to drive the design of my code with tests. Finally things made sense.&lt;/p&gt;

&lt;p&gt;Since then I’ve paired with a fair number of programmers and a common source of discussion has been testing. I’ve found that my experience of being intimidated by testing was not mine alone, and was more common than I ever thought. Over and over I talked to devs that knew they weren’t testing right but weren’t sure how to ask for help or even what questions to ask. What I’ve found is that everyone’s testing switch in their head is different. And the only way to flip it is to just keep testing and asking questions.&lt;/p&gt;

&lt;p&gt;So a couple weeks ago I gave a presentation at &lt;a href=&quot;http://2013.scottishrubyconference.com/&quot;&gt;Scottish Ruby Conf&lt;/a&gt; entitled &lt;a href=&quot;http://programme2013.scottishrubyconference.com/#proposal_44&quot;&gt;Minitest &amp;amp; Rails: Total BFFs”&lt;/a&gt;. I tried to cover the changes Rails 4 has made to its testing infrastructure and how you could enable Minitest features like the spec DSL. My hope is that it will help you flip the switch in your head, because you don’t have to be as hardcore as Ryan Davis or as visionary as DHH to use these tools.&lt;/p&gt;

&lt;p&gt;Also, there are ponies.&lt;/p&gt;

&lt;div class=&quot;video-player widescreen&quot;&gt;
&lt;iframe src=&quot;http://player.vimeo.com/video/67080930&quot; width=&quot;704&quot; height=&quot;424&quot; frameborder=&quot;0&quot; webkitallowfullscreen=&quot;&quot; mozallowfullscreen=&quot;&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt; &lt;p&gt;&lt;a href=&quot;http://vimeo.com/67080930&quot;&gt;1.2.1a. Mike Moore.mp4&lt;/a&gt; from &lt;a href=&quot;http://vimeo.com/edgecaseuk&quot;&gt;Neo (UK)&lt;/a&gt; on &lt;a href=&quot;http://vimeo.com&quot;&gt;Vimeo&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;

&lt;script async=&quot;&quot; class=&quot;speakerdeck-embed&quot; data-id=&quot;1e0ef7809e6b0130f97b020b62fb32fc&quot; data-ratio=&quot;1.77777777777778&quot; src=&quot;//speakerdeck.com/assets/embed.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;Huge thanks to &lt;a href=&quot;https://twitter.com/alancfrancis&quot;&gt;Alan Francis&lt;/a&gt; and &lt;a href=&quot;https://twitter.com/paulanthonywils&quot;&gt;Paul Wilson&lt;/a&gt; for throwing a wonderful conference and for accepting my proposal. Scotland made a pretty big impact on me and I can’t wait to return.&lt;/p&gt;

&lt;p&gt;Also, I want to explain why I presented the material this way, because it wasn’t arbitrary, but that will have to wait for another post.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>minitest-rails: Roadmap to 1.0</title>
   <link href="https://blowmage.com/2013/03/18/minitest-rails-roadmap/"/>
   <updated>2013-03-18T00:00:00+00:00</updated>
   <id>https://blowmage.com/2013/03/18/minitest-rails-roadmap</id>
   <content type="html">&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;: &lt;em&gt;The newest &lt;a href=&quot;https://github.com/seattlerb/minitest&quot;&gt;minitest&lt;/a&gt; and &lt;a href=&quot;https://github.com/blowmage/minitest-rails&quot;&gt;minitest-rails&lt;/a&gt; are awesome.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I’ve been working on the &lt;a href=&quot;https://blowmage.com/minitest-rails&quot;&gt;minitest-rails&lt;/a&gt; project for almost two years now. That is a very long time to work on a project without having a stable release. Fortunately with the &lt;a href=&quot;https://rubygems.org/gems/minitest-rails/versions/0.9.0&quot;&gt;0.9 release&lt;/a&gt;that I released earlier tonight we are on our way to stability.&lt;/p&gt;

&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;

&lt;p&gt;Inheritance is great, except when it isn’t. All Rails tests inherit from &lt;a href=&quot;https://github.com/rails/rails/blob/master/activesupport/lib/active_support/test_case.rb&quot;&gt;ActiveSupport::TestCase&lt;/a&gt;. In Rails 3, ActiveSupport::TestCase inherits from Test::Unit::TestCase, which inherits from MiniTest::Unit::TestCase. (In Ruby 1.9 and higher. If you are on Ruby 1.8 you can install the &lt;a href=&quot;https://github.com/seattlerb/minitest_tu_shim&quot;&gt;minitest_tu_shim&lt;/a&gt; gem to inject minitest into the inheritance chain.)&lt;/p&gt;

&lt;p&gt;What we want, however, is to inherit from MiniTest::Spec. In fact, for almost a year during the development of Rails 4 ActiveSupport::TestCase &lt;a href=&quot;https://github.com/rails/rails/commit/1c09c29a0958eac86fffede00f30a1bee36d09a9#L1L11&quot;&gt;inheritedd&lt;/a&gt; from MiniTest::Spec. Unfortunately, that was &lt;a href=&quot;https://github.com/rails/rails/commit/eb4930e3c724cf71d6ce5bb2aec4af82b2025b03#L4L19&quot;&gt;changed&lt;/a&gt; before the first beta gem was released and it now inherits directly from MiniTest::Unit::TestCase. (Which is disappointing but totally appropriate for the core team to do. It’s their project, and they should do what they think is best, always.) So what do we do? How can we change the nature of the Rails tests? Time to roll up our sleeves and get hacky!&lt;/p&gt;

&lt;h2 id=&quot;a-terrible-solution&quot;&gt;A Terrible Solution&lt;/h2&gt;

&lt;p&gt;One suboptimal solution is to change the ancestry of the ActiveSupport::TestCase and inject MiniTest::Spec. For &lt;a href=&quot;https://github.com/blowmage/minitest-rails&quot;&gt;minitest-rails&lt;/a&gt; this meant creating a &lt;a href=&quot;https://github.com/blowmage/minitest-rails/blob/v0.5.2/lib/minitest/rails/test_case.rb&quot;&gt;MiniTest::Rails::TestCase&lt;/a&gt; that inherits from MiniTest::Spec and includes all the Rails testing modules. For &lt;a href=&quot;https://github.com/metaskills/minitest-spec-rails&quot;&gt;minitest-spec-rails&lt;/a&gt; this meant similarly replacing Test::Unit::TestCase with an implementation that inherits from MiniTest::Spec.&lt;/p&gt;

&lt;p&gt;This approach generally works, but you have to be very careful in how you load the libraries that are getting replaced. You can replace a constant in Ruby, but if another object is already inheriting from the replaced object it will continue to inherit from it even after the constant is replaced. Problems arise when other libraries affect how code is loaded, and upend your carefully planned hack. And ultimately that is inevitable. (Don’t even get me started on what Rails 3’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;threadsafe!&lt;/code&gt; option does to how code is loaded.)&lt;/p&gt;

&lt;h2 id=&quot;a-sustainable-solution&quot;&gt;A Sustainable Solution&lt;/h2&gt;

&lt;p&gt;What we really need is to enable MiniTest::Spec functionality without altering the ancestry. But how? Instead of using Ruby meta-programming for evil, how about we use it for good? Imagine if this worked:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActiveSupport::TestCase&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DSL&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;With the latest release of minitest that’s the case. &lt;a href=&quot;http://zenspider.com/&quot;&gt;Ryan Davis&lt;/a&gt; added the ability to enable the spec DSL on any minitest TestCase. This means that this is functionally equivalent:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TestMyStuff&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Spec&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;works&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TestMyStuffAgain&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Unit&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DSL&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;works&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Because of how MiniTest::Spec was designed this was an easy change to make. I love working in code that Ryan produces because this is generally the case.&lt;/p&gt;

&lt;h2 id=&quot;the-unlimited-possibilities&quot;&gt;The Unlimited Possibilities&lt;/h2&gt;

&lt;p&gt;This change means that supporting minitest’s spec DSL is suddenly trivial. Even for older Rails 2.x apps! Specifying the version of minitest in your Rails project’s Gemfile and including the following in your test helper enables the DSL:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;minitest/spec&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActiveSupport::TestCase&lt;/span&gt;
  &lt;span class=&quot;kp&quot;&gt;extend&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Spec&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DSL&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Unfortunately, that doesn’t do everything for you. If you want to use the full spec DSL, you need to configure which TestCase is used in your tests. To do that you need to register the TestCase with the spec DSL:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActiveSupport::TestCase&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Use AS::TestCase for the base class when describing a model&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;register_spec_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;is_a?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ActionController::TestCase&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# Use AC::TestCase for the base class when describing a controller&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;register_spec_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;Class&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;desc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Metal&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;register_spec_type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/Controller( ?Test)?\z/i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And so on. But there is another piece. Controller/Helper/Mailer tests also attempt to deduce the test subject from the test name to create an instance in the test. This is a bit more involved, and currently minitest-rails uses &lt;a href=&quot;https://github.com/blowmage/minitest-rails/blob/v0.9/lib/minitest/rails/constant_lookup.rb&quot;&gt;MiniTest::Rails::Testing::ConstantLookup&lt;/a&gt; to resolve the constant from the test name.&lt;/p&gt;

&lt;p&gt;There are other little incompatibilities that need to be addressed to enable the spec DSL in your Rails apps. Fortunately, all this and more is included with minitest-rails, which is the easiest way to use advanced minitest functionality within your Rails app. (End sales pitch.)&lt;/p&gt;

&lt;p&gt;What this means is that instead of minitest-rails hacking how Rails loads code, it can just declare how we want our tests configured. This is a huge step forward.&lt;/p&gt;

&lt;h2 id=&quot;upgrade-path&quot;&gt;Upgrade Path&lt;/h2&gt;

&lt;p&gt;If you are already using minitest-rails, thank you! Because of the various ways we’ve attempted to inject minitest into Rails there is some cruft that has accumulated along the way. So for the 0.9 release we’ve added a few deprecation warnings for things that will be removed in 1.0. If you have an existing app using minitest-rails please look at the &lt;a href=&quot;https://github.com/blowmage/minitest-rails/wiki/Upgrading-to-0.9&quot;&gt;Upgrade Guide&lt;/a&gt; and make those changes.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Refactoring towards &quot;better&quot; code</title>
   <link href="https://blowmage.com/2013/03/07/refactoring-better-code"/>
   <updated>2013-02-07T00:00:00+00:00</updated>
   <id>https://blowmage.com/2013/03/07/refactoring-better-code</id>
   <content type="html">&lt;p&gt;What makes code good? How do you quantify code quality? I’m not talking metrics, I’m talking good old gut feel. How do know good code when you see it? Certainly, working code is better than non-working code. But laying that aside; if two codebases work as expected, how do you judge which is better? Instead of talking about how I make that judgement, how about I show you?&lt;/p&gt;

&lt;p&gt;I caught a bit of heat yesterday for &lt;a href=&quot;https://twitter.com/aarongraves/status/299225888338370560&quot;&gt;a retweet&lt;/a&gt; about &lt;a href=&quot;https://github.com/discourse/discourse/blob/21b562852885f883be43032e03c709241e8e6d4f/app/controllers/users_controller.rb#L286&quot;&gt;some code&lt;/a&gt; from the newly released Discourse project that was not following the typical Rails conventions. In retrospect I see that I was wrong to do so, and I apologize. Several people rightly suggested to me that I submit a pull request instead, so &lt;a href=&quot;https://github.com/discourse/discourse/pull/57&quot;&gt;here it is&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code in question is the implementation of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UsersController#search_users&lt;/code&gt; action. This action is not super critical to the running of the site. The changes I’ll be showing here do not represent an unrolling of any significant performance optimization, AFAIK. I am going to attempt to perform a true refactor: improving the internals of the code without affecting the external behavior, &lt;em&gt;including&lt;/em&gt; performance.&lt;/p&gt;

&lt;p&gt;The first step is to make sure that we have adequate code coverage to perform a refactor. We need to know if we have changed any behavior accidentally, or introduced a logical error in our new code. Unfortunately, there are no existing tests for this action. So our first step is to add those tests.&lt;/p&gt;

&lt;p&gt;The Discourse application is using RSpec, so we’ll create a new file for our new tests:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;spec/controllers/users_controller/search_users_spec.rb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I choose to create a new test file instead of adding a new context to the existing UsersController test for a couple reasons: First, because the new tests are going to be specific to the search logic, the data setup will be much different. And second, I dislike traversing a spec file hundreds or thousands of lines long looking for the right nested block. I do, however, have lots of experience traversing directories and finding files.&lt;/p&gt;

&lt;p&gt;The search logic itself is pretty simple. It performs a search using PostgreSQL’s full text capabilities. The only real complexity is in how the results are sorted. The closest matches are sorted first. If a topic is provided then those users with posts on the topic will be ranked higher than those without. So the question is how to add test coverage?&lt;/p&gt;

&lt;p&gt;The approach I’m using here is slow. I’m more concerned with correctness of the behavior than the speed of the tests at the moment. But I am confident these tests will be revisited and improved for speed later, so let’s make the tests as clear as we can.&lt;/p&gt;

&lt;p&gt;Pasting all the tests here isn’t too interesting. You can view the full commit here: &lt;a href=&quot;https://github.com/blowmage/discourse/commit/01367275957146769c79ae5d4f9e7c2a1b9ab9ec&quot;&gt;Add test coverage for UsersController#search_users&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have tests, I want to move the bulk of search logic from UsersController#search_users to a model. The reason is because the majority of the code specific to the implementation. Ideally, I want to be able to change the implementation of my search without having to change the controller code. I don’t want to move even more code to the poor User model; it looks far too bloated already. Instead, lets create a new domain model. I want this new model to just be responsible for the User search, so let’s call it UserSearch.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;app/models/user_search.rb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;   def search_users
     term = (params[:term] || &quot;&quot;).strip.downcase
     topic_id = params[:topic_id]
     topic_id = topic_id.to_i if topic_id
&lt;span class=&quot;gd&quot;&gt;-    sql = &quot;select username, name, email from users u &quot;
-    if topic_id
-      sql &amp;lt;&amp;lt; &quot;left join (select distinct p.user_id from posts p where topic_id = :topic_id) s on
-        s.user_id = u.id &quot;
-    end
-
-    if term.length &amp;gt; 0
-      sql &amp;lt;&amp;lt; &quot;where username_lower like :term_like or
-              to_tsvector(&apos;simple&apos;, name) @@
-              to_tsquery(&apos;simple&apos;,
-                regexp_replace(
-                  regexp_replace(
-                    cast(plainto_tsquery(:term) as text)
-                    ,&apos;\&apos;&apos;(?: |$)&apos;, &apos;:*&apos;&apos;&apos;, &apos;g&apos;),
-                &apos;&apos;&apos;&apos;, &apos;&apos;, &apos;g&apos;)
-              ) &quot;
-
-    end
-
-    sql &amp;lt;&amp;lt; &quot;order by case when username_lower = :term then 0 else 1 end asc, &quot;
-    if topic_id
-      sql &amp;lt;&amp;lt; &quot; case when s.user_id is null then 0 else 1 end desc, &quot;
-    end
-
-    sql &amp;lt;&amp;lt; &quot; case when last_seen_at is null then 0 else 1 end desc, last_seen_at desc, username asc limit(20)&quot;
-
-    results = User.exec_sql(sql, topic_id: topic_id, term_like: &quot;#{term}%&quot;, term: term)
-    results = results.map do |r|
-      r[&quot;avatar_template&quot;] = User.avatar_template(r[&quot;email&quot;])
-      r.delete(&quot;email&quot;)
-      r
-    end
-    render :json =&amp;gt; results
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+
+    results = UserSearch.search term, topic_id
+    render :json =&amp;gt; results
&lt;/span&gt;   end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserSearch&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;term&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;topic_id&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;select username, name, email from users u &quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;topic_id&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;left join (select distinct p.user_id from posts p where topic_id = :topic_id) s on
        s.user_id = u.id &quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;term&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;length&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;where username_lower like :term_like or
              to_tsvector(&apos;simple&apos;, name) @@
              to_tsquery(&apos;simple&apos;,
                regexp_replace(
                  regexp_replace(
                    cast(plainto_tsquery(:term) as text)
                    ,&apos;&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&apos;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&apos;(?: |$)&apos;, &apos;:*&apos;&apos;&apos;, &apos;g&apos;),
                &apos;&apos;&apos;&apos;, &apos;&apos;, &apos;g&apos;)
              ) &quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;order by case when username_lower = :term then 0 else 1 end asc, &quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;topic_id&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot; case when s.user_id is null then 0 else 1 end desc, &quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot; case when last_seen_at is null then 0 else 1 end desc, last_seen_at desc, username asc limit(20)&quot;&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;exec_sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;topic_id: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;topic_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;term_like: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;term&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;%&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;term: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;term&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;avatar_template&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;avatar_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;delete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;email&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Some might wonder why this new object doesn’t go in the lib directory instead. For me, the lib directory is for code that can someday be extracted out of the app. Like a library to access an API. Something like searching users seems to be a core part of the domain for this app. Even though this controller is the only thing calling it now, I can see an admin controller, or a background job (like a mailer) using this functionality in the future. So I think it makes sense to place this in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app/models&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another question some may have is why &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserSearch&lt;/code&gt; doesn’t inherit from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord&lt;/code&gt;. Well, this new domain model isn’t going to be responsible for representing a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User&lt;/code&gt; record; its job is to search for those records. Not every model in your domain needs to inherit from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord&lt;/code&gt;. As apps grow larger I find its better to have more and more of my domain logic outside of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord&lt;/code&gt; models and in other domain models.&lt;/p&gt;

&lt;p&gt;You can view the full commit here: &lt;a href=&quot;https://github.com/blowmage/discourse/commit/972b9d735c9bc07963d77cc53a07ef9bbe0a0e45&quot;&gt;Extract search logic to UserSearch model&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this move the tests still pass. Notice I don’t have any unit tests for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserSearch&lt;/code&gt; object yet. I could add some, but they would only be duplicates of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search_users&lt;/code&gt; test. So for now I’m going to allow those &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search_users&lt;/code&gt; tests to act as acceptance tests for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserSearch&lt;/code&gt;. I have some ideas for what I eventually want this object to be and do, so I’m skipping the tests for just a bit, while I experiment a bit more.&lt;/p&gt;

&lt;p&gt;The controller method now looks much better.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;search_users&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;term&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:term&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;strip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;downcase&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;topic_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:topic_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;topic_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;topic_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_i&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;topic_id&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UserSearch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;search&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;term&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;topic_id&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;render&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:json&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;results&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;There are three main parts to the method now:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Cleansing the options provided by the user&lt;/li&gt;
  &lt;li&gt;Retrieving the data&lt;/li&gt;
  &lt;li&gt;Formatting the data to return to the user&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is how I want to see my controller actions. I don’t want to be distracted by the implementation details of any of those steps. I simply want a declaration of the steps that the controller is responsible for.&lt;/p&gt;

&lt;p&gt;Now that the controller is more declarative and less instructional, let’s see if we can help UserSearch to be less hairy. All the logic is on one big method. It seems we can break that up into individual methods with separate responsibilities. I think it makes sense to separate the code that generates the SQL from the code that executes the SQL.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt; class UserSearch

   def self.search term, topic_id
&lt;span class=&quot;gd&quot;&gt;-    sql = &quot;select username, name, email from users u &quot;
-    if topic_id
-      sql &amp;lt;&amp;lt; &quot;left join (select distinct p.user_id from posts p where topic_id = :topic_id) s on
-        s.user_id = u.id &quot;
-    end
-
-    if term.length &amp;gt; 0
-      sql &amp;lt;&amp;lt; &quot;where username_lower like :term_like or
-              to_tsvector(&apos;simple&apos;, name) @@
-              to_tsquery(&apos;simple&apos;,
-                regexp_replace(
-                  regexp_replace(
-                    cast(plainto_tsquery(:term) as text)
-                    ,&apos;\&apos;&apos;(?: |$)&apos;, &apos;:*&apos;&apos;&apos;, &apos;g&apos;),
-                &apos;&apos;&apos;&apos;, &apos;&apos;, &apos;g&apos;)
-              ) &quot;
-
-    end
-
-    sql &amp;lt;&amp;lt; &quot;order by case when username_lower = :term then 0 else 1 end asc, &quot;
-    if topic_id
-      sql &amp;lt;&amp;lt; &quot; case when s.user_id is null then 0 else 1 end desc, &quot;
-    end
-
-    sql &amp;lt;&amp;lt; &quot; case when last_seen_at is null then 0 else 1 end desc, last_seen_at desc, username asc limit(20)&quot;
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+    sql = sql term, topic_id
&lt;/span&gt;     results = User.exec_sql(sql, topic_id: topic_id, term_like: &quot;#{term}%&quot;, term: term)
     results = results.map do |r|
       r[&quot;avatar_template&quot;] = User.avatar_template(r[&quot;email&quot;])
       r.delete(&quot;email&quot;)
       r
     end
   end
&lt;span class=&quot;gi&quot;&gt;+
+  def self.sql term, topic_id
+    sql = &quot;select username, name, email from users u &quot;
+    if topic_id
+      sql &amp;lt;&amp;lt; &quot;left join (select distinct p.user_id from posts p where topic_id = :topic_id) s on
+        s.user_id = u.id &quot;
+    end
+
+    if term.length &amp;gt; 0
+      sql &amp;lt;&amp;lt; &quot;where username_lower like :term_like or
+              to_tsvector(&apos;simple&apos;, name) @@
+              to_tsquery(&apos;simple&apos;,
+                regexp_replace(
+                  regexp_replace(
+                    cast(plainto_tsquery(:term) as text)
+                    ,&apos;\&apos;&apos;(?: |$)&apos;, &apos;:*&apos;&apos;&apos;, &apos;g&apos;),
+                &apos;&apos;&apos;&apos;, &apos;&apos;, &apos;g&apos;)
+              ) &quot;
+
+    end
+
+    sql &amp;lt;&amp;lt; &quot;order by case when username_lower = :term then 0 else 1 end asc, &quot;
+    if topic_id
+      sql &amp;lt;&amp;lt; &quot; case when s.user_id is null then 0 else 1 end desc, &quot;
+    end
+
+    sql &amp;lt;&amp;lt; &quot; case when last_seen_at is null then 0 else 1 end desc, last_seen_at desc, username asc limit(20)&quot;
+    sql
+  end
&lt;/span&gt;
 end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can view the full commit here: &lt;a href=&quot;https://github.com/blowmage/discourse/commit/1cd565ec0d04c6b26896145eba603eb60fc982fc&quot;&gt;Extract SQL generation method&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That seems to be clearer for me, and all the SQL string concatenation is isolated into one method. If a new mechanism was introduced to generate the SQL string, only &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserSearch.sql&lt;/code&gt; would have to change.&lt;/p&gt;

&lt;p&gt;At this point I’m ready to start making some slight changes to how the code works. I spoke with one of the developers and he mentioned that they wanted the objects returned from the search to be the normal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord&lt;/code&gt; models, and not just an array of hashes. In order to make that change we’ll have to change some of the details of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserSearch&lt;/code&gt;, but also change the formatting of the data in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserController&lt;/code&gt; to ensure that the behavior remains.&lt;/p&gt;

&lt;p&gt;The first change here is to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User.find_by_sql&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User.exec_sql&lt;/code&gt;. This means that the extra work that the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search&lt;/code&gt; method performed to add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;avatar_template&lt;/code&gt; and remove &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;email&lt;/code&gt; can be removed. The User objects can calculate &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;avatar_template&lt;/code&gt;, and UserController can remove email from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;results&lt;/code&gt;. In order to use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;User.find_by_sql&lt;/code&gt;, the SQL string must have the values added to the string.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt; class UserSearch

   def self.search term, topic_id
&lt;span class=&quot;gd&quot;&gt;-    sql = sql term, topic_id
-    results = User.exec_sql(sql, topic_id: topic_id, term_like: &quot;#{term}%&quot;, term: term)
-    results = results.map do |r|
-      r[&quot;avatar_template&quot;] = User.avatar_template(r[&quot;email&quot;])
-      r.delete(&quot;email&quot;)
-      r
-    end
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+    User.find_by_sql sql(term, topic_id)
&lt;/span&gt;   end

+  private
&lt;span class=&quot;gi&quot;&gt;+
&lt;/span&gt;   def self.sql term, topic_id
     sql = &quot;select username, name, email from users u &quot;
     if topic_id
       sql &amp;lt;&amp;lt; &quot;left join (select distinct p.user_id from posts p where topic_id = :topic_id) s on
         s.user_id = u.id &quot;
     end

     if term.length &amp;gt; 0
       sql &amp;lt;&amp;lt; &quot;where username_lower like :term_like or
               to_tsvector(&apos;simple&apos;, name) @@
               to_tsquery(&apos;simple&apos;,
                 regexp_replace(
                   regexp_replace(
                     cast(plainto_tsquery(:term) as text)
                     ,&apos;\&apos;&apos;(?: |$)&apos;, &apos;:*&apos;&apos;&apos;, &apos;g&apos;),
                 &apos;&apos;&apos;&apos;, &apos;&apos;, &apos;g&apos;)
               ) &quot;

     end

     sql &amp;lt;&amp;lt; &quot;order by case when username_lower = :term then 0 else 1 end asc, &quot;
     if topic_id
       sql &amp;lt;&amp;lt; &quot; case when s.user_id is null then 0 else 1 end desc, &quot;
     end

     sql &amp;lt;&amp;lt; &quot; case when last_seen_at is null then 0 else 1 end desc, last_seen_at desc, username asc limit(20)&quot;
&lt;span class=&quot;gd&quot;&gt;-    sql
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+
+    sanitize_sql_array(sql, topic_id: topic_id, term_like: &quot;#{term}%&quot;, term: term)
+  end
+
+  def self.sanitize_sql_array *args
+    ActiveRecord::Base.send(:sanitize_sql_array, args)
&lt;/span&gt;   end

 end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The second change is to properly serialize the objects to JSON and match the existing format.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;   def search_users
     term = (params[:term] || &quot;&quot;).strip.downcase
     topic_id = params[:topic_id]
     topic_id = topic_id.to_i if topic_id

     results = UserSearch.search term, topic_id

-    render :json =&amp;gt; results
&lt;span class=&quot;gi&quot;&gt;+    render json: { users: results.as_json( only:    [ :username, :name ],
+                                           methods: :avatar_template ) }
&lt;/span&gt;   end&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can view the full commit here: &lt;a href=&quot;https://github.com/blowmage/discourse/commit/5b01ac92880e8468f012abd2c844f63dd0db5b7a&quot;&gt;Return User objects instead of hashes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the tests still pass.&lt;/p&gt;

&lt;p&gt;There are a couple more things that stand out to me. The fact that we are downcasing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;term&lt;/code&gt; in the controller, and that the generated SQL is expecting the value to be downcased. This seems odd to me. I’d like these objects to be a little more liberal in the data they accept. One way to address this is to change the conditional from LIKE to ILIKE, which is case insensitive. I’m not sure if there are performance concerns about that though. Another option would be to change the SQL to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lower(username) LIKE lower(:term_like)&lt;/code&gt;, and create an index for lower(username). Yes, PostgreSQL will index values returned from functions like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lower()&lt;/code&gt;. I suspect the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;username_lower&lt;/code&gt; column could be removed as well.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;     if term.length &amp;gt; 0
&lt;span class=&quot;gd&quot;&gt;-      sql &amp;lt;&amp;lt; &quot;where username_lower like :term_like or
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+      sql &amp;lt;&amp;lt; &quot;where username ilike :term_like or
&lt;/span&gt;               to_tsvector(&apos;simple&apos;, name) @@
               to_tsquery(&apos;simple&apos;,&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-diff&quot; data-lang=&quot;diff&quot;&gt;   def search_users
&lt;span class=&quot;gd&quot;&gt;-    term = (params[:term] || &quot;&quot;).strip.downcase
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+    term = params[:term].to_s.strip
&lt;/span&gt;     topic_id = params[:topic_id]
     topic_id = topic_id.to_i if topic_id&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can view the full commit here: &lt;a href=&quot;https://github.com/blowmage/discourse/commit/e41b6537f94805d778d9420a66fe15dd34d8d319&quot;&gt;Remove expectation of term case&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now I am generally happy with the state of the code, and its time to focus on the state of the tests. All of our test coverage is in the controller tests. Controller tests are integration tests, and I dislike having this much detail specified in the controller tests. I like my controller tests to check that the actions are responding as expected, and to do little more than a sanity check that the data returned in as expected. In other words, the integration tests make sure that the integration works. If the data is important enough to be tested, then it should be extracted to its own object and unit tested there. Luckily we have just that with our new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserSearch&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;So my next commit is to move the bulk of the behavior tests for the UserSearch to the new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/models/user_search_spec.rb&lt;/code&gt; file. I’ll also remove the previous &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;search_users&lt;/code&gt; test file and add some very general tests to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spec/controllers/user_controller_spec.rb&lt;/code&gt; file. Yes, I know I said I like separate files for my tests, but this isn’t my project so I’m going to try to follow their conventions in the pull request as much as possible.&lt;/p&gt;

&lt;p&gt;Lots of changes here. You can view the full commit here: &lt;a href=&quot;https://github.com/blowmage/discourse/commit/d72c26ff92fd0ba56a8d5fd6e447fb4a00824679&quot;&gt;Refactor UserSearch tests&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think this is where I’ll leave this refactor for now. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UserSearch.search&lt;/code&gt; still returns an array, but now it is an array of User objects and not an array of hashes. A next step would be to return an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord::Relation&lt;/code&gt; object, so that additional criteria could be added, such as pagination. That would move the query away from the specific SQL statements, and I’m not sure that the developer is ready for this change just yet. Hopefully the advantages of passing an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ActiveRecord::Relation&lt;/code&gt; object instead of an array will change the maintainers’ minds in the future.&lt;/p&gt;

&lt;p&gt;Other changes that could be made include how the user search results are serialized to JSON. Right now that is all happening in the render call in the controller’s action. That could be extracted to a UserSearchSerializer.&lt;/p&gt;

&lt;p&gt;Lastly, I want to point out that concerns such as performance don’t necessitate ugly code. Everything should have its place. Even the most performance intensive and vital code can be given a declarative name and dealt with in a clean, maintainable way. Like testing, I do not believe that there is an advantage to skipping this step to get the code completed faster. It may seem like it at first, but once you get used to taking this design step you quickly realize how much time is wasted on improperly designed code when you go back to old projects.&lt;/p&gt;

&lt;p&gt;Schizophrenic code, by which I mean methods with an inconsistent level of abstraction, is difficult to reason about. Programmers new to your app will struggle more than necessary in order to understand how the app works. I’ve seen this over and over, and the effect is similar to hazing new members of a group. Don’t be a code bully, whether intentional or not. Life is too short to deal with hostile code. Be nice in all that you do, including your code.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>How to write a conference proposal</title>
   <link href="https://blowmage.com/2013/01/24/writing-conf-proposals/"/>
   <updated>2013-01-24T00:00:00+00:00</updated>
   <id>https://blowmage.com/2013/01/24/writing-conf-proposals</id>
   <content type="html">&lt;p&gt;I run a small but successful &lt;a href=&quot;http://mtnwestrubyconf.org/&quot;&gt;Ruby conference&lt;/a&gt;. It is a difficult and draining experience, but I’m generally very happy doing it. We select the majority of our sessions from proposals submitted by folks just like you. One of my least favorite parts of running the conference is letting folks know their proposal was not selected. I had to do that today.&lt;/p&gt;

&lt;p&gt;Every year folks ask why they didn’t get selected. Sometimes they ask me, sometimes they ask themselves loudly on twitter. My conference has a very specific type of session it’s looking for, sort of a niche of a niche. But more often than not the proposals that are not seriously considered are not off topic as much as they are poorly written. I’ve been on all sides of this, as a speaker that has been accepted, a speaker that has not been selected, and an organizer. So here are some thoughts from the mind of a conference organizer on what makes a good conference proposal.&lt;/p&gt;

&lt;h2 id=&quot;make-the-title-good&quot;&gt;Make the title good&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Be clear.&lt;/strong&gt; What makes a title “good” depends on what type of session you are proposing. Is it an exhaustive look at a specific subject? Then make that clear in your title. &lt;em&gt;“Foo Deconstructed”&lt;/em&gt; Is it a beginner-friendly introduction? &lt;em&gt;“Foo for the uninitiated”&lt;/em&gt; You want to convey what the session is about and what experience level it is aimed at.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid hostility.&lt;/strong&gt; You may think it is cute to say something like “You suck at &lt;em&gt;topic&lt;/em&gt;”, but in my experience that isn’t effective. Folks don’t want to feel bad about themselves or mad at the presenter, and that title will make them feel one or the other. A better alternative might be “Being great at &lt;em&gt;topic&lt;/em&gt;”. It should also go without saying that you should avoid sexism and racism in your proposals, but you would be surprised. Just don’t, okay?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Have a hook.&lt;/strong&gt; Most attendees will judge their interest solely by the title. The title is the hook. It’s important to grab attention, but don’t be misleading. The goal isn’t to shock someone into reading your blog post, it’s to give them an idea of why they’d benefit from attending your session (or accepting your proposal).&lt;/p&gt;

&lt;h2 id=&quot;descriptionabstract&quot;&gt;Description/Abstract&lt;/h2&gt;

&lt;p&gt;This is the meat of the proposal. It’s common for conferences to print this in the program unchanged. So consider that this needs to appeal not just to the selection committee, but to conference attendees as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focus on the outcome.&lt;/strong&gt; What will attendees know by the end of the session? Describing the benefits of the session is much more effective than the mechanics of what the session will cover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep it brief.&lt;/strong&gt; It’s unfair to ask a reader to parse through 3 paragraphs setting up a scenario that the session will address. A person ought to be able to scan the description and feel confident about what will be covered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid lists.&lt;/strong&gt; Sticking with the “why” is where the power of your proposal comes from. Some may disagree with this guideline, but if your proposal contains lists (or more than two paragraphs), it’s a strong indicator that you’re diving deeper into the “what” than the “why”. (Yes, I understand the irony of giving the advice to not use lists in a list. But this blog post isn’t a conference proposal either.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Make it topical.&lt;/strong&gt; Consider the audience and the theme of the conference. If you are submitting to a Python conference, then it’s probably not a good idea to submit a proposal on Lisp. Yes, you may think that the audience should all hear how Lisp is great, but will the attendees care? Does the selection committee? If the conference clearly states that they intend to be as beginner-friendly as possible, then your dive into the subtle details of natural language processing libraries is unlikely to be accepted, no matter how great a talk it would be.&lt;/p&gt;

&lt;p&gt;Both attendees and selection committee members are not clairvoyant. They don’t know why you think this subject is good or not, so you need to explain it to them.&lt;/p&gt;

&lt;h2 id=&quot;justification&quot;&gt;Justification&lt;/h2&gt;

&lt;p&gt;Some CFPs allow you to enter some text only to be read and considered by the selection committee. If you have this available, then there are a couple things you should consider:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop selling.&lt;/strong&gt; Any selection member has already read through dozens or hundreds of proposals all vying for their attention. This isn’t the place for the hard sell. This is the place to level with the selection member. Is this only aimed at basket weaving beginners with extensive underwater experience? Then state that here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explain the destination.&lt;/strong&gt; Are you intentionally trying to sell one thing in your description because it’s popular, but then expose it as flawed during your session? Let the selection members know. If you have a hard time quantifying where attendees are going to be at the end of your session, you might have a problem with your proposal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thank them.&lt;/strong&gt; Do you like their conference? Have you been there before? What do you like about it? More than kissing up, it shows you understand the kind of conference the organizers are striving for. It’s not a good place to rant about what is wrong with the conference, the community, or the world.&lt;/p&gt;

&lt;h2 id=&quot;biography&quot;&gt;Biography&lt;/h2&gt;

&lt;p&gt;Most organizers or selection committees don’t care as much about the biography of the speaker as the conference attendees do. So my advice here is to explain what makes you unique. Are you an authority on the subject? Do you have a novel or interesting take? Don’t make this overly long, folks aren’t looking to hire you. They just want to know if you know your stuff and are worth watching.&lt;/p&gt;

&lt;p&gt;If you’re looking to inject humor, my opinion is that your best bet is to play it straight in your proposal and go for broke on humor in your bio.&lt;/p&gt;

&lt;p&gt;I hope this helps. I understand how frustrating it can be to spend so much time and energy on a proposal only to be shattered when it isn’t accepted. My advice is to play for the long term. Pay attention to what ends up in the conference sessions and figure out where you can differentiate yourself while adding value. Speak at user groups. Attend a &lt;a href=&quot;http://www.toastmasters.org/&quot;&gt;Toastmasters&lt;/a&gt; meeting. Write a book. You will only get better.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Escape to RubyConf!</title>
   <link href="https://blowmage.com/2012/09/19/escape-to-rubyconf/"/>
   <updated>2012-09-19T00:00:00+00:00</updated>
   <id>https://blowmage.com/2012/09/19/escape-to-rubyconf</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://rubyconf.org/&quot;&gt;RubyConf 2012&lt;/a&gt; is coming up. I will be there, and I’d love to hang out with you. You should come. It’ll be fun. And if you are coming, you need to train. Fortunately its easy enough to do. Just type the following:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ gem install escape_to_rubyconf
$ escape_to_rubyconf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/images/escape_to_rubyconf.jpg&quot; alt=&quot;Escape to RubyConf!&quot; /&gt;&lt;/p&gt;

&lt;p&gt;So not many know this, but I spoke on Writing Games with Ruby at RubyConf 2010. This game is the end product of my session. I figured it would be fun to throw it in a gem and release it. So here it is!&lt;/p&gt;

&lt;p&gt;There were unfortunate problems with the recording of my session, and I’ve been working with Confreaks to recreate it for the video. But one thing led to another and the &lt;a href=&quot;http://www.confreaks.com/videos/490-rubyconf2010-writing-games-with-ruby&quot;&gt;video&lt;/a&gt; still isn’t finished. You can download a rough cut though. Let me know what you think.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Where to Put Tests in Rails</title>
   <link href="https://blowmage.com/2012/07/12/rails-tests-placement/"/>
   <updated>2012-07-12T00:00:00+00:00</updated>
   <id>https://blowmage.com/2012/07/12/rails-tests-placement</id>
   <content type="html">&lt;p&gt;One of the things that bothers me when developing Rails apps is where tests are placed by default. Rails puts model tests in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/unit&lt;/code&gt;, controller tests in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/functional&lt;/code&gt;, and acceptance tests in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/integration&lt;/code&gt;. But the names of those locations doesn’t always match what those tests do.&lt;/p&gt;

&lt;p&gt;The three main types of tests we typically write in Rails apps are &lt;a href=&quot;http://c2.com/cgi/wiki?UnitTest&quot;&gt;Unit&lt;/a&gt;, &lt;a href=&quot;http://c2.com/cgi/wiki?IntegrationTest&quot;&gt;Integration&lt;/a&gt;, and &lt;a href=&quot;http://c2.com/cgi/wiki?AcceptanceTest&quot;&gt;Acceptance&lt;/a&gt;. Unit tests work on an isolated units of code, exercising a single method or object. Integration tests work on a subsystem within an app, exercising the dependencies within the subsystem. Acceptance tests (previously known as &lt;a href=&quot;http://c2.com/cgi/wiki?FunctionalTest&quot;&gt;functional tests&lt;/a&gt;) work on the application as a whole to determine if it performs according to the customer’s acceptance criteria. I encourage you to follow those links if you want to read more about the definitions and differences between these three types of tests.&lt;/p&gt;

&lt;p&gt;Rails puts model tests under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/unit&lt;/code&gt;, but are they always unit tests? It is common to perform unit &lt;em&gt;and&lt;/em&gt; integration tests on models, depending on what the model is responsible for and how it is designed. In my experience, many Rails apps have a problem overloading the User object with too many responsibilities and dependencies. And the tests for those dependencies are by definition integration tests. However, the location of those tests tell us that they are unit tests.&lt;/p&gt;

&lt;p&gt;This is more than a pedantic exercise. These test names have meanings outside of the Rails community and this has caused much confusion over the years. My opinion is that this naming confusion has contributed to the adoption of Rspec over Rails’ default tests using Test::Unit, because Rspec names their tests according to what is being tested and not the type of tests being performed.&lt;/p&gt;

&lt;p&gt;Part of the reason I &lt;a href=&quot;https://blowmage.com/2012/07/10/announcing-minitest-rails&quot;&gt;started minitest-rails&lt;/a&gt; was to correct this. This is why minitest-rails places model tests in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/models&lt;/code&gt;, controller tests in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/controllers&lt;/code&gt;, helper tests in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/helpers&lt;/code&gt;, mailer tests in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/mailers&lt;/code&gt;, and acceptance tests in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/acceptance&lt;/code&gt;. Now my model, helper, controller, and mailer tests are located where I consider logical. I also find this layout more discoverable. For instance, I don’t have to explain the layout when introducing Rails or testing to new developers.&lt;/p&gt;

&lt;p&gt;All this said, minitest-rails is a young effort and it has much room to improve. Acceptance tests are in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/acceptance&lt;/code&gt;, but the generator is still named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;integration_test&lt;/code&gt;. I think we can to better. If you want to help, jump on the &lt;a href=&quot;https://groups.google.com/group/minitest-rails&quot;&gt;mailing list&lt;/a&gt; and help make it better.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Announcing minitest-rails</title>
   <link href="https://blowmage.com/2012/07/10/announcing-minitest-rails/"/>
   <updated>2012-07-10T00:00:00+00:00</updated>
   <id>https://blowmage.com/2012/07/10/announcing-minitest-rails</id>
   <content type="html">&lt;p&gt;Yesterday I released version 0.1 of &lt;a href=&quot;https://github.com/blowmage/minitest-rails&quot;&gt;minitest-rails&lt;/a&gt;, a library that enables you to test your Rails 3 apps using &lt;a href=&quot;https://github.com/seattlerb/minitest&quot;&gt;minitest&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;

&lt;div class=&quot;video-player widescreen&quot;&gt;
&lt;iframe width=&quot;704&quot; height=&quot;424&quot; src=&quot;http://www.youtube.com/embed/xA2f2zBNvsc?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;h2 id=&quot;installing&quot;&gt;Installing&lt;/h2&gt;

&lt;p&gt;The minitest-rails gem is intended to work side-by-side with Test::Unit and/or Rspec. But if you are starting a new project and only want to use minitest-rails, you can create a new rails app without a testing framework:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rails new MyApp --skip-test-unit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once you have a Rails app all you need to do is add minitest-rails to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;development&lt;/code&gt; groups in your Gemfile:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:development&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;gem&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;minitest-rails&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next run the installation generator to add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/minitest_helper.rb&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rails generate mini_test:install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it! You are ready to write some tests.&lt;/p&gt;

&lt;h2 id=&quot;basic-usage&quot;&gt;Basic Usage&lt;/h2&gt;

&lt;p&gt;You can generate tests, or you can write your own. Let’s assume you have the following model:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;attr_accessible&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;validates_presence_of&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Testing this object is very easy. The only differences between Test::Unit and minitest are the different helper in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;require&lt;/code&gt;, and the TestCase being namespaced under &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MiniTest::Rails&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;minitest_helper&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserTest&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;MiniTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ActiveSupport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TestCase&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_valid&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Ryan Davis&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;valid with a name&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_invalid&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;refute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;invalid without a name&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;using-the-spec-dsl&quot;&gt;Using the Spec DSL&lt;/h2&gt;

&lt;p&gt;The feature that most seem to be excited by is the ability to use the Spec DSL in your tests.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;minitest_helper&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;can be valid&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Ryan Davis&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;must_equal&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;can be invalid&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;wont_equal&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can even tell the generators to output tests using the Spec DSL by providing the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--spec&lt;/code&gt; option:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rails generate model User --spec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or you can set the Spec DSL to be the default by configuring it in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/application.rb&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;generators&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;test_framework&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:mini_test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:spec&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course, you can mix and match between the Unit and Spec styles. I prefer to use the Spec DSL blocks with Unit’s assertions.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;minitest_helper&quot;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;can be valid&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Ryan Davis&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;valid with a name&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;it&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;can be invalid&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;refute&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;valid?&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;invalid without a name&quot;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But however you use it is up to you.&lt;/p&gt;

&lt;h2 id=&quot;test-locations&quot;&gt;Test Locations&lt;/h2&gt;

&lt;p&gt;One of the biggest changes from Test::Unit is the default location of the tests. Like Rspec, minitest-rails organizes the tests by subject and not the type of test that is performed. Its possible to perform integration tests on models. (Rails unfortunately calls these functional tests.) Just as it is possible to perform unit tests on controllers. So the following locations are used:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Test::Unit&lt;/code&gt;&lt;/th&gt;
      &lt;th&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MiniTest::Rails&lt;/code&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Models&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/unit/widget_test.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/models/widget_test.rb&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Helpers&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/unit/helpers/widget_helper_test.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/helpers/widget_helper_test.rb&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Controller&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/functional/widgets_controller_test.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/controllers/widgets_controller_test.rb&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Mailer&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/functional/notifications_test.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/mailers/notifications_test.rb&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Acceptance&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/integration/user_can_login_test.rb&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test/acceptance/user_can_login_test.rb&lt;/code&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;overriding-testunit&quot;&gt;Overriding Test::Unit&lt;/h2&gt;

&lt;p&gt;If you want to use minitest in your current tests, you can inject minitest-rails by adding the following to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test_helper.rb&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;minitest/rails&quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;MiniTest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;override_testunit!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;contribute&quot;&gt;Contribute&lt;/h2&gt;

&lt;p&gt;This is still a young project and needs lots of help. Give it a try and give some feedback. Or a patch. :)&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/blowmage/minitest-rails&quot;&gt;code&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blowmage.com/minitest-rails/&quot;&gt;docs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://groups.google.com/group/minitest-rails&quot;&gt;mailing list&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>Presenters and Decorators: A Code Tour</title>
   <link href="https://blowmage.com/2012/06/13/presenters-and-decorators-a-code-tour/"/>
   <updated>2012-06-13T00:00:00+00:00</updated>
   <id>https://blowmage.com/2012/06/13/presenters-and-decorators-a-code-tour</id>
   <content type="html">&lt;p&gt;I gave a session titled “&lt;a href=&quot;http://confreaks.com/videos/884-railsconf2012-presenters-and-decorators-a-code-tour&quot;&gt;Presenters and Decorators: A Code Tour&lt;/a&gt;” at &lt;a href=&quot;http://railsconf2012.com/&quot;&gt;RailsConf 2012&lt;/a&gt;. The Presenter and Decorator patterns are very useful approaches to keeping your Rails apps clean. I wanted to talk about presenters for a couple of reasons. First, I think we don’t talk about the View layer in our Model-View-Controller apps as much as we should. And second, I think that although “Presenters” have been around for &lt;a href=&quot;http://blog.jayfields.com/2007/03/rails-presenter-pattern.html&quot;&gt;5+ years&lt;/a&gt;, we continue to misunderstand them.&lt;/p&gt;

&lt;div class=&quot;video-player widescreen&quot;&gt;
&lt;iframe width=&quot;704&quot; height=&quot;424&quot; src=&quot;//www.youtube.com/embed/xf7i44HJ_1o?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;script async=&quot;&quot; class=&quot;speakerdeck-embed&quot; data-id=&quot;4f970220947c45001f01d735&quot; data-ratio=&quot;1.3333333333333333&quot; src=&quot;//speakerdeck.com/assets/embed.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;On my list of career goals is to speak at RubyConf and RailsConf. I spoke at RubyConf in 2010, and this year I completed the second half of my goal. I can’t thank the good folks at &lt;a href=&quot;http://www.rubycentral.org/&quot;&gt;Ruby Central&lt;/a&gt; enough for giving me the chance to speak, and for partnering with &lt;a href=&quot;http://confreaks.com/&quot;&gt;Confreaks&lt;/a&gt; to record it and make it freely available.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Outgrowing the Cloud</title>
   <link href="https://blowmage.com/2012/03/01/outgrowing-the-cloud/"/>
   <updated>2012-03-01T00:00:00+00:00</updated>
   <id>https://blowmage.com/2012/03/01/outgrowing-the-cloud</id>
   <content type="html">&lt;p&gt;I had a great time presenting at &lt;a href=&quot;http://ruby.onales.com/&quot;&gt;Ruby on Ales 2012&lt;/a&gt;. It has all the best parts of a really great Ruby conference and the best parts of a two day long party. What’s not to like?&lt;/p&gt;

&lt;p&gt;My presentation was an experience report on some changes that we made to &lt;a href=&quot;http://www.bloomfire.com/&quot;&gt;Bloomfire&lt;/a&gt;’s infrastructure. We had issues that we couldn’t fix on our previous cloud hosting provider, and we had many thoughts on where we wanted to go in the future. In the end we decided on moving to Amazon’s &lt;a href=&quot;http://aws.amazon.com/vpc/&quot;&gt;Virtual Private Cloud&lt;/a&gt; offereing and have been happy with the decison so far.&lt;/p&gt;

&lt;div class=&quot;video-player widescreen&quot;&gt;
&lt;iframe width=&quot;704&quot; height=&quot;424&quot; src=&quot;//www.youtube.com/embed/z1umCUEc7_E?rel=0&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;script async=&quot;&quot; class=&quot;speakerdeck-embed&quot; data-id=&quot;4f4fe67060be14001f0248ff&quot; data-ratio=&quot;1.3333333333333333&quot; src=&quot;//speakerdeck.com/assets/embed.js&quot;&gt;&lt;/script&gt;

&lt;p&gt;I’m very proud of our work on this migration. We picked up many features we had been trying to add for months and months, all with very minor changes to the application’s code. We solved the problems in the most appropriate way, and I feel good about that.&lt;/p&gt;

&lt;p&gt;Lastly, while my presentation looks like a public love letter to &lt;a href=&quot;http://mlp.wikia.com/wiki/Rainbow_Dash&quot;&gt;Rainbow Dash&lt;/a&gt;, it is actually a public love letter to &lt;a href=&quot;http://jameskilton.com/&quot;&gt;Jason Roelofs&lt;/a&gt; who did all of the heavy lifting for this effort. I’m super proud that &lt;a href=&quot;https://github.com/jameskilton/simple_aws&quot;&gt;simple_aws&lt;/a&gt;, the AWS library we use for all our automation, was a product of our efforts and is open source.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Rubiverse Podcast Lives!</title>
   <link href="https://blowmage.com/2011/05/16/rubiverse-lives/"/>
   <updated>2011-05-16T00:00:00+00:00</updated>
   <id>https://blowmage.com/2011/05/16/rubiverse-lives</id>
   <content type="html">&lt;p&gt;Okay, so its been a couple years. So what? Big deal. What is a short 768
day hiatus between friends? The good news is that my &lt;a href=&quot;https://archive.org/details/RubiversePodcast/&quot;&gt;Ruby
podcast&lt;/a&gt; is back. And not just one episode back.
I’ve published three episodes recently with with Brian Ford, Nathan
Esquenazi, and Wayne Seguin. I’m really happy with them and excited for
a future with many more interesting conversations.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://archive.org/details/RubiversePodcast/rubiverse-11-wayne-seguin-on-rvm-and-bdsm.mp3&quot;&gt;I spoke with Wayne
Seguin&lt;/a&gt;
about his über popular &lt;a href=&quot;http://rvm.beginrescueend.com/&quot;&gt;RVM&lt;/a&gt; and his
plans to rewrite it using his up and coming &lt;a href=&quot;http://bdsm.beginrescueend.com/&quot;&gt;BDSM
framework&lt;/a&gt;. BDSM is interesting because
it provides a bit more structure to the default shell environment and in
my mind holds the promise that more can contribute to RVM. I expect
great things from this in the future.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://archive.org/details/RubiversePodcast/rubiverse-10-nathan-esquenazi-on-padrino.mp3&quot;&gt;I spoke with Nathan
Esquenazi&lt;/a&gt;
about the &lt;a href=&quot;http://www.padrinorb.com/&quot;&gt;Padrino&lt;/a&gt; web micro-framework and
its take on creating modular web applications. I’m excited for more
modularity in our web apps, and think Padrino is on the right track.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://archive.org/details/RubiversePodcast/rubiverse-9-brian-ford-on-rubinius.mp3&quot;&gt;I also spoke with Brian
Ford&lt;/a&gt; about the
history and future of &lt;a href=&quot;http://rubini.us/&quot;&gt;Rubinius&lt;/a&gt;. Rubinius seems to
be on the verge of tipping over and being a viable Ruby implementation
for mainstream applications. I’m very excited for this and hope that the
release of 2.0 pushed it over the edge.&lt;/p&gt;

&lt;p&gt;This is just the start. I’m committed to more conversations with
interesting Rubyists. Who would you like to hear from? Let me know and
I’ll make it happen.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Binary Lottery 2008</title>
   <link href="https://blowmage.com/2008/03/30/binary-lottery-2008/"/>
   <updated>2008-03-30T00:00:00+00:00</updated>
   <id>https://blowmage.com/2008/03/30/binary-lottery-2008</id>
   <content type="html">&lt;p&gt;This year at &lt;a href=&quot;http://mtnwestrubyconf.org/&quot;&gt;MountainWest RubyConf 2008&lt;/a&gt; we had a slew of books and t-shirts to give away to attendees. Like last year we printed each attendee’s unique number on their badge in base 2 (binary). We would then randomly select a winner from the attendee list. But we would use their binary number in the reveal, showing only one number at a time. It might be cheesy, but we really enjoy it so deal. :)&lt;/p&gt;

&lt;p&gt;This is what the badges looked like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/binary-lottery-2008/badge.jpg&quot; alt=&quot;My MWRC Badge. I&apos;m number 11111111 because I made the badges. Its my right as an organizer! :)&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Last year I wrote a command line app that used &lt;a href=&quot;http://www.figlet.org/&quot;&gt;figlet&lt;/a&gt; to display the winner. You can see the &lt;a href=&quot;http://mtnwestrubyconf2007.confreaks.com/session07.html&quot;&gt;video of last year’s Lightning Talk&lt;/a&gt; where I show the code if you are interested. This year I wanted to mix it up a bit, and I decided about 10 hours before the conference to try my hand at using &lt;a href=&quot;http://whytheluckystiff.net/&quot;&gt;_why&lt;/a&gt;’s &lt;a href=&quot;http://code.whytheluckystiff.net/shoes/&quot;&gt;Shoes&lt;/a&gt; to build a GUI version. So here it is:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;yaml&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# The source of much evil...&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;update_digit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cnt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wnr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cnt&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dig&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wnr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dig&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chr&lt;/span&gt;
    &lt;span class=&quot;nb&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:stroke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;red&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Use the full path because either Shoes is easily confused, or I am&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;YAML&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/Users/blowmage/Lottery/users.yaml&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;until&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:eligible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;

&lt;span class=&quot;no&quot;&gt;Shoes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;900&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;700&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;keypress&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;k&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;stack&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;para&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;MountainWest RubyConf 2008 Binary Lottery&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;And your winner is...&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:font&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Helvetica 48px&apos;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;lbl_name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;para&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:font&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Helvetica 128px&apos;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;flow&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;para&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:font&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Helvetica 192px&apos;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;b&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;para&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:font&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Helvetica 192px&apos;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;para&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:font&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Helvetica 192px&apos;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;d&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;para&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:font&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Helvetica 192px&apos;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;e&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;para&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:font&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Helvetica 192px&apos;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;para&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:font&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Helvetica 192px&apos;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;g&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;para&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:font&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Helvetica 192px&apos;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;h&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;para&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;rand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:font&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;Helvetica 192px&apos;&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;animate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;60&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;update_digit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;update_digit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;update_digit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;update_digit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;update_digit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;update_digit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;update_digit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;g&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;update_digit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;h&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;

        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;lbl_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replace&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;count&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;
          &lt;span class=&quot;c1&quot;&gt;# Click one more time to flag the user so they won&apos;t win again.&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;lbl_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:stroke&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;red&lt;/span&gt;
          &lt;span class=&quot;n&quot;&gt;winner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:eligible&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
          &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;/Users/blowmage/Lottery/users.yaml&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;w&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
            &lt;span class=&quot;no&quot;&gt;YAML&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dump&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;users&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;out&lt;/span&gt;
          &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here is a sample of what the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;users.yaml&lt;/code&gt; file looked like:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;nn&quot;&gt;---&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;:number: &quot;00000000&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;:name: Jonathan Younger&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;:eligible: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;:number: &quot;00000001&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;:name: Brian Cooke&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;:eligible: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;:number: &quot;00000010&quot;&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;:name: Justin Kay&lt;/span&gt;
  &lt;span class=&quot;s&quot;&gt;:eligible: &lt;/span&gt;&lt;span class=&quot;no&quot;&gt;false&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Here is what the app looked like while running:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/binary-lottery-2008/shoes.gif&quot; alt=&quot;Binary Lottery written in Shoes&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Logan Barnett also wrote a GUI using &lt;a href=&quot;http://jruby.codehaus.org/&quot;&gt;JRuby&lt;/a&gt; and &lt;a href=&quot;http://monkeybars.rubyforge.org/&quot;&gt;MonkeyBars&lt;/a&gt;. I really hope he releases his version as well. Here is what the app looked like on the third draw, sort of an easter egg I discovered during the conference:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/binary-lottery-2008/monkeybars.jpg&quot; alt=&quot;Binary Lottery written in JRuby with MonkeyBars&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Ruby Folk Are Nice</title>
   <link href="https://blowmage.com/2008/03/29/ruby-folk-are-nice/"/>
   <updated>2008-03-29T00:00:00+00:00</updated>
   <id>https://blowmage.com/2008/03/29/ruby-folk-are-nice</id>
   <content type="html">&lt;p&gt;We’re in the midst of &lt;a href=&quot;http://mtnwestrubyconf.org/&quot;&gt;MountainWest RubyConf 2008&lt;/a&gt;, and I have lots I want to blog about it. But before I get there, and before I take a 36 hour nap, I just want to let you know how nice Ruby folk are. This may be a fairly simple example, but I want to share it anyway.&lt;/p&gt;

&lt;p&gt;I’m one of the organizers of the conference, and that has meant that I miss about half the conference sessions because I’m running around trying to fix the power or fix the wifi or organize the distribution of food. On Friday we had a really delicious lunch from &lt;a href=&quot;http://www.rumbi.com/&quot;&gt;Rumbi’s&lt;/a&gt;, but it left the conference room we ate in rather messy. We paid a cleaning deposit, but the trash needed to be collected and the room straightened. I was informed of this just as we were breaking for dinner and I told them we would fix it before the evening session. I ended up stressing over this for the entire dinner break because I hate the idea of inconveniencing the great people at the &lt;a href=&quot;http://www.slcpl.lib.ut.us/locations.jsp?parent_id=8&amp;amp;page_id=20&quot;&gt;Salt Lake City library&lt;/a&gt; who have been so accommodating towards the 200 geeks who descended upon them destroying their power circuits and their wifi.&lt;/p&gt;

&lt;p&gt;I got back from dinner just 20 minutes before the evening session, and called out to the ~30 folks milling around in the foyer asking for help cleaning the room up.  We had ~8 people in the room and had the whole thing straightened up in under 5 minutes. That’s awesome.&lt;/p&gt;

&lt;p&gt;And to add to this, as we left at the end of the day I asked if everyone could pick up their empty water bottles. After chatting with some folks I walked out and the entire auditorium was cleaned out. Not a water bottle in site. Now this may seem like two very small things, but this means the world to me. It shows respect for the venue and I appreciate it very much. Thanks guys!&lt;/p&gt;

&lt;p&gt;I’ve had so much fun at the conference this year. I’ve fully enjoyed meeting new people and talking with everyone I’ve been able talk with. We have a wide variety of folks attending this year; from the mind-bendingly smart gurus to the total noobs. But everyone is getting along and helping each other become better. Not just better technically in regards to Ruby, but I honestly believe that we are helping each other become better people as well. There is a great vibe here, I wish everyone could experience it.&lt;/p&gt;

&lt;p&gt;I know we set the bar pretty high last year, but I think we on our way to being even better this year.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>IronRuby + C# = Awesomeness</title>
   <link href="https://blowmage.com/2008/03/10/ironruby-csharp-awesomeness/"/>
   <updated>2008-03-10T00:00:00+00:00</updated>
   <id>https://blowmage.com/2008/03/10/ironruby-csharp-awesomeness</id>
   <content type="html">&lt;p&gt;Saturday I gave a session at the third &lt;a href=&quot;http://www.boisecodecamp.org/&quot;&gt;Boise Code Camp&lt;/a&gt;. &lt;a href=&quot;http://www.cityofboise.org/&quot;&gt;Boise&lt;/a&gt; is my home town, so I am always happy to get back and enjoy the nostalgia. The Boise Code Camp this year was huge! As of Saturday morning there were 495 folks registered to attend, and 370 that were actually there. Crazy. Much props to the folks running it this year. Having helped organize the first Boise Code Camp and organized both MountainWest RubyConfs I know how hard they worked to pull this off. Great work guys!&lt;/p&gt;

&lt;p&gt;Here are some of my notes for my session. I use a modified &lt;a href=&quot;http://presentationzen.blogs.com/presentationzen/2005/09/living_large_ta.html&quot;&gt;Takahashi Method&lt;/a&gt; of presentation, so my slide deck doesn’t make much sense by itself. Also, I only gave three demos, and the code was really simple because I can’t type and talk at the same time. Another reason for limited demo code is that alot of these concepts are new to folks so I wanted to go slow and make sure I brought everyone along. Disclaimer finished, here is the &lt;a href=&quot;https://blowmage.com/assets/2008/3/10/ironruby-csharp-awesomeness.pdf&quot;&gt;slide deck&lt;/a&gt; and the &lt;a href=&quot;https://blowmage.com/assets/2008/3/10/ironruby-csharp-awesomeness.zip&quot;&gt;code&lt;/a&gt;. (Watch for the random Spider-Man appearance.)&lt;/p&gt;

&lt;h3 id=&quot;can-a-computer-language-be-beautiful&quot;&gt;Can a Computer Language be Beautiful?&lt;/h3&gt;

&lt;p&gt;I believe that software developers tend to create an exclusive culture around their technical knowledge. Most think that you need to “earn” your knowledge, and will respond to naive questions with answers such as “RTFM” or “Just Google it”. While this is understandable I think this attitude hurts more than it helps in the long run.&lt;/p&gt;

&lt;p&gt;Seeing as the majority of sessions are Microsoft-centric, I kept my observations to Microsoft technologies. I’ve got my own path through Microsoft technologies and really came to my own on “classic” ASP. ASP and COM were a great leap forward for web programming compared to what we were using before, but it was typical to end up with spaghetti code. The introduction of .NET was seen as a solution to ASP’s “loose” code, but it came at a cost of restricting how expressive our code could be, making beautiful code more difficult&lt;/p&gt;

&lt;p&gt;It shouldn’t come as a surprise that Ruby folks refer to Ruby code as “beautiful”. This is one of the things that I really love about the Ruby community. Ruby’s notion of Object Orientation is fundamentally different that C++/Java/C#. And Ruby’s meta-programming enables you to place responsibility where it belongs, and write expressive code by avoiding the accidental complexity typical of static language. This is why I like to think of Ruby as beautiful.&lt;/p&gt;

&lt;p&gt;I gave the example of trying to enable sorting of domain objects in a DataGridView control, because that just happened to come up yesterday. The .NET solution is to inherit from BindingList and implement 4 properties and 2 methods. The Ruby solution is to simply call the array’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sort_by&lt;/code&gt; passing a block of how you want the item’s sort value to be calculated. My point being that Ruby code tends to rely on on the trust of the developer, while there is no way to quantify that trust in statically-compiled C#.&lt;/p&gt;

&lt;h3 id=&quot;what-is-polyglot-programming&quot;&gt;What is Polyglot Programming?&lt;/h3&gt;

&lt;p&gt;Another term for “Polyglot Programming” can be “Language Oriented Programming”, or using the right tool for the right job. I asked the folks attending if their language is right for their task, and a few responded that they thought C# was indeed the right language. I countered by putting forth my opinion that unless it is a domain specific language it probably isn’t the right language. DSLs can express the domain instructions without all the cruft or implementation instructions. I’m not sure how well this was received, as most seemed skeptical.&lt;/p&gt;

&lt;p&gt;I also soap-boxed a bit about some developers defending C# to their dying breath, and the irony that the CLR was intended for multiple languages. Then I went into my belief that static languages are not the future, and in 5 years we will be using a dynamic language for the majority of work we are using static languages for today. You can read more about my thoughts on this &lt;a href=&quot;https://blowmage.com/2006/2/17/ruby-thinking&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://blowmage.com/2007/12/18/the-rubification-of-csharp&quot;&gt;here&lt;/a&gt; and &lt;a href=&quot;https://blowmage.com/2007/12/19/static-languages-are-fail&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But I don’t believe that C# will or should go away. C# will still be the best language for system-level coding, because it is so aligned to the CLR. What I do believe is that we will stack dynamic languages on top of the static languages, and DSLs on top of the dynamic languages. You can hear more about this in my &lt;a href=&quot;https://archive.org/details/RubiversePodcast/rubiverse-5-ola-bini-on-polyglot-programming.mp3&quot;&gt;Rubiverse Podcast with Ola Bini&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;what-can-ironruby-do-now&quot;&gt;What can IronRuby do now?&lt;/h3&gt;

&lt;p&gt;IronRuby is not complete and therefore not 100% compatible with MRI. But, there are some great things that we can do with IronRuby today. I gave four examples of how we can use IronRuby now. I didn’t have a demo for the testing though, I ran out of time.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Calling and using .NET objects from IronRuby&lt;/li&gt;
  &lt;li&gt;Replacing tests written in C# with tests written in IronRuby&lt;/li&gt;
  &lt;li&gt;Replacing application code written in C# with code in IronRuby&lt;/li&gt;
  &lt;li&gt;Provide Scripting in Application&lt;/li&gt;
&lt;/ul&gt;
</content>
 </entry>
 
 <entry>
   <title>The Blizzard of 2008</title>
   <link href="https://blowmage.com/2008/02/14/blizzard/"/>
   <updated>2008-02-14T00:00:00+00:00</updated>
   <id>https://blowmage.com/2008/02/14/blizzard</id>
   <content type="html">&lt;p&gt;Please excuse this non-geeky post, but today was harrowing and quite the tale. We came home from my grandmother’s funeral yesterday. We drove from Boise, ID to Cedar Hills, UT. The trip usually takes five hours, but yesterday it took nine due to major road work at Ogden, and a complete freeway closure north of Salt Lake City. My wife was scheduled to work the evening shift at the hospital that night, but the delays were too much and she didn’t make it in. Also, you can imagine how unhappy our two young girls were from sitting in the car for so long.&lt;/p&gt;

&lt;p&gt;Today, we got the &lt;a href=&quot;http://www.ksl.com/?nid=148&amp;amp;sid=2659200&quot;&gt;worst winter storm&lt;/a&gt; I have ever seen. On the way home from appointments in Salt Lake City, my wife and two girls got stuck in huge traffic jam due to the snow storm on the road home. This was just a couple miles from my work, where I was not allowed to leave because of the storm. I became increasingly worried, even more when my wife tried to turn around and got the car stuck in a snow drift. For two hours my little family was stuck and the girls became increasingly stir crazy while strapped in their car seats. Fortunately someone was kind enough to gently nudge our car out of the drift and my family was able to find shelter at a nearby hotel.&lt;/p&gt;

&lt;p&gt;My wife called work to tell them she couldn’t make it to work again. I was not able to get to them to watch our daughters in time for her to leave. And her scrubs were at home where she could not get them. And the freeway was so backed up it would take hours to get there, if at all. What are the odds of being unable to make it to work on consecutive nights for entirely different reasons?&lt;/p&gt;

&lt;p&gt;Two hours later we were told that we could leave the plant heading away from the hotel where my wife and daughters was staying. I decided to take the chance and leave in hopes of reuniting with my family. The doors of the plant were iced over to the point you could not see through them.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/blowmage/2264703038/&quot; title=&quot;IMG_0100 by blowmage, on Flickr&quot;&gt;&lt;img src=&quot;https://farm3.static.flickr.com/2179/2264703038_b5c94e1cbd_z.jpg&quot; alt=&quot;IMG_0100&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the view of the sidewalk out of the plant. The sidewalk is covered with little rolling waves of ice from one to two inches thick. Although it was slick there was enough traction to walk on it.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/blowmage/2264703042/&quot; title=&quot;IMG_0101 by blowmage, on Flickr&quot;&gt;&lt;img src=&quot;https://farm3.static.flickr.com/2086/2264703042_b852ccbffb_z.jpg&quot; alt=&quot;IMG_0101&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I was walking to my truck I was battered with wind gusts upwards of 50 mph. It was hard to keep my feet under me as I crossed the parking lot. As I got to my truck, I scrapped an inch of ice off my side-view mirrors. I have never seen anything like it. Sitting in my truck, the wind battered it back and forth as if an angry mob was trying to get me to leave.&lt;/p&gt;

&lt;p&gt;Visibility was low as plumes of snow and ice blew across the roads. The images captured by my iPhone do not capture the blowing snow and ice as well as I’d hoped.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/blowmage/2264703048/&quot; title=&quot;IMG_0102 by blowmage, on Flickr&quot;&gt;&lt;img src=&quot;https://farm3.static.flickr.com/2187/2264703048_322a03cd2d_z.jpg&quot; alt=&quot;IMG_0102&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I was eventually able to leave the plant traveling at ~5 mph. Even at that speed, I almost slid off the road twice and slid into the truck in front of my once.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/blowmage/2263917683/&quot; title=&quot;IMG_0106 by blowmage, on Flickr&quot;&gt;&lt;img src=&quot;https://farm3.static.flickr.com/2373/2263917683_39a09bc0c0_z.jpg&quot; alt=&quot;IMG_0106&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Eventually I was able to escape the worst of the storm, and then traveled across the valley, where I picked up some food, and then headed back towards the hotel. As I passed under the freeway I could see lines of cars and trucks just sitting there, like a very narrow parking lot. I was on the road three hours to get to the hotel. I was in bumper to bumper traffic most of the way. Eventually, I was able to break off to some back roads and arrived at the hotel.&lt;/p&gt;

&lt;p&gt;The hotel is filled with people trying to get rooms. The lobby is filled with folks tired of waiting on the freeway and deciding to cut their losses. We overheard discussions of folks spending two, three, and four hours stranded on the freeway.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/blowmage/2263917687/&quot; title=&quot;IMG_0107 by blowmage, on Flickr&quot;&gt;&lt;img src=&quot;https://farm3.static.flickr.com/2386/2263917687_aaba983664_z.jpg&quot; alt=&quot;IMG_0107&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since the girls were now asleep in the hotel room, and the lobby was so full, my wife and I decided to eat our dinner in the only other logical place: the bathroom.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.flickr.com/photos/blowmage/2263917689/&quot; title=&quot;IMG_0108 by blowmage, on Flickr&quot;&gt;&lt;img src=&quot;https://farm3.static.flickr.com/2296/2263917689_46c481e7ac_z.jpg&quot; alt=&quot;IMG_0108&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Winter Storm Advisory was supposed to end at 10:00 PM tonight, but has been extended to 5:00 AM tomorrow. Right now I’m not sure if I will be able to get back to work or even get home in the morning. But I’m very happy to be with my family and thankful we are all safe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; We made it home this morning and I made it in to work. I saw many abandoned cars and trucks off to the side of the road buried in snow. The road my family was stuck on was still closed this morning. I’ve heard &lt;a href=&quot;http://www.ksl.com/?nid=148&amp;amp;sid=2661908&quot;&gt;news reports&lt;/a&gt; that there were also elementary school buses stuck for hours on that road, and the children were eventually brought back to the school where they stayed the night.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>What I Do</title>
   <link href="https://blowmage.com/2008/01/06/what-i-do/"/>
   <updated>2008-01-06T00:00:00+00:00</updated>
   <id>https://blowmage.com/2008/01/06/what-i-do</id>
   <content type="html">&lt;p&gt;I don’t like talking about myself much, I’d rather talk about how to do stuff or what I think the best way to do said stuff is. But &lt;a href=&quot;http://www.chadmyers.com/&quot;&gt;Chad Myers&lt;/a&gt; “tagged” me in his &lt;a href=&quot;http://www.lostechies.com/blogs/chad_myers/archive/2008/01/05/what-i-do-with-spoilers.aspx&quot; title=&quot;with spoilers&quot;&gt;What I do&lt;/a&gt; post. Its been so long since someone used the word “tag” outside of &lt;a href=&quot;http://en.wikipedia.org/wiki/Tag_%28metadata%29&quot;&gt;metadata&lt;/a&gt; and &lt;a href=&quot;http://en.wikipedia.org/wiki/Folksonomy&quot;&gt;folksonomies&lt;/a&gt; that it took me a while to realize he meant it like the schoolyard game tag.&lt;/p&gt;

&lt;h3 id=&quot;my-day-job&quot;&gt;My Day Job&lt;/h3&gt;

&lt;p&gt;During the day I spend my time at &lt;a href=&quot;http://www.imftech.com/&quot;&gt;IM Flash&lt;/a&gt; where we make the NAND that makes your cell phones and mp3 players go. My title is “Software Engineer” and I work in the group that supports the engineers. We support a metric ton of systems that help the engineers keep track of the manufacturing process. Most of these systems involve schlepping data from one system to the other. We collect data from the tools, process the data, report on the data, move the data to centralized systems, report on that data again, etc… Some of our systems are 3rd party, but most are homegrown. I end up doing my fair share of Perl on Linux and Solaris, as well as WinForms and ASP.NET apps in C#. We also have a nice backlog of VB apps as well, but we aren’t much of a Java shop.&lt;/p&gt;

&lt;p&gt;When I’m not helping to keep the ship running, I actually get to do a bit of development. I am also the site’s IT architecture oversight representative, although it probably isn’t as fancy as you’d think. I participate in a variety of groups, both on site and around the company. I run a fun little group called TechShare, where we meet weekly and watch interesting videos and screencasts to help us improve as developers. I am also the self-proclaimed Agile and Ruby evangelist, and I’m sure everyone is tired of hearing it from me. When I’m not providing support or new development, I generally try to help improve things as much as I can.&lt;/p&gt;

&lt;h3 id=&quot;my-contribution&quot;&gt;My “Contribution”&lt;/h3&gt;

&lt;p&gt;I don’t consider my time at &lt;a href=&quot;htp://mtnwestruby.org/&quot;&gt;MountainWest Ruby, LLC&lt;/a&gt; a job, per se. I suppose I should as it is a real company, but I don’t take a salary and everything we do is to promote the Ruby language. MountainWest Ruby is the organization that hosts the &lt;a href=&quot;http://mtnwestrubyconf.org/&quot;&gt;MountainWest RubyConf&lt;/a&gt;, which is coming up March 28-29. MountainWest Ruby is a fun thing to be a part of, as I get to meet and interact with alot of really interesting and smart people that I probably wouldn’t have the opportunity otherwise.&lt;/p&gt;

&lt;p&gt;While we have a handful of folks involved with MountainWest Ruby, I think it would be fair to say I do the majority of the behing-the-scenes work. &lt;a href=&quot;http://on-ruby.blogspot.com/&quot;&gt;Pat Eyler&lt;/a&gt; is the public face of the conference, and he and I work very closely to make sure we act responsibly toward the community. I like to think that the community has entrusted us with their money, so I am very conscious where we spend it. Some might say I’m too frugal, but we operate on a shoestring budget so I feel I need to be.&lt;/p&gt;

&lt;p&gt;One thing I can say is that I love the Ruby community. It is by far the nicest and most giving online community I’ve ever been a part of, and I’m humbled that I get to be a part of that in some small way.&lt;/p&gt;

&lt;h3 id=&quot;my-night-job&quot;&gt;My Night Job&lt;/h3&gt;

&lt;p&gt;Forgive me for the shameless self-promotion, but I have been known to moonlight on Ruby, Rails, or .NET projects. My little one-man company is &lt;a href=&quot;http://humanecode.com/&quot;&gt;HumaneCode&lt;/a&gt;. I’m not actively seeking work at the moment so the website is embarrassingly bare. I’m sure you’ll understand.&lt;/p&gt;

&lt;h3 id=&quot;other-stuff&quot;&gt;Other Stuff&lt;/h3&gt;

&lt;p&gt;I’ve got more going on as well. I do a podcast about Ruby called &lt;a href=&quot;https://archive.org/details/RubiversePodcast/&quot;&gt;Rubiverse&lt;/a&gt; that is alot of fun. I try to be active in the &lt;a href=&quot;http://altdotnet.org/&quot;&gt;ALT.NET&lt;/a&gt; community as well, and hope to grow a local &lt;a href=&quot;http://groups.google.com/group/utahaltnet&quot;&gt;group&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;my-family&quot;&gt;My Family&lt;/h3&gt;

&lt;p&gt;My family is supremely important; I have a a wonderful wife and two beautiful little girls (with one in the oven). Without getting too personal, the past year has been hard for us. What I will say is the our trials have brought us even closer.&lt;/p&gt;

&lt;p&gt;I know this list seems like alot of stuff, but it all pales in comparison to the importance of my family. My wife has been very gracious to allow me to follow my interests and take chances. Without her and the girls it would be meaningless.&lt;/p&gt;

&lt;h3 id=&quot;who-next&quot;&gt;Who Next?&lt;/h3&gt;

&lt;p&gt;It seems implicit in this &lt;a href=&quot;http://en.wikipedia.org/wiki/Meme&quot;&gt;meme&lt;/a&gt; is to pass the pain along. Fortunately, there isn’t isn’t a set number I have to inflict, so I’ll just choose the two following.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.lukemelia.com/&quot;&gt;Luke Melia&lt;/a&gt; – Because I’m curious about what he does next.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://objo.com/&quot;&gt;Joe O’Brien&lt;/a&gt; – Because I find his business fascinating and I want to know more.&lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>A brief history of Ruby on .NET</title>
   <link href="https://blowmage.com/2008/01/03/ruby-on-dotnet-history/"/>
   <updated>2008-01-03T00:00:00+00:00</updated>
   <id>https://blowmage.com/2008/01/03/ruby-on-dotnet-history</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://www.iunknown.com/&quot;&gt;John Lam&lt;/a&gt; recently &lt;a href=&quot;http://www.iunknown.com/2008/01/ironruby-vs-rub.html&quot;&gt;responded&lt;/a&gt; to a post by &lt;a href=&quot;http://mdavid.name/&quot;&gt;M. David Peterson&lt;/a&gt; about the &lt;a href=&quot;http://www.oreillynet.com/windows/blog/2008/01/rubynet_vs_ironruby_whats_the.html&quot;&gt;differences between Ruby.NET and IronRuby&lt;/a&gt;. A few months ago &lt;a href=&quot;http://drnicwilliams.com/&quot;&gt;Dr. Nic&lt;/a&gt; (inadvertently) &lt;a href=&quot;http://drnicwilliams.com/2007/10/13/rubynet-goes-open-source/&quot;&gt;made it seem&lt;/a&gt; like the two projects are at odds. Afterwards the &lt;a href=&quot;http://softiesonrails.com/&quot;&gt;Softies on Rails&lt;/a&gt; guys seemed to &lt;a href=&quot;http://softiesonrails.com/2007/10/17/ruby-net-and-ironruby-are-open-sourced&quot;&gt;question Microsoft’s interest in Ruby&lt;/a&gt; and were disappointed with Microsoft’s “lack of support” for Ruby.NET. There seems to be some confusion about the relationship between IronRuby and Ruby.NET. Implicit in some online conversations about IronRuby and Ruby.NET is a distrust of Microsoft doing the right thing in regards to Ruby. I’ve read many statements that imply Microsoft is trying to kill Ruby.NET. I’ve also read that Microsoft is trying to kill Ruby! So for all those that haven’t been following Ruby’s adventures on Microsoft’s .NET framework, here is a (very) brief history as I remember it. Corrections are welcome.&lt;/p&gt;

&lt;h3 id=&quot;netruby&quot;&gt;NETRuby&lt;/h3&gt;

&lt;p&gt;Actually, Ruby.NET wasn’t the first Ruby implementation on .NET. Sure, there were Ruby to .NET bridges like &lt;a href=&quot;http://www.iunknown.com/&quot;&gt;John Lam&lt;/a&gt;’s &lt;a href=&quot;http://www.rubyclr.com/&quot;&gt;RubyCLR&lt;/a&gt; and &lt;a href=&quot;http://www.saltypickle.com/&quot;&gt;SaltyPickle&lt;/a&gt;’s &lt;a href=&quot;http://www.saltypickle.com/rubydotnet&quot;&gt;Ruby/.NET Bridge&lt;/a&gt; before, but there was also an actual port of Ruby 1.6 to .NET by &lt;a href=&quot;http://arton.hp.infoseek.co.jp/&quot;&gt;arton&lt;/a&gt;. This project was called named &lt;a href=&quot;http://www.geocities.co.jp/SiliconValley-PaloAlto/9251/ruby/nrb.html&quot;&gt;NETRuby&lt;/a&gt; and it was a straight port of the Ruby C code to C# in 2001. I think I got it working on some small things, and I found the code pretty interesting. But the effort eventually died from lack of attention and/or need, as it was started and abandoned long before Rails was born and brought so much attention to the Ruby language.&lt;/p&gt;

&lt;h3 id=&quot;the-first-ironruby&quot;&gt;The First IronRuby&lt;/h3&gt;

&lt;p&gt;Another early Ruby on .NET project was &lt;a href=&quot;http://www.wilcob.com/&quot;&gt;Wilco Bauwer&lt;/a&gt;’s &lt;a href=&quot;http://www.wilcob.com/Wilco/IronRuby.aspx&quot;&gt;IronRuby project&lt;/a&gt;. Wilco based his implementation on the IronPython implementation. Although the current &lt;a href=&quot;http://www.ironruby.net/&quot;&gt;IronRuby&lt;/a&gt; project shares the name, the two projects don’t share any code. I don’t believe Wilco ever publicly released the source code for his implementation. I do remember playing with the compiled version he released, but it was far from complete.&lt;/p&gt;

&lt;h3 id=&quot;rubynet&quot;&gt;Ruby.NET&lt;/h3&gt;

&lt;p&gt;Around the time that Wilco was talking about his IronRuby implementation, the &lt;a href=&quot;http://plas.fit.qut.edu.au/&quot;&gt;Queensland University of Technology&lt;/a&gt; folks announced that they had received funding from Microsoft Research to implement &lt;a href=&quot;http://plas.fit.qut.edu.au/Ruby.NET/&quot;&gt;Ruby on the .NET Framework&lt;/a&gt;. I remember that Microsoft was just starting to show more interest in dynamic languages, due to the success of IronPython and the sudden and overwhelming emergence of Ruby (largely because of Rails). &lt;a href=&quot;http://rubydotnet.googlegroups.com/web/Home.htm&quot;&gt;Ruby.NET&lt;/a&gt;’s runtime involves creating wrapper objects for the .NET objects to implement all the Ruby-ish features. This is a very common approach for implementing dynamic languages on a static language runtime. The early work from the Queensland guys resulted in code drops every six months or so. As the project has matured the releases became more frequent until the code was eventually put into a &lt;a href=&quot;http://code.google.com/p/rubydotnetcompiler/&quot;&gt;public subversion repository&lt;/a&gt; on Google’s service.&lt;/p&gt;

&lt;h3 id=&quot;the-current-ironruby&quot;&gt;The Current IronRuby&lt;/h3&gt;

&lt;p&gt;Microsoft’s investment in Ruby.NET was rewarded when &lt;a href=&quot;http://www.iunknown.com/&quot;&gt;John Lam&lt;/a&gt; joined Microsoft to implement Ruby on the yet to be announced Dynamic Language Runtime (DLR). The DLR sits on top of the CLR and allows IronRuby to add Ruby-ish features to the .NET objects without creating proxies. The DLR also allows different dynamic languages to interoperate in the same way that languages such as C# and VB.NET can interoperate on the CLR. For legal reasons John’s team can’t look at the original C implementation of Ruby (you can listen to my &lt;a href=&quot;https://archive.org/details/RubiversePodcast/&quot;&gt;Rubiverse&lt;/a&gt; podcast interview &lt;a href=&quot;https://archive.org/details/RubiversePodcast/rubiverse-1-john-lam-on-ironruby.mp3&quot;&gt;with John&lt;/a&gt; for the details to why this is), but was able to license and look at the Queensland implementation of Ruby.NET. John’s team was also able to use parts of Ruby.NET to more quickly stand up IronRuby. Although it seems that the parts of Ruby.NET in IronRuby are scheduled to be replaced in order to provide better future integration with the Visual Studio IDE. So that’s is. The efforts are dramatically different in their implementation, and address different needs. I see them both as complementary, and having a similar relationship as the one between &lt;a href=&quot;http://jruby.codehaus.org/&quot;&gt;JRuby&lt;/a&gt; and &lt;a href=&quot;http://xruby.com/&quot;&gt;XRuby&lt;/a&gt;.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>The Failure of Static Languages</title>
   <link href="https://blowmage.com/2007/12/19/static-languages-are-fail/"/>
   <updated>2007-12-19T00:00:00+00:00</updated>
   <id>https://blowmage.com/2007/12/19/static-languages-are-fail</id>
   <content type="html">&lt;p&gt;So, &lt;a href=&quot;http://weblogs.asp.net/bleroy/&quot;&gt;Bertrand Le Roy&lt;/a&gt; left a comment on my &lt;a href=&quot;https://blowmage.com/2007/12/18/the-rubification-of-csharp&quot;&gt;previous post&lt;/a&gt; calling me out for saying, “static languages have failed”. I think I need more space than the comment field to make a full reply, so I’ll do it here with a sensational title to gain the maximum attention. ;)&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;I usually enjoy reading your blog but wow. “Static languages have failed”? Just wow. Don’t you think that viewpoint might be just a little myopic?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Like I said in my comment, I hope this view isn’t myopic. The last 20+ years have been dominated by static languages like C++, Java, and C#. I believe we are in the midst of a dynamic language revolution and I expect the landscape to look dramatically different in 3 to 5 years. I’m talking about the entire IT landscape, not just the .NET or J2EE landscape (although they will look different as well).&lt;/p&gt;

&lt;p&gt;My point isn’t that static languages aren’t useful and won’t continue to be used. But they aren’t where I believe the majority of development will be done in the future. Just as you would build a departmental client app using VB instead of C++ 10 years ago, you will be using a dynamic language instead of a static language in 3-5 years.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Because they don’t work for you or for the microcosm you’re hanging out in doesn’t mean it doesn’t work for the rest of the world.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My point is that static languages don’t work &lt;em&gt;for you&lt;/em&gt;! Creating an interface just to run a unit test is a sign (to me) of a weak language. The existence of so many design patterns and anti-patterns are also a sign (to me) of a weak language. There are and will be better languages that allow you to get your work done faster while being more maintainable.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Static languages have been and continue to be very successful. Maybe it would be a little more productive to recognize the merits of each side before making definitive judgements like this one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don’t mean to take any thing away from static languages like C# and Java. Hell, I program in C# every single day. Well, every work day. And I certainly don’t hate C#, but I don’t love it like I love Ruby. I think I’ve been a good soldier in the static camp long enough to make up my own mind and voice my opinion. I don’t do so out of ignorance.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;It might also help the dynamic language world to recognize major advances in language design (which imo Linq is) so that they can incorporate similar concepts in their own favorite technology.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I know many folks have been excited by the prospect of using LINQ from IronRuby. But honestly I don’t know how to take advantage of LINQ from IronRuby. If you have your local objects, you can already iterate and .map() or .collect() on them. And there are already ORM and data access libraries that will use a DSL or take a closure and query your data store. &lt;a href=&quot;http://onestepback.org/&quot;&gt;Jim Weirich&lt;/a&gt; recently discussed building such a library at &lt;a href=&quot;http://rubyconf2007.confreaks.com/d1t1p2_advanced_ruby_class_design.html&quot;&gt;RubyConf&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;LINQ looks to be very useful and a huge win while working in C# and VB.NET. But I don’t need that in a dynamic language like Ruby because I already have those facilities. So I don’t see LINQ as a major advance in language design.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;One thing that can be said from the C# team (which from our own microcosm looks very very far from “desperate”) is that &lt;em&gt;they&lt;/em&gt; know how to keep an eye on the other side and incorporate what looks interesting, while not compromising the integrity of the language.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I didn’t mean to imply that the C# team is desperate, just that the language is trying hard to stay relevant amidst the dynamic language onslaught. There are alot of reasons why I’ll do my best to stay on the .NET platform, but I can’t say the same for C#. From the beginning the CLR was designed to support multiple languages, so I’ve always found the staunch defense of C# confusing. Particularly amongst the &lt;a href=&quot;http://altdotnet.org/&quot;&gt;Alt.NET&lt;/a&gt; crowd, but that’s a topic for a different post. :)&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;In that respect, anonymous types capture an essential aspect of dynamic languages while remaining strongly-typed (and thus keeping IntelliSense working).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think there is the perception that you can’t get IntelliSense©™ or performance with dynamic languages. You certainly can. We might not be there 100% just yet, but we will get there. &lt;a href=&quot;http://www.netbeans.org/&quot;&gt;NetBeans&lt;/a&gt; and &lt;a href=&quot;http://www.activestate.com/Products/komodo_ide/&quot;&gt;Komodo&lt;/a&gt; and &lt;a href=&quot;http://www.sapphiresteel.com/&quot;&gt;Ruby in Steel&lt;/a&gt; are good examples of Ruby IDEs with autocomplete.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You might also want to consult the documentation for anonymous methods, which do capture surrounding local variables (since 2.0), which in my book is pretty much what a closure is.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Great point! Anonymous methods are definitely closer to closures than anonymous delegates. But the point I tried to make about anonymous delegates wasn’t about local variables as parameters; it was about accessing private class variables. Once you replace the delegate you lose the ability to access private class variables.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Don’t want to look aggressive or anything, sorry if I did, not my intention. Keep up the good work and the provocative thinking.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No way man, I appreciate you wanting to engage in a discussion about this! There are a couple more points I want to make to clarify what I’m trying to say but didn’t fit well in response to you comment.&lt;/p&gt;

&lt;p&gt;I say static languages have failed because the features we are looking for in future languages are at odds with static typing. For example, static languages achieve polymorphism through inheritance (or implementation of an interface) instead of duck-typing/composition/parametric polymorphism. So you have an explosion of interfaces when you want to test your code. But those interfaces aren’t for reuse as much as they are for testing. They don’t really help the reason your code exists, just your test cases. You end up with all that code cruft adding little value because static languages achieve polymorphism through tightly coupling implementations. Meanwhile Ruby promotes object composition over class inheritance.&lt;/p&gt;

&lt;p&gt;My problem with the Rubification of C# is that C# has incorporated some of the aspects of Ruby, but not the real power of Ruby. Its all sugar and no meat. OO in Ruby or Smalltalk is very different than OO in C# or Java. I’ve had many discussions where other developers have said they are a better C++ or Java or C# programmer because of the time they’ve spent with Ruby. That isn’t because of Ruby’s syntax, that is because of Ruby’s fundamentally different understanding of objects.&lt;/p&gt;

&lt;p&gt;Am I right or am I wrong? Please let me know. You can either leave me a comment or send me &lt;a href=&quot;mailto:mike@blowmage.com?subject=You Suck! Static Languages ARE the Future!&quot;&gt;hate&lt;/a&gt; (or &lt;a href=&quot;mailto:mike@blowmage.com?subject=You Rock! Static Languages are SO 2007!&quot;&gt;fan&lt;/a&gt;) mail.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>The Rubification of C#</title>
   <link href="https://blowmage.com/2007/12/18/the-rubification-of-csharp/"/>
   <updated>2007-12-18T00:00:00+00:00</updated>
   <id>https://blowmage.com/2007/12/18/the-rubification-of-csharp</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://chadmyers.com/Blog/&quot;&gt;Chad Myers&lt;/a&gt; gave me a nice little surprise by using my surname in his blog post &lt;a href=&quot;http://chadmyers.com/Blog/archive/2007/12/15/moore-on-the-anonymous-delegate-approach.aspx&quot;&gt;“Moore on the Anonymous Delegate Approach”&lt;/a&gt; about an approach to make his statically-typed C# code more easily testable. I struggled with how best to respond because the subject of his code experiments really demonstrates some of the things I’ve been thinking about C#’s evolution for a while now. So while this isn’t a direct response to his post, its something I’ve been wanting to say for a while now.&lt;/p&gt;

&lt;p&gt;Chad started out by showing how you can use anonymous delegates to encapsulate your logic, and then swap out the delegates in his test cases so he can verify the dependent objects work properly. So he is essentially swapping the implementation of the code his object has a reference to. The fly in the ointment is that only the original anonymous delegate can access all the internal data in his object, so once you swap out the object’s original anonymous delegate, the object becomes ineffective. (Unless your logic is dead simple, like the kind you use when you are writing code demos.)&lt;/p&gt;

&lt;p&gt;The thing I really like about Chad’s code is that he’s getting really close to using anonymous delegates as closures, and I really love closures. I believe if Microsoft would (or could) add closures to the C# language (and idioms) that C# would be better for it. But closures aren’t embraced by C#. I understand that he is finding new ways to solve the testability problem with static languages, and while I enjoy his ingenuity, I’m starting to think that C# is moving too far from what it is. My concern is that as C# tries to be all things to all people, it simply stops being as useful.&lt;/p&gt;

&lt;p&gt;Coincidentally, Tim Bray posted his thoughts on &lt;a href=&quot;http://www.tbray.org/ongoing/When/200x/2007/12/16/On-Closures&quot;&gt;adding closures to Java&lt;/a&gt; yesterday, and he said many of the things I’ve been thinking about in regards to C# and Ruby. The only caveat I’d make on applying Tim’s thoughts to C# and .NET is that Java Generics != .NET Generics. Essentially, C# is an 80% language, and I’m totally okay with it. Ruby is also an 80% language, although a much different 80%. Making C#’s 80% overlap overlap Ruby’s 80% as much as possible seems kinda wrong.&lt;/p&gt;

&lt;p&gt;Tim’s favored approach to improving Java is to work on a multi-language VM, not the core language itself. .NET is already ahead because it has facilities for interoperating multiple languages. Therefore I don’t think we should focus on improving C#, but we should start focusing on using the right tool for the right job; meaning the right language. Don’t get me wrong, I appreciate the fact that C# is taking on many Rubyisms in an effort to improve. But C# will never be Ruby, and really it never should. My hopes are that the DLR enables C# and IronRuby to not just co-exist but integrate well.&lt;/p&gt;

&lt;p&gt;In &lt;a href=&quot;http://channel9.msdn.com/ShowPost.aspx?PostID=350187&quot;&gt;this Channel9 video&lt;/a&gt; &lt;a href=&quot;http://channel9.msdn.com/ShowPost.aspx?PostID=350187&quot;&gt;Robert Martin&lt;/a&gt; and &lt;a href=&quot;http://chadfowler.com/&quot;&gt;Chad Fowler&lt;/a&gt; discuss dynamic vs. static languages at JAOO 2007. You should &lt;a href=&quot;http://channel9.msdn.com/ShowPost.aspx?PostID=350187&quot;&gt;watch this video now&lt;/a&gt;. In it, Uncle Bob was asked if he was excited about the new functional programming features of C#; specifically LINQ. His response? “I’m about as excited about that as I am about Fortran 95.” His point was that by the time the new Fortran was released he had already moved on and it was no longer relevant to him. That is certainly how I feel about C#; the more features they add, the more desperate it looks, and the less I feel like investing myself in it.&lt;/p&gt;

&lt;p&gt;Static languages have failed. They aren’t dead, but they aren’t the future. You can only fight a language so long before its time to move on.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Why the kids don&apos;t like the RSS</title>
   <link href="https://blowmage.com/2007/10/20/social-networks-suck/"/>
   <updated>2007-10-20T00:00:00+00:00</updated>
   <id>https://blowmage.com/2007/10/20/social-networks-suck</id>
   <content type="html">&lt;p&gt;A couple days ago Cote asked &lt;a href=&quot;http://twitter.com/cote/statuses/346353002&quot;&gt;“Is Web 2.0 the middle-agers web?”&lt;/a&gt; and today he asks if &lt;a href=&quot;http://www.redmonk.com/cote/2007/10/20/do-the-kids-like-the-rss/&quot;&gt;the kids like the RSS&lt;/a&gt;. The answer is obviously no; kids think blogs and RSS aren’t a big deal. Not to say that blogs and RSS aren’t useful or of value; just that their technical details aren’t important to the kids and their damn rock and roll music. I think that blogs, RSS, and to an extent even twitter succeed because they are geek tools.&lt;/p&gt;

&lt;p&gt;Facebook and MySpace have found success by making the process of creating and consuming blogs and RSS content approachable by the vast sea of non-geek users on the Interweb. They, and other social networks of their ilk, have taken our awesome geek ideas and packaged them up in a seamless… package. Well, maybe not seamless, but they hide most if not all of the complexity and apparently by doing so have created &lt;em&gt;billions&lt;/em&gt; of dollars of value.&lt;/p&gt;

&lt;p&gt;Apparently value is added because those punk kids on MySpace don’t want the complexity of setting up NetNewsWire or Google Reader with their favorite feeds. Or, they have no idea what I just said and are incapable of doing so. They just want to go to a site, any site, and see what their friends are doing. And make the Internet a little bit uglier. Yeah, it doesn’t make much sense to me either but apparently the unwashed masses love it.&lt;/p&gt;

&lt;p&gt;I like the prospects of using Facebook’s platform to build apps and make money, but I don’t personally like the idea of using &lt;a href=&quot;http://www.facebook.com/people/Mike_Moore/582561201&quot;&gt;Facebook&lt;/a&gt;. And I can’t stand &lt;a href=&quot;http://www.myspace.com/blowmage&quot;&gt;MySpace&lt;/a&gt;. I’m more lenient about &lt;a href=&quot;http://www.linkedin.com/in/mikemoore&quot;&gt;LinkedIn&lt;/a&gt;, but I think that is because I have delusions of actually getting work from it… some day… eventually. The reason I don’t like them is because they feel restrictive, and don’t work they way I want them to work. But I’m a geek and like all self-respecting geeks I have a strong opinion of how the world &lt;em&gt;should&lt;/em&gt; work. And so far the social network apps just haven’t done it for me.&lt;/p&gt;

&lt;p&gt;I like &lt;a href=&quot;http://twitter.com/blowmage&quot;&gt;twitter&lt;/a&gt; and I like &lt;a href=&quot;http://del.icio.us/blowmage&quot;&gt;del.icio.us&lt;/a&gt; and I like &lt;a href=&quot;http://www.flickr.com/photos/blowmage/&quot;&gt;Flickr&lt;/a&gt;. I like the fact that &lt;a href=&quot;https://blowmage.com/&quot;&gt;blowmage.com&lt;/a&gt; is the central hub for my online presence. I like keeping track with people through their blogs, and having public discussions on mailing lists. Maybe if I were 20 years younger I’d think differently, but what the hell do those whippersnapper know?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Getting RubyGems to play nice at work</title>
   <link href="https://blowmage.com/2007/10/18/rubygems-at-work/"/>
   <updated>2007-10-18T00:00:00+00:00</updated>
   <id>https://blowmage.com/2007/10/18/rubygems-at-work</id>
   <content type="html">&lt;p&gt;At work I’m on Windows and behind a proxy server. I recently wiped my workstation and started over. After I installed Ruby I found the gems command not working, again. The reason is that my proxy server requires a username and password, and open-uri doesn’t pass them. (Supposedly because keeping your username and password in an environment variable isn’t secure, but at work I don’t care as much about apps snooping my environment variables.)&lt;/p&gt;

&lt;p&gt;To get things working I had to hack up open-uri a bit.  So I found the following code in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C:\ruby\lib\ruby\1.8\open-uri.rb&lt;/code&gt;&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;net/http&apos;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;klass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# HTTP or HTTPS&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proxy&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;klass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;And I make the following change:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;net/http&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;klass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# HTTP or HTTPS&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proxy&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;# Add proxy user and password to work with the proxy server.&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;klass&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;HTTP&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;port&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Then I create the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http_proxy&lt;/code&gt; environment variable with the value of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://blowmage:s3cr3t@proxy:8080&lt;/code&gt; and I’m good to go. Well, I have to log off and log back in for Windows to find and RubyGems to use the new environment variable. Oh, and be sure to create it as a user variable and not a system variable. :)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Ruby Blue Visual Studio Theme</title>
   <link href="https://blowmage.com/2007/10/10/ruby-blue-visual-studio-theme/"/>
   <updated>2007-10-10T00:00:00+00:00</updated>
   <id>https://blowmage.com/2007/10/10/ruby-blue-visual-studio-theme</id>
   <content type="html">&lt;p&gt;Now that I’m blogging again, and I’ve had a resurgence of .NET related posts, I thought I’d add to a meme I saw going around earlier this summer. First John Lam posted his &lt;a href=&quot;http://www.iunknown.com/2007/06/vibrant_ink_vis.html&quot;&gt;Vibrant Ink&lt;/a&gt; theme for Visual Studio, then I saw Tomas Restrepo post his &lt;a href=&quot;http://www.winterdom.com/weblog/2007/09/11/NightingaleAVS2005ColorScheme.aspx&quot;&gt;Nightingale&lt;/a&gt; theme.&lt;/p&gt;

&lt;p&gt;I’ve always been a fan of John Long’s &lt;a href=&quot;http://wiseheartdesign.com/2006/3/11/ruby-blue-textmate-theme/&quot;&gt;Ruby Blue TextMate&lt;/a&gt; theme. So I created my own Visual Studio theme (including the Monaco font) a long time ago and thought I should &lt;a href=&quot;http://github.com/downloads/blowmage/rubyblue/RubyBlueVS2005.zip&quot;&gt;share it here&lt;/a&gt;. Feel free to fork &lt;a href=&quot;http://github.com/blowmage/rubyblue&quot;&gt;my repo&lt;/a&gt;, improve it, and send me a pull request. Enjoy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Will we ever get IronRuby on Rails?</title>
   <link href="https://blowmage.com/2007/10/10/ironruby-on-rails/"/>
   <updated>2007-10-10T00:00:00+00:00</updated>
   <id>https://blowmage.com/2007/10/10/ironruby-on-rails</id>
   <content type="html">&lt;p&gt;Last weekend the ALT.NET Conf I had a couple opportunities to plead my case for Microsoft to embrace the Ruby on Rails framework and better enable it on IIS. I believe IronRuby will eventually run Rails, and I believe when it does the community will implement whatever hooks are required to run Rails using the DLR on IIS. But I want more. I want Microsoft to stand behind Rails and encourage its use. I want an “official” deployment story for Rails using IronRuby and IIS like the one Sun has with JRuby. I was able to discuss this with Scott Guthrie himself at the end of Scott Hanselman’s DLR/MVC session, but I don’t think I changed his mind at all. Sorry guys.&lt;/p&gt;

&lt;p&gt;Half of the resistance seemed to be that Rails was too much of a fad and a moving target. I heard ScottGu say on a couple of occasions that prototype.js was really hot last year, but this year everyone is using jQuery. I also heard him say that last year everyone was all excited about RJS, but this year nobody is actually using it. The implication seemed to be that the Rails community couldn’t make up its mind. But the reality is that the Rails community is having a conversation about how to build web apps better and it seems like Microsoft is either ignoring the conversation or blissfully ignorant of it. Neither is the case, obviously; but Microsoft has yet to join the conversation. In talking with ScottGu it became apparent to me exactly how well tuned in he is. He has obviously talked to enough Rails guys to have satisfied himself that Microsoft is making informed decisions.&lt;/p&gt;

&lt;p&gt;The other half of the resistance to official support for “IronRails” is the giant mutex lock hanging around Rails’ neck. The implication with this one was that as soon as the Rails team fixes the threading model, then Microsoft can have something to work with to get a performant Rails on IIS. I wish I had a good answer to this. The JRuby guys appear to be working hard on improving performance of Rails on JRuby, and having success. Can something similar be done for IronRuby on IIS?&lt;/p&gt;

&lt;p&gt;The third half of the resistance to Rails was that the Rails community is simply not interested in running on Windows in any meaningful way. Its been historically hard to get support from the community for the SQL Server adapter in Rails, and there has been very little activity on getting Rails running on the new FastCGI ISAPI filter on IIS. I don’t have a good answer for these things either. Personally, I don’t have permissions to the web servers at my work; so I can’t get FastCGI on the server because it isn’t on the roadmap. But this is exactly why I want support via the DLR; so I don’t have to have admin access to the server to deploy a Rails app because its all just .NET.&lt;/p&gt;

&lt;p&gt;I disagree with ScottGu’s view was that the Rails community isn’t committed to running on Windows. I don’t think that is it exactly; I think the Rails community isn’t committed to running on IIS. Deploying Rails on IIS is more than just FastCGI; you also need a mod_rewrite replacement and there doesn’t appear to be any free solution for that. Simply put, its too painful to deploy on IIS and its much easier to deploy on Apache. Rails devs are usually pragmatic folks, and will go with a working solution.&lt;/p&gt;

&lt;p&gt;But personally, I really want to build Rails apps. And I’d hate to have to not be able to do that on Windows and .NET. I have very high hopes for the new ASP.NET MVC framework, but I’d also like a commitment from Microsoft to the Rails community similar to the commitment made to the Ruby community. And honestly, I think it would be in their best interest long term.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ALT.NET Conf: Day 3</title>
   <link href="https://blowmage.com/2007/10/08/altnetconf-day-3/"/>
   <updated>2007-10-08T00:00:00+00:00</updated>
   <id>https://blowmage.com/2007/10/08/altnetconf-day-3</id>
   <content type="html">&lt;p&gt;Today was abbreviated with only two sessions this morning and then a combined closing session. I had a late start (again) this morning, and missed the opening comments (again). I know this makes me seem like the kinda guy who always sleeps in (and the guys I work with would definitely agree with that characterization) but I want to point out that a) I was &lt;em&gt;very&lt;/em&gt; sleep deprived going into this weekend, and b) conferences like this are physically draining. There is so much mental engagement that it really does fatigue you physically. Really.&lt;/p&gt;

&lt;p&gt;The first session I attended was about Mono. I haven’t looked at Mono in years, and I was impressed with what I saw. The &lt;a href=&quot;http://mono-project.com/MoMA&quot;&gt;Mono Migration Analyzer&lt;/a&gt; app (MoMA) was really cool. It essentially inspects your .NET *.exe and *.dll files and identifies what calls used are not available on Mono. This is a great idea, and apparently Mono’s compatibility was improved by the real world metrics collected through the tool.&lt;/p&gt;

&lt;p&gt;I cut the session a bit short to catch up with my family, and when I returned the session had migrated next door to a discussion on why the MSDN magazine sucks and how to market the ALT.NET message. I don’t read the MSDN magazine (but I will if someone sends me a free subscription), so I don’t know if it sucks or not and didn’t have much to add. However, I think its a bit premature to focus so much on online community building and marketing. I kinda like ALT.NET being undefinable and using guerilla marketing efforts to build awareness of its ambiguity. It just seems more authentic to me that way. Also, I don’t see a message well defined enough to actively market. After all the current message is basically, “do what works and don’t be afraid to look to other communities to see what works there”.&lt;/p&gt;

&lt;p&gt;The mid morning session was the assigned slot for my proposed “Dynamic Languages on the CLR” session. Last night Scott Bellware suggested that what folks would really benefit from was a real life Ruby on Rails demo using test first with RSpec, so I changed the focus of the session to “Ruby on Rails Demonstration”. Unfortunately Scott Bellware bailed on me (he had a scheduling conflict) and I recruited &lt;a href=&quot;http://lukemelia.com/&quot;&gt;Luke Melia&lt;/a&gt; to pair with me for the session. Scott Hanselman attended and he started a really good discussion before we started writing code. There seemed to be alot of folks who either tried Rails, or knew someone who tried Rails, and were let down by the experience. There was this perception that Rails is &lt;em&gt;so&lt;/em&gt; cool that you don’t have to understand what is going on under the hood. This is true for very simple, golden path apps; but very untrue for most real-world apps. Like anything in life, you get out of Rails what you put into it.&lt;/p&gt;

&lt;p&gt;The Rails session seemed to go well and I had a couple folks thank me for showing them the approach Rails guys take to solve errors when coding. I really like live coding demonstrations where you don’t go in with a scripted plan. For instance, The video of the &lt;a href=&quot;http://mtnwestrubyconf2007.confreaks.com/session10.html&quot;&gt;Jamis Buck and Marcel Molina, Jr. session&lt;/a&gt; at the &lt;a href=&quot;http://mtnwestruby.org/&quot;&gt;MountainWest RubyConf&lt;/a&gt; is by far the most downloaded session of the conference. I hope I continue to see more and more of those types of sessions at future conferences.&lt;/p&gt;

&lt;p&gt;After that all conference attendees poured into one room and one by one we all made comments about what we got from the experience. This was incredibly positive and was a great way to close the conference. This conference wasn’t so much about technology as it was focused on the individual. I was happy to be a part of it and I look forward to more gatherings and communications about the ALT.NET ideals, even if it is a big ball of mud at the moment. :)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ALT.NET Conf: Day 2</title>
   <link href="https://blowmage.com/2007/10/07/altnetconf-day-2/"/>
   <updated>2007-10-07T00:00:00+00:00</updated>
   <id>https://blowmage.com/2007/10/07/altnetconf-day-2</id>
   <content type="html">&lt;p&gt;The next day of the &lt;a href=&quot;http://altnetconf.com/&quot;&gt;ALT.NET Conference&lt;/a&gt; started with me sleeping in and missing the opening announcements. So as far as I know there was an edict to not talk about Ruby. An edict that I bravely disobeyed.&lt;/p&gt;

&lt;p&gt;The first session I attended was “Ruby for &lt;strike&gt;Dummies&lt;/strike&gt; .NET Developers” and I volunteered to show my intro to Ruby slide desk. I originally built the desk for a ~90 minute presentation, so zipping through the desk in 20 minutes may have resulted in making the message come across a bit strong. But we had a really good discussion about what Ruby is and why Rubyists love it. I really emphasized the human oriented view that Rubyists have and that the Ruby language enables. Folks seemed to go back to the open classes feature over and over. I was really happy (and honestly surprised) when Roy Osherove later announced that he now gets why folks love Ruby and is excited about it.&lt;/p&gt;

&lt;p&gt;The next session was BDD. It was held in the big room and there were alot of folks in that session. There was alot of showing BDD-style code, arguing, and pointing to the screen. It seems that everyone had a different view of BDD and nobody could express exactly what the benefits were to using it. I got kinda frustrated in this session because both philosophically and in practice BDD is great, but nobody could express that during the session. I wish we had a more formal presentation to frame the discussion around.&lt;/p&gt;

&lt;p&gt;After lunch Scott Guthrie showed the new ASP.NET MVC framework. His main points for the framework were:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Separation of Concerns, Testing, Red/Green TDD, Maintainable By Default&lt;/li&gt;
  &lt;li&gt;Extensible and Pluggable&lt;/li&gt;
  &lt;li&gt;Enable clean URLs and HTML&lt;/li&gt;
  &lt;li&gt;Integrate nicely with ASP.NET + .NET; Support Static + Dynamic Languages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I was really surprised to see how well thought out the framework is, and how extensible and testable the code you can write in it can be. Throughout the demo he highlighted features that were inspired by web frameworks that real developers are using like &lt;a href=&quot;http://castleproject.org/monorail/index.html&quot;&gt;MonoRail&lt;/a&gt; or &lt;a href=&quot;http://rubyonrails.org/&quot;&gt;Rails&lt;/a&gt; or &lt;a href=&quot;http://djangoproject.com/&quot;&gt;Django&lt;/a&gt;. He also showed how it was designed to be testable using any of the current unit testing frameworks. Even more surprising that is uses the current ASP.NET stack, Provider model, Request and Response objects, &lt;em&gt;.aspx/&lt;/em&gt;.master files, etc. The framework seemed to me to be a brilliant combination of idiomatic .NET features and the agile development practices espoused by open source web frameworks. You can see the video Scott Hanselman took of the demo soon.&lt;/p&gt;

&lt;p&gt;As seamless as ScottGu’s demo was, Scott Hanselman’s follow up presentation on making the MVC framework use dynamic languages (via the DLR) showed the real pain that lies in front us to integrate dynamic languages into existing static code. For example, configuring and using IronPython in ASP.NET or the new MVC framework seems way more painful than it should. Scott showed the several changes to an older version of the MVC framework to use IronPython as both controllers and views. And Phil Haack (who wasn’t here) also did alot of work to use Ruby ERB files for the view using IronRuby. I’m excited that this work is being done, and I hope that the process will lead to a detailed white-paper on how best to integrate static and dynamic code.&lt;/p&gt;

&lt;p&gt;I really liked what I saw with the MVC framework. I can easily see me easing on the fight to get my full-time employer to give Rails a try because the ASP.NET MVC framework will give me all I need and most I want to get my “real” work done. I’ll certainly not leave Ruby, but I will definitely be more active in the .NET world because of the ASP.NET MCV framework. So if convincing me to give .NET another shot was the Scott(s) goal; then mission accomplished.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; I ended the day with a &lt;a href=&quot;http://www.flickr.com/photos/blowmage/1501544424/&quot;&gt;pile of brisket&lt;/a&gt; at Rudy’s and some great conversation with some .NET dignitaries. I tried to get folks to show up for a coordinated hacking session where we will try to make the world better… but the bar seemed to be a bigger attraction for most. (The Ruby community would have combined those two activities…)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>ALT.NET Conf: Day 1</title>
   <link href="https://blowmage.com/2007/10/06/altnetconf-day-1/"/>
   <updated>2007-10-06T00:00:00+00:00</updated>
   <id>https://blowmage.com/2007/10/06/altnetconf-day-1</id>
   <content type="html">&lt;p&gt;I’m at the &lt;a href=&quot;http://altnetconf.com/&quot;&gt;ALT.NET Conference&lt;/a&gt; this weekend to gain a better understanding of where the .NET community is heading and to spread some &lt;a href=&quot;http://ruby-lang.org/&quot;&gt;Ruby love&lt;/a&gt;. The conference is using the Open Space format, which seems to be alot like a BarCamp. Although, since I’ve never been to a BarCamp I’m not so sure what the difference is. This seems to be more focused on interactions and discussions as opposed to presentations, so I suppose that may be the difference. Unfortunately there isn’t alot of information online about what “Open Space” is.&lt;/p&gt;

&lt;p&gt;There are some really stellar folks in attendance, you can see the full list &lt;a href=&quot;http://altnetconf.com/participants&quot;&gt;here&lt;/a&gt;. &lt;a href=&quot;http://iserializable.com/&quot;&gt;Roy Osherove&lt;/a&gt; is here, and he looks way different than I’ve always imagined in my head. I met &lt;a href=&quot;http://blogs.msdn.com/jamesnewkirk/&quot;&gt;Jim Newkirk&lt;/a&gt; of &lt;a href=&quot;http://nunit.org/&quot;&gt;NUnit&lt;/a&gt; fame in the hotel lobby. &lt;a href=&quot;http://hanselman.com/&quot;&gt;Scott Hanselman&lt;/a&gt; is also here, and seems to be a bit defensive about a perceived (or not) anti-Microsoft bias. There are other folks from Microsoft here as well, including folks from the MSDN team. And &lt;a href=&quot;http://agileprogrammer.com/dotnetguy/&quot;&gt;Brad Wilson&lt;/a&gt; (who I just today realized isn’t getting &lt;a href=&quot;http://twitter.com/blowmage&quot;&gt;my twitter replies&lt;/a&gt; because I’m not being followed by &lt;a href=&quot;http://twitter.com/dotnetguy&quot;&gt;him&lt;/a&gt;) is here as well. Oh, and did I mention &lt;a href=&quot;http://weblogs.asp.net/scottgu/&quot;&gt;Scott Guthrie&lt;/a&gt;? Yeah, he’s here too. I was really surprised and happy to see him here, mostly so I can pester him over and over again that we want &lt;a href=&quot;http://www.ironruby.net/&quot;&gt;IronRuby&lt;/a&gt; on Rails support from Microsoft. And of course there is &lt;a href=&quot;http://martinfowler.com/&quot;&gt;Martin Fowler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://blogs.dovetailsoftware.com/blogs/slist/&quot;&gt;Doc List&lt;/a&gt; started the conference by setting the expectations and demonstrating the &lt;a href=&quot;http://en.wikipedia.org/wiki/Fishbowl_(conversation)&quot;&gt;fishbowl discussion technique&lt;/a&gt; by starting a discussion on what exactly “ALT.NET” means. This discussion is an ongoing topic on the blogs right now, so I wasn’t surprised that the discussion continued for a while. It even survived a couple attempts by Doc to kill it. In the end we don’t have any cleared definition for ALT.NET; but we did hear alot of opinions. This seems to me to be alot like the Web 2.0 discussions of a couple years ago. ALT.NET, like art, is hard to define but you know it when you see it.&lt;/p&gt;

&lt;p&gt;Then we were given time to think up the topics we would like to discuss for the next couple days. When someone wanted to add a topic, they would write it down on a post-it, move to the center of the room, introduce themselves and the topic, and put the post-it on the board. We quickly had double the post-its than we have rooms and time slots for. There was some consolidation to the list of topics and eventuality we added our initials to the post-its we want to attend or be involved with.&lt;/p&gt;

&lt;p&gt;Scott Guthrie added a note to discuss the often rumored yet unannounced MVC/Model2 ASP.NET framework. And Scott Hanselman added a note to discuss making said MVC framework work with dynamic languages via the DLR. The topic that got the most submissions seemed to be TDD, BDD, and DDD. I added a note to discuss dynamic languages on the CLR; specifically what folks are looking for from them and why some developers fear them. I also volunteered to present on the topic “Ruby for &lt;strike&gt;Dummies&lt;/strike&gt; .NET Developers”. There was alot of topics and talk about Ruby and Rails, so I expect some good discussion. I look forward to tomorrow.&lt;/p&gt;

&lt;p&gt;Afterwards, I took the opportunity to introduce myself to Scott Guthrie and promptly cornered him on what his thoughts were on the topics put forward. He said that it wasn’t all that surprising and that he was excited to present his new MVC framework. He said he thought it was a good blend of extendibility and “opinionated conventions”. It was a really fun discussion and we got a bit of a crowd at the end. I’ll hopefully record an interview with him tomorrow.&lt;/p&gt;

&lt;p&gt;Scott Hanselman also floated around the room taking notes on what folks &lt;em&gt;specifically&lt;/em&gt; thought was wrong with the current Microsoft toolset. There were some honest gripes, but it mostly seemed to be folks complaining about features that are most likely already in Visual Studio but are either buried in the UI or not as extensible as folks want. But I started thinking those discussions were missing the point of what I think the meaning of ALT.NET is. To me, ALT.NET is at heart the embrace of Agile by the .NET community and the recognition that the .NET community determines its own fate; not the toolset vendor (Microsoft). But the tools aren’t the answer; people are! The &lt;a href=&quot;http://agilemanifesto.org/&quot;&gt;Agile Manifesto&lt;/a&gt; holds the following value: “Individuals and interactions over processes and tools”. My wish is that Microsoft would focus more on improving individuals and facilitating better interactions, but the heart of the matter is that the .NET community needs to do this regardless of what Microsoft does with its tools.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>RailsConf 2006: Day 1</title>
   <link href="https://blowmage.com/2006/06/25/railsconf-2006-day-1/"/>
   <updated>2006-06-25T00:00:00+00:00</updated>
   <id>https://blowmage.com/2006/06/25/railsconf-2006-day-1</id>
   <content type="html">&lt;p&gt;Here are my notes from Day 1. The word of the day was “deployment”. One of the reasons I’m attending RailsConf is to be considered hip and influential by those around me, but the &lt;em&gt;other&lt;/em&gt; reason is to learn how to better deploy Rails apps. It looks like I’m not alone, as many of the sessions seem to touch on deployment in some way.&lt;/p&gt;

&lt;p&gt;Dave Thomas’s opening keynote kicked the conference off with a terrific look at three areas that the Rails community should address to help Rails continue to gain market and mind share: improve ActiveRecord to make better use of database features, improve scaffolding to make it usable and pretty for more complex models (kinda like TurboGears?), and improving deployment so that the various responsibilities in deployment can be delivered by more than one person. Not all developers have full access to their production machines, which is one of the areas Capistrano breaks down. Dave’s vision and common sense was on full display, and his message was simple: Rails needs to see wider adoption because &lt;em&gt;all&lt;/em&gt; developers deserve to be happy.&lt;/p&gt;

&lt;p&gt;Mike Clark’s Capistrano overview was excellent as well. And his session was packed. He presented the basics of Capistrano and showed real affection for it. Its too bad Jamis isn’t here to take the deserved applause for creating such a wonderful tool. I’m looking forward to attending more sessions about deployment.&lt;/p&gt;

&lt;p&gt;As Bob Silva &lt;a href=&quot;http://railtie.net/articles/2006/06/24/railsconf-update&quot;&gt;said&lt;/a&gt;, if you were stuck in the next session you know who you are.&lt;/p&gt;

&lt;p&gt;I attended the Identity/OpenID session next. I didn’t realize it was a Verisign product. The first half was very much like Dick Hardt’s Identity 2.0 presentation, while the second half was a quick demo showing how easy it was to start using OpenID plugins in Rails. I’m very interested in offloading the user creation experience to a third party, but there seem to be many holes in OpenID that I’m not sure its worth it yet. But I did get a t-shirt out of it. :) Cote has an excellent &lt;a href=&quot;http://www.redmonk.com/cote/archives/2006/04/identity_20_tru.html&quot;&gt;review&lt;/a&gt; of the advantages and disadvantages of the various identity standards.&lt;/p&gt;

&lt;p&gt;Geoffrey Grosenbach of the Ruby on Rails Podcast gave a great session on deploying to shared hosts. He covered some of the design choices you can make to fit better into the shared hosting model. He said what we were all thinking, that Typo wasn’t designed and for the most part isn’t a good fit on a shared host. (I’ve really got to get around to writing my way out of Typo.) He also covered some common configuration settings for Capistrano’s recipe files. Although he didn’t cover it, he did mention that some folks are using Mongrel in leu of FastCGI when running on Apache. He said that it wasn’t quite ready for prime time yet, but I’ll have to check that out.&lt;/p&gt;

&lt;p&gt;Martin Fowler gave the first of two closing Keynotes. He just stood up there and talked. No slides. Martin is one of my heros, and one of the reasons I love being here is because I get to meet and chat with folks like him and Dave Thomas and Chad Fowler and … He spoke on what it is about Rails that interested him, seeing as he hasn’t ever actually used it. I see him as the surrogate grand father of Rails because of the influence his Enterprise Patterns and Practices book had on DHH when he was creating Rails. The name Active Record came from that book. I have lots of notes from his keynote that I will post separately.&lt;/p&gt;

&lt;p&gt;Paul Graham also delivered his keynote without slides. He read a very good essay about how great ideas and innovation often come from the marginalized. In many ways it is an advantage to be an outsider rather than an insider. Here are a couple good quotes:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You’re on the right track when people complain you’re unqualified or what you’re doing is “inappropriate”.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Why and his band took the stage next and delivered a great show. This is the first time I’ve gotten to see the Thirsty Cups and it was everything I expected. Although I think there are a number of folks here who love Rails but don’t really understand Ruby and were lost during Why’s show. But maybe that was the point? Either way there was way too much in the show to give it proper coverage here, so I won’t even try. I can say that I look forward to see them perform again.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Ruby.NET Beta Announced</title>
   <link href="https://blowmage.com/2006/06/22/ruby-net-beta-announced/"/>
   <updated>2006-06-22T00:00:00+00:00</updated>
   <id>https://blowmage.com/2006/06/22/ruby-net-beta-announced</id>
   <content type="html">&lt;p&gt;They guys at &lt;a href=&quot;http://www.fit.qut.edu.au/&quot;&gt;Queensland University of Technology&lt;/a&gt; announced the public beta of their &lt;a href=&quot;http://plas.fit.qut.edu.au/Ruby.NET/&quot;&gt;Gardens Point .NET Ruby compiler&lt;/a&gt; yesterday. Not only will it interpret and run Ruby files, but it will compile to .NET executables and assemblies as well. I got it to run on .NET 2.0 on WinXP SP2 and Mono 2.0 on Ubuntu (both running in Parallels on my MacBook Pro). I didn’t get it to run on OS X, but that is more to with the lack of an Intel version of Mono for OS X. (Meaning I didn’t try too hard to get it working on OS X.)&lt;/p&gt;

&lt;p&gt;I skimmed through the Ruby.NET source code last night and I have to say I’m impressed. I’m particularly excited about Ruby.NET because I trotted down the path of building a .NET version of Ruby and have a good idea how challenging it was to get this far. I looked at &lt;a href=&quot;http://www.geocities.co.jp/SiliconValley-PaloAlto/9251/ruby/main.html&quot;&gt;arton&lt;/a&gt;’s older &lt;a href=&quot;http://www.geocities.co.jp/SiliconValley-PaloAlto/9251/ruby/nrb.html&quot;&gt;NetRuby&lt;/a&gt; effort code that was essentially a straight port of Ruby 1.6’s C code to C#. I looked at what &lt;a href=&quot;http://www.codeplex.com/Wiki/View.aspx?ProjectName=IronPython&quot;&gt;IronPython&lt;/a&gt; does to take advantage of the 2.0 CLR. I was at &lt;a href=&quot;http://headius.blogspot.com/&quot;&gt;Charles Nutter&lt;/a&gt;’s RubyConf 2005 &lt;a href=&quot;http://brainspl.at/articles/2005/12/01/rubyconf-files-resurrected&quot;&gt;presentation&lt;/a&gt; and familiarized myself with what his team has done with &lt;a href=&quot;http://jruby.sourceforge.net/&quot;&gt;JRuby&lt;/a&gt;. After starting development on my own C#/.NET version of Ruby I found that I didn’t have the time to devote to such an effort. I’m very glad there is a viable solution now. I hope the .NET and Ruby communities rally around this effort.&lt;/p&gt;

&lt;p&gt;I can’t wait to see if we can get Ruby to play nice Avalon/WPF like &lt;a href=&quot;http://www.simplegeek.com/&quot;&gt;Chris Anderson&lt;/a&gt; has done with IronPython in &lt;a href=&quot;http://www.simplegeek.com/permalink.aspx/e0b2f9bc-5a8f-4d5f-b378-16ca634e0646&quot;&gt;AvPad&lt;/a&gt;. Perhaps we can someday (sooner rather than later) get a Ruby on Rails project fully compiled to an ASP.NET assembly and running on IIS? But what I’m probably most excited for is to be able to create DSLs using Ruby and use them in the .NET applications I write at work.&lt;/p&gt;

&lt;p&gt;Now all we need is for an effort like &lt;a href=&quot;http://www.sapphiresteel.com/&quot;&gt;Steel&lt;/a&gt; or &lt;a href=&quot;http://www.wilcob.com/&quot;&gt;Wilco&lt;/a&gt;’s &lt;a href=&quot;http://www.wilcob.com/wilco/News/IronRuby-integration-with-VisualStudio.aspx&quot;&gt;IronRuby Visual Studio integration&lt;/a&gt; to properly use Ruby.NET within Visual Studio 2005. Then we’ve got our first class IDE support and all those naysayers who complain that Ruby will never see wide-spread corporate adoption can respectfully stick it.:)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Polymorphic Podcast on Object Thinking</title>
   <link href="https://blowmage.com/2006/06/16/object-thinking-podcast/"/>
   <updated>2006-06-16T00:00:00+00:00</updated>
   <id>https://blowmage.com/2006/06/16/object-thinking-podcast</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://www.dotnetjunkies.com/weblog/craigshoemaker/&quot;&gt;Craig Shoemaker&lt;/a&gt; of the &lt;a href=&quot;http://polymorphicpodcast.com/&quot;&gt;Polymorphic Podcast&lt;/a&gt; has just posted &lt;a href=&quot;http://polymorphicpodcast.com/shows/objectthinking/&quot;&gt;part one&lt;/a&gt; of his interview with author Dr. David West about his book Object Thinking.  Craig contacted me a few months ago about &lt;a href=&quot;https://blowmage.com/2005/09/20/object-thinking&quot;&gt;my review of Object Thinking&lt;/a&gt; and asked if I had any questions for Dr. West.  Boy do I.&lt;/p&gt;

&lt;p&gt;Here is the list of questions I sent Craig:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What is formalist thinking and why is it bad? It seems like such a good fit when dealing with computers.&lt;/li&gt;
  &lt;li&gt;Why is OOP/object thinking so hard to get right? Besides making everyone read your book, how can help change a formalist culture to object thinking? Not just an individual’s understanding, but the culture as a whole - like a corporate IT culture.&lt;/li&gt;
  &lt;li&gt;Where are the future object thinkers coming from today? Methodology? (Agile? XP?) Languages? (Ruby? Python?)&lt;/li&gt;
  &lt;li&gt;Are programming languages important? Where are we in regards to object-oriented languages? What is missing? Where do future languages need to go? Is formalist thinking reflected in language design? (I believe so.) What are the best examples of hermeneutic languages?&lt;/li&gt;
  &lt;li&gt;How did you end up with Microsoft Press as a publisher? The book was Microsoft-agnostic, and it seemed like a strange fit to me.&lt;/li&gt;
  &lt;li&gt;(Unsure about this question) Was the book successful? How was it received?&lt;/li&gt;
  &lt;li&gt;What is Dr. West doing now? Teaching? Research? Slinging code?&lt;/li&gt;
  &lt;li&gt;Does he have a blog? If not, why not?!? Seriously, he needs a blog! He is depriving the work of much needed object thinking by not having a blog.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Get the podcast &lt;a href=&quot;http://polymorphicpodcast.com/shows/objectthinking/&quot;&gt;here&lt;/a&gt;!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Agile Databases</title>
   <link href="https://blowmage.com/2006/04/28/agile-databases/"/>
   <updated>2006-04-28T00:00:00+00:00</updated>
   <id>https://blowmage.com/2006/04/28/agile-databases</id>
   <content type="html">&lt;p&gt;I am not an expert on this subject, although I play one on the Internet.
&lt;a href=&quot;http://www.jasonmauer.com/&quot;&gt;Jason&lt;/a&gt; posted
&lt;a href=&quot;http://www.jasonmauer.com/EntryView.aspx?id=5ECDF2A0-68D4-45B9-B69F-48A84616420A&quot;&gt;some thoughts&lt;/a&gt;
about the relatively light use of the data tier in a recent TDD
presentation he attended. Instead of writing the longest comment in the history
of his blog I thought I’d post my response here. The following is a brain dump only,
and subject to change as folks smarter than me correct me.&lt;/p&gt;

&lt;p&gt;Martin Fowler has an
&lt;a href=&quot;http://martinfowler.com/bliki/DatabaseStyles.html&quot;&gt;article where he distinguishes&lt;/a&gt;
between “application” databases and “integration” databases. To me this is the main
difference between traditional “enterprise” thinking and the Agile approach. Most
Agilistas (and the &lt;a href=&quot;http://rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt; framework) like the “application” view, and
every DBA I’ve ever known assumes the “integration” view. Here is what Martin Fowler
has to say about the latter:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Integration databases end up with interfaces that have a large surface area and
limited abilities to separate interface from implementation. The resulting links
between applications and databases end up being brittle and thus difficult to change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That perfectly describes my life.&lt;/p&gt;

&lt;p&gt;I can’t point to too many examples where I’ve actually reused stored procedures.
Database functions yes, stored procedures no. I either end up writing new stored
procedures for my new application, because the new application needs to access the
data differently, or I hack up the stored procedures to do things they weren’t originally
designed for and effectively tightly couple my data and application tiers. In fact,
my opinion is that the current irrational exuberance over SOA is mostly an acknowledgement
of the fact that stored procedures make a shitty abstraction layer. But that’s just
my opinion, and unlike
&lt;a href=&quot;http://www.comicbookresources.com/columns/archive.cgi?column=ofo&quot;&gt;Erik Larsen&lt;/a&gt;
I’m not that willing to concede I might be wrong. :)&lt;/p&gt;

&lt;p&gt;Rails takes the view that you should have your
&lt;a href=&quot;http://www.loudthinking.com/arc/000516.html&quot;&gt;complexity in one place&lt;/a&gt;,
and code is a much nicer place than the database. That is one of the reasons Rails
is called “opinionated software”, because this world-view permeates the framework.
Can you use Rails with an “integration” database? Sure, but it will be harder. And
even though I have no idea exactly what SOA really, truly is, I’d investigate using
SOA before strapping Rails on top of a legacy database where stored procedures are
the only way to get or set data. So the “application” database approach is
different, but its different for a reason.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Ruby and Thought Leadership</title>
   <link href="https://blowmage.com/2006/04/27/ruby-thought-leadership/"/>
   <updated>2006-04-27T00:00:00+00:00</updated>
   <id>https://blowmage.com/2006/04/27/ruby-thought-leadership</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://pluralsight.com/blogs/dbox&quot;&gt;Don Box&lt;/a&gt; may be a &lt;a href=&quot;http://www.loudthinking.com/arc/000569.html&quot;&gt;few&lt;/a&gt; &lt;a href=&quot;http://www.loudthinking.com/arc/000571.html&quot;&gt;weeks&lt;/a&gt; late to &lt;a href=&quot;http://blogs.sun.com/roller/page/jag&quot;&gt;James Gosling&lt;/a&gt;’s &lt;a href=&quot;http://java.sys-con.com/read/193146.htm&quot;&gt;comments&lt;/a&gt; about Ruby, but he does pay Ruby a nice compliment. &lt;a href=&quot;http://pluralsight.com/blogs/dbox/archive/2006/04/27/22819.aspx&quot;&gt;He says&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Ruby has the language thought leadership position…”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the comments Don implies that Ruby will overtake Java once Ruby gets a first class IDE. I think the Java community can rest easy, I don’t see that happening for a long time, if ever.&lt;/p&gt;

&lt;p&gt;Oh, and one more thing, I agree with Don that .NET is slightly less vulnerable to Ruby than Java, but by only a fraction of a percent. Why? Because Ruby does have thought leadership. I gave a presentation on Ruby and Rails to some developer evangelists at Microsoft a few weeks ago and I tried to make that point crystal clear. Perhaps that first class IDE support should come from Visual Studio 2007?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Boise Code Camp Wrap-up</title>
   <link href="https://blowmage.com/2006/03/21/boise-code-camp-wrapup/"/>
   <updated>2006-03-21T00:00:00+00:00</updated>
   <id>https://blowmage.com/2006/03/21/boise-code-camp-wrapup</id>
   <content type="html">&lt;p&gt;The first &lt;a href=&quot;http://www.boisecodecamp.org/&quot;&gt;Boise Code Camp&lt;/a&gt; was this past weekend. It was very well received and turnout was higher than we were expecting. Congratulations go out to &lt;a href=&quot;http://www.bsdg.org/&quot;&gt;Jim&lt;/a&gt; and &lt;a href=&quot;http://blog.coryisakson.com/&quot;&gt;Cory&lt;/a&gt; who were the main local leads for the camp. And special thanks to &lt;a href=&quot;http://www.jasonmauer.com/&quot;&gt;Jason&lt;/a&gt; for presenting the idea to us and putting Microsoft’s considerable weight behind the event.&lt;/p&gt;

&lt;p&gt;I presented sessions about &lt;a href=&quot;http://www.ruby-lang.org/&quot;&gt;Ruby&lt;/a&gt; and &lt;a href=&quot;http://www.rubyonrails.org/&quot;&gt;Rails&lt;/a&gt;. The Rails session went very well, mostly because I stole half of the session from &lt;a href=&quot;http://onestepback.org/&quot;&gt;Jim Weirich&lt;/a&gt;’s &lt;a href=&quot;http://onestepback.org/index.cgi/News/IntroToRailsMovieAvailable.red&quot;&gt;A Quick Introduction to Rails&lt;/a&gt; talk. :)&lt;/p&gt;

&lt;p&gt;The earlier Ruby session didn’t go quite as smooth though. I’ve been struggling for the past two months on how to present about Ruby in 60 minutes. So much of what I love about Ruby are the subtle little things that Ruby does. I felt that you can’t really show those subtle things until you explain where Ruby came from, the structure of the language, and how Ruby scripts are executed. Oh, and I was up almost the entire night before trying to solve a technical issue in my demo that I actually didn’t ever get to. So I was very tired on top of a dry presentation. But the good news is that I learned from my mistake and the next time I present on Ruby I will be better prepared and more coherent. I promise.&lt;/p&gt;

&lt;p&gt;I’ve taken the last couple days to recover and I’ll post the slides and code samples later this week. Thanks to all that attended, it was a great time and I felt that the event was a tremendous success! We hope to make the camp a regular event in Boise. I hope that some who attended my sessions will consider joining the &lt;a href=&quot;http://boiserb.com/&quot;&gt;local Ruby user group&lt;/a&gt; as well.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Ruby Thinking</title>
   <link href="https://blowmage.com/2006/02/17/ruby-thinking/"/>
   <updated>2006-02-17T00:00:00+00:00</updated>
   <id>https://blowmage.com/2006/02/17/ruby-thinking</id>
   <content type="html">&lt;p&gt;I attended the &lt;a href=&quot;http://www.netdug.com/&quot;&gt;local .NET user group&lt;/a&gt; last night and had a few people approach me about &lt;a href=&quot;http://boiserb.com/&quot;&gt;Boise Ruby Brigade&lt;/a&gt; (The first meeting is next week! Sign up for the &lt;a href=&quot;http://groups.google.com/group/boiserb&quot;&gt;mailing list&lt;/a&gt; today!).  In answering their questions about Ruby, I shared the fact that Ruby is a ‘type-less’ language.  You can take a variable and make it hold an integer, and then make it hold an array of strings.  Not that you ever would, but the point is that you could.  I said this to get them thinking about the implications.  Not surprisingly the discussion engendered some fairly strong “but I like my static typing!” reactions.  With this contrast between Ruby and the languages developers are currently using, I started to explain why this isn’t such a bad thing and pointed out some of the benefits of such a language.  I made a claim I’ve made several times before, but not here yet: “In five years half of all new code we produce will be written using a dynamic language”.  I said this at a .NET user group, as a professional .NET developer.&lt;/p&gt;

&lt;p&gt;I readily admit this is a bold statement, especially for those for whom dynamic languages such as Perl, Python, and Ruby aren’t even on the radar.  But I stand by my prediction.  It is an obvious one to me, considering that both the .NET runtime and the JVM are adding more and more dynamic capabilities.  The discussion eventually degraded as one individual took issue with me and got a little upset about the merits of dynamic languages vs. static languages.  He would not concede that dynamic languages can be a better solution for certain applications, much less become convinced that it was a better way of programming.  It was then that I realized  I wasn’t dealing with a static-typing bigot.  I was dealing with a formalist thinker.&lt;/p&gt;

&lt;p&gt;In my &lt;a href=&quot;/2005/09/20/object-thinking&quot;&gt;review&lt;/a&gt; of “&lt;a href=&quot;http://www.amazon.com/exec/obidos/redirect?link_code=ur2&amp;amp;camp=1789&amp;amp;tag=blowmage-20&amp;amp;creative=9325&amp;amp;path=tg/detail/-/0735619654&quot;&gt;Object Thinking&lt;/a&gt;” I outline Dr. West’s definition of formalist and hermeneutic thinking, and how it compared to computer thinking vs. object thinking. Dr. West obviously advocates that we embrace object thinking and shun computer thinking, and outlines the benefits of such a world-view. It is an eye-opening book and I recommend it highly.&lt;/p&gt;

&lt;p&gt;The conclusion I’ve come to is that the C-based languages like C# and Java typically bias our thinking about programming. You need to understand how a computer runs code, but you also need to develop the discipline to divorce yourself from thinking that way when designing software. But this is hard to do because the environment reinforces the computer way of thinking with their structures and base libraries. To borrow from &lt;a href=&quot;http://www.martinfowler.com/&quot;&gt;Martin Fowler&lt;/a&gt;’s article on &lt;a href=&quot;http://www.martinfowler.com/bliki/HumaneInterface.html&quot;&gt;Humane Interfaces&lt;/a&gt;, take this example for getting the last element of an array:&lt;/p&gt;

&lt;p&gt;C#:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-csharp&quot; data-lang=&quot;csharp&quot;&gt;&lt;span class=&quot;n&quot;&gt;anArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;anArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Length&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Ruby:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;anArray&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;last&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;In C# and Java you have the C heritage of accessing variables by shifting your pointer to the correct memory location. In Ruby you simply call the appropriate method. In other words, the C#/Java approach is Implementation-specific, while the Ruby approach is Intention-specific. The difference between the C#/Java and Ruby approach is permeated throughout the base libraries for their respective environments. It is more difficult to write a Ruby-like library in C#/Java because it wouldn’t behave like the other C#/Java libraries. And while you can write a C#/Java-like library in Ruby, the longer you use Ruby the more you start thinking the Ruby way. Ruby does this by making it so easy to stop thinking like a computer and start thinking like a human being.&lt;/p&gt;

&lt;p&gt;I suppose that what made this individual upset was that he worried that Ruby’s simplicity and power would make him obsolete. As if by suggesting that Ruby code was more readable and understandable that the programmer was not needed and that the source code could be put in the hands of end users. Ruby is not a toy; Ruby is a tool to make programmers more productive. It makes programming easier. And I believe you can be happier when using Ruby because it gets out of your way. Ruby makes my life easier because I can focus on getting the job done and not talking to the computer or compiler. That’s why I like Ruby.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Boise Ruby Brigade</title>
   <link href="https://blowmage.com/2006/01/27/boiserb/"/>
   <updated>2006-01-27T00:00:00+00:00</updated>
   <id>https://blowmage.com/2006/01/27/boiserb</id>
   <content type="html">&lt;p&gt;Well, I finally did it. I started a user group for Ruby. I’m very excited about it, and I hope those who attend will be half as enthused about it as I am.&lt;/p&gt;

&lt;p&gt;I expect that the Boise Ruby Brigade (boise.rb for short) group will be two parts &lt;a href=&quot;http://ruby-lang.org/&quot;&gt;Ruby&lt;/a&gt;, two parts &lt;a href=&quot;http://rubyonrails.org/&quot;&gt;Rails&lt;/a&gt;, and one part &lt;a href=&quot;http://agilemanifesto.org/&quot;&gt;Agile&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Check out the site here: &lt;a href=&quot;http://boiserb.com/&quot;&gt;http://boiserb.com/&lt;/a&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Who killed Web 2.0?</title>
   <link href="https://blowmage.com/2005/10/08/who-killed-web-2-0/"/>
   <updated>2005-10-08T00:00:00+00:00</updated>
   <id>https://blowmage.com/2005/10/08/who-killed-web-2-0</id>
   <content type="html">&lt;p&gt;This has been a difficult week for me. I don’t know how it happened, but somehow this week at the &lt;a href=&quot;http://www.web2con.com/&quot;&gt;Web 2.0 Conference&lt;/a&gt; (of all places) Web 2.0 died.&lt;/p&gt;

&lt;p&gt;Before this week I advocated for the notion of “Web 2.0” to those around me. Even with all the &lt;a href=&quot;http://www.oreillynet.com/pub/a/oreilly/tim/news/2005/09/30/what-is-web-20.html&quot;&gt;confusion&lt;/a&gt; over what exactly the title meant, I liked the title because it conveyed the new promise for the Web. (And it was certainly better than the title “Semantic Web”.) After the dot-com bust and the painful years that followed, the importance of the Web was diminished. The Web lost attention and respect. And this was our own fault; we got caught up in our own self-importance and greed. Web 2.0 was a turning point; it was our chance to do it over, to do it right. It wasn’t about technology as much as it is about fulfilling the original promise of what the Web should be.&lt;/p&gt;

&lt;p&gt;We had a pretty good start too. Web services showed that Internet technologies were still innovative and that XML was useful for something other than configuration files. Google reminded us that the Web enables people and not technologies. Google also taught us that you can succeed by doing one thing and doing it well. Syndication feeds changed how users receive and view your content. AJAX showed you can be creative while building upon mature technologies. All this was happening because of people who believed in the promise of the Web post dot-com crash.&lt;/p&gt;

&lt;p&gt;But then suddenly we lost our minds. We changed the notion of Web 2.0 to be something that it can’t be. We started using Web 2.0 as another item on our feature list. We started promoting Web 2.0 as a project management methodology. We started promoting Web 2.0 as a business plan. And although I didn’t attend O’Reilly’s “Web 2.0 Conference”, most of what I’ve gathered from the blogosphere is that the conference was little more than a bazaar for newly-minted start-ups to hype themselves as if we were in the throes of the dot-com bubble. We corrupted ourselves again and tarnished our ideal, and for what? A shot at some new VC cash? A handful of links from the regular bloggers?&lt;/p&gt;

&lt;p&gt;Starting this week I hate the term Web 2.0. Now I have to go back to my job and just code. There is no greater good, there is no larger vision. Its just technology. You’ve killed my idealism. Again. Perhaps this was all my own fault for wanting to believe again. The more I think about it, the more Nicholas Carr is correct in his essay &lt;a href=&quot;http://www.roughtype.com/archives/2005/10/the_amorality_o.php&quot;&gt;The amorality of Web 2.0&lt;/a&gt; where he states that those who view the Web in religious terms can “no longer see it objectively”.&lt;/p&gt;

&lt;p&gt;Coincidentally or not, this week I’ve been viewing the presentations and listening to the podcasts from last week’s &lt;a href=&quot;http://we05.com/podcast/&quot;&gt;Web Essentials 2005&lt;/a&gt; conference in Sidney, Australia. I was originally looking for broadcasts of the Web 2.0 Conference sessions, but apparently the principle of “transparency” only applies to Web 2.0, and not it’s conferences. I can’t shake the thought that the Web Essentials conference is what the Web 2.0 Conference could and should have been. I encourage everyone to give them a listen. If anything, it washed the bad taste out in my mouth left by the Web 2.0 Conference.&lt;/p&gt;

&lt;p&gt;Web 2.0 is dead, long live Web 2.0.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Book Review: Agile Web Development with Rails</title>
   <link href="https://blowmage.com/2005/09/30/rails-book/"/>
   <updated>2005-09-30T00:00:00+00:00</updated>
   <id>https://blowmage.com/2005/09/30/rails-book</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Ruby on Rails is a framework that makes it easier to develop, deploy, and maintain web applications.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;https://www.amazon.com/dp/097669400X?tag=blowmage-20&quot;&gt;Agile Web Development with Rails on Amazon&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://blogs.pragprog.com/cgi-bin/pragdave.cgi&quot;&gt;Dave Thomas&lt;/a&gt; and &lt;a href=&quot;http://www.loudthinking.com/&quot;&gt;David Heinemeier Hansson&lt;/a&gt; wrote a killer book in “&lt;a href=&quot;https://www.amazon.com/dp/097669400X?tag=blowmage-20&quot;&gt;Agile Web Development with Rails&lt;/a&gt;”. I wish more technical books would be as reader-friendly and enjoyable as this book. While the book isn’t a reference book, it isn’t a brain-dead newbie overview either. It covers all of the important technical aspects of the Ruby on Rails framework from design to development and from installation to deployment. It also does a great job of explaining the philosophy behind Rails, and why Rails makes the assumptions it does. (Don’t worry, it also shows you how to configure Rails to use your assumptions if they are different.) Overall, the book is a first-class introduction to the body and soul of the Rails framework.&lt;/p&gt;

&lt;p&gt;The reason I read the book was to really understand the &lt;a href=&quot;http://www.rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt; framework. I have dabbled with Rails for the last nine months or so, ever since I first read about &lt;a href=&quot;http://blog.curthibbs.us/&quot;&gt;Curt Hibbs&lt;/a&gt;’s &lt;a href=&quot;http://www.onlamp.com/pub/a/onlamp/2005/01/20/rails.html&quot;&gt;claim&lt;/a&gt; “that that you could develop a web application at least ten times faster with Rails than you could with a typical Java framework”. Rails seems simple enough when you first approach it, but there is surprising depth once you scratch the surface. So like most things I don’t fully understand right away, I decided to find some books on the subject. This book was so good I’m not sure if I’ll need any others.&lt;/p&gt;

&lt;p&gt;Rails is a new framework for building web applications using the Model-View-Controller pattern, similar to most Java-based web apps. Unlike Java web apps which are usually heavy on configuration, Rails follows the philosophy of “don’t repeat yourself” and “convention over configuration”. This means that Rails is built to improve the time of development. But the promise of Rails to me is beyond making me more productive, once you become familiar with Rails and accept the conventions you will get more done while having more fun in the process.&lt;/p&gt;

&lt;p&gt;I think Rails has the potential to be a disruptive technology for web development. As more users become familiar with and expect Web 2.0 functionality web sites will be forced to change in order to deliver the new standard for usability. Web designers and developers will look to new frameworks such as Rails to help them achieve this functionality. Does this mean that Rails will lead the way to a new utopian world where proprietary frameworks like .NET and J2EE are abandoned, PHP is dethroned, and Rails becomes the de-facto standard for modern web development? Probably not, but I do think that Rails does show just how much everyone else will have to evolve or face extinction. To an extent, I think we are already seeing this with Microsoft’s embrace of &lt;a href=&quot;http://atlas.asp.net/&quot;&gt;AJAX&lt;/a&gt; for the next version of the ASP.NET framework. But for now Rails is here and arguably more fun to use.&lt;/p&gt;

&lt;p&gt;The meat of the book is divided into two main sections; an implementation of a simple e-commerce web site and an in-depth look at the various components of Rails. The book has a great layout. The book’s format, links, tips, index, appendixes, etc are all handy when skimming the book, although I found most of the book so compelling I read it cover to cover. (And I actually finished it, unlike most technical books I read.) Based on your experience you could skip one section or the other, but I would suggest not skipping around too much, the authors build on content presented earlier to deliver an engaging narrative that adds perspective and savor to the content. But if you do skip around the page number of the previous content is always listed.&lt;/p&gt;

&lt;p&gt;The introductory section does what you would expect; introduces Rails and shows you how to use it. In this section I think the readers are presented with more of the soul of Rails; explaining why things are they way they are and now that helps you. While you don’t need to be familiar with the Ruby language, you do need to be familiar with web programming in general. While the authors claim that Rails is inherently Agile, I didn’t find the tutorial particularly Agile. I was disappointed that the chapter on testing was separate from the rest of the tutorial. But I found this acceptable because the section was about getting to know Rails, and not about reinforcing the Agile methodology.&lt;/p&gt;

&lt;p&gt;The advanced section goes into detail of the body of Rails; explaining each component of Rails and how they work. I found these chapters particularly well written and was thankful for the narrative presentation. It would have been very easy for these chapters to be dry and boring to read. Near the end of this section there are chapters on additional topics such as Web 2.0, scalability, and deployment. I think there are more advanced books that could be written on certain subjects, specifically ActiveRecord, deployment, and scalability. (I only mention scalability because it has been a common &lt;a href=&quot;http://discuss.joelonsoftware.com/default.asp?joel.3.159134&quot;&gt;criticism&lt;/a&gt; against Rails. I think the book did a fair job of explaining the framework’s scalability.) Perhaps the Pragmatic Programmer folks could write some &lt;a href=&quot;http://pragmaticprogrammer.com/fridays.html&quot;&gt;Friday&lt;/a&gt; books about these subjects?&lt;/p&gt;

&lt;p&gt;The success of the book is conveying the spirit of Rails. I think the future looks bright for the Rails framework because the contributors are committed to keeping Rails simple, lightweight, and not overly-complex like most J2EE solutions. I’ll be using the lessons learned from the book such as “convention over configuration”, “don’t repeat yourself”, integrated unit testing, and the Rails deployment model while designing a new .NET-based web framework at my work.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Book Review: Object Thinking</title>
   <link href="https://blowmage.com/2005/09/20/object-thinking/"/>
   <updated>2005-09-20T00:00:00+00:00</updated>
   <id>https://blowmage.com/2005/09/20/object-thinking</id>
   <content type="html">&lt;blockquote&gt;
  &lt;p&gt;Despite any appearances to the contrary, objects are not something you do; objects are a way that you think.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&quot;https://www.amazon.com/dp/0735619654?tag=blowmage-20&quot;&gt;Object Thinking on Amazon&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;David West’s book “&lt;a href=&quot;https://www.amazon.com/dp/0735619654?tag=blowmage-20&quot;&gt;Object Thinking&lt;/a&gt;” leads you down the garden path that you can create something new, inventive, and more than the sum of the parts. Your program can be something more than just data with algorithms. And the way to do that is by simply changing your thinking about the problem. Quite a promise, fortunately for me (and hopefully you) it delivers on the promise.&lt;/p&gt;

&lt;p&gt;This book is very different from every other &lt;a href=&quot;http://en.wikipedia.org/wiki/Object-oriented_programming&quot;&gt;OOP&lt;/a&gt; or &lt;a href=&quot;http://en.wikipedia.org/wiki/Design_patterns&quot;&gt;design pattern&lt;/a&gt; book I’ve ever come across. The first half of the book is an intriguing philosophical exploration into both the art and science of programming and how they came about. It is the author’s premise that there are fundamentally two schools of thought in Computer Science; formalist thinkers and &lt;a href=&quot;http://www.cis.drexel.edu/faculty/gerry/publications/interpretations/hermeneutic.html&quot;&gt;hermeneutic thinkers&lt;/a&gt;. The majority of programmers (and western culture) are formalists, who share notions of control, centralization, mathematical, hierarchy, predictability, and provability. While a small minority &lt;sarcasm&gt;of mostly [Smalltalkers](http://en.wikipedia.org/wiki/Smalltalk)&lt;/sarcasm&gt; follow a more humanistic approach to the design of software. I have to say I found the author’s portrayal quite compelling. The concept of designing code using a humanistic approach blew me away.&lt;/p&gt;

&lt;p&gt;According to West’s definitions, I am (or was) a formalist, and have been for as long as I can remember. Most every computer science or programming book I’ve ever read has been from the formalist position. I have worked hard and struggled many times to &lt;a href=&quot;http://en.wikipedia.org/wiki/Grok&quot;&gt;grok&lt;/a&gt; OOP and design patterns in efforts to improve my project’s design. Every time I thought I had a handle on it I found a new way to break the complex model I built. I hope I’m not alone in admitting that I have struggled at times trying to find the “perfect” design solution for a given project.&lt;/p&gt;

&lt;p&gt;The author then extends this discussion to define and differentiate computer thinking and object thinking. Computer thinking is limiting your design of the software to how you think the computer is going to perform. I found this way of thinking analogous to premature optimization, and we all know what &lt;a href=&quot;http://en.wikiquote.org/wiki/Donald_Knuth&quot;&gt;Donald Knuth&lt;/a&gt; has says about that; “Premature optimization is the root of all evil (or at least most of it) in programming.”&lt;/p&gt;

&lt;p&gt;Object thinkers strive to understand and model the domain fully, and letting their designs reflect the real world without respect to implementation. This may seem similar to use-cases, but I think there are important differences. Use-cases are a way to capture requirements, while object thinkers attempt to capture the behavior and responsibilities. Here is what the author has to say about thinking about object design:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;You cannot begin to understand what must be until you understand what is. This assertion has two corollaries:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;Almost all of the objects you will ever need are already defined, and already have behavioral expectations associated with them, in the domain.&lt;/li&gt;
    &lt;li&gt;Almost all of the requirements of new development arise from a misalignment of behaviors and objects. Misalignment results when the wrong object (or group of objects) is providing a particular service, a service is more appropriately provided by a silicon-based object simulation instead of a carbon-based biological object, or, occasionally, no existing object is capable of providing the needed service.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;West gives some great guidelines for composing objects throughout the book. Here are some of the ideas that rang true to me:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Objects are lazy.&lt;/li&gt;
  &lt;li&gt;The four presuppositions for Object Thinking:
    &lt;ol&gt;
      &lt;li&gt;Everything is an object.&lt;/li&gt;
      &lt;li&gt;Simulation of a problem domain drives object discovery and definition.&lt;/li&gt;
      &lt;li&gt;Objects must be composable.&lt;/li&gt;
      &lt;li&gt;Distributed cooperation and communication must replace hierarchical centralized control as an organizational paradigm.&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Eliminating centralized control is one of the hardest lessons to be learned by object developers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part of designing objects is delegating the right responsibilities to them. One way of determining the appropriateness of a design is to treat objects as if they were people. I found this analogy very effective when describing the book to others.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Many readers will be uncomfortable with the object-as-person metaphor. A major source of discomfort arises from consideration of characteristics that humans have that we obviously cannot replicate (and probably do not want to replicate) in software. Emotions, true intelligence, and will are major examples. The problem is made worse when descriptions of objects and their behaviors seem to allude to precisely this kind of nonreplicable characteristic–for example, “Objects are lazy.”&lt;/p&gt;

  &lt;p&gt;Effective use of metaphor requires constant awareness that a metaphor is not a specification. It is often helpful to replace the metaphor–an object is a person–with a supposition: what if an object were a person? You can then ask questions about your design in this form: if an object were a person, would I write my code this way? Here are two examples:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;If an object were a person, would I directly access part of its memory without its knowledge instead of sending it a message asking for the information I need? If a developer is obsessed with performance, direct access is tempting. But reminding yourself that, as a person, you would not like someone directly probing your brain without your knowledge reminds you that this is a bad design choice. (It leads to undesirable coupling.)&lt;/li&gt;
    &lt;li&gt;Technology exists that would allow me to make hardware connections to your brain. I could then build a control box that would allow me to raise your hand whenever I pressed a button. Eventually this same technology might allow me to make you perform a complicated dance. Again, as a person, you might not like this, and neither would an object if it were a person.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once you have properly designed the objects to fit the problem domain, you should find that the application using the objects is less of a controller and more of a collaborator. The metaphor West uses is that of a theater and the application as a director putting the right actors/objects in the right place and the right time. This is in sharp contrast to the majority of projects I’ve worked on, which mostly maintain a death-grip on the application behavior. As I look back at the projects that I’ve been a part of, I’ve found that the more elegantly designed solutions have been the ones that have relied less on rigid control and more on the proper behavior of the actors/objects.&lt;/p&gt;

&lt;p&gt;The book is not without its failings however. I found the latter chapters that focused on a process for discovering object responsibilities using &lt;a href=&quot;http://en.wikipedia.org/wiki/Class-Responsibility-Collaboration_card&quot;&gt;CRC cards&lt;/a&gt; less captivating. The author stated that this approach was presented to support and aid in the discovery of object’s responsibilities, but that the approach was not mandatory to do so. To me the real value of the book was its challenge to how I thought, not what tools or practices I used.&lt;/p&gt;

&lt;p&gt;In the end, this was the book I have been looking for. I was tired of reading about the implementations of OOP, and wanted a book to just help me “get it” on a deeper level. I hope to find more books like this in the future. Highly recommended.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Installing Typo on Shared Hosting</title>
   <link href="https://blowmage.com/2005/08/31/installing-typo/"/>
   <updated>2005-08-31T00:00:00+00:00</updated>
   <id>https://blowmage.com/2005/08/31/installing-typo</id>
   <content type="html">&lt;p&gt;Okay, I’ve succumbed to the peer pressure, I’ve installed &lt;a href=&quot;http://www.rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt; and I’ll even use it for this site. As I said earlier, my hosting provider is &lt;a href=&quot;http://www.hostingplex.com/redir.php?aff=blowmage&quot;&gt;HostingPlex&lt;/a&gt;, and is not yet well suited to host Rails applications because &lt;a href=&quot;http://modruby.net/&quot;&gt;mod_ruby&lt;/a&gt; and &lt;a href=&quot;http://fastcgi.com/&quot;&gt;FastCGI&lt;/a&gt; are not installed with a shared-hosting account. I’ve been begging for first class Ruby support, but so far nothing has come of it. Of course, folks could always &lt;a href=&quot;mailto:sales@hostingplex.com?subject=Ruby and Rails Support?&quot;&gt;contact HostingPlex&lt;/a&gt; and ask that they support Ruby and Rails in the future.&lt;/p&gt;

&lt;p&gt;I’ve used and said nice things about &lt;a href=&quot;http://rubyforge.org/projects/rublog/&quot;&gt;Rublog&lt;/a&gt; in the past, but the reason I’ve decided to move from Rublog is the exact reason I chose it in the first place. Rublog works off the files on the file system, and not a database. I’ve found that if I need to make a change to an older article, the file’s timestamp and subsequently the article’s URL is changed. I’ve discovered that I like to correct spelling and grammar mistakes, and that ended up changing the chronological order of my site.&lt;/p&gt;

&lt;p&gt;I could have extended Rublog to change this behavior (similar to the extensions used in &lt;a href=&quot;http://www.blosxom.com/&quot;&gt;blosxom&lt;/a&gt;), but I decided to switch to &lt;a href=&quot;http://typo.leetsoft.com/&quot;&gt;Typo&lt;/a&gt; which uses Ruby on Rails. Typo recently released a new version that caches the generated pages on the file system, so the blog serves static HTML files the majority of the time. Compared to the performance to the older versions of Typo, the performance seems good enough (for now), even without mod_ruby and FastCGI.&lt;/p&gt;

&lt;p&gt;The first step is to download the latest version:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wget http://rubyforge.org/frs/download.php/5602/typo-2.5.5.tgz
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;zxvf typo-2.5.5.tgz&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Typo uses a database to store articles and comments, so the next step is going to be to create a new MySQL database. It seems HostingPlex doesn’t allow you to create a new database using shell commands, so we’ll use the tools available on CPanel. First, go to the CPanel and choose “MySQL Databases” and create a new DB named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typo&lt;/code&gt;. This will actually create a new database named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*hostname*_typo&lt;/code&gt;, because your account name is always added to the beginning. The same is true for user accounts. On the MySQL page create a new user named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typo&lt;/code&gt;. This will create the user &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*hostname*_typo&lt;/code&gt;. Use the same MySQL page to add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*hostname*_typo&lt;/code&gt; user to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*hostname*_typo&lt;/code&gt; database with “All” privileges.&lt;/p&gt;

&lt;p&gt;Next you click the “phpMyAdmin” link at the bottom of the page. This tool will allow you to create and maintain tables in your databases. Instead of creating each table manually, we will run the script that comes with Typo. Using a new instance of CPanel, go into the “File Manager”, navigate to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typo-2.5.5/db&lt;/code&gt; directory and view the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;schema.mysql.sql&lt;/code&gt; file. Copy the contents of the file and paste them into the “SQL” tab in “phpMyAdmin”. You should add 15 new tables to your schema.&lt;/p&gt;

&lt;p&gt;Now that you have the tables created, you need to tell Typo what database to use. To do this, edit the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typo-2.5.5/config/database.yml&lt;/code&gt; file and add your database credentials.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;na&quot;&gt;production&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;adapter&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;mysql&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;database&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;blowmage_typo&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;host&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;localhost&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;blowmage_typo&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;super_secret_password&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We need to tell Typo where to find Ruby and where to find the Typo scripts. We do this in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typo-2.5.5/public/dispatch.cgi&lt;/code&gt; script. (Because HostingPlex doesn’t have Ruby installed, we installed a local version &lt;a href=&quot;/2005/05/27/installing-ruby&quot;&gt;earlier&lt;/a&gt;.) We also need to tell Typo that we should be running in the production environment by setting the RAILS_ENV environment value. This will tell Typo to use caching for improved performance. Change the first few lines to this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;#!/home/blowmage/local/bin/ruby&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;RAILS_ENV&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;production&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/home/blowmage/typo-2.5.5/config/environment&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We also need to tell Apache to use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch.cgi&lt;/code&gt; script instead of the default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatch.fgci&lt;/code&gt;. In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typo-2.5.5/public/.htaccess&lt;/code&gt; file, change the last RewriteRule line to the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;RewriteRule ^(.*)$ dispatch.cgi [QSA,L]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Like all Rails apps, the “typo-2.5.5/public” directory is what the web server is supposed to serve files from. However, with our shared accounts we don’t have permissions to change where Apache to serves the site. Instead, we will copy the files from the public directory to the directory that Apache uses. We also need to tell Typo where to write the cached files so that Typo and Rails don’t load into memory with every request – killing performance. The configuration isn’t difficult, but it needs to be set in two different files. First, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typo-2.5.5/config/environments/production.rb&lt;/code&gt; file needs the following lines added to it.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;page_cache_directory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/home/blowmage/public_html&quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ActionController&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;page_cache_extension&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;.html&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Second, we need to tell Typo’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PageCache&lt;/code&gt; class where to remove the cached entries from. (I believe this is a flaw in the current design, and I have opened a &lt;a href=&quot;http://typo.leetsoft.com/trac/ticket/328&quot;&gt;case&lt;/a&gt;. As of this writing, the case has not yet been assigned.) So change the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;typo-2.5.5/app/models/page_cache.rb&lt;/code&gt; file to the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;PageCache&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ActiveRecord&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Base&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;cattr_accessor&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;:public_path&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;#  @@public_path = RAILS_ROOT + &quot;/public&quot;&lt;/span&gt;
  &lt;span class=&quot;vc&quot;&gt;@@public_path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/home/blowmage/public_html&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Alright, we’re almost there! We just need to copy the public files to our site.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; –r typo-2.5.5/public/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; public_html
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; –r typo-2.5.5/public/.htaccess public_html/.htaccess&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now when you browse to your site you will be prompted for your login information. Enter your info and you will be redirected to the admin section. You can now create new articles and categories. Enjoy.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Installing Rails on Shared Hosting</title>
   <link href="https://blowmage.com/2005/08/20/installing-rails/"/>
   <updated>2005-08-20T00:00:00+00:00</updated>
   <id>https://blowmage.com/2005/08/20/installing-rails</id>
   <content type="html">&lt;p&gt;I stated in an earlier entry on installing the excellent &lt;a href=&quot;http://www.pragprog.com/pragdave&quot;&gt;Rublog&lt;/a&gt; that I may be simply delaying the inevitable move to &lt;a href=&quot;http://rubyonrails.com/&quot;&gt;Ruby on Rails&lt;/a&gt;, and I was right. The siren call of the new and shiny has won me over, and I decided to extend this site’s Ruby support to include Rails. (The fact that the &lt;a href=&quot;http://typo.leetsoft.com/&quot;&gt;Typo&lt;/a&gt; blog engine now renders static HTML files dramatically improving performance also helped, but more on that next time.)&lt;/p&gt;

&lt;p&gt;It is possible to install and use Rails on &lt;a href=&quot;http://www.hostingplex.com/redir.php?aff=blowmage&quot;&gt;HostingPlex&lt;/a&gt;, but the performance will suffer. Rails is a large framework, and not having &lt;a href=&quot;http://modruby.net/&quot;&gt;mod_ruby&lt;/a&gt; and &lt;a href=&quot;http://fastcgi.com/&quot;&gt;FastCGI&lt;/a&gt; installed hurts performance tremendously. Until they are properly supported by HostingPlex, you should use at your own risk. But since you’re reading this, I’ll assume that you want to continue with installing Rails. ;)&lt;/p&gt;

&lt;p&gt;The first step to installing Rails is to install &lt;a href=&quot;http://docs.rubygems.org/&quot;&gt;RubyGems&lt;/a&gt;. Truth be known, you probably want to install gems whether you install Rails or not. Installing gems is as easy as our previous installations:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wget http://rubyforge.org/frs/download.php/3463/rubygems-0.8.8.tgz
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;zxvf rubygems-0.8.8.tgz
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;rubygems-0.8.8
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby setup.rb&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Once gems is installed, installing Rails or any other gem is the easiest thing in the world:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;rails&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Be sure to answer &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt; when prompted by the Rails installation. While you are at it, you can also install a couple supporting libraries common to Rails apps.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gem update mysql
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;redcloth&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;By default, Rails connects to a MySQL database using a library written in Ruby. This library isn’t as fast as using a compiled library. &lt;a href=&quot;http://blog.x180.net/&quot;&gt;James Duncan Davidson&lt;/a&gt; has a great &lt;a href=&quot;http://blog.x180.net/2005/07/rails_and_mysql.html&quot;&gt;article&lt;/a&gt; on improving the performance or Rails apps by using the native library. To install the native library you should use these commands:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wget http://tmtm.org/downloads/mysql/ruby/mysql-ruby-2.6.3.tar.gz
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;zxvf mysql-ruby-2.6.3.tar.gz
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;mysql-ruby-2.6.3
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby extconf.rb &lt;span class=&quot;nt&quot;&gt;--with-mysql-dir&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/local/mysql &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Is it me, or are these tasks getting easier?&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Extending Rublog with ERb</title>
   <link href="https://blowmage.com/2005/07/08/extending-rublog/"/>
   <updated>2005-07-08T00:00:00+00:00</updated>
   <id>https://blowmage.com/2005/07/08/extending-rublog</id>
   <content type="html">&lt;p&gt;&lt;a href=&quot;http://rubyforge.org/projects/rublog/&quot;&gt;Rublog&lt;/a&gt; is a great web site system and has many interesting features. The script works off the files on the file system and supports different file types including RDoc, HTML, and plain text. I wanted to extend Rublog to also support embedded Ruby in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.rhtml&lt;/code&gt; files, and here is how I did it.&lt;/p&gt;

&lt;p&gt;I decided to use &lt;a href=&quot;http://www.ruby-doc.org/stdlib/libdoc/erb/rdoc/&quot;&gt;ERb&lt;/a&gt; instead of &lt;a href=&quot;http://www.modruby.net/&quot;&gt;eRuby&lt;/a&gt;. eRuby is a native library, while ERb is written in Ruby, which makes it slower. I would have chosen to use eRuby, but I couldn’t find any documentation on how to use it from a Ruby script, so I decided to follow the examples I could find online that were using ERb. I assume there are better ways to implement this, but this is good enough for me (for now).&lt;/p&gt;

&lt;p&gt;Rublog makes supporting new file types easy. The first step is to create a new converter class. You’ll need to inherit from the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;BaseConverter&lt;/code&gt; class. I followed the example of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HtmlConverter&lt;/code&gt; class that comes with Rublog. You can find the converter classes in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*rublog*/convertors/&lt;/code&gt; directory. Here is my &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RHtmlConvertor.rb&lt;/code&gt; script:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;# The rhtml convertor reads a file and performs the ERb render.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# Like the html converter, we look for a natural title.&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# If we can&apos;t find it, we use the first line (removing it&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# if it doesn&apos;t look like valid HTML)&lt;/span&gt;

&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;erb&apos;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;BaseConvertor&apos;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RHtmlConvertor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BaseConvertor&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;handles&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;rhtml&quot;&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;convert_html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file_name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;all_entries&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;  &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Untitled&quot;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/&amp;lt;title&amp;gt;(..*?)&amp;lt;\/title&amp;gt;/m&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elsif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;/&amp;lt;h1[^&amp;gt;&amp;lt;]*&amp;gt;(.*?)&amp;lt;\/h1&amp;gt;/&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elsif&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sub!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/\A\s*([^\s&amp;lt;].*)/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;vg&quot;&gt;$1&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;erb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;ERB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;erb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;erb&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;  &lt;span class=&quot;no&quot;&gt;ERB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;erb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;result&lt;/span&gt;

    &lt;span class=&quot;no&quot;&gt;HTMLEntry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The next step is to add the new converter to your Rublog CGI script. Find where the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;load_convertors&lt;/code&gt; method is called and add the text “RHtml” to the list. You need to add it before the text “Html”, or else the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HtmlConverter.rd&lt;/code&gt; script will handle your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.rhtml&lt;/code&gt; files. You need to be aware of the order the converters are loaded because the converters don’t look at the file extension, they simply match the end of the file name.&lt;/p&gt;

&lt;p&gt;Confused? Don’t worry about it. Here is what the load_convertors line looks like in my CGI script:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;RubLog::load_convertors(*%w{ RHtml RDoc Text Html })
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it, you should be ready to go. Add the following text to a file with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.rhtml&lt;/code&gt; extension to your blog directory and watch the magic.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rhtml&quot; data-lang=&quot;rhtml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Testing ERb and RHTML files in Rublog&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This is just a test of supporting *.rhtml files in
Rublog. This additional functionality will render the *.rhtml source
with ERb and display the output.&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;For example, the following code is executed below:&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;hr&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;pre&amp;gt;&amp;lt;code&amp;gt;&lt;/span&gt;
&lt;span class=&quot;ni&quot;&gt;&amp;amp;lt;&lt;/span&gt;p&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&amp;amp;lt;&lt;/span&gt;% 5.times do %&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&lt;/span&gt;Hello &lt;span class=&quot;ni&quot;&gt;&amp;amp;lt;&lt;/span&gt;% end %&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&lt;/span&gt;Ruby!&lt;span class=&quot;ni&quot;&gt;&amp;amp;lt;&lt;/span&gt;/p&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&lt;/span&gt;

&lt;span class=&quot;ni&quot;&gt;&amp;amp;lt;&lt;/span&gt;ul&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&lt;/span&gt;

&lt;span class=&quot;ni&quot;&gt;&amp;amp;lt;&lt;/span&gt;li&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&lt;/span&gt;Addition: &lt;span class=&quot;ni&quot;&gt;&amp;amp;lt;&lt;/span&gt;%= 1+2 %&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&amp;amp;lt;&lt;/span&gt;/li&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&lt;/span&gt;
&lt;span class=&quot;ni&quot;&gt;&amp;amp;lt;&lt;/span&gt;li&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&lt;/span&gt;Concatenation: &lt;span class=&quot;ni&quot;&gt;&amp;amp;lt;&lt;/span&gt;%= &quot;cow&quot; + &quot;boy&quot; %&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&amp;amp;lt;&lt;/span&gt;/li&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&lt;/span&gt;
&lt;span class=&quot;ni&quot;&gt;&amp;amp;lt;&lt;/span&gt;/ul&lt;span class=&quot;ni&quot;&gt;&amp;amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;hr&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;Hello &lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;Ruby!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Addition: &lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;li&amp;gt;&lt;/span&gt;Concatenation: &lt;span class=&quot;cp&quot;&gt;&amp;lt;%=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;cow&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;boy&quot;&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;hr&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This concludes our test...&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You may be asking yourself why this would be necessary, or even desired. To be honest part of why I wrote this was to see if I could do it, but I do see value in enabling a piece if web content with server-side functionality. It allows me to add new behavior without duplicating the navigation and presentation that Rublog gives you. At least, that’s how I justified it. ;)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Installing Rublog on Shared Hosting</title>
   <link href="https://blowmage.com/2005/06/24/installing-rublog/"/>
   <updated>2005-06-24T00:00:00+00:00</updated>
   <id>https://blowmage.com/2005/06/24/installing-rublog</id>
   <content type="html">&lt;p&gt;One of my requirements when looking for a blogging engine was to have the engine pull the content from the file system. I just wanted a very simple and lightweight framework. I also wanted to edit my content files manually, and not through some web-based WYSIWYG interface. I’m not distrustful of more complicated blogging engines, I use them on my other sites, but I thought this site needed a different approach. I wanted to be closer to the metal.&lt;/p&gt;

&lt;p&gt;I originally started out using &lt;a href=&quot;http://www.blosxom.com/&quot;&gt;blosxom&lt;/a&gt;, and I liked that approach very much. But I’ve never been much of a &lt;a href=&quot;http://www.perl.com/&quot;&gt;Perl&lt;/a&gt; hack, and I was faced with either learning more Perl or finding a different solution. Once I found &lt;a href=&quot;http://ruby-lang.org/&quot;&gt;Ruby&lt;/a&gt; I knew I had my language, and I just needed an engine. I thought about porting blosxom to Ruby, but I decided that I would do better with &lt;a href=&quot;http://rubyforge.org/projects/rublog/&quot;&gt;Rublog&lt;/a&gt; as my starting point. Rublog isn’t 100% what I’d like it to be out of the box, but I find it very easy to change to my liking. I figure one of the tenants of &lt;a href=&quot;http://www.artima.com/intv/dry.html&quot;&gt;“Don’t Repeat Yourself”&lt;/a&gt; is not reinventing the wheel when you don’t have to.&lt;/p&gt;

&lt;p&gt;This may seem to fly in the face of the current &lt;a href=&quot;http://rubyonrails.com/&quot;&gt;Rails&lt;/a&gt; hype, (and I may indeed only be delaying the inevitable move to Rails) but until my hosting provider has more support for Rails I’m very happy with where I’m at with &lt;a href=&quot;http://www.pragprog.com/pragdave&quot;&gt;Rublog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You start by downloading and uncompressing the Rublog source:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wget http://rubyforge.org/frs/download.php/1668/rublog-1.0.tgz
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;zxtar zxvf rublog-1.0.tgz&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The next step is to copy the CGI script to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public_html&lt;/code&gt; directory. I’ve renamed it for reasons that I’ll explain later. I also created a new directory to store all my blog entries. I find it best to not store these blog entries in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public_html&lt;/code&gt; directory, since you probably don’t want folks to bypass the CGI script to get to the content.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cp &lt;/span&gt;rublog-1.0/rublog.cgi public_html/index.cgi
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;mkdir &lt;/span&gt;blog&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Before you browse to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/index.cgi&lt;/code&gt; and see the content, you need to change the CGI file to point to your local instance of Ruby. Be sure to change &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/home/*blowmage*&lt;/code&gt; to the path to your account’s home directory:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;#!/home/&amp;lt;i&amp;gt;blowmage&amp;lt;/i&amp;gt;/local/bin/ruby&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;RubLogSourceLocation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;/home/blowmage/rublog-1.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The CGI will show you the Rublog documentation by default. Once you are comfortable with the way Rublog works, you should update the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;index.cgi&lt;/code&gt; script to look at the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;blog&lt;/code&gt; directory for the blog content:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;no&quot;&gt;BLOG_DIR&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/home/blowmage/blog&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can now browse your site through the script: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/index.cgi&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, I hate seeing a CGI script in the URL. I feel this doesn’t &lt;a href=&quot;http://www.useit.com/alertbox/990321.html&quot;&gt;promote&lt;/a&gt; &lt;a href=&quot;http://www.w3.org/Provider/Style/URI&quot;&gt;good&lt;/a&gt; &lt;a href=&quot;http://www.w3.org/DesignIssues/HTTP-URI&quot;&gt;URIs&lt;/a&gt;. If we could hide the CGI script then we are free to replace our site’s implementation without breaking search engine results and people’s bookmarks. Doing this is much easier than it sounds, and it involves our old friend the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.htaccess&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;What we need to do is tell the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.htaccess&lt;/code&gt; file to route all failed requests through our Rublog CGI script. This means that requests to existing resources such as other CGI scripts or HTML or image files will continue to be served. The requests for files that don’t exist are sent to the Rublog CGI where Rublog determines what to serve for that request. This is accomplished by adding the following rewrite rules to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.htaccess&lt;/code&gt; file.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.cgi/$1 [L,QSA]&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Rublog is now installed and will respond to requests even if they are missing the script path. However, Rublog wasn’t written with the expectation that we would want to hide the script path, and the script path is embedded throughout the Rublog code. I have made additional changes to Rublog to hide the script path, but those are outside of the scope of this document.&lt;/p&gt;

&lt;p&gt;That’s it! Despite the hype machine working overtime promoting Rails, I’ve found the hardest part was installing Ruby. I hope this article has been of interest to you. If you have any suggestions or corrections &lt;a href=&quot;mailto:mike@blowmage.com?subject= Installing Ruby on HostingPlex&quot;&gt;drop me a line&lt;/a&gt;. With Ruby installed, you’re on your way to become more productive and have more fun. Good luck!&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Installing eRuby on Shared Hosting</title>
   <link href="https://blowmage.com/2005/06/10/installing-eruby/"/>
   <updated>2005-06-10T00:00:00+00:00</updated>
   <id>https://blowmage.com/2005/06/10/installing-eruby</id>
   <content type="html">&lt;p&gt;Now that we’ve got &lt;a href=&quot;/2005/05/27/installing-ruby&quot;&gt;Ruby installed&lt;/a&gt;, we can use it to write CGI scripts. But that is so 1995, isn’ t there a better way to use Ruby to create web sites? Of course there is, and the first step is &lt;a href=&quot;http://raa.ruby-lang.org/project/eruby&quot;&gt;eRuby&lt;/a&gt;. eRuby interprets Ruby code that is embedded in a text file. Our interest is embedding Ruby code to a HTML file, much like &lt;a href=&quot;http://microsoft.com/&quot;&gt;Microsoft’s&lt;/a&gt; &lt;a href=&quot;http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnanchor/html/activeservpages.asp&quot;&gt;Active Server Pages&lt;/a&gt;, &lt;a href=&quot;http://sun.com/&quot;&gt;Sun’s&lt;/a&gt; &lt;a href=&quot;http://java.sun.com/products/jsp/&quot;&gt;Java Server Pages&lt;/a&gt;, and &lt;a href=&quot;http://zend.com/&quot;&gt;Zend’s&lt;/a&gt; &lt;a href=&quot;http://php.net/&quot;&gt;PHP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just like installing Ruby, this is surprisingly easy to do, as we simply enter these commands in at the shell:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wget http://www.modruby.net/archive/eruby-1.0.5.tar.gz
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;zxvf eruby-1.0.5.tar.gz
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;eruby-1.0.5
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./configure.rb &lt;span class=&quot;nt&quot;&gt;--prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/local &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cp local&lt;/span&gt;/bin/eruby public_html/cgi-bin/eruby&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We’ve installed, compiled, and copied the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eruby&lt;/code&gt; executable file to our site’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cgi-bin&lt;/code&gt; directory. Our site is now ready to use the executable to parse our HTML files for Ruby code and execute it. To do this, we need to associate our files with the CGI executable. Parsing our files for Ruby code does add some overhead, so we probably don’t want to parse every single file, so we’ll associate the file extension &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.rhtml&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;eruby&lt;/code&gt;. We make this association in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.htaccess&lt;/code&gt; file in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public_html&lt;/code&gt; directory by adding these lines:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;AddType application/x-httpd-eruby .rhtml
Action  application/x-httpd-eruby /cgi-bin/eruby&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you don’t have an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.htaccess&lt;/code&gt; file in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public_html&lt;/code&gt; directory then simply create one. Let’s test this! Create a new file in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public_html&lt;/code&gt; directory named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello.rhtml&lt;/code&gt;. The file should include the following lines:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-rhtml&quot; data-lang=&quot;rhtml&quot;&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Hello Ruby!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Hello Ruby!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
Hello
&lt;span class=&quot;cp&quot;&gt;&amp;lt;%&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;cp&quot;&gt;%&amp;gt;&lt;/span&gt;
Ruby!&lt;span class=&quot;nt&quot;&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;When you browse to the file you will see a message that displays “Hello Hello Hello Hello Hello Ruby!” then you have officially installed eRuby, congratulations.&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Installing Ruby on Shared Hosting</title>
   <link href="https://blowmage.com/2005/05/28/installing-ruby/"/>
   <updated>2005-05-28T00:00:00+00:00</updated>
   <id>https://blowmage.com/2005/05/28/installing-ruby</id>
   <content type="html">&lt;p&gt;The hosting provider for this site is &lt;a href=&quot;http://www.hostingplex.com/redir.php?aff=blowmage&quot;&gt;HostingPlex&lt;/a&gt;. HostingPlex doesn’t support &lt;a href=&quot;http://ruby-lang.org/&quot;&gt;Ruby&lt;/a&gt; “out-of-the-box”, which is unfortunate because Ruby is gaining momentum and has a lot of mind share with web developers right now. I’ve made the following modifications to my account in order to run Ruby and I hope you find them useful. Hopefully the steps I tool will translate to other hosting providers and will be useful to others.&lt;/p&gt;

&lt;p&gt;Much of this information is a direct result of an excellent &lt;a href=&quot;http://www.viarails.net/articles/2005/04/22/installing-typo-on-a-freeshell-org-account&quot;&gt;article&lt;/a&gt; on &lt;a href=&quot;http://www.viarails.net/&quot;&gt;Wesley Moxam&lt;/a&gt;’s blog. I am going to initially focus on installing and using Ruby and Rublog, and not necessarily &lt;a href=&quot;http://rubyonrails.com/&quot;&gt;Rails&lt;/a&gt;. Although once Ruby is installed and configured, installing and using Rails shouldn’t be too hard to do. However, HostingPlex is not well suited to Rails hosting (yet) because some server-side configurations such as &lt;a href=&quot;http://modruby.net/&quot;&gt;mod_ruby&lt;/a&gt; and &lt;a href=&quot;http://fastcgi.com/&quot;&gt;FastCGI&lt;/a&gt; are very difficult or impossible to install with a shared-hosting account. Hopefully HostingPlex will change their mind and start supporting Ruby and Rails in the future.&lt;/p&gt;

&lt;p&gt;Ruby is not currently installed on the HostingPlex servers, as I assume is the case for the majority of Linux hosting. To install Ruby, you’ll need access to the shell for your account. The Unix shell is similar to the Command Prompt on Windows machines. You can access the shell through your CPanel interface by clicking the “SSH/Shell Access” link.&lt;/p&gt;

&lt;p&gt;We’ll start with a simple command to ask Ruby to tell us what version it is.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If you don’t have Ruby installed you should see a message such as “command not found”. If you do have Ruby installed, then you can skip ahead.&lt;/p&gt;

&lt;p&gt;To install Ruby we will download and compile Ruby from the source code. We’ll install Ruby into a new directory named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;local&lt;/code&gt;, because we don’t have permissions to install Ruby for all users on the server. We will follow this process for the other programs we’ll eventually install.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; ~
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;wget ftp://ftp.ruby-lang.org/pub/ruby/ruby-1.8.2.tar.gz
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tar &lt;/span&gt;zxvf ruby-1.8.2.tar.gz
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;ruby-1.8.2
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./configure &lt;span class=&quot;nt&quot;&gt;--prefix&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/local &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; make &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Our next step is to ask Ruby what version is installed.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;ruby &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Did that fail again? Don’t worry, this is because we installed Ruby in a non-standard location, and the shell doesn’t know to look in the new directory. To make the shell look in that directory, run the following command:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;PATH&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$HOME&lt;/span&gt;/local/bin:&lt;span class=&quot;nv&quot;&gt;$PATH&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now you should be able to ask Ruby for its version without receiving an error message. Of course, it would be a pain to run the preceding &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt; command every time we connected to the server, so we’ll create a new file named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.bash_profile&lt;/code&gt;. In this file, you should add the now following line:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;PATH=$HOME/local/bin:$PATH&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now every time you connect to the server through the shell, your account will look in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;local&lt;/code&gt; directory for Ruby.&lt;/p&gt;

&lt;p&gt;Let’s test this! Create a new file in your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;public_html&lt;/code&gt; directory named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello.cgi&lt;/code&gt;. The file should include the following lines:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;c1&quot;&gt;#!/home/blowmage/local/bin/ruby&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;# hello.cgi&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;content-type: text/html&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;html&amp;gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;body&amp;gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;h1&amp;gt;Hello Ruby!&amp;lt;/h1&amp;gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;/body&amp;gt;&quot;&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&amp;lt;/html&amp;gt;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The home directory for my account on HostingPlex is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/home/*blowmage*&lt;/code&gt;. You’ll want to change the first line of your script from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/home/*blowmage*&lt;/code&gt; to the path to your account’s home directory. If you do not, then the script won’t know where to find Ruby and the script will fail. If you see a message that displays “Hello Ruby!” then you have officially installed Ruby, congratulations.&lt;/p&gt;
</content>
 </entry>
 
 
</feed>