davideisinger.com

My personal website
Log | Files | Refs | README

index.md (2271B)


      1 ---
      2 title: "Shoulda Macros with Blocks"
      3 date: 2009-04-29T00:00:00+00:00
      4 draft: false
      5 canonical_url: https://www.viget.com/articles/shoulda-macros-with-blocks/
      6 ---
      7 
      8 When I'm not working on client projects, I keep myself busy
      9 with [SpeakerRate](http://speakerrate.com), a site that lets conference
     10 goers rate the talks they've attended. After a number of similar
     11 suggestions from users, we decided to display the total number of
     12 ratings alongside the averages. Although only talks can be rated,
     13 speakers, events and series also have ratings through their associated
     14 talks. As you can imagine, calculating the total ratings for each of
     15 these required a lot of somewhat repetitive code in the models, and
     16 *very* repetitive code in the associated tests.
     17 
     18 Fortunately, since we're using
     19 [Shoulda](http://thoughtbot.com/projects/shoulda/), we were able to DRY
     20 things up considerably with a macro:
     21 
     22 ```ruby
     23 class Test::Unit::TestCase
     24   def self.should_sum_total_ratings
     25     klass = model_class
     26 
     27     context "finding total ratings" do
     28       setup do
     29         @ratable = Factory(klass.to_s.downcase)
     30       end
     31 
     32       should "have zero total ratings if no rated talks" do
     33         assert_equal 0, @ratable.total_ratings
     34       end
     35 
     36       should "have one total rating if one delivery & content rating" do
     37         talk = block_given? ? yield(@ratable) : @ratable
     38         Factory(:content_rating, :talk => talk)
     39         Factory(:delivery_rating, :talk => talk)
     40 
     41         assert_equal 1, @ratable.reload.total_ratings
     42       end
     43     end
     44   end
     45 end
     46 ```
     47 
     48 This way, if we're testing a talk, we can just say:
     49 
     50 ```ruby
     51 class TalkTest < Test::Unit::TestCase
     52   context "A Talk" do
     53     should_sum_total_ratings
     54   end
     55 end
     56 ```
     57 
     58 But if we're testing something that has a relationship with multiple
     59 talks, our macro accepts a block that serves as a factory to create a
     60 talk with the appropriate relationship. For events, we can do something
     61 like:
     62 
     63 ```ruby
     64 class EventTest < Test::Unit::TestCase
     65   context "An Event" do
     66     should_sum_total_ratings do |event|
     67       Factory(:talk, :event => event)
     68     end
     69   end
     70 end
     71 ```
     72 
     73 I'm pretty happy with this solution, but having to type "event" three
     74 times still seems a little verbose. If you've got any suggestions for
     75 refactoring, let us know in the comments.