You’re Doing It Wrong           Chad Pytel     cpytel@thoughtbot.com            @cpytel
You’re Doing It Wrong      but I love you anyway           Chad Pytel     cpytel@thoughtbot.com            @cpytel
Rake Tasks
Are you testing them?
What makes them hard to test?   • Scripts that live outside app   • Often have network and file access   • Often have output
Example Tasknamespace :twitter do  task :search => :environment do    puts "Searching twitter."    Twitter.search("@cpytel...
One possible way to test
context "rake twitter:search" do  setup do    # How slow is this going to be? Very.    @out = `cd #{Rails.root} &&        ...
should "print a message at the beginning" do  assert_match /Searching/i, @outend
should "find all tweets containing @cpytel" do  # this one would be based entirely on luck.end
This Has Problems• Slow• No mocking or stubbing available• Task isn’t in a transaction
Basically, no sandboxing
How do we fix this?
Rake tasks are just Ruby
Move it all into the Model
class Alert < ActiveRecord::Base  def self.create_all_from_twitter_search(output = $stdout)    output.puts "Searching twit...
The Task is Nice and Skinnynamespace :twitter do  task :search => :environment do    Alert.create_all_from_twitter_search ...
Testing is Pretty Normal# test/unit/alert_test.rbclass AlertTest < ActiveSupport::TestCase  context "create_all_from_twitt...
should "print a message at the beginning" do  Alert.create_all_from_twitter_search(@output)  assert_match /Searching/i, @o...
should "save some cache files" do  Twitter.stubs(:search).returns(["one"])  alert = mock("alert")  alert.expects(:save_cac...
should "find all tweets containing @cpytel" do  Twitter.expects(:search).          with("@cpytel").          returns(["bod...
We can mock and stub!
We can use normal tools!• FakeWeb/WebMock• FileUtils::NoWrite
In Summary• You can test drive development of  your rake tasks• Rake tasks should live inside a  model (or class)
Views
Know Your Helpers
Know How They Change
# Edit form<%= form_for :user,             :url => user_path(@user),             :html => {:method => :put} do |form| %>
<%= form_for @user do |form| %>
<!-- posts/index.html.erb --><% @posts.each do |post| -%>  <h2><%= post.title %></h2>  <%= format_content post.body %>  <p...
Move the post content    into a partial
<!-- posts/index.html.erb --><% @posts.each do |post| -%>  <%= render :partial => post, :object => :post %><% end -%><!-- ...
Looping was built  into render
<!-- posts/index.html.erb --><%= render :partial => post, :collection => @posts %><!-- posts/_post.erb --><h2><%= post.tit...
<!-- posts/index.html.erb --><%= render :partial => post, :collection => @posts %><!-- posts/_post.erb --><h2><%= post.tit...
<%= render :partial => @posts %>
<%= render @posts %>
Dynamic Page Titles
<!-- layouts/application.html.erb --><head>  <title>    Acme Widgets : TX-300 Utility Widget  </title></head>
class PagesController < ApplicationController  def show    @widget = Widgets.find(params[:id])    @title = @widget.name  e...
This is all View
There is a Helper
<!-- layouts/application.html.erb --><head>  <title>    Acme Widgets : <%= yield(:title) %>  </title></head><!-- widgets/s...
Default Title
<!-- layouts/application.html.erb --><head>  <title>    Acme Widgets : <%= yield(:title) || "Home" %>  </title></head>
What else can we use      this for?
<!-- layouts/application.html.erb --><div class="sidebar">  This is content for the sidebar.  <%= link_to "Your Account", ...
<!-- layouts/application.html.erb --><%= yield(:sidebar) %><div class="main">  The main content of the page</div><!-- layo...
Avoid Duplication
<!-- layouts/application.html.erb --><div class="sidebar">  <%= yield(:sidebar) %></div><div class="main">  The main conte...
Conditional Sidebar<!-- layouts/application.html.erb --><% if content_for?(:sidebar) -%>  <div class="sidebar">    <%= yie...
Reviews/Refactoring
You're Doing It Wrong
Upcoming SlideShare
Loading in...5
×

You're Doing It Wrong

635

Published on

Chat Pytel of Thoughtbot's presentation

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
635
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
3
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • You're Doing It Wrong

    1. 1. You’re Doing It Wrong Chad Pytel cpytel@thoughtbot.com @cpytel
    2. 2. You’re Doing It Wrong but I love you anyway Chad Pytel cpytel@thoughtbot.com @cpytel
    3. 3. Rake Tasks
    4. 4. Are you testing them?
    5. 5. What makes them hard to test? • Scripts that live outside app • Often have network and file access • Often have output
    6. 6. Example Tasknamespace :twitter do task :search => :environment do puts "Searching twitter." Twitter.search("@cpytel").each do |result| puts "Processing #{result.inspect}." alert = Alert.create(:body => result) alert.save_cache_file! end end puts "All done!"end
    7. 7. One possible way to test
    8. 8. context "rake twitter:search" do setup do # How slow is this going to be? Very. @out = `cd #{Rails.root} && rake twitter:search 2>&1` end
    9. 9. should "print a message at the beginning" do assert_match /Searching/i, @outend
    10. 10. should "find all tweets containing @cpytel" do # this one would be based entirely on luck.end
    11. 11. This Has Problems• Slow• No mocking or stubbing available• Task isn’t in a transaction
    12. 12. Basically, no sandboxing
    13. 13. How do we fix this?
    14. 14. Rake tasks are just Ruby
    15. 15. Move it all into the Model
    16. 16. class Alert < ActiveRecord::Base def self.create_all_from_twitter_search(output = $stdout) output.puts "Searching twitter." Twitter.search("@cpytel").each do |result| output.puts "Processing #{result.inspect}." alert = create(:body => result) alert.save_cache_file! end output.puts "All done!" end def save_cache_file! # Removes a file from the filesystem. endend
    17. 17. The Task is Nice and Skinnynamespace :twitter do task :search => :environment do Alert.create_all_from_twitter_search endend
    18. 18. Testing is Pretty Normal# test/unit/alert_test.rbclass AlertTest < ActiveSupport::TestCase context "create_all_from_twitter_search" do setup do # Make sure none of the tests below hit the # network or touch the filesystem. Alert.any_instance.stubs(:save_cache_file!) Twitter.stubs(:search).returns([]) @output = StringIO.new end
    19. 19. should "print a message at the beginning" do Alert.create_all_from_twitter_search(@output) assert_match /Searching/i, @output.stringend
    20. 20. should "save some cache files" do Twitter.stubs(:search).returns(["one"]) alert = mock("alert") alert.expects(:save_cache_file!) Alert.stubs(:create).returns(alert) Alert.create_all_from_twitter_search(@output)end
    21. 21. should "find all tweets containing @cpytel" do Twitter.expects(:search). with("@cpytel"). returns(["body"]) Alert.create_all_from_twitter_search(@output)end
    22. 22. We can mock and stub!
    23. 23. We can use normal tools!• FakeWeb/WebMock• FileUtils::NoWrite
    24. 24. In Summary• You can test drive development of your rake tasks• Rake tasks should live inside a model (or class)
    25. 25. Views
    26. 26. Know Your Helpers
    27. 27. Know How They Change
    28. 28. # Edit form<%= form_for :user, :url => user_path(@user), :html => {:method => :put} do |form| %>
    29. 29. <%= form_for @user do |form| %>
    30. 30. <!-- posts/index.html.erb --><% @posts.each do |post| -%> <h2><%= post.title %></h2> <%= format_content post.body %> <p> <%= link_to Email author, mail_to(post.user.email) %> </p><% end -%>
    31. 31. Move the post content into a partial
    32. 32. <!-- posts/index.html.erb --><% @posts.each do |post| -%> <%= render :partial => post, :object => :post %><% end -%><!-- posts/_post.erb --><h2><%= post.title %></h2><%= format_content post.body %><p><%= link_to Email author,mail_to(post.user.email) %></p>
    33. 33. Looping was built into render
    34. 34. <!-- posts/index.html.erb --><%= render :partial => post, :collection => @posts %><!-- posts/_post.erb --><h2><%= post.title %></h2><%= format_content post.body %><p> <%= link_to Email author, mail_to(post.user.email) %></p>
    35. 35. <!-- posts/index.html.erb --><%= render :partial => post, :collection => @posts %><!-- posts/_post.erb --><h2><%= post.title %></h2><%= format_content post.body %><p> <%= link_to Email author, mail_to(post.user.email) %></p>
    36. 36. <%= render :partial => @posts %>
    37. 37. <%= render @posts %>
    38. 38. Dynamic Page Titles
    39. 39. <!-- layouts/application.html.erb --><head> <title> Acme Widgets : TX-300 Utility Widget </title></head>
    40. 40. class PagesController < ApplicationController def show @widget = Widgets.find(params[:id]) @title = @widget.name endend<!-- layouts/application.html.erb --><head> <title>Acme Widgets : <%= @title %></title></head>
    41. 41. This is all View
    42. 42. There is a Helper
    43. 43. <!-- layouts/application.html.erb --><head> <title> Acme Widgets : <%= yield(:title) %> </title></head><!-- widgets/show.html.erb --><% content_for :title, @widget.title %>
    44. 44. Default Title
    45. 45. <!-- layouts/application.html.erb --><head> <title> Acme Widgets : <%= yield(:title) || "Home" %> </title></head>
    46. 46. What else can we use this for?
    47. 47. <!-- layouts/application.html.erb --><div class="sidebar"> This is content for the sidebar. <%= link_to "Your Account", account_url %></div><div class="main"> The main content of the page</div>
    48. 48. <!-- layouts/application.html.erb --><%= yield(:sidebar) %><div class="main"> The main content of the page</div><!-- layouts/application.html.erb --><% content_for :sidebar do %> <div class="sidebar"> This is content for the sidebar. <%= link_to "Your Account", account_url %> </div><% end %>
    49. 49. Avoid Duplication
    50. 50. <!-- layouts/application.html.erb --><div class="sidebar"> <%= yield(:sidebar) %></div><div class="main"> The main content of the page</div><!-- layouts/application.html.erb --><% content_for :sidebar do %> This is content for the sidebar. <%= link_to "Your Account", account_url %><% end %>
    51. 51. Conditional Sidebar<!-- layouts/application.html.erb --><% if content_for?(:sidebar) -%> <div class="sidebar"> <%= yield(:sidebar) %> </div><% end -%><div class="main"> The main content of the page</div><!-- layouts/application.html.erb --><% content_for :sidebar do %> This is content for the sidebar. <%= link_to "Your Account", account_url %><% end %>
    52. 52. Reviews/Refactoring
    1. A particular slide catching your eye?

      Clipping is a handy way to collect important slides you want to go back to later.

    ×