<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>A Single Programmer's Blog</title>
  <subtitle>A really swell blog built with Webby</subtitle>
  <link href="http://blog.teksol.info/" rel="alternate" type="text/html" />
  <link href="http://blog.teksol.info/atom.xml" rel="self" type="application/atom+xml"/>
  <link href="http://feeds.feedburner.com/teksol" rel="alternate" type="application/atom+xml"/>
  <author>
    <name>François Beausoleil</name>
    <email>francois@teksol.info</email>
  </author>
  <id>http://blog.teksol.info/</id>
  <updated>2012-03-09T05:05:43Z</updated>
  <entry>
    <title>Puppet Debugging: --noop</title>
    <link href="http://blog.teksol.info/2012/03/09/puppet-debugging-noop.html" type="text/html" />
    <id>tag:blog.teksol.info,2012-03-09:20120309000543</id>
    <updated>2012-03-09T00:05:43-05:00</updated>
    <content type="html">&lt;p&gt;I do more and more &lt;a href=&quot;http://puppetlabs.com/&quot;&gt;Puppet&lt;/a&gt;, and I really like it. It&amp;#8217;s a simple way to declare the desired state of the world. As I do more and more with Puppet, I needed to debug my manifests: check that they were doing the expected thing. I was looking for some kind of dry run option, similar to &lt;code&gt;apt-get&lt;/code&gt;&amp;#8216;s or other tools, but &lt;code&gt;puppet help apply&lt;/code&gt; didn&amp;#8217;t provide any helpful hints. Until I stumbled upon &lt;a href=&quot;http://bitfieldconsulting.com/puppet-dry-run&quot;&gt;Puppet dry run&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A direct run with &lt;code&gt;--noop --test&lt;/code&gt; failed because &lt;code&gt;--test&lt;/code&gt; was not recognized, but &lt;code&gt;--noop&lt;/code&gt; was!. A sample run with &lt;code&gt;--noop&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# puppet apply puppet/hosts/db03.staging.internal.pp  --noop&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;notice&lt;/span&gt;: &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Stage[main]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;Postgresql&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Database&lt;/span&gt;[svreporting_staging]/&lt;span class=&quot;co&quot;&gt;Exec&lt;/span&gt;[createdb svreporting_staging]/&lt;span class=&quot;ke&quot;&gt;returns&lt;/span&gt;: current_value notrun, should be &lt;span class=&quot;i&quot;&gt;0&lt;/span&gt; (noop)
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;notice&lt;/span&gt;: &lt;span class=&quot;co&quot;&gt;Postgresql&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Database&lt;/span&gt;[svreporting_staging]: &lt;span class=&quot;co&quot;&gt;Would&lt;/span&gt; have triggered &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;refresh&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt; from &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt; events
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;notice&lt;/span&gt;: &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Stage[main]&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;Cron&lt;/span&gt;[replace svreporting_staging with svanalytics_staging]/&lt;span class=&quot;ke&quot;&gt;ensure&lt;/span&gt;: current_value absent, should be present (noop)
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;notice&lt;/span&gt;: &lt;span class=&quot;co&quot;&gt;Class&lt;/span&gt;[&lt;span class=&quot;co&quot;&gt;Main&lt;/span&gt;]: &lt;span class=&quot;co&quot;&gt;Would&lt;/span&gt; have triggered &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;refresh&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt; from &lt;span class=&quot;i&quot;&gt;2&lt;/span&gt; events
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;notice&lt;/span&gt;: &lt;span class=&quot;co&quot;&gt;Stage&lt;/span&gt;[main]: &lt;span class=&quot;co&quot;&gt;Would&lt;/span&gt; have triggered &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;refresh&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt; from &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt; events
&lt;span class=&quot;no&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;notice&lt;/span&gt;: &lt;span class=&quot;co&quot;&gt;Finished&lt;/span&gt; catalog run &lt;span class=&quot;r&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;fl&quot;&gt;10.86&lt;/span&gt; seconds

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Check the wording: &lt;q&gt;Would have triggered&lt;/q&gt;, and &lt;q&gt;should be&lt;/q&gt;. These are great ways to know what Puppet will do for you. There is only one caveat: when you need to spend more than a few minutes debugging your recipes, disable the automatic run. The scheduled run from puppet agent may go behind your back and apply your recipe, which you&amp;#8217;re just debugging. In my case, this is very easy to do since I host my puppet manifests in a Git repository, which is pulled hourly and applied.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m only waiting for my &lt;a href=&quot;https://github.com/puppetlabs/puppet/pull/570&quot;&gt;pull request&lt;/a&gt; to be applied so you don&amp;#8217;t have to go through the same process as I did.&lt;/p&gt;</content>
    
    <category term="puppet" label="Puppet"/>
      </entry>
  <entry>
    <title>Scala Case Classes and State Machines Using Akka Actors</title>
    <link href="http://blog.teksol.info/2012/02/29/scala-case-classes-and-state-machines-using-akka-actors.html" type="text/html" />
    <id>tag:blog.teksol.info,2012-02-29:20120229210454</id>
    <updated>2012-02-29T21:04:54-05:00</updated>
    <content type="html">&lt;p&gt;At &lt;a href=&quot;http://www.seevibes.com/&quot;&gt;Seevibes&lt;/a&gt; we use the &lt;a href=&quot;https://dev.twitter.com/docs/streaming-api&quot;&gt;Twitter Streaming API&lt;/a&gt; to harvest what people say about television shows. The Twitter Streaming API docs state that:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Upon a change, reconnect immediately if no changes have occurred for some time. For example, reconnect no more than twice every four minutes, or three times per six minutes, or some similar metric.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From &lt;a href=&quot;https://dev.twitter.com/docs/streaming-api/concepts#updating-filter-predicates&quot;&gt;Updating Filter Predicates&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The way my system is architected, whenever we change the list of keywords, a &lt;code&gt;ShowDataErased&lt;/code&gt; message is sent, followed by a bunch of &lt;code&gt;ShowKeywordsReplaced&lt;/code&gt; events, one per show. The system uses &lt;a href=&quot;http://en.wikipedia.org/wiki/CQRS&quot;&gt;&lt;acronym title=&quot;Command-Query Responsibility Segregation&quot;&gt;CQRS&lt;/acronym&gt;&lt;/a&gt; where each change that occurs is reflected in the system through one or more events, hence the two events above.&lt;/p&gt;
