Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker

Monthly Archives: October 2007

Christian Sepulveda

Entrepreneur Panel: You've Launched, Now What?

Christian Sepulveda
Friday, October 26, 2007

Pivotal Labs, in collaboration with VentureArchetypes (www.venturearchetypes.com), hosted an entrepreneur panel discussion on October 23, 2007.

(For the impatient, you can jump to the end for podcast links of the event.)

The event topic was:

You’ve Launched, Now What?

Managing the Reality of Life After Launch

We had four fantastic panelists:

  • Chris Law is co-founder and VP Product of Aggregate Knowledge, a leader in the new field of “collective discovery” that creates automated behavior recommendations for websites. Since starting up in 2006, AK has raised $25m from Kleiner Perkins, DAG, and First Round Capital.
  • Jeremy Verba is CEO of Piczo, a leading online site for teenagers that has grown virally to 10 million unique visitors generating 2 billion views a month. The company has raised $18m over three rounds from Sierra, Catamount, Mangrove, and USVP.
  • Prashant Shah is Managing Director at Hummer Winblad, the first venture capital fund to invest exclusively in software companies. HW has just closed a new fund with $225m of new “dry powder” to invest. At HW, Prashant is seeking investments on the business side of the web/software market.
  • Mark Fernandes is Managing Director at Sierra Ventures, which invests broadly across communications, software, and Internet related companies. Mark’s focus at Sierra is on consumer Internet and infrastructure software.

The audience were primarily entrepreneurs and a few investors. A few comments we received include:

“As an entrepreneur at SpongeFish, an early stage consumer Internet startup, this was a highly informative event for entrepreneurs that provided interesting perspective from seasoned Internet execs and VCs. The nice thing about this event was the open nature of the discussions where execs, VCs, and entrepreneurs had a very open forum discussing key issues facing entrepreneurs at early stage startups. Having the partners from several venture firms on hand for off the record perspective was also really valuable.”

–Michael Neril, CEO and Founder, SpongeFish (www.spongefish.com)

Thanks

We’d like to thank our panelists; they had great insights and were incredibly down-to-earth. We’d also like to thank our audience members. Besides their great questions, their participation in the pre/post panel discussions and mingling made it a wonderful night.

What’s Next?

We are planning to host more of these events. We’d love to hear your feedback or comments.

Podcast and Photos

The evening’s podcast is available in two parts: the panel discussion and the audience Q&A.

  • Panel Discussion
  • Audience Question and Answer:
    Note: Due to microphone proximity, some audience questions were inaudible. The audio for these brief sections was omitted for the sake of continuity. In most cases, our moderator repeated these questions or hopefully the response will provide enough context for the original question.
  • Photos on Flickr
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Testing Views by Not Testing Views: Or, The Presenter Pattern

Pivotal Labs
Friday, October 19, 2007

Err The Blog asks: “What’s the best way to test views?”

I think the best way to test views is not to test views. Extract all logic from the view into a model or presenter where it can be unit tested. Your views are then mostly declarative and there’s minimal need to test them.

Here’s an example of the “presenter pattern”.

def create_or_destroy_friendship_link(friend)
  if current_user.friends_with?(friend)
    destroy_friendship_link(friend)
  else
    create_friendship_link(friend)
  end
end

You don’t need a special class to do a Presenter; a good old-fashioned layer of abstraction will do. The basic idea is to write all conditional+iterative view logic in such a way as to never call a Rails helper directly, or generate any HTML directly, or generate any strings directly. The logic merely delegates to other methods closer to the metal.

Tests then become fairly simple. Write tests of the higher-level conditional/iterative logic in terms of the lower-level methods:

describe FriendshipsHelper, '#create_or_destroy...' do
  it "renders create link when two users are not friends" do
    log_in(users(:bob))
    bob.should_not be_friends_with(users(:amy))
    create_or_destroy_friendship_link.should == create_friendship_link
  end
end

