├── README ├── dynamic_toolkit ├── blankslate.rb ├── dynamic_method_definitions_as_test.rb ├── metrics_conversions.rb ├── naive_camping_routes.rb ├── pdf_conversions.rb ├── prawn_dsl.rb ├── prawn_standard.rb ├── rubygems_custom_require.rb └── simple_test_harness.rb ├── stdlib ├── binding_with_logic_erb.rb ├── datetime_example.rb ├── license.rb ├── mathn_examples.rb ├── payment_summary.csv ├── payment_summary.rb ├── payments.csv ├── phone_numbers.csv ├── pretty_printing.rb └── scheduler.rb ├── testing ├── Rakefile ├── prawn_inline_styles.rb ├── test │ ├── test_bar.rb │ └── test_foo.rb ├── test_blog.rb ├── test_lock_box.rb ├── test_questioner.rb ├── test_unit_extensions.rb ├── test_volume_bad.rb └── test_volume_good.rb ├── when_things_go_wrong ├── case_study.rb ├── client.rb ├── foo.rb ├── grouping.rb ├── html_report.rb ├── irb_debugger.rb ├── irb_debugging_example.rb ├── prawn_canvas.rb ├── processor.rb ├── rbprofile.rb ├── rbprofiler.rb ├── report_bench.rb ├── report_data.csv ├── report_line_processor.rb ├── report_optimized.rb ├── report_unoptimized.rb ├── server_logging.rb └── server_logging_initial.rb └── worst_practices ├── kittens.txt ├── library.rb └── user.rb /README: -------------------------------------------------------------------------------- 1 | == "Ruby Best Practices" Code Samples == 2 | 3 | This repository is for the code samples from the O'Reilly book "Ruby Best 4 | Practices" by Gregory Brown, available at: 5 | 6 | http://rubybestpractices.com 7 | 8 | All of the examples here are free software, but some license terms and proper 9 | attributions might be missing from code segments until the time of publication 10 | of the book. This means if you wish to re-use any code seen here, please 11 | contact me at gregory.t.brown@gmail.com and I'll let you know who wrote it and 12 | what the terms are. I will be sure to update these things before the book hits 13 | the shelf. 14 | 15 | Although the code samples might be an interesting teaser, they might not be 16 | useful out of context, as some of the examples seen here are actually 17 | anti-patterns to best practices. 18 | 19 | Eventually, all of the non-trivial code samples that appear in the book will be in 20 | this repository. For now, it will be incomplete and in constant flux. 21 | 22 | Enjoy, and please contact me with any questions. 23 | 24 | -greg 25 | -------------------------------------------------------------------------------- /dynamic_toolkit/blankslate.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #-- 3 | # Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org). 4 | # All rights reserved. 5 | 6 | # Permission is granted for use, copying, modification, distribution, 7 | # and distribution of modified versions of this work as long as the 8 | # above copyright notice is included. 9 | #++ 10 | 11 | ###################################################################### 12 | # BlankSlate provides an abstract base class with no predefined 13 | # methods (except for \_\_send__ and \_\_id__). 14 | # BlankSlate is useful as a base class when writing classes that 15 | # depend upon method_missing (e.g. dynamic proxies). 16 | # 17 | class BlankSlate 18 | class << self 19 | 20 | # Hide the method named +name+ in the BlankSlate class. Don't 21 | # hide +instance_eval+ or any method beginning with "__". 22 | def hide(name) 23 | if instance_methods.include?(name) and 24 | name !~ /^(__|instance_eval)/ 25 | @hidden_methods ||= {} 26 | @hidden_methods[name] = instance_method(name) 27 | undef_method name 28 | end 29 | end 30 | 31 | def find_hidden_method(name) 32 | @hidden_methods ||= {} 33 | @hidden_methods[name] || superclass.find_hidden_method(name) 34 | end 35 | 36 | # Redefine a previously hidden method so that it may be called on a blank 37 | # slate object. 38 | def reveal(name) 39 | unbound_method = find_hidden_method(name) 40 | fail "Don't know how to reveal method '#{name}'" unless unbound_method 41 | define_method(name, unbound_method) 42 | end 43 | end 44 | 45 | instance_methods.each { |m| hide(m) } 46 | end 47 | 48 | ###################################################################### 49 | # Since Ruby is very dynamic, methods added to the ancestors of 50 | # BlankSlate after BlankSlate is defined will show up in the 51 | # list of available BlankSlate methods. We handle this by defining a 52 | # hook in the Object and Kernel classes that will hide any method 53 | # defined after BlankSlate has been loaded. 54 | # 55 | module Kernel 56 | class << self 57 | alias_method :blank_slate_method_added, :method_added 58 | 59 | # Detect method additions to Kernel and remove them in the 60 | # BlankSlate class. 61 | def method_added(name) 62 | result = blank_slate_method_added(name) 63 | return result if self != Kernel 64 | BlankSlate.hide(name) 65 | result 66 | end 67 | end 68 | end 69 | 70 | ###################################################################### 71 | # Same as above, except in Object. 72 | # 73 | class Object 74 | class << self 75 | alias_method :blank_slate_method_added, :method_added 76 | 77 | # Detect method additions to Object and remove them in the 78 | # BlankSlate class. 79 | def method_added(name) 80 | result = blank_slate_method_added(name) 81 | return result if self != Object 82 | BlankSlate.hide(name) 83 | result 84 | end 85 | 86 | def find_hidden_method(name) 87 | nil 88 | end 89 | end 90 | end 91 | 92 | ###################################################################### 93 | # Also, modules included into Object need to be scanned and have their 94 | # instance methods removed from blank slate. In theory, modules 95 | # included into Kernel would have to be removed as well, but a 96 | # "feature" of Ruby prevents late includes into modules from being 97 | # exposed in the first place. 98 | # 99 | class Module 100 | alias blankslate_original_append_features append_features 101 | def append_features(mod) 102 | result = blankslate_original_append_features(mod) 103 | return result if mod != Object 104 | instance_methods.each do |name| 105 | BlankSlate.hide(name) 106 | end 107 | result 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /dynamic_toolkit/dynamic_method_definitions_as_test.rb: -------------------------------------------------------------------------------- 1 | module Test::Unit 2 | # Used to fix a minor minitest/unit incompatibility in flexmock 3 | AssertionFailedError = Class.new(StandardError) 4 | 5 | class TestCase 6 | 7 | def self.must(name, &block) 8 | test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym 9 | defined = instance_method(test_name) rescue false 10 | raise "#{test_name} is already defined in #{self}" if defined 11 | if block_given? 12 | define_method(test_name, &block) 13 | else 14 | define_method(test_name) do 15 | flunk "No implementation provided for #{name}" 16 | end 17 | end 18 | end 19 | 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /dynamic_toolkit/metrics_conversions.rb: -------------------------------------------------------------------------------- 1 | class Numeric 2 | def in 3 | self * 0.0254 4 | end 5 | 6 | def ft 7 | self.in * 12 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /dynamic_toolkit/naive_camping_routes.rb: -------------------------------------------------------------------------------- 1 | module NaiveCampingRoutes 2 | 3 | extend self 4 | 5 | def R(url) 6 | route_lookup = routes 7 | 8 | klass = Class.new 9 | meta = class << klass; self; end 10 | meta.send(:define_method, :inherited) do |base| 11 | raise "Already defined" if route_lookup[url] 12 | route_lookup[url] = base 13 | end 14 | klass 15 | end 16 | 17 | def routes 18 | @routes ||= {} 19 | end 20 | 21 | def process(url, params={}) 22 | routes[url].new.get(params) 23 | end 24 | end 25 | 26 | module NaiveCampingRoutes 27 | class Hello < R '/hello' 28 | def get(params) 29 | puts "hello #{params[:name]}" 30 | end 31 | end 32 | 33 | class Goodbye < R '/goodbye' 34 | def get(params) 35 | puts "goodbye #{params[:name]}" 36 | end 37 | end 38 | end 39 | 40 | NaiveCampingRoutes.process('/hello', :name => "greg") 41 | NaiveCampingRoutes.process('/goodbye', :name => "joe") 42 | 43 | -------------------------------------------------------------------------------- /dynamic_toolkit/pdf_conversions.rb: -------------------------------------------------------------------------------- 1 | class Numeric 2 | 3 | [:in, :ft].each do |e| 4 | if instance_methods.include?(e) 5 | raise "Method '#{e}' exists, PDF Conversions will not override!" 6 | end 7 | end 8 | 9 | 10 | def in 11 | self * 72 12 | end 13 | 14 | def ft 15 | self.in * 12 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /dynamic_toolkit/prawn_dsl.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "prawn" 3 | 4 | Prawn::Document.generate("hello.pdf") do 5 | 6 | font_size 16 do 7 | text "Hello, World" 8 | end 9 | 10 | text "Back to default font size" 11 | 12 | stroke_color "foo bar" 13 | stroke_line [100,100], [400,400] 14 | 15 | end 16 | -------------------------------------------------------------------------------- /dynamic_toolkit/prawn_standard.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "prawn" 3 | 4 | pdf = Prawn::Document.new 5 | 6 | original_font_size = pdf.font_size 7 | pdf.font_size = 16 8 | pdf.text "Hello, World" 9 | pdf.font_size = original_font_size 10 | pdf.text "Back to default font size" 11 | 12 | pdf.stroke_color = "ff00ff" 13 | pdf.line [100,100], [400,400] 14 | pdf.stroke 15 | 16 | pdf.render_file "hello.pdf" 17 | -------------------------------------------------------------------------------- /dynamic_toolkit/rubygems_custom_require.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. 3 | # All rights reserved. 4 | # See LICENSE.txt for permissions. 5 | #++ 6 | 7 | require 'rubygems' 8 | 9 | module Kernel 10 | 11 | ## 12 | # The Kernel#require from before RubyGems was loaded. 13 | 14 | alias gem_original_require require 15 | 16 | ## 17 | # When RubyGems is required, Kernel#require is replaced with our own which 18 | # is capable of loading gems on demand. 19 | # 20 | # When you call require 'x', this is what happens: 21 | # * If the file can be loaded from the existing Ruby loadpath, it 22 | # is. 23 | # * Otherwise, installed gems are searched for a file that matches. 24 | # If it's found in gem 'y', that gem is activated (added to the 25 | # loadpath). 26 | # 27 | # The normal require functionality of returning false if 28 | # that file has already been loaded is preserved. 29 | 30 | def require(path) # :doc: 31 | gem_original_require path 32 | rescue LoadError => load_error 33 | if load_error.message =~ /#{Regexp.escape path}\z/ and 34 | spec = Gem.searcher.find(path) then 35 | Gem.activate(spec.name, "= #{spec.version}") 36 | gem_original_require path 37 | else 38 | raise load_error 39 | end 40 | end 41 | 42 | private :require 43 | private :gem_original_require 44 | 45 | end 46 | -------------------------------------------------------------------------------- /dynamic_toolkit/simple_test_harness.rb: -------------------------------------------------------------------------------- 1 | class SimpleTestHarness 2 | 3 | class << self 4 | 5 | def inherited(base) 6 | tests << base 7 | end 8 | 9 | def tests 10 | @tests ||= [] 11 | end 12 | 13 | def run 14 | tests.each do |t| 15 | t.instance_methods.grep(/^test_/).each do |m| 16 | test_case = t.new 17 | test_case.setup if test_case.respond_to?(:setup) 18 | test_case.send(m) 19 | end 20 | end 21 | end 22 | end 23 | 24 | end 25 | 26 | class SimpleTest < SimpleTestHarness 27 | 28 | def setup 29 | puts "Setting up @string" 30 | @string = "Foo" 31 | end 32 | 33 | def test_string_must_be_foo 34 | answer = (@string == "Foo" ? "yes" : "no") 35 | puts "@string == 'Foo': " << answer 36 | end 37 | 38 | def test_string_must_be_bar 39 | answer = (@string == "bar" ? "yes" : "no") 40 | puts "@string == 'bar': " << answer 41 | end 42 | 43 | end 44 | 45 | class AnotherTest < SimpleTestHarness 46 | 47 | def test_another_lame_example 48 | puts "This got called, isn't that good enough?" 49 | end 50 | 51 | def helper_method 52 | puts "But you'll never see this" 53 | end 54 | 55 | def a_test_method 56 | puts "Or this" 57 | end 58 | 59 | end 60 | 61 | SimpleTestHarness.run 62 | 63 | -------------------------------------------------------------------------------- /stdlib/binding_with_logic_erb.rb: -------------------------------------------------------------------------------- 1 | require "erb" 2 | 3 | class A 4 | 5 | def initialize(x) 6 | @x = x 7 | end 8 | 9 | attr_reader :x 10 | 11 | public :binding 12 | 13 | def eval_template(string) 14 | ERB.new(string,0,'<>').result(binding) 15 | end 16 | 17 | end 18 | 19 | template = <<-EOS 20 | <% if x == 42 %> 21 | You have stumbled across the Answer to the Life, the Universe, and Everything 22 | <% else %> 23 | The value of x is <%= x %> 24 | <% end %> 25 | EOS 26 | 27 | foo = A.new(10) 28 | bar = A.new(21) 29 | baz = A.new(42) 30 | 31 | [foo, bar, baz].each { |e| puts e.eval_template(template) } 32 | -------------------------------------------------------------------------------- /stdlib/datetime_example.rb: -------------------------------------------------------------------------------- 1 | require "date" 2 | 3 | module OpenHours 4 | def open_at?(datetime) 5 | time_ranges.any? do |t| 6 | datetime.between?(*range(datetime, t)) 7 | end 8 | end 9 | 10 | def open_between?(start, finish) 11 | time_ranges.any? do |t| 12 | start.between?(*range(start, t)) && finish.between?(*range(finish, t)) 13 | end 14 | end 15 | 16 | protected 17 | 18 | def range(date, t) 19 | start_time = time(date, t.start_hour, t.start_minutes) 20 | end_time = time(date, t.end_hour, t.end_minutes) 21 | [start_time, end_time] 22 | end 23 | 24 | def time(date, hours, mins) 25 | DateTime.civil(date.year, date.month, date.day, hours, mins) 26 | end 27 | end 28 | 29 | 30 | TimeRange = Struct.new(:start_hour, :start_minutes, :end_hour, :end_minutes) 31 | 32 | class Business 33 | include OpenHours 34 | 35 | def initialize 36 | @time_ranges = [] 37 | end 38 | 39 | attr_accessor :time_ranges 40 | end 41 | 42 | b = Business.new 43 | b.time_ranges << TimeRange.new(9, 30, 12, 30) 44 | b.time_ranges << TimeRange.new(13, 0, 17, 0) 45 | 46 | b.open_at?(DateTime.parse("2008-12-08 9:45")) # => true 47 | b.open_at?(DateTime.parse("2008-12-08 12:45")) # => false 48 | 49 | b.open_between?(DateTime.parse("2008-12-08 13:00"), 50 | DateTime.parse("2008-12-08 16:00")) #=> true 51 | 52 | b.open_between?(DateTime.parse("2008-12-08 12:45"), 53 | DateTime.parse("2008-12-08 1:15")) #=> false 54 | -------------------------------------------------------------------------------- /stdlib/license.rb: -------------------------------------------------------------------------------- 1 | require "open-uri" 2 | 3 | puts open("http://www.ruby-lang.org/en/LICENSE.txt").read 4 | -------------------------------------------------------------------------------- /stdlib/mathn_examples.rb: -------------------------------------------------------------------------------- 1 | require "mathn" 2 | require "rubygems" 3 | require "prawn" 4 | 5 | include Math 6 | 7 | class Canvas < Prawn::Document 8 | 9 | def self.draw(filename, dims=[300,300], &block) 10 | width, height = dims 11 | 12 | generate(filename) do 13 | bounding_box [bounds.width / 2, bounds.height / 2 + height ], 14 | :width => width, :height => height do 15 | instance_eval(&block) 16 | end 17 | end 18 | 19 | end 20 | 21 | def paint_triangle(*points) 22 | fill_polygon *points 23 | end 24 | 25 | end 26 | 27 | Canvas.draw("triangles.pdf") do 28 | points = Matrix[[0,0], [100,200], [200,100]] 29 | 30 | paint_triangle(*points) 31 | 32 | # reflect across y-axis 33 | points *= Matrix[[-1, 0],[0,1]] 34 | 35 | # rotate so bottom is flush with x axis. 36 | theta = -atan(1/2) 37 | points *= Matrix[[cos(theta), -sin(theta)], 38 | [sin(theta), cos(theta)] ] 39 | 40 | # scale down by 50% 41 | points *= 1/2 42 | paint_triangle(*points) 43 | end 44 | 45 | 46 | 47 | 48 | `open triangles.pdf` 49 | -------------------------------------------------------------------------------- /stdlib/payment_summary.csv: -------------------------------------------------------------------------------- 1 | name,total payments 2 | Gregory Brown,225 3 | Joe Comfort,150 4 | Jon Juraschka,450 5 | Jia Wu,100 6 | -------------------------------------------------------------------------------- /stdlib/payment_summary.rb: -------------------------------------------------------------------------------- 1 | 2 | require "csv" 3 | 4 | @totals = Hash.new(0) 5 | 6 | csv_options = {:headers => true, :converters => :all } 7 | 8 | CSV.foreach("payments.csv", csv_options) do |row| 9 | @totals[row['name']] += row['payment'] 10 | end 11 | 12 | CSV.open("payment_summary.csv", "w") do |csv| 13 | csv << ["name","total payments"] 14 | @totals.each { |row| csv << row } 15 | end 16 | -------------------------------------------------------------------------------- /stdlib/payments.csv: -------------------------------------------------------------------------------- 1 | name,payment 2 | Gregory Brown,100 3 | Joe Comfort,150 4 | Jon Juraschka,200 5 | Gregory Brown, 75 6 | Jon Juraschka, 250 7 | Jia Wu, 25 8 | Gregory Brown, 50 9 | Jia Wu, 75 10 | -------------------------------------------------------------------------------- /stdlib/phone_numbers.csv: -------------------------------------------------------------------------------- 1 | applicant,phone_number,spouse,phone_number 2 | James Gray, 555 555 5555, Dana Gray, 123 456 7890 3 | Gregory Brown, 098 765 4321, Jia Wu, 222 222 2222 4 | -------------------------------------------------------------------------------- /stdlib/pretty_printing.rb: -------------------------------------------------------------------------------- 1 | require "pp" 2 | 3 | class Person 4 | def initialize(first_name, last_name, friends) 5 | @first_name, @last_name, @friends = first_name, last_name, friends 6 | end 7 | 8 | def pretty_print(printer) 9 | printer.text "Person <#{object_id}>:\n" << 10 | " Name: #@first_name #@last_name\n Friends:\n" 11 | @friends.each do |f| 12 | printer.text " #{f[:first_name]} #{f[:last_name]}\n" 13 | end 14 | end 15 | 16 | alias_method :inspect, :pretty_print_inspect 17 | 18 | end 19 | 20 | friends = [ { first_name: "Emily", last_name: "Laskin" }, 21 | { first_name: "Nick", last_name: "Mauro" }, 22 | { first_name: "Mark", last_name: "Maxwell" } ] 23 | 24 | p Person.new("Gregory", "Brown", friends).pretty_print_inspect 25 | -------------------------------------------------------------------------------- /stdlib/scheduler.rb: -------------------------------------------------------------------------------- 1 | require "date" 2 | 3 | class Scheduler 4 | 5 | def initialize 6 | @events = [] 7 | end 8 | 9 | def event(from, to, message) 10 | @events << [DateTime.parse(from) .. DateTime.parse(to), message] 11 | end 12 | 13 | def display_events_at(datetime) 14 | datetime = DateTime.parse(datetime) 15 | puts "Events occuring around #{datetime.strftime("%H:%M on %m/%d/%Y")}" 16 | puts "--------------------------------------------" 17 | events_at(datetime).each do |range, message| 18 | puts "#{time_abbrev(range.first)} - #{time_abbrev(range.last)}: #{message}" 19 | end 20 | end 21 | 22 | private 23 | 24 | def time_abbrev(datetime) 25 | datetime.strftime("%H:%M (%m/%d)") 26 | end 27 | 28 | def events_at(datetime) 29 | @events.each_with_object([]) do |event, matched| 30 | matched << event if event.first.cover?(datetime) 31 | end 32 | end 33 | 34 | end 35 | 36 | 37 | sched = Scheduler.new 38 | sched.event "2009.02.04 10:00", "2009.02.04 11:30", "Eat Snow" 39 | sched.event "2009.02.03 14:00", "2009.02.04 14:00", "Wear Special Suit" 40 | sched.display_events_at '2009.02.04 10:20' 41 | -------------------------------------------------------------------------------- /testing/Rakefile: -------------------------------------------------------------------------------- 1 | require "rake/testtask" 2 | 3 | task :default => [:test] 4 | 5 | Rake::TestTask.new do |test| 6 | test.libs << "test" 7 | test.test_files = Dir[ "test/test_*.rb" ] 8 | test.verbose = true 9 | end 10 | 11 | -------------------------------------------------------------------------------- /testing/prawn_inline_styles.rb: -------------------------------------------------------------------------------- 1 | # this file requires Prawn to run. 2 | 3 | require "rubygems" 4 | require "prawn" 5 | require "test/unit" 6 | require "test_unit_extensions" 7 | 8 | class Prawn::Document 9 | module Text::StyleParser 10 | extend self 11 | 12 | TAG_PATTERN = %r{()} 13 | 14 | def process(text) #:nodoc: 15 | segments = text.split(TAG_PATTERN).delete_if{|x| x.empty? } 16 | end 17 | 18 | def style_tag?(text) 19 | !!(text =~ TAG_PATTERN) 20 | end 21 | end 22 | end 23 | 24 | class TestInlineStyleParsing < Test::Unit::TestCase 25 | 26 | def setup 27 | @parser = Prawn::Document::Text::StyleParser 28 | end 29 | 30 | must "parse italic tags" do 31 | assert_equal ["Hello ", "", "Fine", "", " World"], 32 | @parser.process("Hello Fine World") 33 | end 34 | 35 | must "parse bold tags" do 36 | assert_equal ["Some very ", "", "bold text", ""], 37 | @parser.process("Some very bold text") 38 | end 39 | 40 | must "parse mixed italic and bold tags" do 41 | assert_equal ["Hello ", "", "Fine ", "", "World", "", ""], 42 | @parser.process("Hello Fine World") 43 | end 44 | 45 | must "not split out other tags than , , , " do 46 | assert_equal ["Hello Ch", "", "arl", "", "ie"], 47 | @parser.process("Hello Charlie") 48 | end 49 | 50 | must "be able to check whether a string needs to be parsed" do 51 | assert @parser.style_tag?("Hello Fine World") 52 | assert !@parser.style_tag?("Hello World") 53 | end 54 | 55 | end 56 | -------------------------------------------------------------------------------- /testing/test/test_bar.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | 3 | class TestBar < Test::Unit::TestCase 4 | def test_flunk 5 | flunk 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /testing/test/test_foo.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | 3 | class TestFoo < Test::Unit::TestCase 4 | 5 | def test_flunk 6 | flunk 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /testing/test_blog.rb: -------------------------------------------------------------------------------- 1 | require "builder" 2 | require "ostruct" 3 | 4 | class Blog < OpenStruct 5 | 6 | def entries 7 | @entries ||= [] 8 | end 9 | 10 | def to_rss 11 | xml = Builder::XmlMarkup.new 12 | xml.instruct! 13 | xml.rss version: "2.0" do 14 | xml.channel do 15 | xml.title title 16 | xml.link "http://#{domain}/" 17 | xml.description description 18 | xml.language "en-us" 19 | 20 | @entries.each do |entry| 21 | xml.item do 22 | xml.title entry.title 23 | xml.description entry.description 24 | xml.author author 25 | xml.pubDate entry.published_date 26 | xml.link entry.url 27 | xml.guid entry.url 28 | end 29 | end 30 | end 31 | end 32 | end 33 | 34 | end 35 | 36 | require "test/unit" 37 | require "test_unit_extensions" 38 | 39 | require "time" 40 | require "nokogiri" 41 | 42 | class BlogTest < Test::Unit::TestCase 43 | 44 | def setup 45 | @blog = Blog.new 46 | @blog.title = "Awesome" 47 | @blog.domain = "majesticseacreature.com" 48 | @blog.description = "Totally awesome" 49 | @blog.author = "Gregory Brown" 50 | 51 | entry = OpenStruct.new 52 | entry.title = "First Post" 53 | entry.description = "Nothing interesting" 54 | entry.published_date = Time.parse("08/08/2008") 55 | entry.url = "http://majesticseacreature.com/awesome.html" 56 | 57 | @blog.entries << entry 58 | @feed = Nokogiri::XML(@blog.to_rss) 59 | end 60 | 61 | must "be RSS v 2.0" do 62 | assert_equal "2.0", @feed.at("rss")["version"] 63 | end 64 | 65 | must "have a title of Awesome" do 66 | assert_equal "Awesome", text_at("rss","title") 67 | end 68 | 69 | must "have a description of Totally Awesome" do 70 | assert_equal "Totally awesome", text_at("rss", "description") 71 | end 72 | 73 | must "have an author of Gregory Brown" do 74 | assert_equal "Gregory Brown", text_at("rss", "author") 75 | end 76 | 77 | must "have an entry with the title: First Post" do 78 | assert_equal "First Post", text_at("item", "title") 79 | end 80 | 81 | def text_at(*args) 82 | args.inject(@feed) { |s,r| s.send(:at, r) }.inner_text 83 | end 84 | 85 | end 86 | -------------------------------------------------------------------------------- /testing/test_lock_box.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "test_unit_extensions" 3 | 4 | class LockBox 5 | 6 | UnauthorizedAccess = Class.new(StandardError) 7 | InvalidPassword = Class.new(StandardError) 8 | 9 | def initialize(options) 10 | @locked = true 11 | @password = options[:password] 12 | @content = options[:content] 13 | end 14 | 15 | def unlock(pass) 16 | @password == pass ? @locked = false : raise(InvalidPassword) 17 | end 18 | 19 | def content 20 | @locked ? raise(UnauthorizedAccess) : @content 21 | end 22 | end 23 | 24 | class LockBoxTest < Test::Unit::TestCase 25 | 26 | def setup 27 | @lock_box = LockBox.new( password: "secret", 28 | content: "My Secret Message" ) 29 | end 30 | 31 | must "raise an error when an invalid password is used" do 32 | assert_raises(LockBox::InvalidPassword) do 33 | @lock_box.unlock("kitten") 34 | end 35 | end 36 | 37 | must "Not raise error when a valid password is used" do 38 | assert_nothing_raised do 39 | @lock_box.unlock("secret") 40 | end 41 | end 42 | 43 | must "prevent access to content by default" do 44 | assert_raises(LockBox::UnauthorizedAccess) do 45 | @lock_box.content 46 | end 47 | end 48 | 49 | must "allow access to content when box is properly unlocked" do 50 | assert_nothing_raised do 51 | @lock_box.unlock("secret") 52 | @lock_box.content 53 | end 54 | end 55 | 56 | end 57 | -------------------------------------------------------------------------------- /testing/test_questioner.rb: -------------------------------------------------------------------------------- 1 | class Questioner 2 | 3 | def initialize(input=STDIN,out=STDOUT) 4 | @input = input 5 | @output = out 6 | end 7 | 8 | def inquire_about_happiness 9 | ask("Are you happy?") ? "Good I'm Glad" : "That's Too Bad" 10 | end 11 | 12 | def ask(question) 13 | @output.puts question 14 | response = @input.gets.chomp 15 | case(response) 16 | when /^y(es)?$/i 17 | true 18 | when /^no?$/i 19 | false 20 | else 21 | @output.puts "I don't understand." 22 | ask question 23 | end 24 | end 25 | 26 | end 27 | 28 | require "test/unit" 29 | require "test_unit_extensions" 30 | 31 | require "flexmock/test_unit" 32 | 33 | class QuestionerTest < Test::Unit::TestCase 34 | 35 | def setup 36 | @input = flexmock("input") 37 | @output = flexmock("output") 38 | @questioner = Questioner.new(@input,@output) 39 | @question = "Are you happy?" 40 | end 41 | 42 | ["y", "Y", "YeS", "YES", "yes"].each do |y| must "ask #{@question} and returns true when given #{y}" do 43 | expect_output @question 44 | provide_input(y) 45 | assert @questioner.ask(@question), "Expected '#{y}' to be true" 46 | end 47 | end 48 | 49 | ["n", "N", "no", "nO"].each do |no| 50 | must "ask #{@question} return false when parsing #{no}" do 51 | expect_output @question 52 | provide_input(no) 53 | assert !@questioner.ask(@question) 54 | end 55 | end 56 | 57 | [["y", true], ["n", false]].each do |input, state| 58 | must "continue to ask for input until given #{input}" do 59 | %w[Yesterday North kittens].each do |i| 60 | expect_output @question 61 | provide_input(i) 62 | expect_output("I don't understand.") 63 | end 64 | 65 | expect_output @question 66 | provide_input(input) 67 | 68 | assert_equal state, @questioner.ask(@question) 69 | end 70 | end 71 | 72 | def provide_input(string) 73 | @input.should_receive(:gets => string).once 74 | end 75 | 76 | def expect_output(string) 77 | @output.should_receive(:puts).with(string).once 78 | end 79 | 80 | end 81 | 82 | 83 | -------------------------------------------------------------------------------- /testing/test_unit_extensions.rb: -------------------------------------------------------------------------------- 1 | module Test::Unit 2 | # Used to fix a minor minitest/unit incompatibility in flexmock 3 | AssertionFailedError = Class.new(StandardError) 4 | 5 | class TestCase 6 | 7 | def self.must(name, &block) 8 | test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym 9 | defined = instance_method(test_name) rescue false 10 | raise "#{test_name} is already defined in #{self}" if defined 11 | if block_given? 12 | define_method(test_name, &block) 13 | else 14 | define_method(test_name) do 15 | flunk "No implementation provided for #{name}" 16 | end 17 | end 18 | end 19 | 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /testing/test_volume_bad.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "test_unit_extensions" 3 | 4 | 5 | def volume(*args) 6 | if Hash === args.first 7 | x,y,z = [:length,:width,:height].map { |e| args.first[e] || 1 } 8 | else 9 | x,y,z = 3.times.map { |i| args[i] || 1 } 10 | end 11 | x*y*z 12 | end 13 | 14 | class VolumeTest < Test::Unit::TestCase 15 | must "compute volume based on length, width, and height" do 16 | # defaults to l=w=h=1 17 | assert_equal 1, volume 18 | 19 | #when given 1 arg, set l=x, set w,h = 1 20 | x = 6 21 | assert_equal x, volume(x) 22 | 23 | # when given 2 args, set l=x, w=y and h=1 24 | y = 2 25 | assert_equal x*y, volume(x,y) 26 | 27 | # when given 3 args, set l=x, w=y and h=z 28 | z = 7 29 | assert_equal x*y*z, volume(x,y,z) 30 | 31 | # when given a hash, use :length, :width, :height 32 | assert_equal x*y*z, volume(length: x, width: y, height: z) 33 | 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /testing/test_volume_good.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "test_unit_extensions" 3 | 4 | 5 | def volume(*args) 6 | if Hash === args.first 7 | x,y,z = [:length,:width,:height].map { |e| args.first[e] || 1 } 8 | else 9 | x,y,z = 3.times.map { |i| args[i] || 1 } 10 | end 11 | x*y*z 12 | end 13 | 14 | class VolumeTest < Test::Unit::TestCase 15 | 16 | must "return 1 by default if no arguments are given" do 17 | # defaults to l=w=h=1 18 | assert_equal 1, volume 19 | end 20 | 21 | must "set l=x, set w,h = 1 when given 1 numeric argument" do 22 | x = 6 23 | assert_equal x, volume(x) 24 | end 25 | 26 | must "set l=x, w=y, and h=1 when given 2 arguments" do 27 | x, y = 6, 2 28 | assert_equal x*y, volume(x,y) 29 | end 30 | 31 | must "set l=x, w=y, and h=z when given 3 arguments" do 32 | x,y,z = 6, 2, 7 33 | assert_equal x*y*z, volume(x,y,z) 34 | end 35 | 36 | must "use :length, :width, and :height when given a hash argument" do 37 | x,y,z = 6, 2, 7 38 | assert_equal x*y*z, volume(length: x, width: y, height: z) 39 | end 40 | 41 | end 42 | -------------------------------------------------------------------------------- /when_things_go_wrong/case_study.rb: -------------------------------------------------------------------------------- 1 | Application = Struct.new(:state) 2 | 3 | class User 4 | def initialize 5 | @applications = [] 6 | end 7 | 8 | attr_reader :applications 9 | 10 | def can_renew? 11 | return false if applications.empty? 12 | applications.all? { |e| [:accepted, :rejected].include?(e.state) } 13 | end 14 | end 15 | 16 | require "test/unit" 17 | 18 | class UserTest < Test::Unit::TestCase 19 | 20 | def setup 21 | @gregory = User.new 22 | end 23 | 24 | def test_a_new_applicant_cannot_renew 25 | assert_block("Expected User#can_renew? to be false for a new applicant") do 26 | not @gregory.can_renew? 27 | end 28 | end 29 | 30 | def test_a_user_with_pending_applications_cannot_renew 31 | @gregory.applications << app(:accepted) << app(:pending) 32 | 33 | msg = "Expected User#can_renew? to be false when user has pending applications" 34 | assert_block(msg) do 35 | not @gregory.can_renew? 36 | end 37 | end 38 | 39 | def test_a_user_with_only_accepted_and_rejected_applications_can_renew 40 | @gregory.applications << app(:accepted) << app(:rejected) << app(:accepted) 41 | msg = "Expected User#can_renew? to be true when all applications are accepted or rejected" 42 | assert_block(msg) { @gregory.can_renew? } 43 | end 44 | 45 | private 46 | 47 | def app(name) 48 | Application.new(name) 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /when_things_go_wrong/client.rb: -------------------------------------------------------------------------------- 1 | require "socket" 2 | class Client 3 | 4 | def initialize(ip="localhost",port=3333) 5 | @ip, @port = ip, port 6 | end 7 | def send_message(msg) 8 | socket = TCPSocket.new(@ip,@port) 9 | socket.puts(msg) 10 | response = socket.gets 11 | socket.close 12 | return response 13 | end 14 | 15 | def receive_message 16 | socket = TCPSocket.new(@ip,@port) 17 | response = socket.read 18 | socket.close 19 | return response 20 | end 21 | 22 | end 23 | 24 | client = Client.new 25 | 26 | response = client.send_message("* 5 10") 27 | puts response 28 | 29 | response = client.send_message("/ 4 3") 30 | puts response 31 | 32 | response = client.send_message("/ 3 foo") 33 | puts response 34 | 35 | response = client.send_message("* 5 7.2") 36 | puts response 37 | -------------------------------------------------------------------------------- /when_things_go_wrong/foo.rb: -------------------------------------------------------------------------------- 1 | begin 2 | $ 5 3 | rescue SyntaxError 4 | nil 5 | end 6 | -------------------------------------------------------------------------------- /when_things_go_wrong/grouping.rb: -------------------------------------------------------------------------------- 1 | class Grouping 2 | 3 | def initialize(values, by) 4 | @data = Hash.new { |h,k| h[k] = [] } 5 | values.each do |record| 6 | @data[record[by]] << record 7 | end 8 | end 9 | 10 | attr_reader :data 11 | 12 | def [](value) 13 | @data[value] 14 | end 15 | 16 | end 17 | 18 | records = [ { name: 'Gregory', payment: 10 }, 19 | { name: 'Rudolph', payment: 27 }, 20 | { name: 'Gregory', payment: 25 }, 21 | { name: 'Gregory', payment: 35 }, 22 | { name: 'Rudolph', payment: 16 } ] 23 | 24 | grouping = Grouping.new(records, :name) 25 | p grouping['Gregory'].map { |e| e[:payment] } #=> [10, 25, 35] 26 | p grouping['Rudolph'].map { |e| e[:payment] } #=> [27, 16] 27 | -------------------------------------------------------------------------------- /when_things_go_wrong/html_report.rb: -------------------------------------------------------------------------------- 1 | require "csv" 2 | 3 | class Report 4 | 5 | STATUS = { "A" => "Active", "I" => "Inactive", "S" => "Suspended" } 6 | 7 | def initialize(file) 8 | @file = file 9 | end 10 | 11 | def to_html 12 | output = "\n" 13 | CSV.foreach(@file) do |row| 14 | row[-1] = STATUS[row[-1]] 15 | output << " \n" 16 | end 17 | output << "
#{row.join("")}
" 18 | end 19 | 20 | end 21 | 22 | def track_time(message) 23 | puts message 24 | t = Time.now 25 | yield 26 | puts "Took #{Time.now - t}s" 27 | end 28 | 29 | 30 | track_time "CSV TO HTML" do 31 | @report = Report.new("report_data.csv") 32 | @report.to_html 33 | end 34 | -------------------------------------------------------------------------------- /when_things_go_wrong/irb_debugger.rb: -------------------------------------------------------------------------------- 1 | require "irb" 2 | 3 | IRB.setup(nil) 4 | IRB.conf[:MAIN_CONTEXT] = IRB::Context.new(IRB::Irb.new) 5 | require "irb/ext/multi-irb" 6 | 7 | def run_irb(context=self) 8 | IRB.irb(nil,context) 9 | end 10 | -------------------------------------------------------------------------------- /when_things_go_wrong/irb_debugging_example.rb: -------------------------------------------------------------------------------- 1 | require "irb_debugger" 2 | 3 | class Divider 4 | def initialize(x,y) 5 | @x, @y = x, y 6 | end 7 | 8 | def divide 9 | @x / @y 10 | rescue StandardError => e 11 | puts "Encountered an #{e.class.name}, firing up irb" 12 | run_irb(binding) 13 | puts "Trying your fix..." 14 | divide 15 | end 16 | end 17 | 18 | d = Divider.new(4,0) 19 | puts "Returns: #{d.divide}" 20 | -------------------------------------------------------------------------------- /when_things_go_wrong/prawn_canvas.rb: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | 3 | gem "prawn", "=0.2.0" 4 | 5 | require "prawn" 6 | 7 | require "test/unit" 8 | 9 | class CanvasTest < Test::Unit::TestCase 10 | 11 | def setup 12 | @pdf = Prawn::Document.new 13 | end 14 | 15 | def test_canvas_should_not_reset_y_to_zero 16 | after_text_position = nil 17 | 18 | @pdf.canvas do 19 | @pdf.text "Hello World" 20 | after_text_position = @pdf.y 21 | end 22 | 23 | assert_equal after_text_position, @pdf.y 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /when_things_go_wrong/processor.rb: -------------------------------------------------------------------------------- 1 | data = File.foreach("out.txt").map do |line| 2 | line.split(/\s+/) 3 | end 4 | 5 | ms_call_index = data[1].index("ms/call") 6 | calls_index = data[1].index("calls") 7 | name_index = data[1].index("name") 8 | 9 | sorted = data[2..-1].sort_by { |e| 10 | Float(e[-2]) * Float(e[-4]) } 11 | sorted.reverse.first(25).each do |e| 12 | puts "#{e[-1]} #{e[-2]} (x#{e[-4]})" 13 | end 14 | -------------------------------------------------------------------------------- /when_things_go_wrong/rbprofile.rb: -------------------------------------------------------------------------------- 1 | require 'rbprofiler' 2 | 3 | RubyVM::InstructionSequence.compile_option = { 4 | :trace_instruction => true, 5 | :specialized_instruction => false 6 | } 7 | END { 8 | RBProfiler.print_profile(STDERR) 9 | } 10 | RBProfiler.start_profile 11 | -------------------------------------------------------------------------------- /when_things_go_wrong/rbprofiler.rb: -------------------------------------------------------------------------------- 1 | # This is the profiler.rb standard library from Ruby 1.9.1 with minor 2 | # modifications. 3 | 4 | module RBProfiler 5 | # internal values 6 | @@start = @@stack = @@map = nil 7 | PROFILE_PROC = proc{|event, file, line, id, binding, klass| 8 | case event 9 | when "call", "c-call" 10 | now = Process.times[0] 11 | @@stack.push [now, 0.0] 12 | when "return", "c-return" 13 | now = Process.times[0] 14 | key = [klass, id] 15 | if tick = @@stack.pop 16 | data = (@@map[key] ||= [0, 0.0, 0.0, key]) 17 | data[0] += 1 18 | cost = now - tick[0] 19 | data[1] += cost 20 | data[2] += cost - tick[1] 21 | @@stack[-1][1] += cost if @@stack[-1] 22 | end 23 | end 24 | } 25 | module_function 26 | 27 | def start_profile 28 | @@start = Process.times[0] 29 | @@stack = [] 30 | @@map = {} 31 | set_trace_func PROFILE_PROC 32 | end 33 | 34 | def stop_profile 35 | set_trace_func nil 36 | end 37 | 38 | def print_profile(f) 39 | stop_profile 40 | total = Process.times[0] - @@start 41 | if total == 0 then total = 0.01 end 42 | data = @@map.values 43 | data.sort!{|a,b| b[1] <=> a[1] } 44 | sum = 0 45 | f.printf " %% cumulative self self total\n" 46 | f.printf " time seconds seconds calls ms/call ms/call name\n" 47 | for d in data 48 | sum += d[2] 49 | f.printf "%6.2f %8.2f %8.2f %8d ", d[1]/total*100, sum, d[2], d[0] 50 | f.printf "%8.2f %8.2f %s\n", d[2]*1000/d[0], d[1]*1000/d[0], get_name(*d[3]) 51 | end 52 | f.printf "%6.2f %8.2f %8.2f %8d ", 0.0, total, 0.0, 1 # ??? 53 | f.printf "%8.2f %8.2f %s\n", 0.0, total*1000, "#toplevel" # ??? 54 | end 55 | 56 | def get_name(klass, id) 57 | name = klass.to_s || "" 58 | if klass.kind_of? Class 59 | name += "#" 60 | else 61 | name += "." 62 | end 63 | name + id.id2name 64 | end 65 | private :get_name 66 | end 67 | 68 | -------------------------------------------------------------------------------- /when_things_go_wrong/report_bench.rb: -------------------------------------------------------------------------------- 1 | require "report_unoptimized" 2 | require "report_optimized" 3 | require "report_line_processor" 4 | 5 | require "benchmark" 6 | require "csv" 7 | 8 | Benchmark.bmbm do |x| 9 | 10 | x.report("inject based to_html") do 11 | csv_data = CSV.read("report_data.csv") 12 | r1 = ReportUnoptimized.new(csv_data) 13 | r1.to_html 14 | end 15 | 16 | x.report("each_with_object based to_html") do 17 | csv_data = CSV.read("report_data.csv") 18 | r2 = ReportOptimized.new(csv_data) 19 | r2.to_html 20 | end 21 | 22 | x.report("line processor") do 23 | r3 = ReportLineProcessor.new("report_data.csv") 24 | r3.to_html 25 | end 26 | 27 | 28 | end 29 | -------------------------------------------------------------------------------- /when_things_go_wrong/report_line_processor.rb: -------------------------------------------------------------------------------- 1 | require "csv" 2 | 3 | class ReportLineProcessor 4 | 5 | STATUS = { "A" => "Active", "I" => "Inactive", "S" => "Suspended" } 6 | 7 | def initialize(file) 8 | @file = file 9 | end 10 | 11 | def to_html 12 | output = "\n" 13 | CSV.foreach(@file) do |row| 14 | row[-1] = STATUS[row[-1]] 15 | output << " \n" 16 | end 17 | output << "
#{row.join("")}
" 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /when_things_go_wrong/report_optimized.rb: -------------------------------------------------------------------------------- 1 | class ReportOptimized 2 | 3 | STATUS = { "A" => "Active", "I" => "Inactive", "S" => "Suspended" } 4 | 5 | def initialize(data) 6 | @data = data 7 | update_status 8 | end 9 | 10 | def update_status 11 | @data.each { |e| e[3] = STATUS[e[3]] } 12 | end 13 | 14 | def to_html 15 | @data.each_with_object("\n") do |row, output| 16 | output << " \n" 17 | end << "
#{row.join("")}
" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /when_things_go_wrong/report_unoptimized.rb: -------------------------------------------------------------------------------- 1 | class ReportUnoptimized 2 | 3 | STATUS = { "A" => "Active", "I" => "Inactive", "S" => "Suspended" } 4 | 5 | def initialize(data) 6 | @data = data 7 | update_status 8 | end 9 | 10 | def update_status 11 | @data.each { |e| e[3] = STATUS[e[3]] } 12 | end 13 | 14 | def to_html 15 | @data.inject("\n") do |output, row| 16 | output + " \n" 17 | end << "
#{row.join("")}
" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /when_things_go_wrong/server_logging.rb: -------------------------------------------------------------------------------- 1 | require "socket" 2 | require "logger" 3 | 4 | class StandardError 5 | def report 6 | "#{self.class}: #{message}\n#{backtrace.join($/)}" 7 | end 8 | end 9 | 10 | class Server 11 | 12 | def initialize(logger) 13 | @logger = logger 14 | @server = TCPServer.new('localhost',port=3333) 15 | end 16 | 17 | def *(x, y) 18 | "#{Float(x) * Float(y)}" 19 | end 20 | 21 | def /(x, y) 22 | "#{Float(x) / Float(y)}" 23 | end 24 | 25 | def handle_request(session) 26 | action, *args = session.gets.split(/\s/) 27 | if ["*", "/"].include?(action) 28 | @logger.info "executing: '#{action}' with #{args.inspect}" 29 | session.puts(send(action, *args)) 30 | else 31 | session.puts("Invalid command") 32 | end 33 | rescue StandardError => e 34 | @logger.error(e.report) 35 | session.puts "Sorry, something went wrong." 36 | end 37 | 38 | def run 39 | while session = @server.accept 40 | handle_request(session) 41 | end 42 | end 43 | end 44 | 45 | begin 46 | logger = Logger.new("development.log") 47 | host = Server.new(logger) 48 | 49 | host.run 50 | rescue StandardError => e 51 | logger.fatal(e.report) 52 | puts "Something seriously bad just happened, exiting" 53 | end 54 | -------------------------------------------------------------------------------- /when_things_go_wrong/server_logging_initial.rb: -------------------------------------------------------------------------------- 1 | require "socket" 2 | 3 | 4 | class Server 5 | 6 | def initialize 7 | @server = TCPServer.new('localhost',port=3333) 8 | end 9 | 10 | def *(x, y) 11 | "#{Float(x) * Float(y)}" 12 | end 13 | 14 | def /(x, y) 15 | "#{Float(x) / Float(y)}" 16 | end 17 | 18 | def handle_request(session) 19 | action, *args = session.gets.split(/\s/) 20 | session.puts(send(action, *args)) 21 | end 22 | 23 | def run 24 | while session = @server.accept 25 | handle_request(session) 26 | end 27 | end 28 | 29 | end 30 | 31 | Server.new.run 32 | -------------------------------------------------------------------------------- /worst_practices/kittens.txt: -------------------------------------------------------------------------------- 1 | The kittens make me happy. They are hidden between the pages of this book, 2 | secretly smiling at you while you read. 3 | 4 | Wasn't that a great story? 5 | -------------------------------------------------------------------------------- /worst_practices/library.rb: -------------------------------------------------------------------------------- 1 | module FattyRBP 2 | class Formatter 3 | 4 | def self.formats 5 | @formats ||= {} 6 | end 7 | 8 | def self.format(name, options={}, &block) 9 | formats[name] = Class.new(FattyRBP::Format, &block) 10 | end 11 | 12 | def self.render(format, options={}) 13 | formats[format].new(options).render 14 | end 15 | end 16 | 17 | class Format 18 | def initialize(options) 19 | # not important 20 | end 21 | end 22 | end 23 | 24 | 25 | class Hello < FattyRBP::Formatter 26 | format :text do 27 | def render 28 | "Hello World" 29 | end 30 | end 31 | 32 | format :html do 33 | def render 34 | "Hello World" 35 | end 36 | end 37 | end 38 | 39 | puts Hello.render(:text) 40 | puts Hello.render(:html) 41 | 42 | 43 | class Goodbye < FattyRBP::Formatter 44 | format :text do 45 | def render 46 | "Goodbye Cruel World!" 47 | end 48 | end 49 | end 50 | 51 | puts Goodbye.render(:text) 52 | 53 | 54 | # Should not have changed 55 | puts Hello.render(:text) 56 | 57 | # Shouldn't exist 58 | puts Goodbye.render(:html) 59 | -------------------------------------------------------------------------------- /worst_practices/user.rb: -------------------------------------------------------------------------------- 1 | require "pstore" 2 | 3 | class User 4 | 5 | def self.data 6 | @data ||= PStore.new("users.store") 7 | end 8 | 9 | def self.add(id, user_data) 10 | data.transaction do 11 | data[id] = user_data 12 | end 13 | end 14 | 15 | def self.find(id) 16 | data.transaction do 17 | data[id] or raise "User not found" 18 | end 19 | end 20 | 21 | def initialize(id) 22 | @user_id = id 23 | end 24 | 25 | def attributes 26 | self.class.find(@user_id) 27 | end 28 | 29 | def first_name 30 | attributes[:first_name] 31 | end 32 | 33 | end 34 | --------------------------------------------------------------------------------