&lt;p&gt;Here&amp;#8217;s my state machine in all it&amp;#8217;s gory details:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/2012/02/29/streaming-harvester-state-machine.png&quot; width=&quot;480&quot; alt=&quot;Seevibes Streaming Harvester State Machine&quot;&gt;&lt;/p&gt;
&lt;p&gt;What the machine does is:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;When streaming, and time passes, nothing happens;&lt;/li&gt;
	&lt;li&gt;When streaming and we change keywords (either reset or add keyword), we enter a state where we&amp;#8217;re pending some changes;&lt;/li&gt;
	&lt;li&gt;When we&amp;#8217;re pending, and we change keywords (reset or add again), we stay in the pending state;&lt;/li&gt;
	&lt;li&gt;When we&amp;#8217;re pending, and insufficient time has passed, then we must stay in the same state, in case we receive more keyword change events;&lt;/li&gt;
	&lt;li&gt;Finally, if we&amp;#8217;re pending and sufficient time has passed, we just go back to the streaming state.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The obvious first test is to start with the fact that when we&amp;#8217;re streaming, we must still be streaming. Here&amp;#8217;s an implementation of this test.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; import org.junit.Test
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; import org.scalatest.Assertions
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; import org.joda.time.Instant
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; class StreamingStateMachineTest extends Assertions {
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;     val KEY2 = Set(&amp;quot;b&amp;quot;)
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;     @Test
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     def whenStreaming_andReceiveTick_thenShouldStillBeStreaming() {
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;         expect(Streaming(KEY2)) {
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;             Streaming(KEY2).tick(new Instant())
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;         }
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;     }
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; }

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For the Rubyists out there, &lt;a href=&quot;http://www.scala-lang.org/node/107&quot;&gt;case classes&lt;/a&gt; are a kind of class that has a few nice properties:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;They act like methods, thus we can call the &lt;code&gt;Streaming&lt;/code&gt; method (which the compiler turns into a call of &lt;code&gt;Streaming.apply()&lt;/code&gt;);&lt;/li&gt;
	&lt;li&gt;They have an &lt;code&gt;unapply()&lt;/code&gt; method, which enables pattern-matching (which I won&amp;#8217;t use in this case);&lt;/li&gt;
	&lt;li&gt;They are serializable, externalizable, clonable, have a free &lt;code&gt;equals()&lt;/code&gt; and &lt;code&gt;hashCode()&lt;/code&gt; implementation that is correct and consistent with the Java &lt;code&gt;equals()&lt;/code&gt; and &lt;code&gt;hashCode()&lt;/code&gt; guidelines.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Case classes are an ideal vehicle to transport data with one or more methods. One thing that you must realize when using case classes is that you must use only immutable objects as parameters to your case classes. If you used a mutable object, your case class would suddenly be mutable, and the &lt;code&gt;equals()&lt;/code&gt; and &lt;code&gt;hashCode()&lt;/code&gt; methods would be useless for you. The Scala compiler has no way to enfore this.&lt;/p&gt;
&lt;p&gt;Getting back to our business, the obvious implementation is a no-op:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; import org.joda.time.ReadableInstant
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; case class Streaming(keywords: Set[String]) {
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;     def tick(now: ReadableInstant) = this
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; }

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;One event down, 6 to go! Let&amp;#8217;s change state : go from the streaming state to the pending keywords change state. I&amp;#8217;ll start with the simpler case: resetting the keywords list. Here&amp;#8217;s an implementation:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; @Test
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; def whenStreaming_andReceiveReset_thenShouldBePending() {
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;     expect(PendingKeywordsChange(Set.empty[String], now)) {
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;         Streaming(KEY1).resetKeywords(now)
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     }
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt; }

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice that I&amp;#8217;m passing explicit times to my methods. This is to make testing much easier. It doesn&amp;#8217;t really bother me to send the current time along. It&amp;#8217;s certainly easier than sending a factory which returns the current time, which would add a level of indirection, and be more complicated.&lt;/p&gt;
&lt;p&gt;The implementation is also pretty simple:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; def resetKeywords(now: ReadableInstant) =
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;     PendingKeywordsChange(Set.empty[String], now)

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And so on for the other implementations of Streaming. The guts of the state machine are really in the PendingKeywordsChange class. First, a test:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; @Test
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; def whenStreaming_andReceiveAddKeyword_thenShouldBePending() {
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;     expect(PendingKeywordsChange(KEY1 + &amp;quot;b&amp;quot;, now)) {
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;         Streaming(KEY1).addKeyword(&amp;quot;b&amp;quot;, now)
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     }
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; }
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; case class PendingKeywordsChange(keywords: Set[String],
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;                                  lastChangedAt: ReadableInstant) {
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;     def addKeyword(keyword: String, now: ReadableInstant) =
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;         PendingKeywordsChange(newKeywords + keyword, now)
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; }

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When I wrote the code, I was really excited by how concise and clear my intents shined through the code. The final method I&amp;#8217;d like to show is the &lt;code&gt;tick()&lt;/code&gt; method on &lt;code&gt;PendingKeywordsChange&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; @Test
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; def whenPending_andReceiveTick_andInsufficientTimeHasPassed_thenShouldStayPending() {
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;     expect(PendingKeywordsChange(KEY1, oneMinuteAgo)) {
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;         PendingKeywordsChange(KEY1, oneMinuteAgo).tick(now)
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     }
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;     expect(PendingKeywordsChange(KEY1, twoMinutesAgo)) {
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;         PendingKeywordsChange(KEY1, twoMinutesAgo).tick(now)
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     }
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;     expect(PendingKeywordsChange(KEY1, twoMinutesAgo)) {
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;         PendingKeywordsChange(KEY1, twoMinutesAgo).tick(twoMinutesAgo)
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;     }
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; }
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt; @Test
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt; def whenPending_andReceiveTick_andSufficientTimeHasPassed_thenShouldBeStreaming() {
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt;     expect(Streaming(KEY1, now)) {
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt;         PendingKeywordsChange(KEY1, twoMinutesAgo.minus(TimeUnit.SECONDS.toMillis(1))).tick(now)
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt;     }
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt; }
&lt;span class=&quot;no&quot;&gt;22&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;23&lt;/span&gt; // in class PendingKeywordsChange
&lt;span class=&quot;no&quot;&gt;24&lt;/span&gt; def nextChangeAt = lastChangedAt.toInstant.plus(TimeUnit.MINUTES.toMillis(2))
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;26&lt;/span&gt; def tick(now: ReadableInstant = new Instant()) =
&lt;span class=&quot;no&quot;&gt;27&lt;/span&gt;     if (now.isAfter(nextChangeAt))
&lt;span class=&quot;no&quot;&gt;28&lt;/span&gt;         Streaming(newKeywords)
&lt;span class=&quot;no&quot;&gt;29&lt;/span&gt;     else
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;30&lt;/strong&gt;&lt;/span&gt;         this

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The final part is where and how I regularly call the tick event. Because the rest of my infrastructure is tied to Akka actors, I used the Akka scheduler API to send a message to an actor, which hid my state machine behind a nice and consistent facade:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; import akka.actor.Actor
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; case object Tick
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; case object ResetKeywords
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; case class AddKeyword(keyword: String)
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; trait State {
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;     def resetKeywords(now: ReadableInstant): State
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     def addKeyword(keyword: String, now: ReadableInstant): State
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;     def tick(now: ReadableInstant): State
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;     def keywords: Set[String]
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; }
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; case class Streaming(keywords: Set[String],
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;                      lastChangedAt: ReadableInstant) extends State {
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt;     def resetKeywords(now: ReadableInstant) =
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;         PendingKeywordsChange(Set.empty[String], now)
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt;     def addKeyword(keyword: String, now: ReadableInstant) =
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt;         PendingKeywordsChange(Set(keyword), now)
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;22&lt;/span&gt;     def tick(now: ReadableInstant) = this
&lt;span class=&quot;no&quot;&gt;23&lt;/span&gt; }
&lt;span class=&quot;no&quot;&gt;24&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;/span&gt; case class PendingKeywordsChange(keywords: Set[String],
&lt;span class=&quot;no&quot;&gt;26&lt;/span&gt;                                  lastChangedAt: ReadableInstant) extends State {
&lt;span class=&quot;no&quot;&gt;27&lt;/span&gt;     def resetKeywords(now: ReadableInstant) =
&lt;span class=&quot;no&quot;&gt;28&lt;/span&gt;         PendingKeywordsChange(Set.empty[String], now)
&lt;span class=&quot;no&quot;&gt;29&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;30&lt;/strong&gt;&lt;/span&gt;     def addKeyword(keyword: String, now: ReadableInstant) =
&lt;span class=&quot;no&quot;&gt;31&lt;/span&gt;         PendingKeywordsChange(keywords + keyword, now)
&lt;span class=&quot;no&quot;&gt;32&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;33&lt;/span&gt;     def nextChangeAt = lastChangedAt.toInstant.plus(TimeUnit.MINUTES.toMillis(2))
&lt;span class=&quot;no&quot;&gt;34&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;35&lt;/strong&gt;&lt;/span&gt;     def tick(now: ReadableInstant = new Instant()) =
&lt;span class=&quot;no&quot;&gt;36&lt;/span&gt;         if (now.isAfter(nextChangeAt))
&lt;span class=&quot;no&quot;&gt;37&lt;/span&gt;             Streaming(keywords)
&lt;span class=&quot;no&quot;&gt;38&lt;/span&gt;         else
&lt;span class=&quot;no&quot;&gt;39&lt;/span&gt;             this
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;40&lt;/strong&gt;&lt;/span&gt; }
&lt;span class=&quot;no&quot;&gt;41&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;42&lt;/span&gt; class KeywordsStateMachine extends Actor {
&lt;span class=&quot;no&quot;&gt;43&lt;/span&gt;     private var state: State = Streaming(Set.empty[String], new Instant())
&lt;span class=&quot;no&quot;&gt;44&lt;/span&gt;     private var currentKeywords = Set.empty[String]
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;45&lt;/strong&gt;&lt;/span&gt;     private val stream = ... // Twitter4J Streaming API
&lt;span class=&quot;no&quot;&gt;46&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;47&lt;/span&gt;     def receive = {
&lt;span class=&quot;no&quot;&gt;48&lt;/span&gt;         case Tick =&amp;gt;
&lt;span class=&quot;no&quot;&gt;49&lt;/span&gt;             state = state.tick(new Instant)
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;50&lt;/strong&gt;&lt;/span&gt;             if (state.keywords != currentKeywords)
&lt;span class=&quot;no&quot;&gt;51&lt;/span&gt;                 // reset stream with new keywords
&lt;span class=&quot;no&quot;&gt;52&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;53&lt;/span&gt;         case AddKeyword(keyword) =&amp;gt;
&lt;span class=&quot;no&quot;&gt;54&lt;/span&gt;             state = state.addKeyword(keyword, new Instant())
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;55&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;56&lt;/span&gt;         case ResetKeywords =&amp;gt;
&lt;span class=&quot;no&quot;&gt;57&lt;/span&gt;             state = state.resetKeywords(new Instant())
&lt;span class=&quot;no&quot;&gt;58&lt;/span&gt;     }
&lt;span class=&quot;no&quot;&gt;59&lt;/span&gt; }
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;60&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;61&lt;/span&gt; object Main {
&lt;span class=&quot;no&quot;&gt;62&lt;/span&gt;     def main(args : Array[String]) {
&lt;span class=&quot;no&quot;&gt;63&lt;/span&gt;         val keywordsStateMachine =
&lt;span class=&quot;no&quot;&gt;64&lt;/span&gt;             Actor.actorOf[KeywordsStateMachine].start()
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;65&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;66&lt;/span&gt;         // Send the Tick message to the keywordsStateMachine now,
&lt;span class=&quot;no&quot;&gt;67&lt;/span&gt;         // and every minute thereafter
&lt;span class=&quot;no&quot;&gt;68&lt;/span&gt;         Scheduler.schedule(keywordsStateMachine, Tick, 0, 1, TimeUnit.MINUTES)
&lt;span class=&quot;no&quot;&gt;69&lt;/span&gt;     }
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;70&lt;/strong&gt;&lt;/span&gt; }

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A few things contributed to the clarity of the implementation:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;The Joda Time library is really easy to use and understand. You don&amp;#8217;t need to use java.util.Date or java.util.Calendar : ditch them as soon as you can;&lt;/li&gt;
	&lt;li&gt;Scala&amp;#8217;s case classes reduced boilerplate: no &lt;code&gt;new&lt;/code&gt; operator in sight;&lt;/li&gt;
	&lt;li&gt;Public by default enhances clarity by removing keywords where they don&amp;#8217;t matter;&lt;/li&gt;
	&lt;li&gt;Libraries, yes, but not when something simple is required. Clarity of implementation before reuse.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that this state machine does not have to deal with dropped connections, or rate limiting or anything of the sort, because it&amp;#8217;s all hidden behind Twitter4J&amp;#8217;s interface. The resulting state machine is much easier to understand and reason about.&lt;/p&gt;
&lt;p&gt;In the interest of fun, profit and learning, I&amp;#8217;ve made available a GitHub repository with similar code to what&amp;#8217;s here at &lt;a href=&quot;https://github.com/francois/streaming-state-machine&quot;&gt;streaming-state-machine&lt;/a&gt;. The code is different because it doesn&amp;#8217;t actually connect to Twitter, and thus only needs to demonstrate how changing the states works. The time scale was changed from minutes to seconds.&lt;/p&gt;</content>
    
    <category term="scala" label="Scala"/>
      </entry>
  <entry>
    <title>Scala Hackaton and SQL Workshop Wrap Up</title>
    <link href="http://blog.teksol.info/2012/02/06/scala-hackaton-and-sql-workshop-wrap-up.html" type="text/html" />
    <id>tag:blog.teksol.info,2012-02-06:20120206141951</id>
    <updated>2012-02-06T14:19:51-05:00</updated>
    <content type="html">&lt;p&gt;A great time was had by all!&lt;/p&gt;
&lt;p&gt;Seriously, the Scala workshop was very cool. The actor model was new to almost all attendants and needed some explaining. Looking back, I should have explained a bit more about how the this works. From what I saw, nobody stumbled upon the Scala syntax that much. The people that were there were all experienced programmers, thus may not have needed much hand-holding regarding syntax.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://www.linkedin.com/pub/martin-provencher/22/7a7/561&quot;&gt;Martin Provencher&lt;/a&gt;, organizer of &lt;a href=&quot;http://www.montrealonrails.com/&quot;&gt;Montreal.rb&lt;/a&gt;, and his teammate, &lt;a href=&quot;http://www.linkedin.com/in/omelcher&quot;&gt;Olivier Melcher&lt;/a&gt;, wrote an actor-based histogram producing word counter that counted which programming languages were most spoken of. In their tests, Ruby and PHP were the most frequent ones.&lt;/p&gt;
&lt;p&gt;The SQL Workshop was more difficult for all. The first exercise was much harder than I anticipated. The first question was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Calculate the top 5 shows, ordered by social impressions, for the period between 2011-10-17 and 2011-10-23. Return an answer under 3 seconds.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;First, people had to get familiar with the data model. I asked people to get the data in under 3 seconds, thus was more of an optimization problem, rather than just getting the correct query. I allowed about 40 minutes for this exercise, and most of the time was used in getting a correct query. Nobody had time to get to the optimization part. Looking back, I&amp;#8217;d make this question be two parts: first write the query, then optimize it, or provide the correct query and let people optimize it.&lt;/p&gt;
&lt;p&gt;I hope everybody learned a lot by coming to the hackaton.&lt;/p&gt;
&lt;p&gt;A big &amp;#8220;Thank You&amp;#8221; to the event&amp;#8217;s sponsors for the beer and pizza:&lt;/p&gt;
&lt;p&gt;&lt;a title=&quot;Seevibes: TV Ratings for the Social Media Audience&quot; href=&quot;http://seevibes.com/&quot;&gt;&lt;img src=&quot;http://seevibes.com/images/logo_seevibes-trans.png&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a title=&quot;Needium: Tap the Need Medium&quot; href=&quot;http://needium.com/&quot;&gt;&lt;img src=&quot;http://needium.com/wp-content/uploads/needium-logo-name.png&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Notes for Hackaton organizers&lt;/h3&gt;
&lt;p&gt;If you can, provide a starter application or package. All attendees were happy they could get up and running with little to no fuss. It saved a lot of time because they didn&amp;#8217;t have to resolve dependency nightmares.&lt;/p&gt;
&lt;p&gt;Move around the room, look over everybody&amp;#8217;s shoulders. Without fail, when I walked up to a team, they&amp;#8217;d have a question for me.&lt;/p&gt;
&lt;p&gt;Speaking of teams, it&amp;#8217;s useful for people to work in teams. I&amp;#8217;d say this harkens back to Pair Programming with a Novice / Novice pair. Two novices working together can get further along than a single novice. One of the members will have an insight that the other member didn&amp;#8217;t have, and both can move forward from there.&lt;/p&gt;</content>
    
    <category term="scala" label="Scala"/>
    
    <category term="sql" label="Sql"/>
      </entry>
  <entry>
    <title>Quick Puppet Tip</title>
    <link href="http://blog.teksol.info/2012/01/24/quick-puppet-tip.html" type="text/html" />
    <id>tag:blog.teksol.info,2012-01-24:20120124094614</id>
    <updated>2012-01-24T09:46:14-05:00</updated>
    <content type="html">&lt;p&gt;While testing my &lt;a href=&quot;http://puppetlabs.com/&quot;&gt;Puppet&lt;/a&gt; recipes, I usually boot a new instance then apply configuration manually. I have a base configuration, which is extended with other things. I just noticed I could apply a manifest using STDIN:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# ( echo &amp;quot;include sysbase&amp;quot; ; echo &amp;quot;include rabbitmq&amp;quot; ) | puppet apply -v&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Viva the Unix tradition!&lt;/p&gt;
&lt;p&gt;If you do any kind of configuration management, I highly recommend Puppet.&lt;/p&gt;</content>
    
    <category term="tips" label="Tips"/>
      </entry>
  <entry>
    <title>Needium Joins Seevibes for Beer</title>
    <link href="http://blog.teksol.info/2012/01/23/needium-joins-seevibes-for-beer.html" type="text/html" />
    <id>tag:blog.teksol.info,2012-01-23:20120123100205</id>
    <updated>2012-01-23T10:02:05-05:00</updated>
    <content type="html">&lt;p&gt;I just learned this morning that &lt;a href=&quot;http://needium.com/&quot;&gt;Needium&lt;/a&gt; will join &lt;a href=&quot;http://seevibes.com/&quot;&gt;Seevibes&lt;/a&gt; in sponsoring the beer at the &lt;a href=&quot;http://blog.teksol.info/2012/01/18/announcing-scala-and-sql-event-on-february-2nd-in-montreal.html&quot;&gt;Scala Hackaton and SQL Workshop&lt;/a&gt;. Both events are on February 2nd. If you haven&amp;#8217;t already done so and wish to join us, please register at EventBrite:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;http://www.eventbrite.ca/event/2806810245&quot;&gt;Analyzing Twitter Social Data using Scala and Akka Actors&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;http://www.eventbrite.ca/event/2806810245&quot;&gt;Social Media Metrics using SQL Engines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hope to see you there!&lt;/p&gt;</content>
    
    <category term="scala" label="Scala"/>
    
    <category term="sql" label="Sql"/>
      </entry>
  <entry>
    <title>Announcing Scala and SQL Event on February 2nd in Montreal</title>
    <link href="http://blog.teksol.info/2012/01/18/announcing-scala-and-sql-event-on-february-2nd-in-montreal.html" type="text/html" />
    <id>tag:blog.teksol.info,2012-01-18:20120118221454</id>
    <updated>2012-01-18T22:14:54-05:00</updated>
    <content type="html">&lt;p&gt;I would like to invite you to attend one or two events on February 2nd: &lt;em&gt;Analyzing Twitter Social Data using Scala and Akka Actors&lt;/em&gt; and &lt;em&gt;Social Media Metrics using SQL Engines&lt;/em&gt;.&lt;/p&gt;
&lt;h2&gt;Schedule&lt;/h2&gt;
&lt;ul&gt;
	&lt;li&gt;2:00 PM Doors open&lt;/li&gt;
	&lt;li&gt;2:30 PM Hackaton &amp;mdash; Analyzing Twitter Social Data using Scala and Akka Actors&lt;/li&gt;
	&lt;li&gt;5:00 PM Beer &amp;amp; Pizza, sponsored by &lt;a href=&quot;http://seevibes.com/&quot;&gt;Seevibes&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;6:30 PM Workshop &amp;mdash; Social Media Metrics using SQL Engines&lt;/li&gt;
	&lt;li&gt;9:00 PM Socializing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The event will be at the &lt;a href=&quot;http://notman.org/en/contact/&quot;&gt;Notman House, 51 Sherbrooke W&lt;/a&gt;. We have a limited number of places available, so be sure to reserve your seat now. There are two events, both are free to attend, and both are bilingual French and English.&lt;/p&gt;
&lt;h2&gt;Registration&lt;/h2&gt;
&lt;ul&gt;
	&lt;li&gt;Reserve for &lt;a href=&quot;http://www.eventbrite.ca/event/2803777173&quot;&gt;Analyzing Twitter Social Data using Scala and Akka Actors&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;Reserve for &lt;a href=&quot;http://www.eventbrite.ca/event/2806810245&quot;&gt;Social Media Metrics using SQL Engines&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;Before you come in, please be sure to follow these instructions to get you started:&lt;/p&gt;
&lt;h3&gt;Scala Hackaton&lt;/h3&gt;
&lt;p&gt;The &lt;a href=&quot;http://scala-lang.org/&quot;&gt;Scala&lt;/a&gt; Hackaton is an event where you&amp;#8217;ll build whatever you wish: word counter, word frequency, hashtag frequency, etc. You get to choose. There will be people more familiar with Scala and &lt;a href=&quot;http://akka.io/&quot;&gt;Akka&lt;/a&gt; at the event which can help you. The first 30 minutes of the event will be reserved for a quick introduction to Scala.&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Clone the &lt;a href=&quot;https://github.com/seevibes/scala-hackaton&quot;&gt;scala-hackaton&lt;/a&gt; Git repository, or download a &lt;a href=&quot;https://github.com/seevibes/scala-hackaton/zipball/master&quot;&gt;ZIP&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;In the repository / project, run &lt;code&gt;mvn test&lt;/code&gt; and &lt;code&gt;mvn exec:java -Dexec.mainClass=seevibes.HelloActor&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The two Maven steps are to download all necessary dependencies. If you don&amp;#8217;t, you&amp;#8217;ll lose a lot of time at the event downloading your dependencies.&lt;/p&gt;
&lt;p&gt;If you are unfamiliar with Java and Scala, I strongly recommend you use an IDE, which will help with code completion and syntax awareness. I happen to prefer &lt;a href=&quot;http://www.jetbrains.com/idea/download/index.html&quot;&gt;JetBrains&amp;#8217; IDEA&lt;/a&gt;, but this is like Vim vs Emacs. You can use &lt;a href=&quot;http://www.eclipse.org/downloads/&quot;&gt;Eclipse&lt;/a&gt; if you prefer. If you use Eclipse, be sure to use the &lt;a href=&quot;http://scala-ide.org/&quot;&gt;Scala IDE&lt;/a&gt; extension. In the case of IDEA, download and install the &lt;a href=&quot;http://confluence.jetbrains.net/display/SCA/Scala+Plugin+for+IntelliJ+IDEA&quot;&gt;Scala plugin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you have any issues, please email me, &lt;a href=&quot;mailto:francois@seevibes.com&quot;&gt;François Beausoleil&lt;/a&gt;, and I&amp;#8217;ll help you out. I&amp;#8217;ll post updates to this page if common errors pop up.&lt;/p&gt;
&lt;h3&gt;SQL Workshop&lt;/h3&gt;
&lt;p&gt;The SQL workshop will be a series of directed examples:&lt;/p&gt;
&lt;p&gt;1. I will present a problem, a report or a question we want answered, and some details on how you can accomplish the goal;&lt;br /&gt;
2. You will answer the question with the knowledge you have;&lt;br /&gt;
3. I&amp;#8217;ll ask people to present their solutions;&lt;br /&gt;
4. I&amp;#8217;ll present my solution and discuss specifics&lt;/p&gt;
&lt;p&gt;I have 6 exercises planned out, from 15 to 45 minutes each. The topics range from indexing to joining to using intersections and unions and ending with windowing functions. The workshop is for people who wish to learn more about SQL and how to more effectively use thecapabilites of their favorite SQL engines.&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Install &lt;a href=&quot;http://www.postgresql.org/download/&quot;&gt;PostgreSQL 9.1&lt;/a&gt; (latest is currently 9.1.2)&lt;/li&gt;
	&lt;li&gt;Load this PostgreSQL database dump &lt;a href=&quot;http://bit.ly/svworkshop&quot;&gt;svworkshop.sql.bz2&lt;/a&gt; (315 MiB) in your cluster using the following command:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; bzcat svworkshop.sql.bz2 | psql

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The dump file expects to create a new database named &lt;code&gt;svworkshop&lt;/code&gt; using your default user.&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;Familiarize yourself with the &lt;a href=&quot;/2012/01/18/seevibes-erd-sql-workshop.pdf&quot;&gt;entity-relationship diagram&lt;/a&gt; (PDF) of the dump&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hope to see you there!&lt;/p&gt;</content>
    
    <category term="scala" label="Scala"/>
    
    <category term="sql" label="Sql"/>
      </entry>
  <entry>
    <title>Daemontools Best Practices</title>
    <link href="http://blog.teksol.info/2011/09/12/daemontools-best-practices.html" type="text/html" />
    <id>tag:blog.teksol.info,2011-09-12:20110912083302</id>
    <updated>2011-09-12T08:33:02-04:00</updated>
    <content type="html">&lt;p&gt;I sometimes have to do sysadmin work, such as when I&amp;#8217;m the sole technical person on a probject. When I need to keep a service running, I usually turn to &lt;a href=&quot;http://cr.yp.to/daemontools.html&quot;&gt;daemontools&lt;/a&gt;. Daemontools was written by &lt;a href=&quot;http://en.wikipedia.org/wiki/Daniel_J._Bernstein&quot;&gt;D. J. Bernstein&lt;/a&gt;, a mathematician and author of &lt;a href=&quot;http://cr.yp.to/unix.html&quot;&gt;many UNIX utilities&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;From daemontools&amp;#8217; home page:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;daemontools is a collection of tools for managing UNIX services.&lt;/p&gt;
&lt;p class=&quot;signature&quot;&gt;&lt;a href=&quot;http://cr.yp.to/daemontools.html&quot;&gt;daemontools home page&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What this means is daemontools is designed to run, and keep running, a service. Daemontools also includes other utilities which I find useful, such as &lt;a href=&quot;http://cr.yp.to/daemontools/setuidgid.html&quot;&gt;setuidgid&lt;/a&gt;, &lt;a href=&quot;http://cr.yp.to/daemontools/envdir.html&quot;&gt;envdir&lt;/a&gt; and &lt;a href=&quot;http://cr.yp.to/daemontools/multilog.html&quot;&gt;multilog&lt;/a&gt;,. I searched for an article such as this, but couldn&amp;#8217;t find it. If you find a factual error, please let me know immediately. If you have your own best practices, let me know so I can expand on the list.&lt;/p&gt;
&lt;p&gt;Read the articles themselves here:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;/pages/daemontools/tutorial.html&quot;&gt;Daemontools: Tutorial&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;/pages/daemontools/best-practices.html&quot;&gt;Daemontools: Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content>
    
    <category term="sysadmin" label="Sysadmin"/>
      </entry>
  <entry>
    <title>Running ScalaTest Tests Under IntelliJ IDEA</title>
    <link href="http://blog.teksol.info/2011/08/15/running-scalatest-tests-under-intellij-idea.html" type="text/html" />
    <id>tag:blog.teksol.info,2011-08-15:20110815140954</id>
    <updated>2011-08-15T14:09:54-04:00</updated>
    <content type="html">&lt;p&gt;I had lots of difficulties running my tests under IDEA. The exact error message was:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Error running All Tests:&lt;br/&gt;Not found suite class.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Where &lt;q&gt;All Tests&lt;/q&gt; was the name of my Run configuration.&lt;/p&gt;
&lt;p&gt;I finally ended up with the right incantations. In my &lt;a href=&quot;http://maven.apache.org/pom.html&quot;&gt;POM&lt;/a&gt;, I have the following:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;ta&quot;&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;     &lt;span class=&quot;ta&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;       &lt;span class=&quot;ta&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;junit&lt;span class=&quot;ta&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;       &lt;span class=&quot;ta&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;junit&lt;span class=&quot;ta&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;       &lt;span class=&quot;ta&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;4.8.1&lt;span class=&quot;ta&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;       &lt;span class=&quot;ta&quot;&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;test&lt;span class=&quot;ta&quot;&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;     &lt;span class=&quot;ta&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;     &lt;span class=&quot;ta&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;       &lt;span class=&quot;ta&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.scalatest&lt;span class=&quot;ta&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;       &lt;span class=&quot;ta&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;scalatest_2.9.0-1&lt;span class=&quot;ta&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;       &lt;span class=&quot;ta&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.6.1&lt;span class=&quot;ta&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;     &lt;span class=&quot;ta&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;     &lt;span class=&quot;ta&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;       &lt;span class=&quot;ta&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.mockito&lt;span class=&quot;ta&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;       &lt;span class=&quot;ta&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;mockito-core&lt;span class=&quot;ta&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt;       &lt;span class=&quot;ta&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.8.1&lt;span class=&quot;ta&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;       &lt;span class=&quot;ta&quot;&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;test&lt;span class=&quot;ta&quot;&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt;     &lt;span class=&quot;ta&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt; &lt;span class=&quot;ta&quot;&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then, I had to extend &lt;code&gt;org.scalatest.junit.JUnitSuite&lt;/code&gt; for my test classes, like this:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; import JUnitSuite
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; import org.junit.Test
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; class GivenAnEmptyQueue extends JUnitSuite {
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;   @Test def thenItShouldNotHaveAnyElements() {
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt;     assert(new Queue.empty)
&lt;span class=&quot;no&quot;&gt;7&lt;/span&gt;   }
&lt;span class=&quot;no&quot;&gt;8&lt;/span&gt; }

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Finally, I had to verify that both the JUnit and Scala plugins were enabled and at their latest versions in the IDE itself. After that, I was able to run my tests from within the IDE.&lt;/p&gt;</content>
    
    <category term="scala" label="Scala"/>
      </entry>
  <entry>
    <title>Simple-Build-Tool: Error Getting Started</title>
    <link href="http://blog.teksol.info/2011/07/30/simple-build-tool-error-getting-started.html" type="text/html" />
    <id>tag:blog.teksol.info,2011-07-30:20110730225009</id>
    <updated>2011-07-30T22:50:09-04:00</updated>
    <content type="html">&lt;p&gt;I&amp;#8217;m starting in Scala, because &lt;a href=&quot;http://www.seevibes.com/&quot;&gt;Seevibes&lt;/a&gt;&amp;#8217; code is in Scala. Scala has a tool named &lt;a href=&quot;https://github.com/harrah/xsbt&quot;&gt;simple-build-tool&lt;/a&gt; for managing your projects. &lt;abbr title=&quot;simple-build-tool&quot;&gt;sbt&lt;/abbr&gt; is similar to Ruby&amp;#8217;s Bundler and Clojure&amp;#8217;s Leiningen in that it manages dependencies and helps build a project for you.&lt;/p&gt;
&lt;p&gt;Unfortunately, I had problems getting started. After following the &lt;a href=&quot;https://github.com/harrah/xsbt/wiki/Setup&quot;&gt;Setup&lt;/a&gt; instructions, I was consistently getting this error:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Getting&lt;/span&gt; org.scala-tools.sbt sbt_2.&lt;span class=&quot;fl&quot;&gt;8.1&lt;/span&gt; &lt;span class=&quot;fl&quot;&gt;0.10&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;1&lt;/span&gt; ...
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; :: problems summary ::
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; :::: &lt;span class=&quot;co&quot;&gt;WARNINGS&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     [&lt;span class=&quot;co&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;FOUND&lt;/span&gt;  ] commons-logging&lt;span class=&quot;c&quot;&gt;#commons-logging;1.0.4!commons-logging.jar (5ms)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;   ==== &lt;span class=&quot;co&quot;&gt;Maven2&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;Local&lt;/span&gt;: tried
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     file&lt;span class=&quot;sy&quot;&gt;:/&lt;/span&gt;/&lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/.m2/repository/commons-logging/commons-logging/&lt;span class=&quot;fl&quot;&gt;1.0&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;4&lt;/span&gt;/commons-logging-&lt;span class=&quot;fl&quot;&gt;1.0&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;4&lt;/span&gt;.jar
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;     ::::::::::::::::::::::::::::::::::::::::::::::
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;     ::              &lt;span class=&quot;co&quot;&gt;FAILED&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;DOWNLOADS&lt;/span&gt;            ::
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;     :: ^ see resolution messages &lt;span class=&quot;r&quot;&gt;for&lt;/span&gt; details  ^ ::
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;     ::::::::::::::::::::::::::::::::::::::::::::::
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt;     :: commons-logging&lt;span class=&quot;c&quot;&gt;#commons-logging;1.0.4!commons-logging.jar&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt;     ::::::::::::::::::::::::::::::::::::::::::::::
&lt;span class=&quot;no&quot;&gt;22&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;23&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;24&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;/span&gt; :: &lt;span class=&quot;co&quot;&gt;USE&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;VERBOSE&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;OR&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;DEBUG&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;MESSAGE&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;LEVEL&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;FOR&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;MORE&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;DETAILS&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;26&lt;/span&gt; download &lt;span class=&quot;ke&quot;&gt;failed&lt;/span&gt;: commons-logging&lt;span class=&quot;c&quot;&gt;#commons-logging;1.0.4!commons-logging.jar&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;27&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Error&lt;/span&gt; during sbt &lt;span class=&quot;ke&quot;&gt;execution&lt;/span&gt;: &lt;span class=&quot;co&quot;&gt;Error&lt;/span&gt; retrieving required libraries
&lt;span class=&quot;no&quot;&gt;28&lt;/span&gt;   (see &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/&lt;span class=&quot;co&quot;&gt;Projects&lt;/span&gt;/project/boot/update.log &lt;span class=&quot;r&quot;&gt;for&lt;/span&gt; complete log)
&lt;span class=&quot;no&quot;&gt;29&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;Error&lt;/span&gt;: &lt;span class=&quot;co&quot;&gt;Could&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;not&lt;/span&gt; retrieve sbt &lt;span class=&quot;fl&quot;&gt;0.10&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I hit &lt;a href=&quot;irc://irc.freenode.net/scala&quot;&gt;#scala&lt;/a&gt; and RSchulz pointed me to &lt;code&gt;~/.ivy2&lt;/code&gt;. After I &lt;code&gt;rm -rf&lt;/code&gt;&amp;#8217;d &lt;code&gt;~/.ivy2&lt;/code&gt; and &lt;code&gt;~/.m2&lt;/code&gt;, sbt ran to completion. I was unable to find any mentions of the errors above, so hopefully this may help someone else.&lt;/p&gt;</content>
    
    <category term="scala" label="Scala"/>
      </entry>
  <entry>
    <title>Sequel's #set_all still restricts the primary key</title>
    <link href="http://blog.teksol.info/2011/07/08/sequels-set_all-still-restricts-the-primary-key.html" type="text/html" />
    <id>tag:blog.teksol.info,2011-07-04:20110708105700</id>
    <updated>2011-07-04T10:57:00-04:00</updated>
    <content type="html">&lt;p&gt;I ran into a little gotcha today, using &lt;a href=&quot;http://sequel.rubyforge.org/&quot;&gt;Sequel&lt;/a&gt;. I&amp;#8217;m writing an importer, you know the kind: read record from database A, apply some transformations, write to database B. No rocket science required. &lt;strong&gt;But&lt;/strong&gt;, Sequel has a little gotcha that stumped me for a bit. My script looked like this:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;DBa&lt;/span&gt; = &lt;span class=&quot;co&quot;&gt;Sequel&lt;/span&gt;.connect &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;DBb&lt;/span&gt; = &lt;span class=&quot;co&quot;&gt;Sequel&lt;/span&gt;.connect &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;APerson&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;Sequel&lt;/span&gt;::Model(&lt;span class=&quot;co&quot;&gt;DBa&lt;/span&gt;[&lt;span class=&quot;sy&quot;&gt;:people&lt;/span&gt;])
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;BContact&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;Sequel&lt;/span&gt;::Model(&lt;span class=&quot;co&quot;&gt;DBb&lt;/span&gt;[&lt;span class=&quot;sy&quot;&gt;:contacts&lt;/span&gt;])
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; contacts = &lt;span class=&quot;co&quot;&gt;Hash&lt;/span&gt;.new
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;APerson&lt;/span&gt;.all.each &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |person|
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   contact = &lt;span class=&quot;co&quot;&gt;BContact&lt;/span&gt;.create(
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;     &lt;span class=&quot;sy&quot;&gt;:name&lt;/span&gt;        =&amp;gt; person.last_name + &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; + person.first_name,
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;     &lt;span class=&quot;sy&quot;&gt;:tenant_code&lt;/span&gt; =&amp;gt; &lt;span class=&quot;co&quot;&gt;ENV&lt;/span&gt;[&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;TENANT_CODE&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;],
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;     &lt;span class=&quot;sy&quot;&gt;:updated_by&lt;/span&gt;  =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt;     &lt;span class=&quot;sy&quot;&gt;:updated_at&lt;/span&gt;  =&amp;gt; &lt;span class=&quot;co&quot;&gt;Time&lt;/span&gt;.now)
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;   contacts[ person.id ] = contact.contact_id
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Now I can map A's IDs to the correct value in database B, such as&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# for attaching email addresses, phone numbers, etc.&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;Contact&lt;/code&gt; model in the B database is declared like this:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; create_table &lt;span class=&quot;sy&quot;&gt;:contacts&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   column &lt;span class=&quot;sy&quot;&gt;:tenant_code&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:integer&lt;/span&gt;,       &lt;span class=&quot;sy&quot;&gt;:null&lt;/span&gt; =&amp;gt; &lt;span class=&quot;pc&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;   column &lt;span class=&quot;sy&quot;&gt;:contact_id&lt;/span&gt;,  &lt;span class=&quot;sy&quot;&gt;:serial&lt;/span&gt;,        &lt;span class=&quot;sy&quot;&gt;:null&lt;/span&gt; =&amp;gt; &lt;span class=&quot;pc&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;   column &lt;span class=&quot;sy&quot;&gt;:name&lt;/span&gt;,        &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;varchar(240)&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:null&lt;/span&gt; =&amp;gt; &lt;span class=&quot;pc&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt;   primary_key [&lt;span class=&quot;sy&quot;&gt;:tenant_code&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:contact_id&lt;/span&gt;]
&lt;span class=&quot;no&quot;&gt;7&lt;/span&gt;   foreign_key [&lt;span class=&quot;sy&quot;&gt;:tenant_code&lt;/span&gt;], &lt;span class=&quot;sy&quot;&gt;:tenants&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice &lt;code&gt;tenant_code&lt;/code&gt; and &lt;code&gt;contact_id&lt;/code&gt; are part of the primary key. I don&amp;#8217;t write to &lt;code&gt;contact_id&lt;/code&gt; because I want the sequence&amp;#8217;s value to be returned to me. But I must write my own value to the &lt;code&gt;tenant_code&lt;/code&gt; column. I was receiving an exception on the &lt;code&gt;#create&lt;/code&gt; call:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/.rvm/gems/ruby-&lt;span class=&quot;fl&quot;&gt;1.9&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;-p180/gems/sequel-&lt;span class=&quot;fl&quot;&gt;3.25&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;/lib/sequel/model/base.rb:&lt;span class=&quot;i&quot;&gt;1491&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;block in set_restricted': method tenant_code= doesn't exist or access is restricted to it (Sequel::Error)
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:1486:in &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;each&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;   from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:1486:in `set_restricted&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   from &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/.rvm/gems/ruby-&lt;span class=&quot;fl&quot;&gt;1.9&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;-p180/gems/sequel-&lt;span class=&quot;fl&quot;&gt;3.25&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;/lib/sequel/model/base.rb:&lt;span class=&quot;i&quot;&gt;1077&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;set'
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;   from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:1456:in &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;initialize_set&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:764:in `initialize&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;   from &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/.rvm/gems/ruby-&lt;span class=&quot;fl&quot;&gt;1.9&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;-p180/gems/sequel-&lt;span class=&quot;fl&quot;&gt;3.25&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;/lib/sequel/model/base.rb:&lt;span class=&quot;i&quot;&gt;134&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new'
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:134:in &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;create&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   from /Users/francois/.rvm/gems/ruby-1.9.2-p180/gems/sequel-3.25.0/lib/sequel/model/base.rb:248:in `find_or_create&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   from script/import:&lt;span class=&quot;i&quot;&gt;65&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;block (2 levels) in &amp;lt;top (required)&amp;gt;'
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I was very much stumped, and turned to the excellent documentation. I eventually found my way to &lt;code&gt;#set_all&lt;/code&gt;, and changed my code accordingly:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;APerson&lt;/span&gt;.all.each &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |person|
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   contact = &lt;span class=&quot;co&quot;&gt;BContact&lt;/span&gt;.new.set_all(
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;     &lt;span class=&quot;sy&quot;&gt;:name&lt;/span&gt;            =&amp;gt; person.last_name + &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;, &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; + person.first_name,
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;     &lt;span class=&quot;sy&quot;&gt;:tenant_code&lt;/span&gt;     =&amp;gt; &lt;span class=&quot;co&quot;&gt;ENV&lt;/span&gt;[&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;TENANT_CODE&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;],
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     &lt;span class=&quot;sy&quot;&gt;:last_updated_by&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt;     &lt;span class=&quot;sy&quot;&gt;:last_updated_at&lt;/span&gt; =&amp;gt; &lt;span class=&quot;co&quot;&gt;Time&lt;/span&gt;.now)
&lt;span class=&quot;no&quot;&gt;7&lt;/span&gt;   contacts[ person.id ] = contact.contact_id
&lt;span class=&quot;no&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Even though the &lt;a href=&quot;http://sequel.rubyforge.org/rdoc/classes/Sequel/Model/InstanceMethods.html#M000309&quot;&gt;Sequel RDoc&lt;/a&gt; says &lt;code&gt;#set_all&lt;/code&gt; ignores restricted columns, I was still receiving the same exception. I was now doubly stumped, until I found a reference to &lt;a href=&quot;http://sequel.rubyforge.org/rdoc/classes/Sequel/Model/ClassMethods.html#M000275&quot;&gt;&lt;code&gt;#unrestrict_primary_key&lt;/code&gt;&lt;/a&gt;. I added the declaration to &lt;code&gt;BContact&lt;/code&gt; and was able to proceed:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;BContact&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;Sequel&lt;/span&gt;::Model(&lt;span class=&quot;co&quot;&gt;DBb&lt;/span&gt;[&lt;span class=&quot;sy&quot;&gt;:contacts&lt;/span&gt;])
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   unrestrict_primary_key
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You know the drill though: where you import one model, you&amp;#8217;ll have more to import shortly. Ruby to the rescue!&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Sequel::Model&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# Open classes win every time!&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;   unrestrict_primary_key
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Problem solved!&lt;/p&gt;</content>
    
    <category term="sequel" label="Sequel"/>
      </entry>
  <entry>
    <title>Counters: An Easy Way to Gather Metrics From Your Ruby Code</title>
    <link href="http://blog.teksol.info/2011/04/24/counters-an-easy-way-to-gather-metrics-from-your-ruby-code.html" type="text/html" />
    <id>tag:blog.teksol.info,2011-04-24:20110424230735</id>
    <updated>2011-04-24T23:07:35-04:00</updated>
    <content type="html">&lt;p&gt;Sometimes, you need to know what your program&amp;#8217;s doing, or how long it&amp;#8217;s taking to do something. You could always log to a file, then use a combination of &lt;a href=&quot;http://en.wikipedia.org/wiki/Grep&quot;&gt;grep&lt;/a&gt;, &lt;a href=&quot;http://en.wikipedia.org/wiki/Awk&quot;&gt;awk&lt;/a&gt; and/or &lt;a href=&quot;http://en.wikipedia.org/wiki/Wc_(Unix)&quot;&gt;wc&lt;/a&gt; to gather the statistics yourself, but why bother? There are many tools out there which will do exactly what you want, just use them: &lt;a href=&quot;http://www.cacti.net/&quot;&gt;Cacti&lt;/a&gt;, &lt;a href=&quot;http://graphite.wikidot.com/start&quot;&gt;Graphite&lt;/a&gt; or plain-old &lt;a href=&quot;http://www.mrtg.org/rrdtool/&quot;&gt;RRD&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For instance, at &lt;a href=&quot;http://yatter.com/&quot;&gt;Yatter&lt;/a&gt;, we need to know how fast our ranking algorithms are running, and we must know how long the ranking takes compared to the number of users and pages we have on hand. Graphing is the perfect solution for that, and Graphite fit the bill just fine for us. But Graphite alone won&amp;#8217;t do all that we need: we also needed a way to instrument our code, hence the Counters library:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;counters&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;sequel&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;DB&lt;/span&gt; = &lt;span class=&quot;co&quot;&gt;Sequel&lt;/span&gt;.connect &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;jdbc:postgres://127.0.0.1:5432/db&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Counter&lt;/span&gt; = &lt;span class=&quot;co&quot;&gt;Counters&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;StatsD&lt;/span&gt;.new(&lt;span class=&quot;sy&quot;&gt;:url&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;udp://127.0.0.1:8125&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:namespace&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ranker&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; users = &lt;span class=&quot;co&quot;&gt;Counter&lt;/span&gt;.latency &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fetch.users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;DB&lt;/span&gt;[&lt;span class=&quot;sy&quot;&gt;:users&lt;/span&gt;].all
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; pages = &lt;span class=&quot;co&quot;&gt;Counter&lt;/span&gt;.latency &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fetch.pages&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;DB&lt;/span&gt;[&lt;span class=&quot;sy&quot;&gt;:pages&lt;/span&gt;].all
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Counter&lt;/span&gt;.magnitude &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;count.users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, users.length
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Counter&lt;/span&gt;.magnitude &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;count.pages&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, pages.length
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Counter&lt;/span&gt;.latency &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ranking&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt;   entropy = &lt;span class=&quot;fl&quot;&gt;1.0&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;while&lt;/span&gt; entropy &amp;gt; &lt;span class=&quot;co&quot;&gt;MIN_ENTROPY&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt;     &lt;span class=&quot;co&quot;&gt;Counter&lt;/span&gt;.hit &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;iteration&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;22&lt;/span&gt;     &lt;span class=&quot;c&quot;&gt;# Reduce entropy&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;23&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;24&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;At the end of the day, we&amp;#8217;ll have hierarchical counters in Graphite which will give us all kinds of statistics. From the API above, you can gather that values are stored under hierarchical keys separated by fullstops. If you&amp;#8217;re interested in the code, make yourself at home with the &lt;a href=&quot;https://github.com/francois/counters&quot;&gt;Counters GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Counters is certified to run on JRuby 1.6.0 in 1.8 mode, and MRI 1.9.2.&lt;/p&gt;</content>
    
    <category term="metrics" label="Metrics"/>
      </entry>
  <entry>
    <title>JRuby, PostgreSQL and Bulk Copy Operations</title>
    <link href="http://blog.teksol.info/2011/04/10/jruby-postgresql-and-bulk-copy-operations.html" type="text/html" />
    <id>tag:blog.teksol.info,2011-04-10:20110410210900</id>
    <updated>2011-04-10T21:09:00-04:00</updated>
    <content type="html">&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# JRuby 1.6.0 required&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;sequel&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;jdbc/postgresql&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;DB&lt;/span&gt; = &lt;span class=&quot;co&quot;&gt;Sequel&lt;/span&gt;.connect &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;jdbc:postgresql://127.0.0.1:5432/mydb&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; table = &lt;span class=&quot;co&quot;&gt;DB&lt;/span&gt;[&lt;span class=&quot;sy&quot;&gt;:mytable&lt;/span&gt;]
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; time = &lt;span class=&quot;co&quot;&gt;Benchmark&lt;/span&gt;.measure &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   (&lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;..&lt;span class=&quot;i&quot;&gt;10000&lt;/span&gt;).each &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |n|
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;     table.insert(&lt;span class=&quot;sy&quot;&gt;:a&lt;/span&gt; =&amp;gt; n, &lt;span class=&quot;sy&quot;&gt;:b&lt;/span&gt; =&amp;gt; &lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;*n)
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; puts time

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;How much time do you think this is going to take? This is the most inefficient way to insert many rows to a database. Remember each call to &lt;code&gt;#insert&lt;/code&gt; will do a round trip to the database. Even if your round trip time is 1ms, you&amp;#8217;ll still pay for 10 seconds of round trip time, time which your program could be doing something much more useful, such as generating revenue (somehow).&lt;/p&gt;
&lt;p&gt;Instead, you should use the bulk copy feature of your database engine. In my case, that&amp;#8217;s &lt;a href=&quot;http://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt;. Since I&amp;#8217;m using &lt;a href=&quot;http://www.jruby.org/&quot;&gt;JRuby&lt;/a&gt;, I have to turn to the &lt;a href=&quot;http://en.wikipedia.org/wiki/Java_Database_Connectivity&quot;&gt;JDBC&lt;/a&gt; world, but that&amp;#8217;s all right: everything has been implemented already, by someone, somewhere. I&amp;#8217;ll refer you to the relevant pages:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;PostgreSQL&amp;#8217;s &lt;a href=&quot;http://www.postgresql.org/docs/8.4/static/sql-copy.html&quot;&gt;COPY FROM STDIN&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;http://jdbc.postgresql.org/development/privateapi/org/postgresql/copy/CopyManager.html&quot;&gt;org.postgresql.copy.CopyManager&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;http://jdbc.postgresql.org/development/privateapi/org/postgresql/copy/CopyIn.html&quot;&gt;org.postgresql.copy.CopyIn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the relevant code would be:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# JRuby 1.6.0 required&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;sequel&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;jdbc/postgresql&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;DB&lt;/span&gt; = &lt;span class=&quot;co&quot;&gt;Sequel&lt;/span&gt;.connect &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;jdbc:postgresql://127.0.0.1:5432/mydb&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; time = &lt;span class=&quot;co&quot;&gt;Benchmark&lt;/span&gt;.measure &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;DB&lt;/span&gt;.synchronize &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |connection|
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;     copy_manager = org.postgresql.copy.&lt;span class=&quot;co&quot;&gt;CopyManager&lt;/span&gt;.new(connection)
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;     stream = copy_manager.copy_in(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;COPY mytable(a, b) FROM STDIN WITH CSV&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;begin&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;       (&lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;..&lt;span class=&quot;i&quot;&gt;10000&lt;/span&gt;).each &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |n|
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;         &lt;span class=&quot;c&quot;&gt;# Don't forget we're streaming CSV data, thus each row/line MUST be terminated with a newline&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt;         row = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;il&quot;&gt;&lt;span class=&quot;idl&quot;&gt;#{&lt;/span&gt;n&lt;span class=&quot;idl&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;il&quot;&gt;&lt;span class=&quot;idl&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;*n&lt;span class=&quot;idl&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ch&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.to_java_bytes
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;         stream.write_to_copy(row, &lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;, row.length)
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt;       &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;rescue&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt;       stream.cancel_copy
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt;       raise
&lt;span class=&quot;no&quot;&gt;22&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;else&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;23&lt;/span&gt;       stream.end_copy
&lt;span class=&quot;no&quot;&gt;24&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;26&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;27&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;28&lt;/span&gt; puts time

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This will execute a single round trip to the database server: you&amp;#8217;ll pay the latence cost only once.&lt;/p&gt;
&lt;p&gt;On an unrelated note, this is the first time ever I use an &lt;code&gt;else&lt;/code&gt; clause on a &lt;code&gt;begin&lt;/code&gt;/&lt;code&gt;rescue&lt;/code&gt;. If an exception is raised, we want to cancel the copy (the &lt;code&gt;rescue&lt;/code&gt; clause), but on the other hand, if nothing is raised, we want to end the copy (the &lt;code&gt;else&lt;/code&gt; clause). One or the other must happen, but not both.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re curious what difference bulk copying makes, here are the benchmark results:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10000 INSERT statements
  7.012000   0.000000   7.012000 (  7.012000)

1 COPY FROM STDIN statement
  0.848000   0.000000   0.848000 (  0.848000)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The numbers speak for themselves: 8&amp;times; faster. Not too shabby, and remember this ratio will simply increase as the number of rows increases.&lt;/p&gt;</content>
    
    <category term="postgresql" label="Postgresql"/>
    
    <category term="jruby" label="Jruby"/>
      </entry>
  <entry>
    <title>DataDumper: Copy Your Test Data to Your Development Environment With Ease</title>
    <link href="http://blog.teksol.info/2011/03/31/datadumper-copy-your-test-data-to-your-development-environment-with-ease.html" type="text/html" />
    <id>tag:blog.teksol.info,2011-03-31:20110331220813</id>
    <updated>2011-03-31T22:08:13-04:00</updated>
    <content type="html">&lt;p&gt;The &lt;a href=&quot;https://github.com/francois/data_dumper#readme&quot;&gt;README&lt;/a&gt; says it all:&lt;/p&gt;
&lt;hr/&gt;
&lt;p&gt;You&amp;#8217;re knee deep in a debugger session, and you can&amp;#8217;t understand why something&amp;#8217;s wrong. You wish you could fire up your application against the test database, but sadly, the process which is running the tests is within a transaction, and thus the actual data is opaque. What can you do?&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Somewhere deep in your tests&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; test &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;the frobble touches the widget&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;   assert_equal &lt;span class=&quot;i&quot;&gt;42&lt;/span&gt;, frobble.widget_id
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You&amp;#8217;ve been on this &lt;code&gt;assert_equal&lt;/code&gt; call for the past hour wondering. Frustration&amp;#8217;s been mounting, because you don&amp;#8217;t understand why the frobble doesn&amp;#8217;t touch the widget. Clearly, there&amp;#8217;s something wrong with the fixtures, but you can&amp;#8217;t understand what it is. Time to fire up the debugger and dump the data:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; [&lt;span class=&quot;i&quot;&gt;814&lt;/span&gt;, &lt;span class=&quot;i&quot;&gt;823&lt;/span&gt;] &lt;span class=&quot;r&quot;&gt;in&lt;/span&gt; test/unit/widget_test.rb
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;814&lt;/span&gt;          frobble.save!
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;815&lt;/span&gt;        &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;816&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;817&lt;/span&gt;        test &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;the frobble touches the widget&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;818&lt;/span&gt;          debugger
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; =&amp;gt; &lt;span class=&quot;i&quot;&gt;819&lt;/span&gt;          assert_equal &lt;span class=&quot;i&quot;&gt;42&lt;/span&gt;, frobble.widget_id
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;820&lt;/span&gt;        &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;821&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;822&lt;/span&gt;        test &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;the widget touched the frobble in turn&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;823&lt;/span&gt;          assert widget.touched_by_frobble?
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; test/unit/widget_test.rb:&lt;span class=&quot;i&quot;&gt;819&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; =&amp;gt; &lt;span class=&quot;i&quot;&gt;819&lt;/span&gt;          assert_equal &lt;span class=&quot;i&quot;&gt;42&lt;/span&gt;, frobble.widget_id
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; (rdb:&lt;span class=&quot;i&quot;&gt;112&lt;/span&gt;)

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Since the &lt;code&gt;data_dumper&lt;/code&gt; gem is already declared in your Gemfile (if not, declare it, &lt;code&gt;bundle install&lt;/code&gt;, then run your tests again), type:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; (rdb:&lt;span class=&quot;i&quot;&gt;112&lt;/span&gt;) &lt;span class=&quot;co&quot;&gt;File&lt;/span&gt;.mkdir(&lt;span class=&quot;co&quot;&gt;Rails&lt;/span&gt;.root + &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;dump&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; (rdb:&lt;span class=&quot;i&quot;&gt;113&lt;/span&gt;) &lt;span class=&quot;co&quot;&gt;DataDumper&lt;/span&gt;.dump(&lt;span class=&quot;co&quot;&gt;Rails&lt;/span&gt;.root + &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;dump&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then, quit your failing tests, and from the trusty command line:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; rails console
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &amp;gt; &lt;span class=&quot;co&quot;&gt;DataDumper&lt;/span&gt;.load(&lt;span class=&quot;co&quot;&gt;Rails&lt;/span&gt;.root + &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;dump&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &amp;gt; exit
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; rails server

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Any and all data from your test database will be loaded in your development environment. You can now explore your model with your trusty application, to find out what&amp;#8217;s really going on.&lt;/p&gt;</content>
    
    <category term="activerecord" label="Activerecord"/>
      </entry>
  <entry>
    <title>Development Environment Benefits From Config File Tweaking as Well</title>
    <link href="http://blog.teksol.info/2011/03/17/development-environment-benefits-from-config-file-tweaking-as-well.html" type="text/html" />
    <id>tag:blog.teksol.info,2011-03-17:20110317110501</id>
    <updated>2011-03-17T11:05:01-04:00</updated>
    <content type="html">&lt;p&gt;If you&amp;#8217;re like me, you installed PostgreSQL (or MySQL or whatever) locally for development purposes. Then you copied data into your instance, and along you went.&lt;/p&gt;
&lt;p&gt;Eventually, you wrote a migration that touched nearly all rows in your database, and you waited, and waited, and waited some more (grab some coffee!) for the migration to finish.&lt;/p&gt;
&lt;p&gt;In my case, I had a migration using pure Ruby which took 30 minutes. The migration ran along the lines of:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;pc&quot;&gt;self&lt;/span&gt;.&lt;span class=&quot;fu&quot;&gt;up&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   add_column &lt;span class=&quot;sy&quot;&gt;:segments&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:pathname&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:string&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# There are 200,000 segments in the DB&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Segment&lt;/span&gt;.find_each &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |segment|
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;     segment.pathname = segment.full_pathname
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   change_column &lt;span class=&quot;sy&quot;&gt;:segments&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:pathname&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:string&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:null&lt;/span&gt; =&amp;gt; &lt;span class=&quot;pc&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Segment&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;   belongs_to &lt;span class=&quot;sy&quot;&gt;:parent&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:class_name&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Segment&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;full_pathname&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt;     full_name = []
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;     m = &lt;span class=&quot;pc&quot;&gt;self&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;while&lt;/span&gt; m
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt;       full_name &amp;lt;&amp;lt; m.name
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt;       m = m.parent
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;22&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;23&lt;/span&gt;     full_name.reverse.join(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt; / &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;24&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The intent being that we&amp;#8217;d cache the segment&amp;#8217;s pathname in each record (think a directory tree). Since we had 200,000 segments, and each of those would walk up the chain, it took around 30 minutes to run that migration. I rewrote the migration to use &lt;abbr title=&quot;Common Table Expressions&quot;&gt;CTE&lt;/abbr&gt;, which PostgreSQL supports out-of-the-box. My migration now ran in 17 minutes or so. I warned my teammates about this migration, and let it be. Eventually, a colleague took my branch and ran the migration. When he reported the migration took 30 seconds for him, at first I thought his &lt;abbr title=&quot;Solid State Drive&quot;&gt;SSD&lt;/abbr&gt; was the major difference between our machines. It turns out it wasn&amp;#8217;t&amp;hellip;&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# PostgreSQL installed through Homebrew&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# /usr/local/var/postgres/postgresql.conf&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Your file location will vary&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; shared_buffers = &lt;span class=&quot;i&quot;&gt;2400&lt;/span&gt;kB
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# ...&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Changing that single line from &lt;code&gt;2400kB&lt;/code&gt; to &lt;code&gt;96MB&lt;/code&gt; let me run the migration in 30 seconds as well&amp;hellip; I was very impressed at my stupidity. Let it be a lesson learned!&lt;/p&gt;</content>
    
    <category term="postgresql" label="Postgresql"/>
    
    <category term="tips" label="Tips"/>
      </entry>
  <entry>
    <title>PostgreSQL, Foreign Keys and Deadlocks</title>
    <link href="http://blog.teksol.info/2010/11/23/postgresql-foreign-keys-and-deadlocks.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-11-23:20101123083640</id>
    <updated>2010-11-23T08:36:40-05:00</updated>
    <content type="html">&lt;p&gt;What happens when you mix &lt;a href=&quot;http://en.wikipedia.org/wiki/Foreign_key&quot;&gt;foreign key constraints&lt;/a&gt;, &lt;a href=&quot;http://www.postgresql.org/&quot;&gt;PostgreSQL&lt;/a&gt;, &lt;a href=&quot;http://rubyonrails.com/&quot;&gt;ActiveRecord&lt;/a&gt; and &lt;a href=&quot;http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to&quot;&gt;cached counters&lt;/a&gt; (search for &lt;q&gt;:counter_cache&lt;/q&gt;)? &lt;a href=&quot;http://en.wikipedia.org/wiki/Deadlock&quot;&gt;Deadlocks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My colleague, &lt;a href=&quot;http://ca.linkedin.com/in/minaguib&quot;&gt;Mina Naguib&lt;/a&gt;, spent some time while away from the office to dig and diagnose some problems. The underlying issue is probably not related to PostgreSQL itself, but cached counters, which Rails makes very easy to do, sometimes result in deadlocks. We&amp;#8217;ve seen those backtraces in Hoptoad, but had never really investigated them: they were &lt;q&gt;rare&lt;/q&gt;, meaning nobody complained about them. They were just a thorn in our side.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In a nutshell, this flow is problematic:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt; Adding or modifying a record in a table, when: &lt;/li&gt;
&lt;li&gt; That table is a child (using a foreign key contraint) of another table  &lt;/li&gt;
&lt;li&gt; and then modifying the parent record in the parent table &lt;/li&gt;
&lt;li&gt; Using transactions &lt;/li&gt;
&lt;li&gt; Concurrently &lt;/li&gt;
&lt;li&gt; Sometimes (race condition) &lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;From Mina&amp;#8217;s &lt;a href=&quot;http://mina.naguib.ca/blog/2010/11/22/postgresql-foreign-key-deadlocks.html&quot;&gt;PostgreSQL transactions wrapping child+parent modifications, deadlocks, and ActiveRecord&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Read the full text: it&amp;#8217;s well worth it. Unfortunately, Mina doesn&amp;#8217;t have a resolution, but he wants more people to be aware of the issue, in the hopes that somebody will find a clean solution.&lt;/p&gt;</content>
    
    <category term="postgresql" label="Postgresql"/>
    
    <category term="activerecord" label="Activerecord"/>
      </entry>
  <entry>
    <title>Presenter or Command?</title>
    <link href="http://blog.teksol.info/2010/10/09/presenter-or-command.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-10-09:20101009225425</id>
    <updated>2010-10-09T22:54:25-04:00</updated>
    <content type="html">&lt;p&gt;The more I think about it, the more I believe Commands and Presenters are intertwined. If you don&amp;#8217;t know what a Presenter is, I suggest reading these articles:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model&quot;&gt;Skinny Controller, Fat Model&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;http://weblog.jamisbuck.org/2007/1/11/moving-associated-creations-to-the-model&quot;&gt;Moving associated creations to the model&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;http://blog.jayfields.com/2007/03/rails-presenter-pattern.html&quot;&gt;Rails: Presenter Pattern&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;http://blog.jayfields.com/2007/10/rails-rise-fall-and-potential-rebirth.html&quot;&gt;Rails: Rise, Fall, and Potential Rebirth of the Presenter Pattern&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;http://kpumuk.info/ruby-on-rails/simplifying-your-ruby-on-rails-code/&quot;&gt;Simplifying your Ruby on Rails code: Presenter pattern, cells plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;#8217;m building a gem to abstract the Command pattern in your applications. It&amp;#8217;s not Rails-specific, but does know about ActiveRecord. Anyway, as I was writing my sample application, I noticed some duplication:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;app/controllers/invitations_controller.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;InvitationsController&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ApplicationController&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;show&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;     &lt;span class=&quot;iv&quot;&gt;@user&lt;/span&gt; = &lt;span class=&quot;co&quot;&gt;User&lt;/span&gt;.invited.with_token(params[&lt;span class=&quot;sy&quot;&gt;:id&lt;/span&gt;]).first
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;     raise &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;RecordNotFound&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;iv&quot;&gt;@user&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     render &lt;span class=&quot;sy&quot;&gt;:action&lt;/span&gt; =&amp;gt; &lt;span class=&quot;sy&quot;&gt;:confirm&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;app/commands/confirm_invitation_request_command.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;ConfirmInvitationRequestCommand&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Komando&lt;/span&gt;.make_command &lt;span class=&quot;pc&quot;&gt;self&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   mandatory_steps &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     &lt;span class=&quot;iv&quot;&gt;@user&lt;/span&gt; = &lt;span class=&quot;co&quot;&gt;User&lt;/span&gt;.invited.with_token(params[&lt;span class=&quot;sy&quot;&gt;:id&lt;/span&gt;]).first
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;     raise &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;RecordNotFound&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;iv&quot;&gt;@user&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;     &lt;span class=&quot;iv&quot;&gt;@user&lt;/span&gt;.activate!(&lt;span class=&quot;iv&quot;&gt;@attributes&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice the first two lines of &lt;code&gt;InvitationsController#show&lt;/code&gt; and &lt;code&gt;ConfirmInvitationRequestCommand#mandatory_steps&lt;/code&gt;: identical. Then I thought, what if the Command was also a Presenter? Then, I could refactor appropriately:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;app/commands/confirm_invitation_request_command.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;ConfirmInvitationRequestCommand&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Komando&lt;/span&gt;.make_command &lt;span class=&quot;pc&quot;&gt;self&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;user&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     &lt;span class=&quot;iv&quot;&gt;@user&lt;/span&gt; ||= &lt;span class=&quot;r&quot;&gt;begin&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;       &lt;span class=&quot;co&quot;&gt;User&lt;/span&gt;.invited.with_token(params[&lt;span class=&quot;sy&quot;&gt;:id&lt;/span&gt;]).first.tap &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |user|
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;         raise &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;RecordNotFound&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;unless&lt;/span&gt; user
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;       &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   mandatory_steps &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;     &lt;span class=&quot;iv&quot;&gt;@user&lt;/span&gt; = &lt;span class=&quot;co&quot;&gt;User&lt;/span&gt;.invited.with_token(params[&lt;span class=&quot;sy&quot;&gt;:id&lt;/span&gt;]).first
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;     raise &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;RecordNotFound&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;iv&quot;&gt;@user&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt;     &lt;span class=&quot;iv&quot;&gt;@user&lt;/span&gt;.activate!(&lt;span class=&quot;iv&quot;&gt;@attributes&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;app/controllers/invitations_controller.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;InvitationsController&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ApplicationController&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;show&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;     &lt;span class=&quot;iv&quot;&gt;@user&lt;/span&gt; = &lt;span class=&quot;co&quot;&gt;ConfirmInvitationRequestCommand&lt;/span&gt;.new(&lt;span class=&quot;sy&quot;&gt;:token&lt;/span&gt; =&amp;gt; params[&lt;span class=&quot;sy&quot;&gt;:id&lt;/span&gt;]).user
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;     render &lt;span class=&quot;sy&quot;&gt;:action&lt;/span&gt; =&amp;gt; &lt;span class=&quot;sy&quot;&gt;:confirm&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice how the business logic of finding a user by token is nicely tucked away.&lt;/p&gt;
&lt;p&gt;Of course, while writing this post, I realized that finding a User by token should live in my model, not in the controller or the command, but still: bear with me.&lt;/p&gt;
&lt;p&gt;The Presenter pattern makes some decisions for the view: how to get at the objects to be shown, how to arrange them, how to save them as a group. Turns out my Command does essentially the same thing. Maybe we can think of Presenter as a special-case of Command? Such that Presenter is a Command to view an object? That seems logical to me.&lt;/p&gt;</content>
    
    <category term="pattern" label="Pattern"/>
      </entry>
  <entry>
    <title>Unintented Consequences: The Pitfalls of ActiveRecord Callbacks</title>
    <link href="http://blog.teksol.info/2010/09/28/unintented-consequences-the-pitfalls-of-activerecord-callbacks.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-09-28:20100928084217</id>
    <updated>2010-09-28T08:42:17-04:00</updated>
    <content type="html">&lt;p&gt;At &lt;a href=&quot;http://bloomdigital.com/&quot;&gt;Bloom&lt;/a&gt;, we have a largish &lt;a href=&quot;http://rubyonrails.org/&quot;&gt;Rails&lt;/a&gt; application: 12&amp;nbsp;000 lines of model code, alone. Yesterday, we found a major problem, and the cause was very non-obvious.&lt;/p&gt;
&lt;p&gt;Bloom Digital Platforms has a product called &lt;a href=&quot;http://adgear.com/&quot;&gt;AdGear&lt;/a&gt;. AdGear is an ad distribution platform. The obvious models are Ad and Campaign: a Campaign has many ads. From a UI perspective, it&amp;#8217;s nice to show how many active ads are in the campaign. In the campaign&amp;#8217;s index view, we have something like this:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &amp;lt;&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;%#&lt;/span&gt;&lt;span class=&quot;k&quot;&gt; app/views/campaigns/_campaign.html.erb %&amp;gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &amp;lt;tr&amp;gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;   &amp;lt;td&amp;gt;&amp;lt;%= link_to(h(campaign.name), campaign) %&amp;gt;&amp;lt;/td&amp;gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;   &amp;lt;td&amp;gt;&amp;lt;%= pluralize(campaign.active_ad_units_count, &amp;quot;active ads&amp;quot;) %&amp;gt;&amp;lt;/td&amp;gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &amp;lt;/tr&amp;gt;
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice how we&amp;#8217;re using an attribute named &lt;code&gt;active_ad_units_count&lt;/code&gt;. This is a cached value. We keep this value updated by doing the following:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Ad&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   belongs_to &lt;span class=&quot;sy&quot;&gt;:campaign&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;   after_save &lt;span class=&quot;sy&quot;&gt;:update_campaign_cached_values&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;   private
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;update_campaign_cached_values&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;     campaign.update_cached_values
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Campaign&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;update_cached_values&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;     update_attribute(&lt;span class=&quot;sy&quot;&gt;:active_ad_units_count&lt;/span&gt;, ad_units.active.count)
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Pretty reasonable, you&amp;#8217;ll say. And I agree: good names explain the intent, without needing to read all the details.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s throw a monkey wrench in there:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Campaign&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   before_save &lt;span class=&quot;sy&quot;&gt;:ensure_consistency&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   attr_accessor &lt;span class=&quot;sy&quot;&gt;:conditions&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;conditions&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;iv&quot;&gt;@conditions&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;iv&quot;&gt;@conditions&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;     &lt;span class=&quot;iv&quot;&gt;@conditions&lt;/span&gt; =|| serialized_conditions ? &lt;span class=&quot;co&quot;&gt;YAML&lt;/span&gt;.load(serialized_conditions) : &lt;span class=&quot;pc&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;   private
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# Method renamed to protect the innocent&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;ensure_consistency&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; some_condition &lt;span class=&quot;r&quot;&gt;then&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt;       &lt;span class=&quot;pc&quot;&gt;self&lt;/span&gt;.serialized_conditions = conditions ? conditions.to_yaml : &lt;span class=&quot;pc&quot;&gt;nil&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;       &lt;span class=&quot;r&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;pc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt;     &lt;span class=&quot;pc&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;22&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Can you spot the issue? Note this code is in &lt;code&gt;Campaign&lt;/code&gt;, not &lt;code&gt;Ad&lt;/code&gt;. It&amp;#8217;s pretty obvious when boiled down to 20 lines of code. When you&amp;#8217;re wading through 12&amp;nbsp;000, it&amp;#8217;s pretty tough.&lt;/p&gt;
&lt;p&gt;If you read the documentation for &lt;code&gt;#before_save&lt;/code&gt;, you&amp;#8217;ll notice this gem:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# == Canceling callbacks&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# If a &amp;lt;tt&amp;gt;before_*&amp;lt;/tt&amp;gt; callback returns +false+, all the later callbacks and the associated action are cancelled. If an &amp;lt;tt&amp;gt;after_*&amp;lt;/tt&amp;gt; callback returns&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# +false+, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# defined as methods on the model, which are called last.&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Excerpt from &lt;a href=&quot;http://github.com/rails/rails/blob/2-3-stable/activerecord/lib/active_record/callbacks.rb#L196&quot;&gt;activerecord/lib/active_record/callbacks.rb&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the example above, &lt;code&gt;some_condition&lt;/code&gt; might return &lt;code&gt;false&lt;/code&gt;, which would cancel the save, but let&amp;#8217;s unwind the stack:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;AdsController#update&lt;/li&gt;
	&lt;li&gt;Ad#update_campaign_cached_values&lt;/li&gt;
	&lt;li&gt;Campaign#update_cached_values&lt;/li&gt;
	&lt;li&gt;ActiveRecord::Base#update_attribute&lt;/li&gt;
	&lt;li&gt;Campaign#ensure_consistency &amp;#8211; returns false&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because we were calling &lt;code&gt;#update_attribute&lt;/code&gt;, which returns &lt;code&gt;false&lt;/code&gt; when it fails to update the object, we ended up not updating the Campaign&amp;#8217;s cached values. And we ended up with a stale cache. And ads which didn&amp;#8217;t get updated. A careful tower of convoluted code tumbled down upon us.&lt;/p&gt;
&lt;p&gt;Why did this happen? It&amp;#8217;s obvious the developer who wrote the &lt;code&gt;#ensure_consistency&lt;/code&gt; method didn&amp;#8217;t know about ActiveRecord&amp;#8217;s &lt;code&gt;#serialize&lt;/code&gt;, which did exactly what he wanted to. He also didn&amp;#8217;t know about the dangers of returning &lt;code&gt;false&lt;/code&gt; from a callback. These two point to relative inexperience / lack of knowledge about Rails &amp;amp; ActiveRecord.&lt;/p&gt;
&lt;p&gt;We&amp;#8217;re obviously not the first ones to hit this kind of problem:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;http://lists.rubyonrails.org/pipermail/rails/2006-February/017197.html&quot;&gt;before_save gotcha&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;http://siddharthdawara.blogspot.com/2008/09/rails-beforesave-and-validations.html&quot;&gt;Rails before_save and validations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;http://jamesgolick.com/&quot;&gt;James Golick&lt;/a&gt; also ranted off about something similar a while back: &lt;a href=&quot;http://jamesgolick.com/2010/3/14/crazy-heretical-and-awesome-the-way-i-write-rails-apps.html&quot;&gt;Crazy, Heretical, and Awesome: The Way I Write Rails Apps&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This points to a misfeature of ActiveRecord: a seemingly innocuous change had unintended consequences in a very unrelated portion of the code. Rails has a lot of magic / implicits. You have to be very careful. Ignorance is no excuse. The more I think about it, the more I like the service / command oriented way James offers, and it boils down to the discussion we had at the last Montreal.rb: implicit (Rails/Ruby) vs explicit (Django/Python).&lt;/p&gt;</content>
    
    <category term="active-record" label="Active-record"/>
      </entry>
  <entry>
    <title>Bundler &amp; Continuous Testing Scenarios: Usage Tips</title>
    <link href="http://blog.teksol.info/2010/08/19/bundler-and-continuous-testing-scenarios-usage-tips.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-08-19:20100819103024</id>
    <updated>2010-08-19T10:30:24-04:00</updated>
    <content type="html">&lt;p&gt;I just pushed a change for my teammates: we&amp;#8217;re now using Bundler on our Rails 2.3.5 application. Things went rather well, but our continuous testing server choked a bit on that.&lt;/p&gt;
&lt;p&gt;First, I had forgotten to run &lt;code&gt;bundle install&lt;/code&gt; after pulling changes. Then I had to ignore our cucumber environment, because it depends on &lt;code&gt;rb-appscript&lt;/code&gt; which is a native gem on Mac OS X. Our &lt;acronym title=&quot;continuous testing&quot;&gt;CT&lt;/acronym&gt; machine is a Linux box, hence the change. Then, &lt;code&gt;bundle install&lt;/code&gt; would fail installation because it didn&amp;#8217;t have sudo access:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Fetching&lt;/span&gt; git&lt;span class=&quot;sy&quot;&gt;:/&lt;/span&gt;/github.com/bloom/rails.git
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Fetching&lt;/span&gt; git&lt;span class=&quot;sy&quot;&gt;:/&lt;/span&gt;/github.com/francois/active_url
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Fetching&lt;/span&gt; git&lt;span class=&quot;sy&quot;&gt;:/&lt;/span&gt;/github.com/francois/safariwatir.git
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Fetching&lt;/span&gt; source index &lt;span class=&quot;r&quot;&gt;for&lt;/span&gt; http&lt;span class=&quot;sy&quot;&gt;:/&lt;/span&gt;/rubygems.org/
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;sudo&lt;/span&gt;: no tty present &lt;span class=&quot;r&quot;&gt;and&lt;/span&gt; no askpass program specified
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;sudo&lt;/span&gt;: no tty present &lt;span class=&quot;r&quot;&gt;and&lt;/span&gt; no askpass program specified
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Using&lt;/span&gt; rake (&lt;span class=&quot;fl&quot;&gt;0.8&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;7&lt;/span&gt;) 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;sudo&lt;/span&gt;: no tty present &lt;span class=&quot;r&quot;&gt;and&lt;/span&gt; no askpass program specified
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;sudo&lt;/span&gt;: no tty present &lt;span class=&quot;r&quot;&gt;and&lt;/span&gt; no askpass program specified
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;usr&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;lib64/ruby/site_ruby/&lt;span class=&quot;fl&quot;&gt;1.8&lt;/span&gt;/rubygems/format.rb:&lt;span class=&quot;i&quot;&gt;38&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;from_file_by_path': Cannot load gem at [/usr/lib64/ruby/gems/1.8/cache/SystemTimer-1.2.gem] in /home/francois/adgear-admin (Gem::Exception)
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;   from /usr/lib64/ruby/gems/1.8/gems/bundler-1.0.0.rc.5/lib/bundler/source.rb:77:in &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;fetch&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   from /usr/lib64/ruby/gems/1.8/gems/bundler-1.0.0.rc.5/lib/bundler/installer.rb:45:in `run&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;   from &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;usr&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;lib64/ruby/gems/&lt;span class=&quot;fl&quot;&gt;1.8&lt;/span&gt;/gems/bundler-&lt;span class=&quot;fl&quot;&gt;1.0&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;.rc.&lt;span class=&quot;i&quot;&gt;5&lt;/span&gt;/lib/bundler/spec_set.rb:&lt;span class=&quot;i&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;each'
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;   from /usr/lib64/ruby/gems/1.8/gems/bundler-1.0.0.rc.5/lib/bundler/spec_set.rb:12:in &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;each&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;   from /usr/lib64/ruby/gems/1.8/gems/bundler-1.0.0.rc.5/lib/bundler/installer.rb:44:in `run&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt;   from &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;usr&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;lib64/ruby/gems/&lt;span class=&quot;fl&quot;&gt;1.8&lt;/span&gt;/gems/bundler-&lt;span class=&quot;fl&quot;&gt;1.0&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;.rc.&lt;span class=&quot;i&quot;&gt;5&lt;/span&gt;/lib/bundler/installer.rb:&lt;span class=&quot;i&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;install'
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;   from /usr/lib64/ruby/gems/1.8/gems/bundler-1.0.0.rc.5/lib/bundler/cli.rb:185:in &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;install&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt;   from /usr/lib64/ruby/gems/1.8/gems/bundler-1.0.0.rc.5/lib/bundler/vendor/thor/task.rb:22:in `send&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt;   from &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;usr&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;lib64/ruby/gems/&lt;span class=&quot;fl&quot;&gt;1.8&lt;/span&gt;/gems/bundler-&lt;span class=&quot;fl&quot;&gt;1.0&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;.rc.&lt;span class=&quot;i&quot;&gt;5&lt;/span&gt;/lib/bundler/vendor/thor/task.rb:&lt;span class=&quot;i&quot;&gt;22&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;run'
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt;   from /usr/lib64/ruby/gems/1.8/gems/bundler-1.0.0.rc.5/lib/bundler/vendor/thor/invocation.rb:118:in &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;invoke_task&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt;   from /usr/lib64/ruby/gems/1.8/gems/bundler-1.0.0.rc.5/lib/bundler/vendor/thor.rb:246:in `dispatch&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;22&lt;/span&gt;   from &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;usr&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;lib64/ruby/gems/&lt;span class=&quot;fl&quot;&gt;1.8&lt;/span&gt;/gems/bundler-&lt;span class=&quot;fl&quot;&gt;1.0&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;.rc.&lt;span class=&quot;i&quot;&gt;5&lt;/span&gt;/lib/bundler/vendor/thor/base.rb:&lt;span class=&quot;i&quot;&gt;389&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;start'
&lt;span class=&quot;no&quot;&gt;23&lt;/span&gt;   from /usr/lib64/ruby/gems/1.8/gems/bundler-1.0.0.rc.5/bin/bundle:13
&lt;span class=&quot;no&quot;&gt;24&lt;/span&gt;   from /usr/bin/bundle:19:in &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;load&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;/span&gt;   from /usr/bin/bundle:19
&lt;span class=&quot;no&quot;&gt;26&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I was busy writing a message to the &lt;a href=&quot;http://groups.google.com/group/ruby-bundler&quot;&gt;Ruby Bundler Google Group&lt;/a&gt; when I remembered &lt;code&gt;bundle install&lt;/code&gt; has an &lt;a href=&quot;http://gembundler.com/man/bundle-install.1.html&quot;&gt;optional parameter&lt;/a&gt; which specifies where the bundle should be installed. I changed my &lt;acronym title=&quot;continuous testing&quot;&gt;CT&lt;/acronym&gt; script to run &lt;code&gt;bundle install --without=cucumber vendor/gems&lt;/code&gt; and bundle did it&amp;#8217;s magic, and my &lt;acronym title=&quot;continuous testing&quot;&gt;CT&lt;/acronym&gt; box started running tests.&lt;/p&gt;
&lt;p&gt;Much rejoicing ensued.&lt;/p&gt;
&lt;p&gt;For the record, here&amp;#8217;s our testing script:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; #!/bin/bash
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; cd /home/francois/adgear-admin
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; if [ -z $1 ] ; then
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;         echo &amp;quot;Can't proceed if I don't know which branch to test&amp;quot;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;         exit 1
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; fi
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; TSTAMP=`date +%Y-%m-%d-%H:%M`
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; BRANCH=$1
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; LOG=/home/francois/build-logs/$BRANCH-$TSTAMP.log
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; (
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;         git fetch origin                                           &amp;amp;&amp;amp; \
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;         git reset --hard origin/$BRANCH                            &amp;amp;&amp;amp; \
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt;         echo =======================                               &amp;amp;&amp;amp; \
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;         git show                                                   &amp;amp;&amp;amp; \
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt;         ( bundle install --without=cucumber vendor/cache || true ) &amp;amp;&amp;amp; \
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt;         nice rake db:adgear:production:update parallel:prepare parallel:test
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt; ) &amp;gt; $LOG 2&amp;gt;&amp;amp;1
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;22&lt;/span&gt; if [ &amp;quot;$?&amp;quot; = &amp;quot;0&amp;quot; ] ; then
&lt;span class=&quot;no&quot;&gt;23&lt;/span&gt;         /usr/bin/curl -s -o /dev/null -d &amp;quot;{\&amp;quot;message\&amp;quot;:{\&amp;quot;body\&amp;quot;:\&amp;quot;Test Suite Passed -- $BRANCH -- http://ct.local:9001/logs/$BRANCH/$TSTAMP\&amp;quot;}}&amp;quot; -HContent-Type:application/json -u 12345:X http://oursite.campfirenow.com/room/12345/speak.json
&lt;span class=&quot;no&quot;&gt;24&lt;/span&gt; else
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;/span&gt;         /usr/bin/curl -s -o /dev/null -d &amp;quot;{\&amp;quot;message\&amp;quot;:{\&amp;quot;body\&amp;quot;:\&amp;quot;Test Suite FAILED -- $BRANCH -- http://ct.local:9001/logs/$BRANCH/$TSTAMP\&amp;quot;}}&amp;quot; -HContent-Type:application/json -u 12345:X http://oursite.campfirenow.com/room/12345/speak.json
&lt;span class=&quot;no&quot;&gt;26&lt;/span&gt; fi
&lt;span class=&quot;no&quot;&gt;27&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;28&lt;/span&gt; # vi: sw=8 ts=8 expandtab

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This script is run from cron.&lt;/p&gt;</content>
    
    <category term="bundler" label="Bundler"/>
      </entry>
  <entry>
    <title>ActiveRecord STI + abstract_class? have a gotcha</title>
    <link href="http://blog.teksol.info/2010/07/25/activerecord-sti-abstract_class-have-a-gotcha.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-07-25:20100725224425</id>
    <updated>2010-07-25T22:44:25-04:00</updated>
    <content type="html">&lt;p&gt;To try and ease my pain, I am refactoring a Rails 2.3.5 application using Hobo from multiple tables to a single table. I&amp;#8217;m talking about models which are essentially the same: companies, people and employees. I went from this:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Company&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   has_many &lt;span class=&quot;sy&quot;&gt;:employees&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;   has_many &lt;span class=&quot;sy&quot;&gt;:people&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:through&lt;/span&gt; =&amp;gt; &lt;span class=&quot;sy&quot;&gt;:employees&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Person&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;   has_many &lt;span class=&quot;sy&quot;&gt;:employees&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   has_many &lt;span class=&quot;sy&quot;&gt;:companies&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:through&lt;/span&gt; =&amp;gt; &lt;span class=&quot;sy&quot;&gt;:employees&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Employee&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   belongs_to &lt;span class=&quot;sy&quot;&gt;:company&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;   belongs_to &lt;span class=&quot;sy&quot;&gt;:person&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To an &lt;acronym title=&quot;single-table inheritance&quot;&gt;STI&lt;/acronym&gt;-enabled class hierarchy:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Addressee&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   &lt;span class=&quot;pc&quot;&gt;self&lt;/span&gt;.abstract_class = &lt;span class=&quot;pc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Company&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;Addressee&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   has_many &lt;span class=&quot;sy&quot;&gt;:employees&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;   has_many &lt;span class=&quot;sy&quot;&gt;:people&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:through&lt;/span&gt; =&amp;gt; &lt;span class=&quot;sy&quot;&gt;:employees&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Person&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;Addressee&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;   has_many &lt;span class=&quot;sy&quot;&gt;:employees&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   has_many &lt;span class=&quot;sy&quot;&gt;:companies&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:through&lt;/span&gt; =&amp;gt; &lt;span class=&quot;sy&quot;&gt;:employees&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Employee&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;Addressee&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt;   belongs_to &lt;span class=&quot;sy&quot;&gt;:company&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;   belongs_to &lt;span class=&quot;sy&quot;&gt;:person&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Can you spot the mistake? Here&amp;#8217;s where it breaks down:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; script/console
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &amp;gt; &lt;span class=&quot;co&quot;&gt;PersonAddressee&lt;/span&gt;.count
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;SQL&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;9.9&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;SELECT&lt;/span&gt; count(*) &lt;span class=&quot;co&quot;&gt;AS&lt;/span&gt; count_all &lt;span class=&quot;co&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;addressees&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; =&amp;gt; &lt;span class=&quot;i&quot;&gt;10231&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When the classes are defined with abstract_class? set to true, calling &lt;code&gt;Company#count&lt;/code&gt; executes the following:&lt;/p&gt;
&lt;ol&gt;
	&lt;li&gt;ActiveRecord &lt;code&gt;Company#superclass&lt;/code&gt;, which answers &lt;code&gt;Addressee&lt;/code&gt;&lt;/li&gt;
	&lt;li&gt;ActiveRecord calls &lt;code&gt;Addressee#abstract_class?&lt;/code&gt;, which answers &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
	&lt;li&gt;Because &lt;code&gt;Company#base_class == Company#superclass&lt;/code&gt;, no STI is involved, and ActiveRecord queries the &lt;code&gt;addressees&lt;/code&gt; table with no STI hints (type column).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When removing the abstract class declaration, here&amp;#8217;s how it works:&lt;/p&gt;
&lt;ol&gt;
	&lt;li&gt;ActiveRecord &lt;code&gt;Company#superclass&lt;/code&gt;, which answers &lt;code&gt;Addressee&lt;/code&gt;&lt;/li&gt;
	&lt;li&gt;ActiveRecord calls &lt;code&gt;Addressee#abstract_class?&lt;/code&gt;, which answers &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
	&lt;li&gt;ActiveRecord &lt;code&gt;Addressee#superclass&lt;/code&gt;, which answers &lt;code&gt;ActiveRecord::Base&lt;/code&gt;&lt;/li&gt;
	&lt;li&gt;Because &lt;code&gt;Company#base_class != Company#superclass&lt;/code&gt;, STI is involved and ActiveRecord adds the type hint to the &lt;code&gt;addressees&lt;/code&gt; table query.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;a href=&quot;http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001822&quot;&gt;documentation&lt;/a&gt; is quite clear though:&lt;/p&gt;
&lt;blockquote cite=&quot;http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001822&quot;&gt;&lt;h4&gt;abstract_class?()&lt;/h4&gt;
&lt;p&gt;Returns whether this class is a base AR class. If A is a base class and B descends from A, then B.base_class will return B.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;The documentation on &lt;a href=&quot;http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001821&quot;&gt;&lt;code&gt;#base_class&lt;/code&gt;&lt;/a&gt; is much better though:&lt;/p&gt;
&lt;blockquote cite=&quot;http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M001821&quot;&gt;&lt;h4&gt;base_class()&lt;/h4&gt;
&lt;p&gt;Returns the base AR subclass that this class descends from. If A extends AR::Base, A.base_class will return A. If B descends from A through some arbitrarily deep hierarchy, B.base_class will return A.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;And &lt;a href=&quot;http://github.com/rails/rails/blob/13df581ec77cc7e5351570c5035a563b946532d5/activerecord/lib/active_record/base.rb#L773&quot;&gt;current HEAD documentation&lt;/a&gt; is the best of them all:&lt;/p&gt;
&lt;blockquote cite=&quot;http://github.com/rails/rails/blob/13df581ec77cc7e5351570c5035a563b946532d5/activerecord/lib/active_record/base.rb#L773&quot;&gt;&lt;h4&gt;base_class()&lt;/h4&gt;
&lt;p&gt;Returns the base AR subclass that this class descends from. If A extends AR::Base, A.base_class will return A. If B descends from A through some arbitrarily deep hierarchy, B.base_class will return A.&lt;/p&gt;
&lt;p&gt;If B &amp;lt; A and C &amp;lt; B and if A is an abstract_class then both B.base_class and C.base_class would return B as the answer since A is an abstract_class.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Luckily, I found this before users got a chance to report bugs. And no, I would never have thought to add a test for this: I&amp;#8217;m not in the habit of testing base ActiveRecord functionality.&lt;/p&gt;</content>
    
    <category term="activerecord" label="Activerecord"/>
      </entry>
  <entry>
    <title>Updated Mongo Explorer to v1.0.2</title>
    <link href="http://blog.teksol.info/2010/07/16/updated-mongo-explorer-to-v1-dot-0-dot-2.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-07-16:20100716235204</id>
    <updated>2010-07-16T23:52:04-04:00</updated>
    <content type="html">&lt;p&gt;&lt;a href=&quot;https://twitter.com/damiencaselli&quot;&gt;Damien Caselli&lt;/a&gt;, one of the first brave souls to use Mongo Explorer, reported an issue with time travelling in &lt;a href=&quot;http://github.com/francois/mongo_explorer&quot;&gt;Mongo Explorer&lt;/a&gt;. Luckily, it wasn&amp;#8217;t anything serious: just a misunderstanding between &lt;a href=&quot;http://github.com/francois/mongo_explorer/commit/74b645c77ac7086be0fdafb8de349a49e64365a3&quot;&gt;me, Cocoa and JavaScript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To celebrate the &lt;em&gt;No Time Travelling Issues&lt;/em&gt;, I&amp;#8217;ve released version 1.0.2 of Mongo Explorer. Get yours: &lt;a href=&quot;https://teksol.s3.amazonaws.com:443/mongo-explorer-v1.0.2.zip&quot;&gt;mongo-explorer-v1.0.2.zip&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Yes, I&amp;#8217;m aware of &lt;a href=&quot;http://github.com/andymatuschak/Sparkle&quot;&gt;Sparkle&lt;/a&gt;, and yes, it will eventually make it into Mongo Explorer. That, and a million other little things.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://d.staging.adgear.com/data/dp=34/chip=0e6ff78073d8012d291b0024e87a30c2.gif&quot; width=&quot;1&quot; height=&quot;1&quot; alt=&quot;&quot;/&gt;&lt;/p&gt;</content>
    
    <category term="cocoa" label="Cocoa"/>
    
    <category term="mongo" label="Mongo"/>
      </entry>
  <entry>
    <title>Mongo Explorer: Explore your MongoDB</title>
    <link href="http://blog.teksol.info/2010/07/14/mongo-explorer-explore-your-mongodb.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-07-14:20100714234403</id>
    <updated>2010-07-14T23:44:03-04:00</updated>
    <content type="html">&lt;p&gt;In the past few months, I was looking for an excuse to write a native Mac &lt;a href=&quot;http://developer.apple.com/technologies/mac/cocoa.html&quot;&gt;Cocoa&lt;/a&gt; application. We use &lt;a href=&quot;http://www.mongodb.org/&quot;&gt;MongoDB&lt;/a&gt; at &lt;a href=&quot;http://bloomdigital.com/&quot;&gt;Bloom Digital&lt;/a&gt; to handle our analytics needs. I had tried &lt;a href=&quot;http://github.com/bububa/MongoHub-Mac&quot;&gt;mongohub&lt;/a&gt;, but my itch wasn&amp;#8217;t scratched: I wanted something that displayed my data in tables, not as some kind of outline. Thus was born &lt;strong&gt;Mongo Explorer&lt;/strong&gt;.&lt;/p&gt;
&lt;p style=&quot;text-align:center&quot;&gt;&lt;a href=&quot;/2010/07/14/mongo-explorer-v1.0.0.png&quot;&gt;&lt;img src=&quot;/2010/07/14/mongo-explorer-v1.0.0.png&quot; width=&quot;300&quot; alt=&quot;Mongo Explorer v1.0.0, detailing how to view a single collection's documents and a single document opened in flat view.&quot; title=&quot;&quot;/&gt;&lt;/a&gt;&lt;br/&gt;Click to enlarge&lt;/p&gt;
&lt;p&gt;If you tell me &amp;#8220;Why didn&amp;#8217;t you fork mongohub and add your code&amp;#8221;, I&amp;#8217;ll tell you I was intimidated by a &lt;em&gt;larger&lt;/em&gt; Cocoa application. I wanted something simple from which I could learn everything I wanted: from the grounds up. Of course, once I&amp;#8217;m &amp;#8220;done&amp;#8221; (for some measure of done-ness), the code base will be much larger.&lt;/p&gt;
&lt;p&gt;One useful piece of code that might be useful for others is a Mongo Driver for &lt;a href=&quot;http://en.wikipedia.org/wiki/Objective-C&quot;&gt;Objective-C&lt;/a&gt;. Underlying Mongo Explorer is an abstraction for Mongo connections that&amp;#8217;s similar to the Ruby driver. It&amp;#8217;s not yet a framework, since I don&amp;#8217;t yet know how to do that, but hey, this is a learning exercise for me. One nice thing about this abstraction is that it&amp;#8217;s built directly atop the official Mongo C driver: thus it inherits it&amp;#8217;s abilities, but some of it&amp;#8217;s quirks as well.&lt;/p&gt;
&lt;p&gt;What can you expect from Mongo Explorer today? You can open a connection to any local MongoDB instance (manually open an SSH tunnel if you want a remote instance), browse the server&amp;#8217;s databases and collections, and list your collection&amp;#8217;s documents. For each document, you&amp;#8217;ll get what I call the &lt;em&gt;flat view&lt;/em&gt;. The flat view is a dot-separated list of keys, followed by the value. It&amp;#8217;s useful to view nested objects:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; obj = {&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;rp&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; =&amp;gt; {&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; =&amp;gt; &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; =&amp;gt; {&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;}}}
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Flat view&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# rp.a   =&amp;gt; 1&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# rp.b.c =&amp;gt; d&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The flat view was my biggest impetus to write Mongo Explorer.&lt;/p&gt;
&lt;p&gt;What&amp;#8217;s broken? First, this needs some kind of icon. Also, don&amp;#8217;t expect to browse multi-million document collections just yet. Cocoa wants to fully materialize the NSTableView rows, and I don&amp;#8217;t know how to handle that yet. I do have an MEArray class that helps somewhat, but it&amp;#8217;s not perfect yet. Learning exercise, remember? I&amp;#8217;ve successfully viewed collections with slightly less than 20,000 document with no issues.&lt;/p&gt;
&lt;p&gt;If you&amp;#8217;re interested in following, you can play around on &lt;a href=&quot;http://github.com/francois/mongo_explorer&quot;&gt;GitHub&lt;/a&gt;, or even download the first official release: &lt;a href=&quot;https://s3.amazonaws.com/teksol/mongo-explorer-v1.0.1.zip&quot;&gt;Mongo Explorer, v1.0.1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;UPDATE: Pushed 1.0.1, as 1.0.0 didn&amp;#8217;t include it&amp;#8217;s frameworks, and was 10.6 only.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;http://d.staging.adgear.com/data/dp=34/chip=0e6ff78073d8012d291b0024e87a30c2.gif&quot; width=&quot;1&quot; height=&quot;1&quot; alt=&quot;&quot;/&gt;&lt;/p&gt;</content>
    
    <category term="mongo" label="Mongo"/>
    
    <category term="cocoa" label="Cocoa"/>
      </entry>
  <entry>
    <title>Using the Mongo C Driver</title>
    <link href="http://blog.teksol.info/2010/06/06/using-the-mongo-c-driver.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-06-06:20100606153058</id>
    <updated>2010-06-06T15:30:58-04:00</updated>
    <content type="html">&lt;p&gt;I&amp;#8217;m writing a &lt;a href=&quot;http://developer.apple.com/technologies/mac/cocoa.html&quot;&gt;Cocoa&lt;/a&gt; application to browse and administer a &lt;a href=&quot;http://www.mongodb.org/&quot;&gt;Mongo DB&lt;/a&gt;. This is a fun side-project. I&amp;#8217;m using the Mongo driver from Objective-C, hence there might be differences for you.&lt;/p&gt;
&lt;p&gt;First off, we need to connect to Mongo, and to do that, we need to tell Mongo about the connection information, namely the host and port:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; mongo_connection_options opts;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; strcpy(opts.host,
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;        [[self.connectionInfo objectForKey:MEHost] cString]);
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; opts.port = [[self.connectionInfo objectForKey:MEPort] intValue];
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;/* mongo doesn't have a default port constant or define */&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; (&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt; == opts.port) opts.port = &lt;span class=&quot;i&quot;&gt;27017&lt;/span&gt;;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;connectionInfo&lt;/code&gt; above is an instance of &lt;a href=&quot;http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/Reference/Reference.html&quot;&gt;&lt;code&gt;NSDictionary&lt;/code&gt;&lt;/a&gt;. The available keys are declared in &lt;a href=&quot;http://github.com/francois/mongo_explorer/blob/b19664b0303ceabff0a55add044fd4d4e494fd41/NewConnectionController.h#L11-14&quot;&gt;&lt;code&gt;NewConnectionController&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After we&amp;#8217;ve prepared the connection options, it&amp;#8217;s time to connect:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; connection = (mongo_connection *)malloc(&lt;span class=&quot;r&quot;&gt;sizeof&lt;/span&gt;(mongo_connection));
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; (!connection) &lt;span class=&quot;r&quot;&gt;return&lt;/span&gt;;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; mongo_conn_return result = mongo_connect(connection, &amp;amp;opts);
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; (mongo_conn_success == result) &lt;span class=&quot;r&quot;&gt;return&lt;/span&gt;;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code&gt;connection&lt;/code&gt; above was declared as a pointer to a &lt;code&gt;mongo_connection&lt;/code&gt; structure in &lt;a href=&quot;http://github.com/francois/mongo_explorer/blob/b19664b0303ceabff0a55add044fd4d4e494fd41/DatabaseController.h#L13&quot;&gt;&lt;code&gt;DatabaseController&lt;/code&gt;&lt;/a&gt;. If the connection succeeds, I simply continue. If the connection fails, I fall-through and inform the user using an &lt;a href=&quot;http://developer.apple.com/mac/library/documentation/cocoa/reference/ApplicationKit/Classes/NSAlert_Class/Reference/Reference.html&quot;&gt;NSAlert&lt;/a&gt;, shown as a sheet.&lt;/p&gt;
&lt;p&gt;I haven&amp;#8217;t yet needed to authenticate to Mongo, but it seems simple to call &lt;a href=&quot;http://github.com/mongodb/mongo-c-driver/blob/e62001c51fec4b0f3d0b5626e0b3f0b515367a30/src/mongo.h#L169&quot;&gt;&lt;code&gt;mongo_cmd_authenticate()&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Don&amp;#8217;t forget the &lt;a href=&quot;http://github.com/mongodb/mongo-c-driver/tree/master/test&quot;&gt;tests&lt;/a&gt; available in the mongo driver itself.&lt;/p&gt;</content>
    
    <category term="mongo" label="Mongo"/>
      </entry>
  <entry>
    <title>Autosave Associations and validates_uniqueness_of: Don't Do That</title>
    <link href="http://blog.teksol.info/2010/06/02/autosave-associations-and-validates-uniqueness-of-dont-do-that.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-06-02:20100602150820</id>
    <updated>2010-06-02T15:08:20-04:00</updated>
    <content type="html">&lt;p&gt;We received a nasty &lt;a href=&quot;http://hoptoadapp.com/&quot;&gt;Hoptoad&lt;/a&gt; notification today at &lt;a href=&quot;http://adgear.com/&quot;&gt;AdGear&lt;/a&gt; :&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;StatementInvalid&lt;/span&gt;: &lt;span class=&quot;ke&quot;&gt;PGError&lt;/span&gt;: &lt;span class=&quot;co&quot;&gt;ERROR&lt;/span&gt;:
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; duplicate key value violates unique constraint &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;index_placement_rules_on_type_and_placement_id_and_bookable_typ&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; :
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;placement_rules&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;placement_id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;bookable_type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;bookable_id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;updated_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;) VALUES(&lt;span class=&quot;co&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;PlacementInclusion&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 19:29:24.479983&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;i&quot;&gt;2320&lt;/span&gt;, &lt;span class=&quot;co&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Site&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;i&quot;&gt;162&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 19:29:24.479983&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;) &lt;span class=&quot;co&quot;&gt;RETURNING&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This manifested itself when a customer called and told us he&amp;#8217;d found our &lt;a href=&quot;http://admin.adgear.com/500.html&quot;&gt;500 error page&lt;/a&gt;. Ooops.&lt;/p&gt;
&lt;p&gt;Our problem turned out to be a misunderstanding of &lt;a href=&quot;http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html&quot;&gt;autosave&lt;/a&gt; association validations.&lt;/p&gt;
&lt;p&gt;Autosave associations are a wonderful beast: with a single &lt;code&gt;#save&lt;/code&gt; call, the nested entities are saved within the same transaction, without any intervention on the developer&amp;#8217;s part. Unfortunately, validations behave differently than what we expected.&lt;/p&gt;
&lt;p&gt;For the sake of argument, let&amp;#8217;s use these models:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Order&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   has_many &lt;span class=&quot;sy&quot;&gt;:items&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;OrderItem&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   belongs_to &lt;span class=&quot;sy&quot;&gt;:order&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   validates_presence_of &lt;span class=&quot;sy&quot;&gt;:product_number&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   validates_uniqueness_of &lt;span class=&quot;sy&quot;&gt;:product_number&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:scope&lt;/span&gt; =&amp;gt; &lt;span class=&quot;sy&quot;&gt;:order&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you build an Order and add two items, you&amp;#8217;ll have troubles, unless you wrap your save in a transaction block:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &amp;gt; o = &lt;span class=&quot;co&quot;&gt;Order&lt;/span&gt;.new
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; &amp;gt; &lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;.times { o.items &amp;lt;&amp;lt; &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt;.new(&lt;span class=&quot;sy&quot;&gt;:product_number&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;) }
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; &amp;gt; o.save
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# Validates entities&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Load&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;0.2&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.id &lt;span class=&quot;co&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;WHERE&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;product_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.order_id &lt;span class=&quot;co&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;NULL&lt;/span&gt;) &lt;span class=&quot;co&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Load&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;0.1&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.id &lt;span class=&quot;co&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;WHERE&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;product_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.order_id &lt;span class=&quot;co&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;NULL&lt;/span&gt;) &lt;span class=&quot;co&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Create&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;0.7&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;orders&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;customer_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;updated_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;) VALUES(&lt;span class=&quot;co&quot;&gt;NULL&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 19:15:57&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 19:15:57&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# Validate 1st entity again&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Load&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;0.1&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.id &lt;span class=&quot;co&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;WHERE&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;product_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.order_id = &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;) &lt;span class=&quot;co&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Create&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;0.1&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;order_id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;updated_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;product_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;) VALUES(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 19:15:57&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 19:15:57&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# Validate 2nd entity again, but fails this time around&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Load&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;0.1&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.id &lt;span class=&quot;co&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;WHERE&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;product_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.order_id = &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;) &lt;span class=&quot;co&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;  =&amp;gt; [&lt;span class=&quot;pc&quot;&gt;false&lt;/span&gt;, &lt;span class=&quot;pc&quot;&gt;true&lt;/span&gt;] 
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; &amp;gt; o.items.map(&amp;amp;&lt;span class=&quot;sy&quot;&gt;:errors&lt;/span&gt;).flatten.map(&amp;amp;&lt;span class=&quot;sy&quot;&gt;:full_messages&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;  =&amp;gt; [[], [&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Product number has already been taken&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;]] 

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;A partially saved order is rarely what you&amp;#8217;re looking for. Adding the &lt;code&gt;:autosave =&amp;gt; true&lt;/code&gt; option&amp;hellip;&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Order&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   has_many &lt;span class=&quot;sy&quot;&gt;:items&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:autosave&lt;/span&gt; =&amp;gt; &lt;span class=&quot;pc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Generates a very different SQL trace:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &amp;gt; o = &lt;span class=&quot;co&quot;&gt;Order&lt;/span&gt;.new
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; &amp;gt; &lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;.times { o.items &amp;lt;&amp;lt; &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt;.new(&lt;span class=&quot;sy&quot;&gt;:product_number&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;) }
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; &amp;gt; o.save
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# Validate associated entities...&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Load&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;0.2&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.id &lt;span class=&quot;co&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;WHERE&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;product_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.order_id &lt;span class=&quot;co&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;NULL&lt;/span&gt;) &lt;span class=&quot;co&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Load&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;0.1&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;SELECT&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.id &lt;span class=&quot;co&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;WHERE&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;product_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;.order_id &lt;span class=&quot;co&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;NULL&lt;/span&gt;) &lt;span class=&quot;co&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Order&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Create&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;0.4&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;orders&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;customer_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;updated_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;) VALUES(&lt;span class=&quot;co&quot;&gt;NULL&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 20:03:12&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 20:03:12&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# Then save them, even though they're invalid.&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Create&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;0.1&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;order_id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;updated_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;product_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;) VALUES(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 20:03:12&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 20:03:12&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Create&lt;/span&gt; (&lt;span class=&quot;fl&quot;&gt;0.1&lt;/span&gt;ms)   &lt;span class=&quot;co&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; (&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;created_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;order_id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;updated_at&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;product_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;) VALUES(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 20:03:12&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2010-06-02 20:03:12&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;  =&amp;gt; &lt;span class=&quot;pc&quot;&gt;true&lt;/span&gt; 

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is even worse: instead of a partial save, ActiveRecord told us that everything was good. This is how ActiveRecord behaves:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; order.save &lt;span class=&quot;r&quot;&gt;then&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   order.items.each(&amp;amp;&lt;span class=&quot;sy&quot;&gt;:save&lt;/span&gt;) &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; order.items.all?(&amp;amp;&lt;span class=&quot;sy&quot;&gt;:valid?&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;What we need instead is to hook into the autosave callback chain:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Order&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   has_many &lt;span class=&quot;sy&quot;&gt;:items&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:autosave&lt;/span&gt; =&amp;gt; &lt;span class=&quot;pc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   private
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# items is the has_many name. If you had posts, it would be&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# validate_associated_records_for_posts. If you have multiple&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# autosave associations, each can have a method such as this&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# one to handle it's validation needs.&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;validate_associated_records_for_items&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;     product_number_errors = &lt;span class=&quot;pc&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;     items.group_by(&amp;amp;&lt;span class=&quot;sy&quot;&gt;:product_number&lt;/span&gt;).reject {|_, group| group.length &amp;lt;= &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;}.each &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |product_number, group|
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;       product_number_errors = &lt;span class=&quot;pc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;       errors.add_to_base &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Product &lt;/span&gt;&lt;span class=&quot;il&quot;&gt;&lt;span class=&quot;idl&quot;&gt;#{&lt;/span&gt;product_number&lt;span class=&quot;idl&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt; used &lt;/span&gt;&lt;span class=&quot;il&quot;&gt;&lt;span class=&quot;idl&quot;&gt;#{&lt;/span&gt;group.length&lt;span class=&quot;idl&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt; times on the order&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;     errors.add_to_base(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Each product number can be used at most once per order&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;) &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; product_number_errors
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With this validation in place, validations will run on the full collection in a single pass, rather than piecemeal.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &amp;gt; o = &lt;span class=&quot;co&quot;&gt;Order&lt;/span&gt;.new
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &amp;gt; &lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;.times { o.items &amp;lt;&amp;lt; &lt;span class=&quot;co&quot;&gt;Item&lt;/span&gt;.new(&lt;span class=&quot;sy&quot;&gt;:product_number&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A-113&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;) }
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &amp;gt; o.save &lt;span class=&quot;c&quot;&gt;# Will run in-memory validations&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;  =&amp;gt; &lt;span class=&quot;pc&quot;&gt;false&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &amp;gt; pp o.errors.full_messages
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt; [&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Product A-113 used 2 times on the order&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt;7&lt;/span&gt;  &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Each product number can be used at most once per order&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;]

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice I didn&amp;#8217;t say anything about the &lt;code&gt;validates_uniqueness_of&lt;/code&gt; validation in &lt;code&gt;Item&lt;/code&gt;. I left it in place. It doesn&amp;#8217;t harm anything, and if you ever create items without going through the parent model, your code is ready to take care of itself.&lt;/p&gt;</content>
    
    <category term="activerecord" label="Activerecord"/>
      </entry>
  <entry>
    <title>Bookmarks Are King, Even on Applications</title>
    <link href="http://blog.teksol.info/2010/05/24/bookmarks-are-king-even-on-applications.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-05-24:20100524095446</id>
    <updated>2010-05-24T09:54:46-04:00</updated>
    <content type="html">&lt;p&gt;I built an application for a non-profit in my region. It allows them to track donations and statistics about their work. One thing that&amp;#8217;s very important for them is to know if / when they called a previous donor, to see if they will give again this year. They also need to emit receipts for those people that donated. And they must not emit the same receipt twice, etc. You know the drill. I have a page in the application where they can build such a query:&lt;/p&gt;
&lt;p style=&quot;text-align:center&quot;&gt;&lt;img src=&quot;/2010/05/24/query-screen.png&quot; width=&quot;251&quot; height=&quot;230&quot; alt=&quot;&quot; title=&quot;&quot;/&gt;&lt;/p&gt;
&lt;p&gt;On this page, they can choose which campaign they&amp;#8217;re working on, and different event types that occur during a campaign. They can also choose how to present the result: the date at which the event occured, or the sum of money or quantity they acquired. It&amp;#8217;s a large form, since there are almost 15 event types that can occur in a single campaign.&lt;/p&gt;
&lt;p&gt;René, my customer, asked me if they could save queries between sessions, so they can go home in the evening, and come back the next day. I said &amp;#8220;Sure, I&amp;#8217;ll just need a Search model here, and &amp;hellip; Hold on a minute: I have a much simpler solution: bookmark the result page.&amp;#8221;&lt;/p&gt;
&lt;p&gt;You see, I built this application on &lt;a href=&quot;http://c2.com/cgi/wiki?RepresentationalStateTransfer&quot;&gt;REST&lt;/a&gt; principles. The query itself is idempotent: it either shows events, or not. And since this is idempotent, a &lt;tt&gt;GET&lt;/tt&gt; query is just what the doctor ordered. And browsers are nice enough to allow bookmarking &lt;tt&gt;GET&lt;/tt&gt; requests. Yup: 10 hours of work just disappeared because I used what&amp;#8217;s already available. My customer was happy: he didn&amp;#8217;t have to wait a minute to get the functionality he needed. I was happy because I had 10 hours of work NOT to do, and I don&amp;#8217;t have to maintain that code: it&amp;#8217;s in somebody else&amp;#8217;s hands. &lt;a href=&quot;http://c2.com/cgi/wiki?DoTheSimplestThingThatCouldPossiblyWork&quot;&gt;TSTTCPW&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This was a real &lt;a href=&quot;http://en.wikipedia.org/wiki/Win-win_game&quot;&gt;win-win&lt;/a&gt; situation.&lt;/p&gt;</content>
    
    <category term="miscellaneous" label="Miscellaneous"/>
      </entry>
  <entry>
    <title>What Does Your Workday Look Like?</title>
    <link href="http://blog.teksol.info/2010/05/23/what-does-your-workday-look-like.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-05-23:20100523105123</id>
    <updated>2010-05-23T10:51:23-04:00</updated>
    <content type="html">&lt;p&gt;I spend 70% to 90% of my day in a full-screen &lt;a href=&quot;http://iterm.sourceforge.net/&quot;&gt;iTerm&lt;/a&gt;, in Vim. I find it ironic that I use a 3kCAD machine to do what &lt;a href=&quot;http://en.wikipedia.org/wiki/Green_screen_display&quot;&gt;green screen terminals&lt;/a&gt; did 30 years ago&amp;#8230; My iTerm&amp;#8217;s color scheme is green text on a black background. I read somewhere that this color combination was the easiest on eyes, but can&amp;#8217;t find the reference right now.&lt;/p&gt;
&lt;p&gt;When I&amp;#8217;m not in Vim, I&amp;#8217;m usually in Safari or Firefox. What does &lt;strong&gt;your&lt;/strong&gt; day look like?&lt;/p&gt;</content>
    
    <category term="miscellaneous" label="Miscellaneous"/>
      </entry>
  <entry>
    <title>FAQs Reduce Support Requests</title>
    <link href="http://blog.teksol.info/2010/05/21/faqs-reduce-support-requests.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-05-21:20100521083154</id>
    <updated>2010-05-21T08:31:54-04:00</updated>
    <content type="html">&lt;p&gt;I&amp;#8217;m a happy &lt;a href=&quot;http://dreamhost.com/&quot;&gt;DreamHost&lt;/a&gt; customer, for many years. Recently, they moved my data to a new server. That in itself wasn&amp;#8217;t a problem, but this blog went down with their &amp;#8220;bad_httpd_conf&amp;#8221; error. DreamHost support has always been prompt to answer my questions. But more importantly, when a customer asks for support, they have answer &lt;strong&gt;right where it matters&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;As part of the support process, DreamHost asks what site you have a problem with. Then they run some diagnostics on the network and report those as warnings.&lt;/p&gt;
&lt;p style=&quot;text-align:center&quot;&gt;&lt;a href=&quot;/2010/05/21/dreamhost-panel-diagnostics.png&quot;&gt;&lt;img src=&quot;/2010/05/21/dreamhost-panel-diagnostics-thumbnail.png&quot; width=&quot;360&quot; height=&quot;90&quot; alt=&quot;DreamHost Support gives you diagnostics when you ask for support on a site. In this case, DreamHost is telling me my DNS servers are non-DreamHost, which might cause problems.&quot; title=&quot;DreamHost Support telling me my DNS records for teksol.info are non-DreamHost&quot;/&gt;&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;/2010/05/21/dreamhost-panel-diagnostics.png&quot;&gt;Click to view full size&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After they&amp;#8217;ve done that, DreamHost asks for what type of problem you have. And the different categories have tips you can apply to try to correct the problem, before you have to wait for DreamHost support.&lt;/p&gt;
&lt;p style=&quot;text-align:center&quot;&gt;&lt;a href=&quot;/2010/05/21/dreamhost-faq.png&quot;&gt;&lt;img src=&quot;/2010/05/21/dreamhost-faq-thumbnail.png&quot; width=&quot;360&quot; height=&quot;207&quot; alt=&quot;DreamHost Support offers tips on fixing your problems before waiting for them, in this case telling me that editing and saving the site might correct the &amp;quot;bad_httpd_conf&amp;quot; error&quot; title=&quot;DreamHost Support s tips right where we need them: when you're asking for help&quot;/&gt;&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;/2010/05/21/dreamhost-faq.png&quot;&gt;Click to view full size&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Another thing that&amp;#8217;s very nice: after logging in, one of the 8 main options is, you guessed it, Support. DreamHost doesn&amp;#8217;t hide their support. It&amp;#8217;s very visible. I&amp;#8217;m very satisfied with the services I use.&lt;/p&gt;</content>
    
    <category term="tech-support" label="Tech-support"/>
      </entry>
  <entry>
    <title>Using Watchr to Check on Your Tests</title>
    <link href="http://blog.teksol.info/2010/05/20/using-watchr-to-check-on-your-tests.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-05-20:20100520215251</id>
    <updated>2010-05-20T21:52:51-04:00</updated>
    <content type="html">&lt;p&gt;I started using &lt;a href=&quot;http://github.com/mynyml/watchr&quot;&gt;Watchr&lt;/a&gt; in a couple of projects. An interesting use of Watchr is to check on your tests. I have been using this script with great success so far:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;test.watchr&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;failed&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   system(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;growlnotify --name adgear-reporting-tests --image /Applications/Mail.app/Contents/Resources/Caution.tiff -m 'Oops, tests failed'&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;   system(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;say 'failed'&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;succeeded&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;   system(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;growlnotify --name adgear-reporting-tests -m 'Green tests'&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   system(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;say 'pass'&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; watch(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;(lib|test)/.+&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;(js|rb)&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;) &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |_|
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   system(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;rake&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;   &lt;span class=&quot;gv&quot;&gt;$?&lt;/span&gt;.success? ? succeeded : failed
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice I&amp;#8217;m using Mac OS X application paths, but it works just fine for me. And the regular &amp;#8220;pass&amp;#8221; and &amp;#8220;failed&amp;#8221; messages keep me abreast of what&amp;#8217;s going on. But this style of continuous testing only works when your tests take a few seconds at most. When my tests take much longer than that, I start fiddling, opening Google Reader, checking news, whatever.&lt;/p&gt;
&lt;p&gt;Your mileage may vary.&lt;/p&gt;</content>
    
    <category term="watchr" label="Watchr"/>
    
    <category term="testing" label="Testing"/>
      </entry>
  <entry>
    <title>Use Rdebug When Debugging Statements Don't Take Effect</title>
    <link href="http://blog.teksol.info/2010/03/15/use-rdebug-when-debugging-statements-dont-take-effect.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-03-15:20100315120051</id>
    <updated>2010-03-15T12:00:51-04:00</updated>
    <content type="html">&lt;p&gt;I was meta-meta-programming, and had an issue when an &lt;code&gt;#included&lt;/code&gt; method was called:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; anon = &lt;span class=&quot;co&quot;&gt;Module&lt;/span&gt;.new
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; anon.class_eval &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;pc&quot;&gt;self&lt;/span&gt;.&lt;span class=&quot;fu&quot;&gt;included&lt;/span&gt;(base)
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;     debugger
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; some_method &lt;span class=&quot;r&quot;&gt;then&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;       &lt;span class=&quot;c&quot;&gt;# code&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;else&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;       &lt;span class=&quot;c&quot;&gt;# more code&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The code above resulted in:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/&lt;span class=&quot;co&quot;&gt;Projects&lt;/span&gt;/project/lib/extensions.rb:&lt;span class=&quot;i&quot;&gt;214&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;included': undefined local variable or method &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;some_method&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt; for #&amp;lt;Module:0x103305b20&amp;gt; (NameError)
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;         from /Users/francois/Projects/project/lib/extensions.rb:245:in `include&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;         from &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/&lt;span class=&quot;co&quot;&gt;Projects&lt;/span&gt;/project/lib/extensions.rb:&lt;span class=&quot;i&quot;&gt;245&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;send'
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;         ...
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The debugger statement above just wouldn&amp;#8217;t take: Ruby ran right over it. I happened to look at ruby-debug&amp;#8217;s Rubygem spec file:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; --- !ruby/object&lt;span class=&quot;sy&quot;&gt;:Gem&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Specification&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;name&lt;/span&gt;: ruby-debug
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; ...
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;executables&lt;/span&gt;: 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; - rdebug
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt; ...

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Oh, had never noticed the executables before&amp;#8230; Sure enough, I managed to run under debugger control immediately:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; rdebug -&lt;span class=&quot;co&quot;&gt;I&lt;/span&gt; test test/functional/ad_spots_controller_test.rb [&lt;span class=&quot;i&quot;&gt;-4&lt;/span&gt;, &lt;span class=&quot;i&quot;&gt;5&lt;/span&gt;] &lt;span class=&quot;r&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/&lt;span class=&quot;co&quot;&gt;Projects&lt;/span&gt;/bloom/adgear-admin/test/functional/ad_spots_controller_test.rb
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; =&amp;gt; &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;  require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;test_helper&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;  
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;3&lt;/span&gt;  &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;AdSpotsControllerTest&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActionController&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;TestCase&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;4&lt;/span&gt;  
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;    &lt;span class=&quot;i&quot;&gt;5&lt;/span&gt;    &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;setup&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/&lt;span class=&quot;co&quot;&gt;Projects&lt;/span&gt;/bloom/adgear-admin/test/functional/ad_spots_controller_test.rb:&lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;test_helper&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; (rdb:&lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;) 

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;From there, I was able to use &lt;code&gt;(C)ontinue&lt;/code&gt; to end up on my debugger statement.  Happy times ensued!&lt;/p&gt;
&lt;p&gt;For the curious, the &lt;code&gt;NoMethodError&lt;/code&gt; is because &lt;code&gt;#some_method&lt;/code&gt; is defined on &lt;code&gt;base&lt;/code&gt;, not on &lt;code&gt;self&lt;/code&gt;. &lt;code&gt;self&lt;/code&gt; in this context is the included module, while base is the place where we&amp;#8217;re including it into.&lt;/p&gt;</content>
    
    <category term="ruby" label="Ruby"/>
    
    <category term="debugging" label="Debugging"/>
      </entry>
  <entry>
    <title>Hobo Fields: Lots of ActiveRecord Goodness</title>
    <link href="http://blog.teksol.info/2010/03/03/hobo-fields-lots-of-activerecord-goodness.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-03-03:20100303153105</id>
    <updated>2010-03-03T15:31:05-05:00</updated>
    <content type="html">&lt;p&gt;What? You&amp;#8217;re still generating migrations manually? I don&amp;#8217;t anymore. I have tools which do it for me.&lt;/p&gt;
&lt;p&gt;Hobofields is a set of extensions to ActiveRecord that gives us lots of goodies, including the ability to generate migrations automatically.  The key to achieving this automatic property declaration is schema declaration in the model.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Post&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   fields &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;     title &lt;span class=&quot;sy&quot;&gt;:string&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;     published_at &lt;span class=&quot;sy&quot;&gt;:datetime&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     timestamps
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;7&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you ever used DataMapper, the idea is similar. Where Hobofields shines though is in reading your model and inferring many, many conventions:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Comment&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   belongs_to &lt;span class=&quot;sy&quot;&gt;:post&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Running the hobo_migration generator results in:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; $ script/generate hobo_migration
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; ---------- Up Migration ----------
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; add_column :comments, :post_id, :integer
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; add_index :comments, [:post_id]
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; ----------------------------------
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; ---------- Down Migration --------
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; remove_column :comments, :post_id
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; remove_index :comments, :name =&amp;gt; :index_comments_on_post_id rescue ActiveRecord::StatementInvalid
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; ----------------------------------

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you use acts_as_list, Hobofields also knows to add a &lt;code&gt;position&lt;/code&gt; column (or whatever name is needed):&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Comment&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   acts_as_list
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Running the migration generator, you get:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; $ script/generate  hobo_migration
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; ---------- Up Migration ----------
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; add_column :comments, :position, :integer
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; ----------------------------------
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; ---------- Down Migration --------
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; remove_column :comments, :position
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; ----------------------------------
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; What now: [g]enerate migration, generate and [m]igrate now or [c]ancel? 

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Another thing I love about Hobofields is rich type support. Need Textile?&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Post&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   fields &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;     body &lt;span class=&quot;sy&quot;&gt;:textile&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; content = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;lt;&amp;lt;EOTEXTILE&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;k&quot;&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; This is some Textile content
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; h1. Automatically formatted
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; To your:
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; * specifications,
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; * using standard tools&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; EOTEXTILE&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Post&lt;/span&gt;.new(&lt;span class=&quot;sy&quot;&gt;:body&lt;/span&gt; =&amp;gt; content).body.to_html
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#=&amp;gt; &amp;quot;&amp;lt;p&amp;gt;This is some Textile content&amp;lt;/p&amp;gt;\n&amp;lt;h1&amp;gt;Automatically formatted&amp;lt;/h1&amp;gt;\n&amp;lt;p&amp;gt;To your:&amp;lt;/p&amp;gt;\n&amp;lt;ul&amp;gt;&amp;lt;li&amp;gt;specifications,&amp;lt;/li&amp;gt;\n&amp;lt;li&amp;gt;using standard tools&amp;lt;/li&amp;gt;\n&amp;lt;/ul&amp;gt;\n&amp;quot;&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Or maybe you need an enumeration?&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Comment&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   fields &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;     status enum_string(&lt;span class=&quot;sy&quot;&gt;:spam&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:ham&lt;/span&gt;), &lt;span class=&quot;sy&quot;&gt;:default&lt;/span&gt; =&amp;gt; &lt;span class=&quot;sy&quot;&gt;:spam&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:required&lt;/span&gt; =&amp;gt; &lt;span class=&quot;pc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; $ script/generate hobo_migration
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; ---------- Up Migration ----------
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; add_column :comments, :status, :string, :default =&amp;gt; &amp;quot;ham&amp;quot;, :required =&amp;gt; true
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; ----------------------------------
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; ---------- Down Migration --------
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; remove_column :comments, :status
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; ----------------------------------

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &amp;gt;&amp;gt;&lt;span class=&quot;co&quot;&gt;Comment&lt;/span&gt;.new.status
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#=&amp;gt; &amp;quot;ham&amp;quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class=&quot;co&quot;&gt;Comment&lt;/span&gt;.create!(&lt;span class=&quot;sy&quot;&gt;:status&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;foo&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#=&amp;gt; ActiveRecord::RecordInvalid: Validation failed: Status must be one of spam, ham&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Admittedly, this last example could be better handled with Hobo&amp;#8217;s lifecycle (state machine) implementation, but still, it&amp;#8217;s nice that the validation already exists. Using &lt;code&gt;:null =&amp;gt; false&lt;/code&gt;, &lt;code&gt;:required =&amp;gt; true&lt;/code&gt; creates the appropriate validation as well:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Comment&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   fields &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;     author &lt;span class=&quot;sy&quot;&gt;:string&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:required&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;     email  &lt;span class=&quot;sy&quot;&gt;:string&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:required&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     status enum_string(&lt;span class=&quot;sy&quot;&gt;:spam&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:ham&lt;/span&gt;), &lt;span class=&quot;sy&quot;&gt;:required&lt;/span&gt; =&amp;gt; &lt;span class=&quot;pc&quot;&gt;true&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:default&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ham&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt;     timestamps
&lt;span class=&quot;no&quot;&gt;7&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And Hobofields is smart enough to know when no changes are required:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; script/generate hobo_migration
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Database&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;and&lt;/span&gt; models match -- nothing to change

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;But with this model definition, we now have new validation automatically:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class=&quot;co&quot;&gt;Comment&lt;/span&gt;.create!
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#=&amp;gt; ActiveRecord::RecordInvalid: Validation failed: Author can't be blank, Email can't be blank&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Oh wait, just realized that the email address should be an email address:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Comment&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   fields &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;     email &lt;span class=&quot;sy&quot;&gt;:email_address&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:required&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &amp;gt;&amp;gt; &lt;span class=&quot;co&quot;&gt;Comment&lt;/span&gt;.create!(&lt;span class=&quot;sy&quot;&gt;:email&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#=&amp;gt; ActiveRecord::RecordInvalid: Validation failed: Author can't be blank, Email is invalid&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I really enjoy Hobofields for all the goodies it brings to the table. I haven&amp;#8217;t even started on all the automatic scopes goodies, the lifecycle methods (which I haven&amp;#8217;t used yet), or scoped associations.&lt;/p&gt;
&lt;p&gt;Until we meet again tomorrow, I suggest you read &lt;a href=&quot;http://cookbook.hobocentral.net/manual/hobofields&quot;&gt;Hobofields Reference&lt;/a&gt;, as well as &lt;a href=&quot;http://cookbook.hobocentral.net/manual/hobofields/rich_types&quot;&gt;Hobofields rich types&lt;/a&gt; to get the full goods.&lt;/p&gt;</content>
    
    <category term="hobo" label="Hobo"/>
      </entry>
  <entry>
    <title>Using Hobo: Info From the Trenches</title>
    <link href="http://blog.teksol.info/2010/03/02/using-hobo-info-from-the-trenches.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-03-02:20100302114147</id>
    <updated>2010-03-02T11:41:47-05:00</updated>
    <content type="html">&lt;p&gt;Recently, a friend asked me to help him rewrite the Access database they use to track donations into something better. I offered to rewrite as a web app (surprise!), and chose to use &lt;a href=&quot;http://hobocentral.net/&quot;&gt;Hobo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I&amp;#8217;m not a designer, and it shows. My apps are horrible, and have bad choice of color. Red on blue? Check. Green on orange? Check. Wide margins and misaligned text base? Check. Name any design mistakes, and I can make it happen.&lt;/p&gt;
&lt;p&gt;I had seen Hobo used by another developer, and I was impressed with the ActiveRecord extensions. There was also the fact that Hobo has a pleasing design that could serve as an initial draft, until external help can be acquired.&lt;/p&gt;
&lt;p&gt;Hobo isn&amp;#8217;t really different from Rails: &lt;code&gt;hobo name_of_new_app&lt;/code&gt;. Done.&lt;/p&gt;
&lt;p&gt;Things I liked immediately: Declarative declaration of fields and validations, automatic generation of migrations, permissions built-in. You know, stuff that covers the 80%&lt;/p&gt;
&lt;p&gt;The thing I didn&amp;#8217;t like: DRYML. Don&amp;#8217;t get me wrong: DRYML is a very nice tool, and I didn&amp;#8217;t have any problems understanding the implicit context. All I had was the age-old problem: learning a new API. After 2-3 weeks (very part time), I was more comfortable with the different tags, and how to use them.&lt;/p&gt;
&lt;p&gt;I really enjoy writing this:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;ta&quot;&gt;&amp;lt;table-plus&lt;/span&gt; &lt;span class=&quot;an&quot;&gt;fields&lt;/span&gt;=&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this, city.name, toll_free_number, main_number&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;ta&quot;&gt;/&amp;gt;&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;and receiving this in return:&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;/2010/03/02/table-plus.png&quot;&gt;
&lt;img src=&quot;/2010/03/02/table-plus.png&quot; alt=&quot;&quot; width=&quot;460&quot;/&gt;
&lt;/a&gt;
&lt;a href=&quot;/2010/03/02/table-plus.png&quot;&gt;Click for larger view&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Right now, I&amp;#8217;ve had a very positive experience with Hobo. Having good documentation helps a lot, and Hobo hasn&amp;#8217;t disappointed: &lt;a href=&quot;http://cookbook.hobocentral.net/&quot;&gt;http://cookbook.hobocentral.net/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Over the next few days, I&amp;#8217;ll discuss Hobo in more details. Hope you follow along!&lt;/p&gt;</content>
    
    <category term="rails" label="Rails"/>
    
    <category term="hobo" label="Hobo"/>
      </entry>
  <entry>
    <title>Looking for Best Practice on Using Bundler With Multiple Ruby Implementations</title>
    <link href="http://blog.teksol.info/2010/02/24/looking-for-best-practice-on-using-bundler-with-multiple-ruby-implementations.html" type="text/html" />
    <id>tag:blog.teksol.info,2010-02-24:20100224185634</id>
    <updated>2010-02-24T18:56:34-05:00</updated>
    <content type="html">&lt;p&gt;At &lt;a href=&quot;http://bloomdigital.com/&quot;&gt;Bloom&lt;/a&gt;, we&amp;#8217;re looking into using &lt;a href=&quot;http://jruby.org/&quot;&gt;JRuby&lt;/a&gt; instead of &lt;a href=&quot;http://www.ruby-lang.org/&quot;&gt;MRI&lt;/a&gt;. To support the two implementations, some gems are different. Case in point: RMagick and the JSON gems:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;config/environment.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# More config.gem declarations elided&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;defined?&lt;/span&gt;(&lt;span class=&quot;co&quot;&gt;JRUBY_VERSION&lt;/span&gt;) &lt;span class=&quot;r&quot;&gt;then&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   config.gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;activerecord-jdbc-adapter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:version&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;0.9.2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,  &lt;span class=&quot;sy&quot;&gt;:lib&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;jdbc_adapter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;   config.gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;json-jruby&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,  &lt;span class=&quot;sy&quot;&gt;:version&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;1.2.0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,  &lt;span class=&quot;sy&quot;&gt;:lib&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   config.gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;rmagick4j&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,   &lt;span class=&quot;sy&quot;&gt;:version&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;0.3.6&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,  &lt;span class=&quot;sy&quot;&gt;:lib&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;RMagick&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;else&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   config.gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,        &lt;span class=&quot;sy&quot;&gt;:version&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;1.2.0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   config.gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;rmagick&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,     &lt;span class=&quot;sy&quot;&gt;:version&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;2.12.2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:lib&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;RMagick&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I created the following in Gemfile:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;Gemfile&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; source &lt;span class=&quot;sy&quot;&gt;:gemcutter&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;rails&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;= 2.3.4&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;pg&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;~&amp;gt; 0.8.0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; !&lt;span class=&quot;r&quot;&gt;defined?&lt;/span&gt;(&lt;span class=&quot;co&quot;&gt;JRUBY_VERSION&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;defined?&lt;/span&gt;(&lt;span class=&quot;co&quot;&gt;JRUBY_VERSION&lt;/span&gt;) &lt;span class=&quot;r&quot;&gt;then&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;   gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;activerecord-jdbc-adapter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&amp;gt;= 0.9.2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,  &lt;span class=&quot;sy&quot;&gt;:lib&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;jdbc_adapter&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;json-jruby&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&amp;gt;= 1.2.0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;   gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;rmagick4j&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&amp;gt;= 0.3.6&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;else&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;   gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&amp;gt;= 1.2.0&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;rmagick&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;&amp;gt;= 2.12.2&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Ruby's 1.9 stdlib replaced CSV with the implementation of 1.8's FasterCSV, but&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# the upgrade path is problematic: we have to change how we're loading the library.&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt; gem &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fastercsv&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;RUBY_VERSION&lt;/span&gt; =~ &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;^1&lt;/span&gt;&lt;span class=&quot;ch&quot;&gt;\.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice I target 3 versions of Ruby: MRI 1.8, MRI 1.9 and JRuby. If I &lt;tt&gt;bundle lock&lt;/tt&gt; from one Ruby implementation, then the other ones won&amp;#8217;t be able to use the locked bundle:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; [jruby-1.4.0]$ bundle unlock &amp;amp;&amp;amp; bundle lock
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; The bundle is now unlocked. The dependencies may be changed.
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; The bundle is now locked. Use `bundle show` to list the gems in the environment.
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; [jruby-1.4.0]$ rvm use 1.8.7
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; Now using ruby 1.8.7 p248
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; [ruby-1.8.7-p248]$ bundle check
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; Could not find gem 'rmagick4j (= 0.3.7, runtime)' in any of the sources.

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I&amp;#8217;m using Bundler in the context of a Rails 2.3.4 application, but we&amp;#8217;ll eventually move to Rails 3. Anybody has any experience using Bundler in this way?&lt;/p&gt;</content>
    
    <category term="bundler" label="Bundler"/>
      </entry>
  <entry>
    <title>Nestor: A Butler for Your Tests</title>
    <link href="http://blog.teksol.info/2009/11/18/nestor-a-butler-for-your-tests.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-11-18:20091118093847</id>
    <updated>2009-11-18T09:38:47-05:00</updated>
    <content type="html">&lt;p&gt;At yesterday&amp;#8217;s &lt;a href=&quot;http://www.montrealonrails.com/&quot;&gt;Montreal.rb&lt;/a&gt; I presented &lt;a href=&quot;http://github.com/francois/nestor&quot;&gt;Nestor&lt;/a&gt;, an autotest-like framework.  This is it&amp;#8217;s official release announcement.&lt;/p&gt;
&lt;p&gt;Nestor is different in that it uses an explicit state machine, namely &lt;a href=&quot;http://www.aaronpfeifer.com/&quot;&gt;Aaron Pfeifer&lt;/a&gt;&amp;#8216;s &lt;a href=&quot;http://github.com/pluginaweek/state_machine&quot;&gt;StateMachine&lt;/a&gt;.  Nestor also uses &lt;a href=&quot;http://mynyml.com/&quot;&gt;Martin Aumont&lt;/a&gt;&amp;#8217;s &lt;a href=&quot;http://github.com/mynyml/watchr&quot;&gt;Watchr&lt;/a&gt; to listen to filesystem events.  But the biggest difference is that the default Rails + Test::Unit is a forking test server.  Nestor will load the framework classes&amp;mdash;ActiveRecord, ActionController, ActionView, plugins and gems&amp;mdash;only once.  This saves a lot of time on the aggregate versus running &lt;tt&gt;rake&lt;/tt&gt; everytime.&lt;/p&gt;
&lt;p style=&quot;text-align:center&quot;&gt;&lt;a href=&quot;/2009/11/18/nestor-state-diagram.png&quot;&gt;&lt;img style=&quot;margin:1em auto;padding:2px;border:1px silver solid&quot; src=&quot;/2009/11/18/nestor-state-diagram-extract.png&quot; alt=&quot;Nestor's state diagram with events denoting success or failure of a run, and states such as green, running_all or run_focused_pending.&quot;/&gt;&lt;/a&gt;&lt;br/&gt;&lt;a class=&quot;caption&quot; href=&quot;/2009/11/18/nestor-state-diagram.png&quot;&gt;Click for larger version&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This release of Nestor is 0.2 quality: it&amp;#8217;s not ready for large projects.  It only supports Rails + Test::Unit, probably doesn&amp;#8217;t run on 1.9 or JRuby, but it&amp;#8217;s a solid foundation for going further.  In the coming days, I will blog on the internals of Nestor and how StateMachine allowed me to add plugins with very little effort.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; $ gem install nestor
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; $ cd railsapp
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; $ # edit config/environments/test.rb to set cache_classes to false
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; $ nestor

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I already have a plugin that enables Growl notifications.  Install and use:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; $ gem install nestor_growl
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; $ cd railsapp
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; $ nestor start --require nestor/growl

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;tt&gt;&amp;#8212;require&lt;/tt&gt; option is where plugins are loaded.  This is an Array of files Nestor will require on startup.&lt;/p&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;p&gt;You must set &lt;tt&gt;cache_classes&lt;/tt&gt; to &lt;tt&gt;false&lt;/tt&gt; in test mode for now.  This is a limitation of how Rails boots.  With &lt;tt&gt;cache_classes&lt;/tt&gt; set to &lt;tt&gt;true&lt;/tt&gt;, Rails will load the controllers and models when it boots.  Since this happens before forking, the code under test would never get reloaded.  Did I say it was 0.2 quality?&lt;/p&gt;</content>
    
    <category term="testing" label="Testing"/>
    
    <category term="autotest" label="Autotest"/>
      </entry>
  <entry>
    <title>Piston 2.0.8: minor bugfix release</title>
    <link href="http://blog.teksol.info/2010/01/12/piston-2.0.8-minor-bugfix-release.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-11-18:20091118093847</id>
    <updated>2009-11-18T09:38:47-05:00</updated>
    <content type="html">&lt;p&gt;Piston 2.0.8 is here.  This is a minor bugfix release:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;piston status with no path would not check any status.  Thanks to &lt;a href=&quot;http://pragmatig.wordpress.com/&quot;&gt;Michael Grosser&lt;/a&gt; for the heads up;&lt;/li&gt;
	&lt;li&gt;The ActiveSupport gem deprecated require &amp;#8220;activesupport&amp;#8221; in favor of &amp;#8220;active_support&amp;#8221;. Thanks for Michael for reporting this as well;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;http://github.com/scottj97&quot;&gt;Scott Johnson&lt;/a&gt; reported and fixed a problem where a git mv would fail because a parent directory was missing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thanks to all contributors!&lt;/p&gt;
&lt;h2&gt;Installing&lt;/h2&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; gem install piston

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;</content>
    
    <category term="piston" label="Piston"/>
      </entry>
  <entry>
    <title>Entering the Enterprise Space: Moving to Bloom Digital Platforms</title>
    <link href="http://blog.teksol.info/2009/09/29/entering-the-enterprise-space-moving-to-bloom-digital-platforms.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-09-29:20090929103131</id>
    <updated>2009-09-29T10:31:31-04:00</updated>
    <content type="html">&lt;p&gt;After a few years as a consultant, I received an offer from &lt;a href=&quot;http://twitter.com/bosko&quot;&gt;Bosko Milekic&lt;/a&gt; from &lt;a href=&quot;http://bloomdigital.com/&quot;&gt;Bloom Digital Platforms Inc&lt;/a&gt;.  Starting on October 5th, I will be a permanent Bloom employee.  &lt;a href=&quot;http://macournoyer.com/blog/&quot;&gt;Marc-André&lt;/a&gt; and &lt;a href=&quot;http://garyharan.com/&quot;&gt;Gary&lt;/a&gt; are moving outside the enterprise, while I&amp;#8217;m moving inside.&lt;/p&gt;
&lt;p&gt;At Bloom, I will be responsible for the &lt;a href=&quot;http://www.adgear.com/&quot;&gt;AdGear&lt;/a&gt; API, enabling applications to easily connect to their platform for ad serving and management.  We even have an open source Ruby client available on GitHub as &lt;a href=&quot;http://github.com/bloom/ad_gear_client&quot;&gt;ad_gear_client&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This will be a welcome change of pace for me.&lt;/p&gt;
&lt;p&gt;See you at the next &lt;a href=&quot;http://www.montrealonrails.com/&quot;&gt;Montreal.rb&lt;/a&gt;, at my new office!&lt;/p&gt;</content>
    
    <category term="employment" label="Employment"/>
      </entry>
  <entry>
    <title>RestClient + Multipart Form Data: See My Branch</title>
    <link href="http://blog.teksol.info/2009/08/12/restclient-+-multipart-form-data-see-my-branch.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-08-12:20090812110538</id>
    <updated>2009-08-12T11:05:38-04:00</updated>
    <content type="html">&lt;p&gt;I just merged Rick Olson&amp;#8217;s (&lt;a href=&quot;http://techno-weenie.net/&quot;&gt;technoweenie&lt;/a&gt;) rest-client &lt;a href=&quot;http://github.com/technoweenie/rest-client/tree/multipart_streaming&quot;&gt;multipart_streaming&lt;/a&gt; branch with RestClient 1.0.4.  You can see the results at &lt;a href=&quot;http://github.com/francois/rest-client&quot;&gt;francois/rest-client&lt;/a&gt;.  I sent a pull request to &lt;a href=&quot;http://adam.blog.heroku.com/&quot;&gt;Adam&lt;/a&gt;, so if he pulls it in, everyone will benefit from this code.&lt;/p&gt;</content>
    
    <category term="restclient" label="Restclient"/>
      </entry>
  <entry>
    <title>Using ActiveResource With Digest Authentication: Branch in Progress</title>
    <link href="http://blog.teksol.info/2009/08/04/using-activeresource-with-digest-authentication-branch-in-progress.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-08-04:20090804160411</id>
    <updated>2009-08-04T16:04:11-04:00</updated>
    <content type="html">&lt;p&gt;I have a work-in-progress branch for using Digest authentication with ActiveResource.&lt;/p&gt;
&lt;p&gt;So, how did I do it?  It wasn&amp;#8217;t too hard actually.  When I spiked, I changed &lt;a href=&quot;http://github.com/francois/rails/blob/ar_digest/activeresource/lib/active_resource/connection.rb#L109&quot;&gt;&lt;code&gt;ActiveResource::Connection#request&lt;/code&gt;&lt;/a&gt; to handle authentication itself.  I ended up with a big mess: a new rescue clause, 10 lines of code to calculate the digest and so on.  But I knew it would work.  So, I &lt;code&gt;git checkout .&lt;/code&gt; and started with tests, as it should.&lt;/p&gt;
&lt;p&gt;The way ActiveResource is built, if a username / password is sent in, ActiveResource will send those automatically in an Authorization header, using the &lt;a href=&quot;http://en.wikipedia.org/wiki/Basic_access_authentication&quot;&gt;Basic&lt;/a&gt; authentication method.  I need a way to turn this off.  Thus grew &lt;a href=&quot;http://github.com/francois/rails/commit/c33a0a015af50aa3cbf7cc4071e24ec91cfbedb9&quot;&gt;#use_basic_authentication=&lt;/a&gt; and &lt;code&gt;#use_digest_authentication=&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next up, actually being able to calculate the Digest.  A quick search turned up code by &lt;a href=&quot;http://blog.segment7.net/&quot;&gt;Eric Hodel&lt;/a&gt; in the form of a Ruby module: &lt;a href=&quot;http://segment7.net/projects/ruby/snippets/digest_auth.rb&quot;&gt;An implementation of HTTP Digest Authentication in Ruby&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After a bit of cleanup and rewriting, I have a branch of ActiveResource that&amp;#8217;s ready to be commented on. Please see &lt;a href=&quot;http://github.com/francois/rails/tree/ar_digest&quot;&gt;francois/ar_digest&lt;/a&gt; and leave comments there.&lt;/p&gt;
&lt;p&gt;An example of using Digest would be:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;activeresource&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;pp&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;ActiveResource&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;.logger = &lt;span class=&quot;co&quot;&gt;Logger&lt;/span&gt;.new(&lt;span class=&quot;co&quot;&gt;STDERR&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;ActiveResource&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;.site     = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;http://adgear.local/api&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;ActiveResource&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;.user     = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;francois&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;ActiveResource&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;.password = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;my-funny-new-password-which-you've-never-seen-before&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;ActiveResource&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;.timeout  = &lt;span class=&quot;i&quot;&gt;30&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Don't attempt Basic authentication, but be sure to use Digest&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;ActiveResource&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;.use_basic_authentication  = &lt;span class=&quot;pc&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;ActiveResource&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;.use_digest_authentication = &lt;span class=&quot;pc&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Site&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveResource&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt; pp sites = &lt;span class=&quot;co&quot;&gt;Site&lt;/span&gt;.find(&lt;span class=&quot;sy&quot;&gt;:all&lt;/span&gt;)

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This work was sponsored by &lt;a href=&quot;http://www.bloomdigital.com/&quot;&gt;Bloom Digital Platforms&lt;/a&gt;, as part of my work on their AdGear API.&lt;/p&gt;</content>
    
    <category term="active_resource" label="Active resource"/>
    
    <category term="activeresource" label="Activeresource"/>
    
    <category term="active-resource" label="Active-resource"/>
      </entry>
  <entry>
    <title>Piston 2.0.6: minor bug fixes</title>
    <link href="http://blog.teksol.info/2009/08/01/piston-2.0.6-minor-bug-fixes.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-08-01:20090801132114</id>
    <updated>2009-08-01T13:21:14-04:00</updated>
    <content type="html">&lt;p&gt;I just pulled in a couple of patches from outside contributors.  These are all minor bug fixes, but are important:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;http://github.com/chrisgibson&quot;&gt;Chris Gibson&lt;/a&gt;: When forcing the repository type, Piston would break because it called #downcase on a Symbol. &lt;a href=&quot;http://github.com/francois/piston/commit/1bcc16bf874afca88e061ea35cd049b7a1d38ab4&quot;&gt;1bcc16bf8&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;Terry Heath:  &lt;a href=&quot;http://subversion.tigris.org/&quot;&gt;Subversion&lt;/a&gt;&amp;#8216;s &lt;tt&gt;&amp;#8212;non-interactive&lt;/tt&gt; would prevent OS X&amp;#8217;s keychain from kicking in. &lt;a href=&quot;http://github.com/francois/piston/commit/93d9a9578f0947c62a70b52f86d8d4033cfb56ad&quot;&gt;93d9a957&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;Florian Aßmann: In certain cases, the revision would be a String, and other times it would be an Integer.  Normalize before comparing. &lt;a href=&quot;http://github.com/francois/piston/commit/40c0bc4e3c63aa398d319ec08846538705950e3d&quot;&gt;40c0bc4e&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All users are advised to upgrade:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; sudo gem install piston

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;</content>
    
    <category term="piston" label="Piston"/>
      </entry>
  <entry>
    <title>Clear and Concise Explanation of RESTful Resources</title>
    <link href="http://blog.teksol.info/2009/07/22/clear-and-concise-explanation-of-restful-resources.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-07-22:20090722143023</id>
    <updated>2009-07-22T14:30:23-04:00</updated>
    <content type="html">&lt;p&gt;I just found &lt;a href=&quot;http://www.theamazingrando.com/blog/?p=107&quot;&gt;Your Web Service Might Not Be RESTful If&amp;hellip;&lt;/a&gt; by &lt;a href=&quot;http://www.theamazingrando.com/blog/&quot;&gt;Paul Sadauskas&lt;/a&gt;.  This article is a very clear and concise explanation of how to do real RESTful services.&lt;/p&gt;
&lt;p&gt;His arguments are:&lt;/p&gt;
&lt;p&gt;1. Present a single top-level resource;&lt;br /&gt;
2. All resources should be accessible by followling links from the top-level resource;&lt;br /&gt;
3. Don&amp;#8217;t put API tokens in the URL or headers (use representations instead).&lt;/p&gt;
&lt;p&gt;A very enjoyable read, and something we should all be following.&lt;/p&gt;</content>
    
    <category term="rest" label="Rest"/>
      </entry>
  <entry>
    <title>Piston 2.0.4: minor fixes</title>
    <link href="http://blog.teksol.info/2009/07/20/piston-2-dot-0.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-07-20:20090720111811</id>
    <updated>2009-07-20T11:18:11-04:00</updated>
    <content type="html">&lt;p&gt;I just release Piston 2.0.4.  This is a minor fix to enable Piston to work with more Git versions.&lt;/p&gt;
&lt;p&gt;Git 1.6.0.1 accepts checking out a new branch without specifying the remote&amp;#8217;s name:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; git checkout -b my-&lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;-&lt;span class=&quot;i&quot;&gt;3&lt;/span&gt;-stable &lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;-&lt;span class=&quot;i&quot;&gt;3&lt;/span&gt;-stable
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# implies origin/2-3-stable&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Versions prior to that expect the full name of the remote branch:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; git checkout -b my-&lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;-&lt;span class=&quot;i&quot;&gt;3&lt;/span&gt;-stable origin/&lt;span class=&quot;i&quot;&gt;2&lt;/span&gt;-&lt;span class=&quot;i&quot;&gt;3&lt;/span&gt;-stable

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Installation is the usual incantation:  sudo gem install piston&lt;/p&gt;
&lt;h2&gt;What is Piston?&lt;/h2&gt;
&lt;p&gt;Piston is a utility that eases vendor branch management. This is similar to &lt;tt&gt;svn:externals&lt;/tt&gt;, except you have a local copy of the files, which you can modify at will.  As long as the changes are mergeable, you should have no problems.&lt;/p&gt;</content>
    
    <category term="piston" label="Piston"/>
      </entry>
  <entry>
    <title>MongoDB or Redis?  The Choice Is Yours</title>
    <link href="http://blog.teksol.info/2009/07/15/mongodb-or-redis-the-choice-is-yours.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-07-15:20090715093554</id>
    <updated>2009-07-15T09:35:54-04:00</updated>
    <content type="html">&lt;p&gt;In the past couple of weeks, I had the opportunity to look at &lt;a href=&quot;http://www.mongodb.org/&quot;&gt;MongoDB&lt;/a&gt; and &lt;a href=&quot;http://code.google.com/p/redis/&quot;&gt;Redis&lt;/a&gt; in more details.  Both are fast, but serve different needs.  Here&amp;#8217;s my take on them both.&lt;/p&gt;
&lt;h2&gt;MongoDB&lt;/h2&gt;
&lt;p&gt;MongoDB is a &lt;a href=&quot;http://en.wikipedia.org/wiki/Document-oriented_database&quot;&gt;document oriented database&lt;/a&gt;.  MongoDB stores structured data, as documents:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; {
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;: &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;François Beausoleil&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;   &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;: &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;francois@teksol.info&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;   &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;year-of-birth&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;: &lt;span class=&quot;i&quot;&gt;1973&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;: [&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;friendly&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ruby&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;coder&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;father&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;]
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt; }

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Since MongoDB stores structured data, it has a richer query language:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Find&lt;/span&gt; people that are fathers &lt;span class=&quot;co&quot;&gt;OR&lt;/span&gt; coders
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; db.people.find( { &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; : { &lt;span class=&quot;gv&quot;&gt;$in&lt;/span&gt; : [&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;father&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;coder&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;] } } );
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Find&lt;/span&gt; people that are fathers &lt;span class=&quot;co&quot;&gt;AND&lt;/span&gt; coders
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; db.people.find( { &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; : { &lt;span class=&quot;gv&quot;&gt;$all&lt;/span&gt; : [&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;father&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;coder&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;] } } );
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Index&lt;/span&gt; the email member of documents
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; db.people.ensureIndex({&lt;span class=&quot;ke&quot;&gt;email&lt;/span&gt;: &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;});
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Query&lt;/span&gt; using the index
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; db.people.findOne({&lt;span class=&quot;ke&quot;&gt;email&lt;/span&gt;: &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;francois@teksol.info&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;})

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Redis&lt;/h2&gt;
&lt;p&gt;Redis is a key-value store that is strongly typed.  Redis has three types of values: strings, sets and lists.  Out of the box, you cannot store structured data in Redis.  You have to build the index manually, storing yet other keys.&lt;/p&gt;
&lt;p&gt;Redis is conceptually simple.  All operations are either stores or fetches:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;PUT&lt;/span&gt; key value
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;GET&lt;/span&gt; key

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can think of Redis as a persistent Hash or Dictionary.  The fact that Redis is strongly typed makes some operations very interesting.&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Store other data as serialized JSON object, but we won't be able to query the value itself&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;people:francois&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;{'name': 'François Beausoleil', 'email': 'francois@teksol.info', 'year-of-birth': 1973, 'tags': ['friendly', 'ruby', 'coder', 'father']}&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Build ourselves an index on the email value&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;SET&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;email:francois@teksol.info&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;francois&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Build another index for tags&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;SADD&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;tags:friendly&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;francois&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;SADD&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;tags:ruby&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;     &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;francois&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;SADD&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;tags:coder&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;    &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;francois&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;SADD&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;tags:father&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;   &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;francois&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# During authentication, we would find the person using the email address&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;email:francois@teksol.info&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Get the rest of the data&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;people:francois&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Find the keys that have both father AND coder as tags&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;SINTER&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;tags:father&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;tags:coder&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;So, which one should you choose?  The short answer is &amp;#8220;it depends&amp;#8221;.&lt;/p&gt;
&lt;p&gt;The longer answer is to use the best tool for the job.  Storing documents is different than storing simple key values.  Of course, both can be used for the same job, but Redis will be easier if you need a simple Hash/Dictionary.  Use MongoDB for storing structured data.&lt;/p&gt;</content>
    
    <category term="redis" label="Redis"/>
    
    <category term="mongodb" label="Mongodb"/>
    
    <category term="key-value-store" label="Key-value-store"/>
      </entry>
  <entry>
    <title>Cliaws 1.5.0: cliec2 commands implemented</title>
    <link href="http://blog.teksol.info/2009/07/07/cliaws-1-dot-5-dot-0: cliec2 commands implemented.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-07-07:20090707204844</id>
    <updated>2009-07-07T20:48:44-04:00</updated>
    <content type="html">&lt;p&gt;Please grab cliaws from Rubyforge:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; gem install cliaws

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h2&gt;What is Cliaws?&lt;/h2&gt;
&lt;p&gt;Cliaws is a replacement for the Amazon EC2 API tools, but uses Ruby, and thus does not suffer from a long boot time.  Cliaws is also easier to setup:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;=&lt;span class=&quot;c&quot;&gt;# Your Access ID&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;=&lt;span class=&quot;c&quot;&gt;# Your secret ID&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Setup is done, enjoy!&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; clis3 list &lt;span class=&quot;co&quot;&gt;YOUR_BUCKET&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; cliec2 launch &lt;span class=&quot;co&quot;&gt;AMI&lt;/span&gt; --keypair &lt;span class=&quot;co&quot;&gt;KEYNAME&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Please view the &lt;a href=&quot;http://github.com/francois/cliaws&quot;&gt;README&lt;/a&gt; for some details.  The implementation is still the best place to get information about the options you can pass.&lt;/p&gt;</content>
    
    <category term="aws" label="Aws"/>
      </entry>
  <entry>
    <title>We've Been Doing TDD for Centuries, You Know..</title>
    <link href="http://blog.teksol.info/2009/06/20/weve-been-doing-tdd-for-centuries-you-know-dot.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-06-20:20090620215529</id>
    <updated>2009-06-20T21:55:29-04:00</updated>
    <content type="html">&lt;p&gt;Today, I was working on my lawn.  Spreading new soil to sow new grass.  I was looking at the ground, and I was pretty sure drainage wasn&amp;#8217;t going to be good.  I decided to make a test: I got out the garden hose and let it flow.  Sure enough, water was pooling instead of draining.  Back to the drawing board (or at least, a couple more rakes to spread things around).&lt;/p&gt;
&lt;p&gt;As I was working the soil, a thought hit me:  I&amp;#8217;d say nearly every profession has been doing test driven design (not development) since pretty much the dawn of time:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;the toolsmith in the ancient tribes would hit the stone, see if it did work or not, and repeat as appropriate;&lt;/li&gt;
	&lt;li&gt;the chef tastes his food before letting other people eat it;&lt;/li&gt;
	&lt;li&gt;the plumber tests the pipes before letting the water flow in;&lt;/li&gt;
	&lt;li&gt;the soldier tests his equipment before setting off on the battlefield;&lt;/li&gt;
	&lt;li&gt;naval engineers are &lt;a href=&quot;http://www.foxnews.com/story/0,2933,526833,00.html&quot;&gt;simulation testing their latest aircraft carrier&lt;/a&gt; in an effort to ensure nothing is out of place;&lt;/li&gt;
	&lt;li&gt;certain programmers write automated tests for their code, to ensure it works as designed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If nearly every profession has been doing it for thousands of years, why aren&amp;#8217;t you doing it today?&lt;/p&gt;</content>
    
    <category term="tdd" label="Tdd"/>
    
    <category term="test-driven-design" label="Test-driven-design"/>
      </entry>
  <entry>
    <title>Piston on Windows: Some Pointers</title>
    <link href="http://blog.teksol.info/2009/06/07/piston-on-windows-some-pointers.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-06-07:20090607073420</id>
    <updated>2009-06-07T07:34:20-04:00</updated>
    <content type="html">&lt;p&gt;If you need to run Piston on Windows, &lt;a href=&quot;http://boxcycle.com&quot;&gt;BoxCycle&lt;/a&gt; wrote some information about it at: &lt;a href=&quot;http://blog.boxcycle.com/2009/05/plugin-updates-svn-piston-windows-git/&quot;&gt;Rails Plugin Updates, SVN, and Piston 2.0.2 on Windows&lt;/a&gt;&lt;/p&gt;</content>
    
    <category term="piston" label="Piston"/>
    
    <category term="windows" label="Windows"/>
      </entry>
  <entry>
    <title>How to Parse HTML Documents Under JRuby Using XOM, TagSoup and Nux</title>
    <link href="http://blog.teksol.info/2009/05/01/how-to-parse-html-documents-under-jruby-using-xom-tagsoup-and-nux.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-05-01:20090501160316</id>
    <updated>2009-05-01T16:03:16-04:00</updated>
    <content type="html">&lt;p&gt;It&amp;#8217;s not that hard, but it still took me 2 hours to do it.  I had a couple of false starts, and I pored over documentation for a while until I hit upon the excellent &lt;a href=&quot;http://acs.lbl.gov/nux/&quot;&gt;Nux&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;I won&amp;#8217;t let you go through the same failures I had.  Here&amp;#8217;s the code:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Demonstrates how to parse a local HTML document using XOM,&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# TagSoup and Nux, under JRuby.&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# http://www.xom.nu/&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# http://home.ccil.org/~cowan/XML/tagsoup/&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# http://acs.lbl.gov/nux/&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; include &lt;span class=&quot;co&quot;&gt;Java&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; mydir = &lt;span class=&quot;co&quot;&gt;File&lt;/span&gt;.expand_path(&lt;span class=&quot;co&quot;&gt;File&lt;/span&gt;.dirname(&lt;span class=&quot;pc&quot;&gt;__FILE__&lt;/span&gt;))
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# This is how you require libraries without touching your&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# CLASSPATH from JRuby. I put the required files in vendor/.&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Nux includes it's dependencies (XOM and saxon), so I didn't&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# have any other libraries to add.&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt; require &lt;span class=&quot;co&quot;&gt;File&lt;/span&gt;.join(mydir, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;vendor&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;tagsoup.jar&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;%w(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;nux.jar saxon8.jar xom.jar&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;)&lt;/span&gt;&lt;/span&gt;.each &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |filename|
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt;   require &lt;span class=&quot;co&quot;&gt;File&lt;/span&gt;.join(mydir, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;vendor&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;nux&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;lib&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, filename)
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt; import &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;org.ccil.cowan.tagsoup.Parser&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt; import &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;nu.xom.Builder&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;22&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;23&lt;/span&gt; builder = &lt;span class=&quot;co&quot;&gt;Builder&lt;/span&gt;.new(&lt;span class=&quot;co&quot;&gt;Parser&lt;/span&gt;.new)
&lt;span class=&quot;no&quot;&gt;24&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;25&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# XOM's Builder expects a full URL, so tell it where to find the&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;26&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# document.&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;27&lt;/span&gt; doc = builder.build(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;file://&lt;/span&gt;&lt;span class=&quot;il&quot;&gt;&lt;span class=&quot;idl&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;co&quot;&gt;File&lt;/span&gt;.expand_path(&lt;span class=&quot;co&quot;&gt;File&lt;/span&gt;.join(mydir, &lt;span class=&quot;pc&quot;&gt;ARGV&lt;/span&gt;[&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;]))&lt;span class=&quot;idl&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;28&lt;/span&gt; puts doc.toXML

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;h3&gt;Extra!  Add XPath querying&lt;/h3&gt;
&lt;p&gt;Continuing from above, you can add XPath querying:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; import &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;nux.xom.xquery.XQueryUtil&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# Must use '*:p'.  '*' stands for any/default namespace.&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; results = &lt;span class=&quot;co&quot;&gt;XQueryUtil&lt;/span&gt;.xquery(doc, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;//*:p&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; p results.size
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt; results.size.times &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |index|
&lt;span class=&quot;no&quot;&gt;7&lt;/span&gt;   puts results.get(index).toXML
&lt;span class=&quot;no&quot;&gt;8&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt; 

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Why am I going through these motions?  Because I wanted to use my 20% for fun.  Besides, I need to process large quantities of HTML as quickly as possible for a cool project I&amp;#8217;m working on, and JRuby seems to be the &lt;a href=&quot;http://twitter.com/fbeausoleil/status/1670082272&quot;&gt;fastest implementation&lt;/a&gt;, according to my unscientific benchmark.&lt;/p&gt;
&lt;p&gt;But the real reason was that both &lt;a href=&quot;http://nokogiri.rubyforge.org/nokogiri/&quot;&gt;Nokogiri&lt;/a&gt; and &lt;a href=&quot;http://wiki.github.com/why/hpricot&quot;&gt;Hpricot&lt;/a&gt; wouldn&amp;#8217;t load/run under JRuby 1.2.0.&lt;/p&gt;
&lt;p&gt;Actually, let me rephrase that:  Nokogiri did load, but crashed while requiring the library:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; jruby -w test.rb data.html 
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/&lt;span class=&quot;co&quot;&gt;Library&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;Java&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;JRuby&lt;/span&gt;/jruby-&lt;span class=&quot;fl&quot;&gt;1.2&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;/lib/ruby/gems/&lt;span class=&quot;fl&quot;&gt;1.8&lt;/span&gt;/gems/nokogiri-&lt;span class=&quot;fl&quot;&gt;1.2&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;3&lt;/span&gt;-java/lib/nokogiri/xml/node.rb:&lt;span class=&quot;i&quot;&gt;180&lt;/span&gt;: undefined method &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;next_sibling' for class &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;co&quot;&gt;Nokogiri&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;XML&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt; (NameError)
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;         from /Users/francois/Library/Java/JRuby/jruby-1.2.0/lib/ruby/gems/1.8/gems/nokogiri-1.2.3-java/lib/nokogiri/xml/node.rb:31:in `require&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;         from &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/&lt;span class=&quot;co&quot;&gt;Library&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;Java&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;JRuby&lt;/span&gt;/current/lib/ruby/site_ruby/&lt;span class=&quot;fl&quot;&gt;1.8&lt;/span&gt;/rubygems/custom_require.rb:&lt;span class=&quot;i&quot;&gt;31&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;require'
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;         from /Users/francois/Library/Java/JRuby/jruby-1.2.0/lib/ruby/gems/1.8/gems/nokogiri-1.2.3-java/lib/nokogiri/xml.rb:3
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;         from /Users/francois/Library/Java/JRuby/jruby-1.2.0/lib/ruby/gems/1.8/gems/nokogiri-1.2.3-java/lib/nokogiri/xml.rb:31:in &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;require&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;         from /Users/francois/Library/Java/JRuby/current/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:31:in `require&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;         from &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/&lt;span class=&quot;co&quot;&gt;Library&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;Java&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;JRuby&lt;/span&gt;/jruby-&lt;span class=&quot;fl&quot;&gt;1.2&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;/lib/ruby/gems/&lt;span class=&quot;fl&quot;&gt;1.8&lt;/span&gt;/gems/nokogiri-&lt;span class=&quot;fl&quot;&gt;1.2&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;3&lt;/span&gt;-java/lib/nokogiri.rb:&lt;span class=&quot;i&quot;&gt;10&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;         from &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/&lt;span class=&quot;co&quot;&gt;Library&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;Java&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;JRuby&lt;/span&gt;/jruby-&lt;span class=&quot;fl&quot;&gt;1.2&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;/lib/ruby/gems/&lt;span class=&quot;fl&quot;&gt;1.8&lt;/span&gt;/gems/nokogiri-&lt;span class=&quot;fl&quot;&gt;1.2&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;3&lt;/span&gt;-java/lib/nokogiri.rb:&lt;span class=&quot;i&quot;&gt;36&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;require'
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;         from /Users/francois/Library/Java/JRuby/current/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:36:in &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;/span&gt;require&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;         from test.rb:2
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I have reported this &lt;a href=&quot;http://github.com/tenderlove/nokogiri/issues#issue/31&quot;&gt;bug to the proper authorities&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Hpricot is another matter entirely.  When I tried to use it earlier, I hit a roadblock because JRuby couldn&amp;#8217;t install the native extensions.  I tried again just now, and if you specify the version to be &lt;code&gt;~&amp;gt; 0.6.1&lt;/code&gt;, it works.  Specify any other version, and you&amp;#8217;re a sitting duck:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; jruby -&lt;span class=&quot;co&quot;&gt;S&lt;/span&gt; gem install -v &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;~&amp;gt; 0.6&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt; hpricot
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Building&lt;/span&gt; native extensions.  &lt;span class=&quot;co&quot;&gt;This&lt;/span&gt; could take a &lt;span class=&quot;r&quot;&gt;while&lt;/span&gt;...
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; &lt;span class=&quot;ke&quot;&gt;ERROR&lt;/span&gt;:  &lt;span class=&quot;co&quot;&gt;Error&lt;/span&gt; installing hpricot:
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;         &lt;span class=&quot;ke&quot;&gt;ERROR&lt;/span&gt;: &lt;span class=&quot;co&quot;&gt;Failed&lt;/span&gt; to build gem native extension.
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; /&lt;span class=&quot;co&quot;&gt;Users&lt;/span&gt;/francois/&lt;span class=&quot;co&quot;&gt;Library&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;Java&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;JRuby&lt;/span&gt;/current/bin/jruby extconf.rb install -v ~&amp;gt; &lt;span class=&quot;fl&quot;&gt;0.6&lt;/span&gt; hpricot
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Gem&lt;/span&gt; files will remain installed &lt;span class=&quot;r&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/&lt;span class=&quot;co&quot;&gt;Library&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;Java&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;JRuby&lt;/span&gt;/jruby-&lt;span class=&quot;fl&quot;&gt;1.2&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;/lib/ruby/gems/&lt;span class=&quot;fl&quot;&gt;1.8&lt;/span&gt;/gems/hpricot-&lt;span class=&quot;fl&quot;&gt;0.8&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;for&lt;/span&gt; inspection.
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Results&lt;/span&gt; logged to &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Users&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;/span&gt;francois/&lt;span class=&quot;co&quot;&gt;Library&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;Java&lt;/span&gt;/&lt;span class=&quot;co&quot;&gt;JRuby&lt;/span&gt;/jruby-&lt;span class=&quot;fl&quot;&gt;1.2&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;/lib/ruby/gems/&lt;span class=&quot;fl&quot;&gt;1.8&lt;/span&gt;/gems/hpricot-&lt;span class=&quot;fl&quot;&gt;0.8&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;1&lt;/span&gt;/ext/hpricot_scan/gem_make.out
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;er&quot;&gt;$&lt;/span&gt; jruby -&lt;span class=&quot;co&quot;&gt;S&lt;/span&gt; gem install -v &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;~&amp;gt; 0.6.1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;/span&gt; hpricot
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Successfully&lt;/span&gt; installed hpricot-&lt;span class=&quot;fl&quot;&gt;0.6&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;164&lt;/span&gt;-java
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;i&quot;&gt;1&lt;/span&gt; gem installed
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Installing&lt;/span&gt; ri documentation &lt;span class=&quot;r&quot;&gt;for&lt;/span&gt; hpricot-&lt;span class=&quot;fl&quot;&gt;0.6&lt;/span&gt;.&lt;span class=&quot;i&quot;&gt;164&lt;/span&gt;-java...

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;</content>
    
    <category term="jruby" label="Jruby"/>
      </entry>
  <entry>
    <title>Keeping Yourself DRY in Shoulda Tests</title>
    <link href="http://blog.teksol.info/2009/04/29/keeping-yourself-dry-in-shoulda-tests.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-04-29:20090429220242</id>
    <updated>2009-04-29T22:02:42-04:00</updated>
    <content type="html">&lt;p&gt;When I&amp;#8217;m testing admin controllers, I often have tests that follow this form:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;test/functional/admin/orders_controller_test.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;OrdersControllerTest&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActionController&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;TestCase&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   logged_in_as &lt;span class=&quot;sy&quot;&gt;:active_user&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;     context &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;on GET to :index&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;       setup &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;         get &lt;span class=&quot;sy&quot;&gt;:index&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;       &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;       should_deny_access
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   not_logged_in &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;     context &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;on GET to :index&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;       setup &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt;         get &lt;span class=&quot;sy&quot;&gt;:index&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;16&lt;/span&gt;       &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;17&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;18&lt;/span&gt;       should_deny_access
&lt;span class=&quot;no&quot;&gt;19&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;20&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;21&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Well, this is all Ruby, right?  And Ruby has wonderful blocks, and blocks can be passed around&amp;#8230;&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;test/functional/admin/orders_controller_test.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;OrdersControllerTest&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActionController&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;TestCase&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   deny_access_tests = lambda &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;     context &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;on GET to :index&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;       setup &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;         get &lt;span class=&quot;sy&quot;&gt;:index&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;       &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;       should_deny_access
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   logged_in_as &lt;span class=&quot;sy&quot;&gt;:active_user&lt;/span&gt;, &amp;amp;deny_access_tests
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;   not_logged_in, &amp;amp;deny_access_tests
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is valid for any block of code that you want to test again and again:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;test/functional/admin/orders_controller_test.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;OrdersControllerTest&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActionController&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;TestCase&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   successful_index_render = lambda &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;     should_respond_with &lt;span class=&quot;sy&quot;&gt;:success&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;     should_render_template &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     should_assign_to &lt;span class=&quot;sy&quot;&gt;:orders&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   logged_in_as &lt;span class=&quot;sy&quot;&gt;:admin&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     context &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &amp;amp;successful_index_render
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   logged_in_as &lt;span class=&quot;sy&quot;&gt;:sub_admin&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;     context &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &amp;amp;successful_index_render
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Alternatively, and it might be easier in the end, you could use methods:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;test/functional/admin/orders_controller_test.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;OrdersControllerTest&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActionController&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;TestCase&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;pc&quot;&gt;self&lt;/span&gt;.&lt;span class=&quot;fu&quot;&gt;should_render_successful_index_response&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;     should_respond_with &lt;span class=&quot;sy&quot;&gt;:success&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;     should_render_template &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     should_assign_to &lt;span class=&quot;sy&quot;&gt;:orders&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;     
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   logged_in_as &lt;span class=&quot;sy&quot;&gt;:admin&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     should_render_successful_index_response
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   logged_in_as &lt;span class=&quot;sy&quot;&gt;:sub_admin&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;     should_render_successful_index_response
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;15&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Note thought that you must define your methods at the top of your test case.  Remember that Ruby executes a class definition, so when you suddenly call &lt;code&gt;should_render_successful_index_response&lt;/code&gt;, the method definition has to be available, or else Ruby will complain with a &lt;code&gt;NoMethodError&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Ain&amp;#8217;t Ruby sweet?&lt;/p&gt;</content>
    
    <category term="unit-testing" label="Unit-testing"/>
      </entry>
  <entry>
    <title>What if You Didn't Have Access to If/Then/Else?</title>
    <link href="http://blog.teksol.info/2009/04/17/what-if-you-didnt-have-access-to-if-then-else.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-04-17:20090417113316</id>
    <updated>2009-04-17T11:33:16-04:00</updated>
    <content type="html">&lt;p&gt;Image a Ruby where &lt;code&gt;if/then/else&lt;/code&gt; isn&amp;#8217;t available:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Account&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# This method returns the balance in words, negative or positive, ready for display in the UI&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;balance_in_words&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;     &lt;span class=&quot;c&quot;&gt;# Err...  How do I do that?&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you were a strict Object-Oriented person, you&amp;#8217;d do it this way:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;PositiveBalanceAccount&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;balance_in_words&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;     &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Positive&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;NegativeBalanceAccount&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;balance_in_words&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Negative&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;But then, your objects would have to change class whily-nilly.  Pretty darn hard.  But multi-method dispatching gives us a nice solution:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Account&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;initialize&lt;/span&gt;(balance)
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;     &lt;span class=&quot;iv&quot;&gt;@balance&lt;/span&gt; = balance
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   defmulti &lt;span class=&quot;sy&quot;&gt;:balance_in_words&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;     lambda { &lt;span class=&quot;iv&quot;&gt;@balance&lt;/span&gt; &amp;lt; &lt;span class=&quot;i&quot;&gt;0&lt;/span&gt; } =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Negative&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;     lambda { &lt;span class=&quot;iv&quot;&gt;@balance&lt;/span&gt; &amp;gt; &lt;span class=&quot;i&quot;&gt;0&lt;/span&gt; } =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Positive&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Account&lt;/span&gt;.new(&lt;span class=&quot;i&quot;&gt;15&lt;/span&gt;).balance_in_words &lt;span class=&quot;c&quot;&gt;# =&amp;gt; &amp;quot;Positive&amp;quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Account&lt;/span&gt;.new(&lt;span class=&quot;i&quot;&gt;-5&lt;/span&gt;).balance_in_words &lt;span class=&quot;c&quot;&gt;# =&amp;gt; &amp;quot;Negative&amp;quot;&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For the observant amongst you, you might have noticed I forgot the nil balance case.  This is really a programming error, so it should be treated as such:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Account&lt;/span&gt;.new(&lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;).balance_in_words
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; lib/defmulti.rb:&lt;span class=&quot;i&quot;&gt;46&lt;/span&gt;&lt;span class=&quot;sy&quot;&gt;:in&lt;/span&gt; &lt;span class=&quot;sh&quot;&gt;&lt;span class=&quot;dl&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;balance_in_words': #&amp;lt;Account:0x197222c @balance=0&amp;gt; received balance_in_words but did not have a guard clause that matched and no else clause. (Defmulti::MissingGuardClause)
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;         from test.rb:16
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For the even more observant, yes, there exists an implementation that does exactly what I have described above.  It&amp;#8217;s called defmulti, it&amp;#8217;s a gem, and it&amp;#8217;s on GitHub:  http://github.com/francois/defmulti.&lt;/p&gt;
&lt;h2&gt;What is this useful for?&lt;/h2&gt;
&lt;p&gt;This library is a thought experiment.  When you lose the familiar tools, what can you do?  Of course, this library works atop the existing Ruby implementation and to be truly useful, syntax would have to be provided to make this much less verbose.  It&amp;#8217;s interesting nonetheless to see what can be done without the syntax extensions.&lt;/p&gt;
&lt;p&gt;Anyway, what would you use this for?  Multi-method dispatching is a tool that helps writing code without conditionals.  The conditionals are specified outside the block of code that executes.  The examples above are pretty thin, but looking at &lt;a href=&quot;http://blog.thinkrelevance.com/2008/8/26/java-next-3-dispatch-2&quot;&gt;Java.next #3: Dispatch&lt;/a&gt;, I can provide another solution to his Ruby example:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; defmulti &lt;span class=&quot;sy&quot;&gt;:letter_grade&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   lambda {|grade| (&lt;span class=&quot;i&quot;&gt;90&lt;/span&gt;..&lt;span class=&quot;i&quot;&gt;100&lt;/span&gt;).include?(grade) || grade == &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;} =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;   lambda {|grade| (&lt;span class=&quot;i&quot;&gt;80&lt;/span&gt;...&lt;span class=&quot;i&quot;&gt;90&lt;/span&gt;).include?(grade) || grade == &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;} =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;B&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   lambda {|grade| (&lt;span class=&quot;i&quot;&gt;70&lt;/span&gt;...&lt;span class=&quot;i&quot;&gt;80&lt;/span&gt;).include?(grade) || grade == &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;} =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;C&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;   lambda {|grade| (&lt;span class=&quot;i&quot;&gt;60&lt;/span&gt;...&lt;span class=&quot;i&quot;&gt;70&lt;/span&gt;).include?(grade) || grade == &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;D&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;} =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;D&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   lambda {|grade| ( &lt;span class=&quot;i&quot;&gt;0&lt;/span&gt;...&lt;span class=&quot;i&quot;&gt;60&lt;/span&gt;).include?(grade) || grade == &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;} =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;F&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; letter_grade &lt;span class=&quot;i&quot;&gt;60&lt;/span&gt;  &lt;span class=&quot;c&quot;&gt;# =&amp;gt; &amp;quot;D&amp;quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; letter_grade &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;A&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# =&amp;gt; &amp;quot;A&amp;quot;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; letter_grade &lt;span class=&quot;pc&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;# =&amp;gt; Defmulti::MissingGuardClause&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Again, I&amp;#8217;m struck by the clunky syntax, but if we ignore that for a second, could this be even better than Stuart Halloway&amp;#8217;s example?  Ruby&amp;#8217;s case statement is very, very powerful and very easy to use.  Is this useful?  Not at the moment.  But it&amp;#8217;s a thought experiment I thought I&amp;#8217;d throw out there.&lt;/p&gt;</content>
    
    <category term="language-design" label="Language-design"/>
      </entry>
  <entry>
    <title>Beware of Git's Content Tracking</title>
    <link href="http://blog.teksol.info/2009/04/15/beware-of-gits-content-tracking.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-04-15:20090415003152</id>
    <updated>2009-04-15T00:31:52-04:00</updated>
    <content type="html">&lt;p&gt;A while ago, I was wondering how rename tracking in Git worked.  I was told that renames didn&amp;#8217;t really exist in Git, as Git tracked content, not files themselves.  Fair enough.&lt;/p&gt;
&lt;p&gt;But, I just stumbled upon something:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; $ gdc vendor/plugins/acts_as_money/LICENSE 
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; diff --git a/vendor/plugins/acts_as_money/LICENSE b/vendor/plugins/acts_as_money/LICENSE
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; index e69de29..a273c73 100644
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt; --- a/vendor/plugins/acts_as_money/LICENSE
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; +++ b/vendor/plugins/acts_as_money/LICENSE
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt; @@ -0,0 +1,4 @@
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt; +one:
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt; +  user: active
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; +  name: name
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt; +  description: description

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Tell me, does that look right?  I&amp;#8217;ll manually fix that file, but that just decreased my confidence level in Git.&lt;/p&gt;
&lt;p&gt;The original LICENSE file was empty, and there apparently was another file (a fixture file) that was empty too, and the latter saw some content added.&lt;/p&gt;
&lt;p&gt;Why did this happen?  LICENSE was updated as part of a merge&amp;mdash;from a branch in which the LICENSE file doesn&amp;#8217;t exist.&lt;/p&gt;</content>
    
    <category term="git" label="Git"/>
      </entry>
  <entry>
    <title>Returning CSV data to the browser - revisited</title>
    <link href="http://blog.teksol.info/2009/04/06/returning-csv-data-to-the-browser-revisited.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-04-06:20090406102330</id>
    <updated>2009-04-06T10:23:30-04:00</updated>
    <content type="html">&lt;p&gt;Back in 2006, I wrote &lt;a href=&quot;http://blog.teksol.info/2006/03/23/returning-csv-data-to-the-browser&quot;&gt;Returning CSV data to the browser&lt;/a&gt;.  The method I was using back then is way obsolete.  Let&amp;#8217;s use the correct way to do it.  If you want to follow along, just browse over to &lt;a href=&quot;http://github.com/francois/blog.teksol.info/tree/master/content/2009/04/06/returning-csv-data-to-the-browser-revisited&quot;&gt;returning-csv-data-to-the-browser-revisited&lt;/a&gt; and read on.&lt;/p&gt;
&lt;p&gt;First of all, the correct way to return non HTML data to the browser is to use &lt;a href=&quot;http://api.rubyonrails.org/classes/ActionController/MimeResponds/InstanceMethods.html#M000368&quot;&gt;respond_to&lt;/a&gt;.  Let&amp;#8217;s do so here:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;app/controllers/reports/timelines_controller.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Reports::TimelinesController&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ApplicationController&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;show&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;     &lt;span class=&quot;iv&quot;&gt;@timelines&lt;/span&gt; = &lt;span class=&quot;co&quot;&gt;Timeline&lt;/span&gt;.all
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;     respond_to &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt; |format|
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;       format.csv &lt;span class=&quot;r&quot;&gt;do&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;         response.headers[&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Content-Type&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;]        = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;text/csv; charset=UTF-8; header=present&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;         response.headers[&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;Content-Disposition&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;] = &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;attachment; filename=timeline-report.csv&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;       &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The rest is pretty easy.  Generating the data is easy enough:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;app/models/timeline.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; require &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;fastercsv&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Timeline&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   default_scope &lt;span class=&quot;sy&quot;&gt;:order&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;started_at ASC&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;to_csv&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;     &lt;span class=&quot;co&quot;&gt;FasterCSV&lt;/span&gt;.generate_line([
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;           started_at.to_s(&lt;span class=&quot;sy&quot;&gt;:db&lt;/span&gt;),
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;           ended_at.to_s(&lt;span class=&quot;sy&quot;&gt;:db&lt;/span&gt;),
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;           project_id]).chomp
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And rendering the view?  Look how easy this gets:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;app/views/reports/timelines/show.csv.erb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;Started&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;At&lt;/span&gt;,&lt;span class=&quot;co&quot;&gt;Ended&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;At&lt;/span&gt;,&lt;span class=&quot;co&quot;&gt;Project&lt;/span&gt; &lt;span class=&quot;co&quot;&gt;ID&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &amp;lt;&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;%=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt; render :partial &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;=&lt;/span&gt;&lt;/span&gt;&amp;gt; &lt;span class=&quot;iv&quot;&gt;@timelines&lt;/span&gt; %&amp;gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Notice the file&amp;#8217;s name?  &lt;code&gt;show.csv.erb&lt;/code&gt;?  The &lt;code&gt;csv&lt;/code&gt; in the filename is what connects the respond_to call with the view.&lt;/p&gt;
&lt;p&gt;And the final piece, the timeline partial:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;app/views/reports/timelines/_timeline.csv.erb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &amp;lt;&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;%=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt; timeline.to_csv %&amp;gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Now, on to the next obsolete article in the bunch&amp;#8230;&lt;/p&gt;</content>
      </entry>
  <entry>
    <title>Building the SQL WHERE Clause Dynamically - Updated</title>
    <link href="http://blog.teksol.info/2009/04/02/building-the-sql-where-clause-dynamically-updated.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-04-02:20090402092911</id>
    <updated>2009-04-02T09:29:11-04:00</updated>
    <content type="html">&lt;p&gt;Back in October 2005, I wrote an article entitled &lt;a href=&quot;http://blog.teksol.info/2005/10/31/building-the-sql-where-clause-dynamically-in-rails&quot;&gt;Building the SQL WHERE Clause Dynamically&lt;/a&gt;.  This article is obsolete and I&amp;#8217;m replacing it with this one instead.&lt;/p&gt;
&lt;p&gt;What do you do with a FORM that has multiple conditions?  Something like this:&lt;/p&gt;
&lt;form action=&quot;#&quot; method=&quot;get&quot;&gt;
&lt;p&gt;
    &lt;label for=&quot;region&quot;&gt;Region:&lt;/label&gt;
&lt;select id=&quot;region&quot; name=&quot;region&quot;&gt;
      &lt;option&gt;All&lt;/option&gt;
      &lt;option&gt;North&lt;/option&gt;
      &lt;option&gt;East&lt;/option&gt;
      &lt;option&gt;West&lt;/option&gt;
      &lt;option&gt;South&lt;/option&gt;
&lt;/select&gt;
&lt;/p&gt;
&lt;p&gt;
    &lt;label for=&quot;salary&quot;&gt;Salary:&lt;/label&gt;
&lt;select id=&quot;salary&quot; name=&quot;salary&quot;&gt;
      &lt;option&gt;All&lt;/option&gt;
      &lt;option&gt;0-10K&lt;/option&gt;
      &lt;option&gt;10K-20K&lt;/option&gt;
      &lt;option&gt;20K-50K&lt;/option&gt;
      &lt;option&gt;50K+&lt;/option&gt;
&lt;/select&gt;
&lt;/p&gt;
&lt;p&gt;
    &lt;label for=&quot;tags&quot;&gt;Tags:&lt;/label&gt;
&lt;input id=&quot;tags&quot; name=&quot;tags&quot;/&gt;
&lt;/p&gt;
&lt;/form&gt;
&lt;p&gt;The way I would do it today is using named scopes:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;app/models/person.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;Person&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActiveRecord&lt;/span&gt;::&lt;span class=&quot;co&quot;&gt;Base&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   is_taggable
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;   named_scope &lt;span class=&quot;sy&quot;&gt;:in_region&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     lambda {|region| {&lt;span class=&quot;sy&quot;&gt;:conditions&lt;/span&gt; =&amp;gt; {&lt;span class=&quot;sy&quot;&gt;:region&lt;/span&gt; =&amp;gt; region}}}
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;   named_scope &lt;span class=&quot;sy&quot;&gt;:with_salary&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;     lambda {|lower, upper| {&lt;span class=&quot;sy&quot;&gt;:conditions&lt;/span&gt; =&amp;gt;
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;       {&lt;span class=&quot;sy&quot;&gt;:salary&lt;/span&gt; =&amp;gt; (lower .. upper)}}}
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt; 
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;   &lt;span class=&quot;c&quot;&gt;# Elided, but something similar to the above&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;   named_scope &lt;span class=&quot;sy&quot;&gt;:tagged_with&lt;/span&gt;,
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;     lambda {|tags| {&lt;span class=&quot;sy&quot;&gt;:conditions&lt;/span&gt; =&amp;gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt;       ...}}
&lt;span class=&quot;no&quot;&gt;14&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Then you need your controller to translate between what the view provides and what the model expects:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;app/controllers/searches_controller.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt; 1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;SearchesController&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ApplicationController&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 2&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;show&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 3&lt;/span&gt;     root = &lt;span class=&quot;co&quot;&gt;Person&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 4&lt;/span&gt;     root = root.in_region(params[&lt;span class=&quot;sy&quot;&gt;:region&lt;/span&gt;]) &lt;span class=&quot;r&quot;&gt;unless&lt;/span&gt; params[&lt;span class=&quot;sy&quot;&gt;:region&lt;/span&gt;] =~ &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mod&quot;&gt;i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; &lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;unless&lt;/span&gt; params[&lt;span class=&quot;sy&quot;&gt;:salary&lt;/span&gt;] =~ &lt;span class=&quot;rx&quot;&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;all&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mod&quot;&gt;i&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt; 6&lt;/span&gt;       params[&lt;span class=&quot;sy&quot;&gt;:salary&lt;/span&gt;].gsub!(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;000&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; 7&lt;/span&gt;       lower, upper = params[&lt;span class=&quot;sy&quot;&gt;:salary&lt;/span&gt;].split(&lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;)
&lt;span class=&quot;no&quot;&gt; 8&lt;/span&gt;       root = root.with_salary(lower, upper)
&lt;span class=&quot;no&quot;&gt; 9&lt;/span&gt;     &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;10&lt;/strong&gt;&lt;/span&gt;     root = root.tagged_with(&lt;span class=&quot;co&quot;&gt;Tag&lt;/span&gt;.parse(params[&lt;span class=&quot;sy&quot;&gt;:tags&lt;/span&gt;])) &lt;span class=&quot;r&quot;&gt;unless&lt;/span&gt; params[&lt;span class=&quot;sy&quot;&gt;:tags&lt;/span&gt;].blank?
&lt;span class=&quot;no&quot;&gt;11&lt;/span&gt;     &lt;span class=&quot;iv&quot;&gt;@people&lt;/span&gt; = root.paginate(&lt;span class=&quot;sy&quot;&gt;:page&lt;/span&gt; =&amp;gt; params[&lt;span class=&quot;sy&quot;&gt;:page&lt;/span&gt;])
&lt;span class=&quot;no&quot;&gt;12&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;13&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For simpler forms, the approach above would be sufficient.  For more complex forms, I suggest you look at plugins that aim to provide more structure around your SQL clauses, such as:&lt;/p&gt;
&lt;ul&gt;
	&lt;li&gt;&lt;a href=&quot;http://ambition.rubyforge.org/&quot;&gt;Ambition&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;http://agilewebdevelopment.com/plugins/criteria_query&quot;&gt;criteria_query&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href=&quot;http://agilewebdevelopment.com/plugins/ez_where&quot;&gt;ez_where&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also suggest looking at &lt;a href=&quot;http://agilewebdevelopment.com/plugins/category/4&quot;&gt;Searching and Queries Plugins &amp;#8211; Agile Web Development&lt;/a&gt; to find other plugins.&lt;/p&gt;</content>
    
    <category term="tips" label="Tips"/>
      </entry>
  <entry>
    <title>Actions With Dashes - Updated</title>
    <link href="http://blog.teksol.info/2009/04/02/actions-with-dashes-updated.html" type="text/html" />
    <id>tag:blog.teksol.info,2009-04-02:20090402092259</id>
    <updated>2009-04-02T09:00:00-04:00</updated>
    <content type="html">&lt;p&gt;Back in October 2005, I wrote an article entitled &lt;a href=&quot;http://blog.teksol.info/2005/10/25/actions-with-dashes&quot;&gt;Actions with Dashes&lt;/a&gt;.  This article is perfectly obsolete and uses a trick instead of the router as it was intended.&lt;/p&gt;
&lt;p&gt;Let&amp;#8217;s say you want an action where the URL is &lt;code&gt;http://myproject.com/contact-us&lt;/code&gt;.  The best way to do that currently is to use Rails&amp;#8217; router:&lt;/p&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;config/routes.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; map.contact_us &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;/contact-us&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:controller&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;pages&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:action&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;show&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;, &lt;span class=&quot;sy&quot;&gt;:page&lt;/span&gt; =&amp;gt; &lt;span class=&quot;s&quot;&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;contact_us&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;code&quot;&gt;
&lt;h5&gt;app/controllers/pages_controller.rb&lt;/h5&gt;
&lt;div class=&quot;CodeRay&quot;&gt;&lt;pre&gt;
&lt;span class=&quot;no&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;cl&quot;&gt;PagesController&lt;/span&gt; &amp;lt; &lt;span class=&quot;co&quot;&gt;ActionController&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;2&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;fu&quot;&gt;show&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;3&lt;/span&gt;     render &lt;span class=&quot;sy&quot;&gt;:action&lt;/span&gt; =&amp;gt; params[&lt;span class=&quot;sy&quot;&gt;:page&lt;/span&gt;]
&lt;span class=&quot;no&quot;&gt;4&lt;/span&gt;   &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/span&gt; &lt;span class=&quot;r&quot;&gt;end&lt;/span&gt;

&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code class=&quot;class&quot;&gt;PagesController&lt;/code&gt; above is also good for any other static pages you want to serve on your site.&lt;/p&gt;</content>
    
    <category term="tips" label="Tips"/>
      </entry>
</feed>