This minimizes the need for view specs. I find in practice that a high percentage of view tests slow development down””they’re implemented not to aid development (since you typically debug views in-browser), but to prevent regression (i.e., they minimize the likelihood of introducing defects later). But since views are one of the most variable parts of a web application, regression tests are of the least value.

As a side note, I love integrate_views — not because I like to make assertions about the view in my controller tests, but because I hate mocks! I want a controller test to fail if I have a syntax error in my view!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Test-Driven

Alex Chaffee
Thursday, October 18, 2007

I’ve revised and extended my old talk on Test-Driven Development. Here it is as a nice PDF, and below the fold as a text outline.

And here are my book references in an Amazon List

Test-Driven by Alex Chaffee (alex@pivotallabs.com)

  • Test-Driven
    • by Alex Chaffee
      • alex @ pivotallabs.com
    • Pivotal Labs
  • Intended Audience
    • Developers or QA Engineers
    • Familiar with JUnit
    • Want more detail on Automated Testing in general
    • Want to know the case for Test-Driven Development
    • Want to know style tips and gotchas for Testing and TDD
  • Part I: The Blank Page
    • Let’s test-drive a utility class…
    • Red-Green-Refactor
    • 3A
      • Arrange (set up preconditions)
      • Act (call production code)
      • Assert (check the results)
    • Assert
      • The heart of a unit test
      • An Assert is a declaration of truth
      • Failed assert -> false (incorrect) behavior
      • “assert your postconditions”

Example:

  Set set = new MySet();
  set.add("Ice Cream");
  assertTrue(set.contains("Ice Cream"));
    • One Step At A Time
      • Don’t be over-ambitious
      • Each test — especially each new test — should add one brick to the wall of knowledge
      • Code:Brick :: Test:Mortar
      • Pick tests (features) in order of growth
    • The Null Test
      • A great first test to write
      • Input and output are trivial
      • Helps you focus on infrastructure and API
    • Assert First
      • When you get stuck on a test, try starting with the assertion(s) and then work your way backwards to the setup
      • Start with the assert

e.g.

  assertTrue(set.contains("alex"));

  * Then add the code above it

e.g.

  Set set = new MySet();
  set.add("alex");
    • Helps focus on goal
      • Fake It ‘Til You Make It
    • Start with hardcoded results and wait until later tests to force them to become real
      • Triangulate To Abstraction
    • Make the code abstract only when you have two or more examples

Before:

public void testSum() {
  assertEquals(4, plus(3,1));
}
int plus(int x, y) {
  return 4;
}

After:

