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.