├── 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{(?[ib]>)}
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 << " #{row.join(" | ")} |
\n"
16 | end
17 | output << "
"
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 << " #{row.join(" | ")} |
\n"
16 | end
17 | output << "
"
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 << " #{row.join(" | ")} |
\n"
17 | end << "
"
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 + " #{row.join(" | ")} |
\n"
17 | end << "
"
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 |
--------------------------------------------------------------------------------