public void testSum() {
  assertEquals(4, plus(3,1));
  assertEquals(5, plus(3,2));
}
int plus(int x, y) {
  return x + y;
}
    • Test List
      • Before you begin, make a TODO list
      • Write down a bunch of operations
      • For each operation, list the null test and some others
      • Also put down refactorings that come to mind
      • Why not write all the tests in code first?
      • Could box you in
      • Interferes with red-green-refactor
    • Obvious Implementation
      • aka Don't Be Stupid
      • If you really, really, honestly know the right way to implement it, then write it that way
      • But keep track of how many times your "obvious implementation" was broken or untested
      • Edge cases, off-by-one errors, null handling... all deserve tests and often the Obvious Implementation is not covered
  • Part II: Testing Philosophy
    • Automated Testing Layers
      • Unit
      • Integration
      • Acceptance
      • QA
      • UI
    • A Good Test Is...
      • Automated
      • Isolated
      • Repeatable
      • Fast
      • Easy to write and maintain
      • Focused
      • Easy to read
      • Robust (opposite: Brittle)
    • Tests are "Executable Specifications"
      • Someone should be able to understand your class by reading the tests
      • Live documentation (better than dead trees)
        "Any fool can write code that a computer can understand. Good programmers write code that humans can understand."
        "“ Martin Fowler
    • Why do you test?
    • Why do you test?
      • Prevent bugs
      • Regress bugs ("bug repellant")
      • Localize defects
      • Understand design
      • Document (or specify) design
      • Improve design
      • Support refactorings
      • Enable experimentation and change
      • Confidence
      • Catch errors the language can't
      • Long-term sustainability and maintainability
    • When do you test?
    • When do you test?
      • Before checkin
      • After update
      • Before deploy
      • While coding
      • In the background
    • When do you test?
      • All the time
    • When do you write tests?
      • Never
      • After coding
      • During coding
      • Before coding
    • Why test first?
    • Why test first?
      • Gets tests written
      • Easier than retrofitting tests onto an existing system
      • Guarantees 100% test coverage
      • In practice, you never have time after the code is written, but you always have the time before
        • Go figure :-)
      • Reduces scope of production code
      • Proves that your objects have usable interfaces
      • more useful methods and fewer useless ones
      • Guarantees testability
      • Test-last code is often hard to test
      • Sustainable Feature Velocity
    • How can you write tests for code that doesn't exist?
      • Think of tests as examples or specs
      • One trick: write code in test class, then extract to production
        "If you can't write a test, then you don't know what the code should do. And what business do you have writing code in the first place when you can't say what it's supposed to do?" - Rob Mee
    • What to test?
      • Simple Rule:
      • Test everything that could possibly break
      • Depends on definitions of "everything" and "possibly"
      • (and "break")
      • This means, don't test things that couldn't possibly break
      • E.g. Getters and Setters
      • Unless you think they could fail!
      • Better safe than sorry
    • How much to test?
      • Personal judgement, skill, experience
      • Usually, you start by testing too little, then you let a bug through
      • Then you start testing a lot more, then you gradually test less and less, until you let a bug through
      • Then you start testing too much again :-)
      • Eventually you reach homeostasis
    • Test for "essential complexity"
      • Not too big, not too small
      • Same concept as high coherence, low coupling
    • Meszaros' Principles of Test Automation
      • Write the tests first
      • Design for testability
      • Use the front door first
      • Communicate intent
      • Don't modify the SUT
      • Keep tests independent
      • Isolate the SUT
      • Minimize test overlap
      • Minimize untestable code
      • Keep test logic out of production code
      • Verify one condition per test
      • Test separate concerns separately
      • Ensure commensurate effort and responsibility
    • Tests Are An Extension of Code
      • Every time you write code, you write tests that exercise it
      • That means that if you change the code, and the tests break, you must either
        • Change the tests to match the new spec
        • Change the code to meet the old spec
      • Do not remove the failing tests
      • Unless they no longer apply to the new code's design or API
      • Do not work around the failing tests
      • Test code is not "wasted" or "extra" -- tests are first-class citizens
      • If you feel like they're too much work, examine your process
      • Maybe you're writing the wrong tests, or your tests are too brittle, or they're not refactored enough
    • Unit Testing Is Hard
      • It forces you to really understand the code
      • It forces you to really understand the tests
      • It forces you to create code that is truly reusable and modular and testable
      • "put your money where your mouth is"
      • These forces drive you to keep your code and your tests simple and easy to understand
    • Test-Driving Is Slower At First
      • Need to spend time on infrastructure, fixtures, getting comfortable with TDD
      • Business case for TDD: sustainable velocity
      • for feature velocity, stabilty > early oomph
      • Famous graph
    • Two D's
      • Test-Driven Development
      • Good old-fashioned coding, now with tests!
      • Test-Driven Design
      • Free your mind and the code will follow
      • Quite a lot of overlap, but worth keeping difference in mind
      • Lots of XP gurus are all about the Zen, but you don't need to buy into that
      • But it's actually pretty cool to try Zen Testing
  • Part III: Advanced Techniques

    • Full Range Testing
      • Positive Tests
        exercise normal conditions ("sunny day scenarios")
        E.g. Verify that after adding an element to a set, that element exists in the set
      • Negative Tests
        Exercise failure conditions ("rainy day scenarios")
        E.g. verify that trying to remove an element from an empty set throws an exception
      • Boundary Conditions
        Exercise the limits of the system ("cloudy day")
        E.g. adding the maximum number of elements to a set
        E.g. test 0, -1, maximum, max+1
    • Outside-in vs. Inside-out
      • Matter of preference
      • Both are useful at times
    • Inside-out
      • Start with domain objects
      • Next layer of tests
    • Outside-in
      • Start with customer story or user interface
      • Makes you think like a user
      • Tests capture these requirements
      • Lower layers implemented with Test Doubles (mocks)
      • After you're done, either replace mocks with real objects, or leave them there (perhaps at higher maintenance cost)
    • Outside-in design, inside-out development
      • Write a bunch of UI-level tests
      • Leave them there while you test-drive inside-out
      • When they all pass, you're done
    • Test Doubles
      • A Test Double replaces the "real" instance of an object used by the production code with something suitable for the currently running test, but with the same interface
      • Stubs
      • Hard-coded values
      • Mocks
      • Pre-programmed with expectations
      • Fail-fast
      • Test Doubles in general are often called Mock Objects, so be careful about terminology
      • Fakes
      • Can store values across calls, but don't really do what the live object would do
      • E.g. in-memory database
      • Spies
      • Remember what methods were called with what values
      • Tests can inspect these lists after code returns
      • Saboteurs
      • Blow up in ways that would be difficult to make happen for real
      • To test what would happen when, e.g., the database goes away, or the disk is full
      • Self Shunt
      • The test itself declares methods or classes implementing the above, and passes in a pointer to itself
    • Dependency Injection
      • Two types:
      • DI Frameworks
      • Complete Construction
        • This is the one I'm talking about
        • Pass in dependencies to the constructor (or, if necessary, to setters)
      • An object under test will receive references to all external services
      • Allows tests to inject Test Doubles at will
      • Forces objects to be isolated
      • Example: TBD
    • Verbose Test Naming
      • instead of SetTest.testEmpty
      • how about SetTest.testShouldHaveSizeZeroWhenEmpty
      • or EmptySetTest.testHasSizeZero
    • Should Statements

      • Optional first parameter to JUnit asserts is "message"
      • Assertion messages can be confusing
      • Example:
        e.g.
        assertEquals("set is empty", set.isEmpty());

      • Does it mean "the set must be empty" or "the test is failing because the set is empty"?

      • Solution: should statements

      assertEquals("set should be empty", set.isEmpty())

      • or even better:

      assertEquals("a newly-created set should be empty", set.isEmpty())

    • EDD/BDD (specs)

      • Changes the language of tests to emphasize that they're specifications or examples
      • Replaces "assert" with "should"
    • Fixtures and Object Mothers
      • A natural progression of refactoring your test data
      • literals
      • constants
      • local variables
      • instance variables (defined in setUp())
      • creation methods
      • parameterized creation methods or objects ("object mothers")
      • Other patterns
      • test objects / graphs ("fixtures" or "cast of characters" or "menagerie")
      • external fixture files
    • Test-Only Methods
      • Philosophy: a test is a valid client of an object
      • Therefore don't be ashamed of adding a method just because it would make a test easier to write
      • Used -> Useful
      • Remember, tests are examples of use
    • Refactor Test Code
      • Spend time refactoring your tests
      • It'll pay off later, when writing new tests or extending/debugging old ones
      • Refactor for readability, not necessarily for removing all duplication
      • Different priorities than for production code
      • Extract methods
      • Shorter lines
      • Break up long tests (scenario tests) into several short tests (feature tests)
      • One technique: "Refactor production code on green, Refactor test code on red."
      • for complex cases, break the code, make sure the refactored tests still reveal the breakage, then fix it
    • Evident Data

e.g.
assertEquals(86400, new Day().getSeconds())

vs.

  assertEquals(60 * 60 * 24, new Day().getSeconds())

vs.

  secondsPerMinute = 60
  minutesPerHour = 60
  hoursPerDay = 24
  assertEquals(secondsPerMinute * minutesPerHour * hoursPerDay, new Day().getSeconds())
    • Matrix Tests
      • Problem: several axes of variability, combinatorial explosion
      • Solution: Loop through a matrix of data in your test, call a "check" function on each row
    • Characterization Tests
      • aka "Golden Data Tests"
      • Grab the complete output of a routine, put it into the test
      • Not amenable to test-driven development
      • Effective for large or risky refactorings
      • Quite brittle, so often thrown away after the refactoring is done
    • Exception Tests

e.g.

  public void testUnknownCountry() {
    try {
      currencyConverter.getRate("Snozistan");
      fail("Should have thrown an exception for unknown country");
   } catch (UnknownCountryException e) {
      // ok
   }
  }
    • Pair Programming
      • A pair's job is to keep you focused
      • "Wait, let's write a test first."
      • "Wait, let's refactor first."
      • "Wait, let's discuss this."
      • "Can I drive?"
    • Ping-Pong Pairing
      • One pair writes a test
      • The other pair makes it pass and writes the next test
      • Repeat
      • Good way to get out of a rut, or cure a keyboard hog
    • Regression Test
      • When a defect is reported...
      • The first step is to write a (failing) test that reproduces the bug
        Fix the bug by writing code to make the test run successfully
        Verify the bug in the running application
        Add the bug test to the automated suite
        Check in the bugfix code and test
        Now it's always run "“ instant regression test!
      • "Regression tests are test that you would have written originallly." - Kent Beck
      • May also want to write a failing Acceptance Test, but that's optional -- you definitely want a failing unit test
    • Do Over
      • Often the best thing to do is throw away your work and start again
    • Leave One For Tomorrow
      • At the end of the day, write a failing test and leave it there for tomorrow
      • Based on writer's trick: start a sentence and leave it unfinished
    • Continuous Integration
      • Any time all the tests are green, you can check in
      • Run all the tests all the time
      • Don't check in until all tests pass
      • If you broke "someone else's" tests, you are responsible for fixing "their" code
      • Remember, they are in the room, so go get them if you need help
      • Learn to Love the Orb
      • ccmenu
    • The Need For Speed
      • Tests are only valuable if they're run all the time
      • If they're slow, people will not want to run them all the time
      • So keep them fast!
      • Difficult quest, but worth it
    • Automatic Suites
      • Suites are a pain to maintain
      • Write code to automatically scan for tests and run them together
      • Possible to do in JUnit, but annoying
    • Mock Clock
  • Part IV: Test-Driving UI
  • Part V: Q&A
  • Thanks!
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

CUDdly Models

Pivotal Labs
Tuesday, October 16, 2007

Teddy Bear

Better Rails Code through

…ActiveRecords with no public methods that have side-effects–other than Create, Update, and Destroy (CUD).

CUDly Models

In a typical web application, some “triggered actions” or “side-effects” occur in response to various events. Examples: A confirmation email is sent when a User registers. A ping is sent to Technorati when a BlogArticle is published. Cookies are set when a user logs in. Two Friendship objects are created between two users when one approves the other’s FriendshipRequest.

Often these side-effects are modeled with public methods on an ActiveRecord. For instance,

class FriendshipRequest < ActiveRecord::Base
  def accept!
    self.status = ACCEPTED
    save!
    Friendship.create!(:from => from, :to => to)
    Friendship.create!(:to => from, :from => to)
  end
end

This is code is dangerous, as shown below. The principal of CUDly models is eliminate all public methods on your ActiveRecord that have any side effects. CUDly models are much safer. Let’s replace the dangerous code with the equivalent, more friendly, CUDly code:

class FriendshipRequest
  after_update :create_mutual_friendship

  private
  def send_email_on_accept
    if status == ACCEPTED
      Friendship.create!(:from => from, :to => to)
      Friendship.create!(:to => from, :from => to)
    end
  end
end

The above CUDly code exploits the richness of ActiveRecord’s lifecycle callbacks to trigger the side-effect. This illustrates a general principle: in a perfectly CUDly world, constrain the interface to your models such that no methods have side effects other than Create, Update, and Destroy.

There are several virtues to CUDly models: encapsulation, transactions, consistent interface, and lifecycle power.

Encapsulation

Your side-effects can be hidden behind the standard ActiveRecord interface. And your Controllers will be skinny as can be! Compare:

class FriendshipRequestsController
  def update
    friendship_request = FriendshipRequest.find(params[:id])
    if params[:friendship_request][:state] == ACCEPTED
      friendship_request.accept!
    else
      friendship_request.reject!
    end
  end
end

Instead, you can write an equivalent, Formulaic Controller:

class FriendshipRequestsController
  def update
    friendship_request = FriendshipRequest.find(params[:id])
    friendship_request.attributes = params[:friendship_request]
    friendship_request.save
  end
end

Transactions

Thanks to ActiveRecord, the CUDly approach has rich transactional semantics. In the CUDly implementation, if any of the Friendship.create! invocations fails, the entire transaction is rolled back, meaning the you cannot put the world in an incoherent state (where Tom and Dick are only half-friends). The equivalent non-CUDly code is the onerous and obese:

class FriendshipRequest < ActiveRecord::Base
  def accept!
    self.status = ACCEPTED
    self.class.transaction do
      save!
      Friendship.create!(:from => from, :to => to)
      Friendship.create!(:to => from, :from => to)
    end
  end
end

Who wants to cuddle with code like that?!

A Rich Consistent Interface

ActiveRecord already has a wonderful pattern: build, then test. It’s so simple, and yet so powerful. Why not re-use it?

f = Friendship.new
if f.save ...

#Save returns true or false, and it sets errors on the model to be displayed by the user. Using CUDly code, you continue to do that:

class FriendshipRequestsController
  def update
    friendship_request = FriendshipRequest.find(params[:id])
    friendship_request.attributes = params[:friendship_request]
    if friendship_request.save
      flash[:notice] = ...
    else
      flash[:error] = ...
      render :action => :edit
    end
  end
end

Try doing that with unCUDly #accept and #reject methods! Surely it will be unCUDly and ungodly!

Leverage the Power of the Lifecycle

A fundamental limitation of public, side-effecting methods is that they can be called at any time for any reason. Suppose we had something like this:

class MyModel < ...
  def foo=(bar)
    send_an_email!
  end

This could be called as part of #attributes=, triggering the email deilvery regardless of whether the model was valid and could be saved! In the CUDly implementation, on the other hand:

class MyModel
  after_save :send_an_email
  def send_an_email
  ...

Thanks to the flexibility of #before_validation_on_create, #before_create, #after_update, #after_initialize, #after_find, etc., you can ensure that your triggered action only happens after successful validation, or regardless of validation, or only on update, or only on destroy–you name it! Try enforcing that with a public method!

Make Your Code a CUDly Code

There is a beautiful symmetry in having all side-effecting methods “funneled” through the three “dangerous” methods (create, update, and destroy). It appeals to my sense of elegance and order. I’ve used this design strategy 100% for the last few months and it’s been a smashing success! It truly is the way ActiveRecord was meant to be used. So give it a try!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

dot.rake

Alex Chaffee
Monday, October 15, 2007

[Update: 10/15/07 - incorporated changes by David Vrensk (and a few more from me). Now it merges in associations into the arc, and also deals with inheritance (e.g. STI).]

While googling for articles on Rails associations, I happened upon
this gem of a script by Matt Biddulph. I loved it so much I made it a rake task! Once you install GraphViz like this:

sudo port install graphviz

and put dot.rake in your lib/tasks directory, then running this:

rake dot

produces diagrams like this:

BBC Programme Catalogue codebase

And you can also import the DOT source into OmniGraffle for further editing, like this:

open -a "OmniGraffle" model.dot

Here’s the source for dot.rake:

# dot.rake
# Creates a DOT format file showing the model objects and their associations
# Authors:
#   Matt Biddulph - http://www.hackdiary.com/archives/000093.html
#   Alex Chaffee - http://www.pivotalblabs.com/articles/2007/09/29/dot-rake
#   David Vrensk - david@vrensk.com
# Usage:
#  rake dot
#  To open in OmniGraffle, run
#    open -a 'OmniGraffle' model.dot
#  or
#    open -a 'OmniGraffle Professional' model.dot

desc "Generate a DOT diagram of the ActiveRecord model objects in 'model.dot'"
task :dot => :environment do
  Dir.glob("app/models/*rb") { |f|
    require f
  }
  File.open("model.dot", "w") do |out|
    out.puts "digraph x {"
    #out.puts "tnode [fontname=Helvetica,fontcolor=blue]"
    out.puts "tnode [fontname=Helvetica]"
    out.puts "tedge [fontname=Helvetica,fontsize=10]"
    Dir.glob("app/models/*rb") { |f|
      f.match(//([a-z_]+).rb/)
      classname = $1.camelize
      klass = Kernel.const_get classname
      if (klass.class != Module) && (klass.ancestors.include? ActiveRecord::Base)
        if klass.include? ActiveRecord::Acts::List::InstanceMethods
          scope = klass.new.scope_condition.sub(/(_id?) .*/,'').camelize
          out.puts "t#{classname} [label="#{classname}n(list in #{scope})"]"
        elsif klass.superclass != ActiveRecord::Base
          out.puts "t#{classname} -> #{klass.superclass.name} [arrowhead=empty]"
        else
          out.puts "t#{classname}"
        end
        klass.reflect_on_all_associations.select { |a| a.macro.to_s.starts_with? 'has_' }.each do |a|
          target = a.name.to_s.camelize.singularize
          if a.klass.name != target
            target = a.klass.name
            label = ",label="as #{a.name}""
          else
            label =""
          end
          case a.macro.to_s
          when 'has_many'
            out.puts "t#{classname} -> #{target} [arrowhead=crow#{label}]"
          when 'has_and_belongs_to_many'
            out.puts "t#{classname} -> #{target} [arrowhead=crow,arrowtail=crow#{label}]" if classname < target
          when 'has_one'
            out.puts "t#{classname} -> #{target} [arrowhead=diamond#{label}]"
          else
            $stderr.puts "No support for #{a.macro.to_s} in #{classname}"
          end
        end
      end
    }
    out.puts "}"
  end
  system "dot -Tpng model.dot -o model.png"
  system "/Applications/Graphviz.app/Contents/MacOS/dot -Tpng model.dot -o model.png" unless $?.success?
  puts "Could not write model.png. Please install graphviz (http://www.graphviz.org)." unless $?.success?
end

Put that in a file called “dot.rake” and put it in your lib/tasks directory and Dot’s your uncle. Aunt. Whatever…

Any suggestions? Should I be writing the output file to a subdirectory, like maybe db, instead?

(BTW, it looks like OmniGraffle doesn’t support the font style features of DOT, so all the nodes are 12-point black on import :-( .)

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Cacheable Flash 0.1.4 — Test Helpers

Pivotal Labs
Sunday, October 7, 2007

I just released Cacheable Flash 0.1.4. This version includes test helpers so you can easily test your cache messages. It works by allowing you to make assertions on the flash cookie.

Here is a test/unit example:

  require "cacheable_flash/test_helpers"

  class TestController < ActionController::Base
    def index
      flash["notice"] = "In index"
    end
  end

  class ControllerTest < Test::Unit::TestCase
    include CacheableFlash::TestHelpers

    def setup
      @controller = TestController.new
      @request = ActionController::TestRequest.new
      @response = ActionController::TestResponse.new
    end

    def test_cacheable_flash_action
      get :index
      asset_equal "In index", flash_cookie["notice"]
    end
  end

Here is a rspec example:

  require "cacheable_flash/test_helpers"

  class TestController < ActionController::Base
    def index
      flash["notice"] = "In index"
    end
  end

  describe TestController, "#index" do
    include CacheableFlash::TestHelpers

    it "writes to the flash cookie" do
      get :index
      flash_cookie["notice"].should == "In index"
    end
  end

You can install Cacheable Flash by running:

ruby script/plugin install svn://rubyforge.org/var/svn/pivotalrb/cacheable_flash/trunk

See the Cacheable Flash blog post, Show Flash Messages on Cached Pages, and the README for more information.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

sub

Alex Chaffee
Tuesday, October 2, 2007

Yellow Submarine

(Update — version 0.3 released 2-Oct-07. Release notes are here.)

We use subversion for our source control. We love it. But we’ve noticed a few flaws, and a few weeks ago I decided I’d had enough and wrote a wrapper for it that fixes a few of the most glaring ones:

  • Externals get messed up pretty frequently. If you remove or rename an external, the old one gets left around on disk, and if you convert an external to a “real” directory or vice versa then the next update simply fails.
  • Externals are updated in series, not in parallel, meaning that if you have a lot of externals your updates can take an excruciatingly long time.
  • Externals are updated even if they’re frozen to a specific revision number, which wastes even more time on update.
  • If you want a clean checkout — say, for an automated build — the only way to do it is to do a full checkout, even if 99% of the files are already there on disk.
  • The co command is not compatible with the convention of putting files under /trunk, requiring you to type out your whole repository URL followed by /foo/trunk foo
  • The name of the executable is hard to pronounce — either “ess vee enn” or “seven”, but nobody says “seven” except when they’re saying “seven up”, which is, I admit, a pretty good pun, but come on, how much cooler is it to say, “sub”?

The current version of sub fixes all of the above (except for converting directories to and from externals, and I’m going to make that work pretty soon).

Install with

sudo gem install sub

Help text is below the fold.

Usage:
  sub co project_name [dir_name]
    checks out [base_url]/project_name/trunk into ./project_name (or dir_name if specified)
  sub up [dir]*
    fast update (default command, so 'sub dir...' or just 'sub' work too)
  sub help
    prints this message

Options:
  --verbose, -v
      lots of output
  --quiet, -q
      no output at all except for errors
  --help, -h
      prints this message
  --clean, -c
      'up' command removes all unversioned files and directories
  --url [base_url]
      sets base repository url for 'co' command
      (default is ENV['SUB_BASE_URL'] or ENV['SVN'] or svn+ssh://rubyforge.org/var/svn)
  --version
      prints release version

Using sub up took the update time for my main project from 16.5 sec to 3.5 sec (we have about 20 externals).

If you have a common repository like we do, set it in your .bash_profile like this

export SUB_BASE_URL=https://svn.mygreatcompany.com/svn

and then

sub co foo

will run

svn co https://svn.mygreatcompany.com/svn/foo/trunk foo

Otherwise it’ll default to Rubyforge’s repository.

If you want to see what commands it’s executing, --verbose will show you (it indents the system commands so they’re easier to spot).

Enjoy! And please suggest improvements of any kind.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Topics

  • agile (780)
  • rails (113)
  • testing (88)
  • ruby (83)
  • ruby on rails (70)
  • jobs (62)
  • javascript (55)
  • techtalk (44)
  • rspec (38)
  • ironblogger (32)
  • productivity (30)
  • activerecord (29)
  • gogaruco (29)
  • git (28)
  • nyc (27)
  • rubymine (26)
  • bloggerdome (23)
  • mobile (22)
  • process (21)
  • pivotal tracker (20)
  • cucumber (20)
  • jasmine (19)
  • design (18)
  • ios (18)
  • webos (17)
  • objective-c (17)
  • android (16)
  • palm (16)
  • "soft" ware (16)
  • fun (15)
  • tracker ecosystem (15)
  • ci (15)
  • cedar (15)
  • rails3 (14)
  • performance (14)
  • bdd (14)
  • gem (13)
  • css (13)
  • tdd (13)
  • selenium (12)
  • goruco (12)
  • bundler (12)
  • meetup (11)
  • railsconf (11)
  • nyc-standup (11)
  • capybara (10)
  • mac (10)
  • mojo (10)
  • chef (10)
  • api (10)
Subscribe to Community Feed
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >