├── koans ├── example_file.txt ├── koans.watchr ├── code_mash.rb ├── test_helper.rb ├── first_test.rb ├── Rakefile ├── about_extra_credit.rb ├── triangle.rb ├── about_triangle_project_2.rb ├── about_true_and_false.rb ├── about_triangle_project.rb ├── about_open_classes.rb ├── about_asserts.rb ├── path_to_enlightenment.rb ├── about_nil.rb ├── array_test.rb ├── about_modules.rb ├── about_to_str.rb ├── about_array_assignment.rb ├── about_objects.rb ├── about_dice_project.rb ├── about_exceptions.rb ├── about_inheritance.rb ├── about_scope.rb ├── about_scoring_project.rb ├── about_arrays.rb ├── about_constants.rb ├── GREED_RULES.txt ├── about_blocks.rb ├── about_symbols.rb ├── about_sandwich_code.rb ├── about_control_statements.rb ├── about_hashes.rb ├── about_iteration.rb ├── about_proxy_object_project.rb ├── about_methods.rb ├── about_class_methods.rb ├── about_java_interop.rb ├── about_classes.rb ├── about_message_passing.rb ├── about_regular_expressions.rb ├── about_strings.rb └── README.rdoc ├── src ├── example_file.txt ├── koans.watchr ├── code_mash.rb ├── test_helper.rb ├── Rakefile ├── first_test.rb ├── about_extra_credit.rb ├── about_triangle_project_2.rb ├── triangle.rb ├── about_triangle_project.rb ├── about_true_and_false.rb ├── about_open_classes.rb ├── path_to_enlightenment.rb ├── about_nil.rb ├── array_test.rb ├── about_asserts.rb ├── about_modules.rb ├── about_to_str.rb ├── about_array_assignment.rb ├── about_objects.rb ├── about_dice_project.rb ├── about_exceptions.rb ├── about_inheritance.rb ├── about_scope.rb ├── about_constants.rb ├── about_arrays.rb ├── GREED_RULES.txt ├── about_scoring_project.rb ├── about_blocks.rb ├── about_symbols.rb ├── about_sandwich_code.rb ├── about_control_statements.rb ├── about_hashes.rb ├── about_iteration.rb ├── about_proxy_object_project.rb ├── about_class_methods.rb ├── about_methods.rb ├── about_java_interop.rb ├── about_classes.rb ├── about_message_passing.rb ├── about_regular_expressions.rb └── about_strings.rb ├── .gitignore ├── keynote └── RubyKoans.key ├── rakelib ├── run.rake └── checks.rake ├── Rakefile └── README.rdoc /koans/example_file.txt: -------------------------------------------------------------------------------- 1 | this 2 | is 3 | a 4 | test 5 | -------------------------------------------------------------------------------- /src/example_file.txt: -------------------------------------------------------------------------------- 1 | this 2 | is 3 | a 4 | test 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | .project_env.rc 3 | .path_progress 4 | *.rbc 5 | -------------------------------------------------------------------------------- /koans/koans.watchr: -------------------------------------------------------------------------------- 1 | watch( '.*\.rb' ) do 2 | system 'rake' 3 | end 4 | -------------------------------------------------------------------------------- /src/koans.watchr: -------------------------------------------------------------------------------- 1 | watch( '.*\.rb' ) do 2 | system 'rake' 3 | end 4 | -------------------------------------------------------------------------------- /src/code_mash.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | -------------------------------------------------------------------------------- /koans/code_mash.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | -------------------------------------------------------------------------------- /keynote/RubyKoans.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edebill/ruby_koans/master/keynote/RubyKoans.key -------------------------------------------------------------------------------- /koans/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | 3 | def __ 4 | "FILL ME IN" 5 | end 6 | 7 | EdgeCase = Test::Unit 8 | -------------------------------------------------------------------------------- /src/test_helper.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | 3 | def __ 4 | "FILL ME IN" 5 | end 6 | 7 | EdgeCase = Test::Unit 8 | -------------------------------------------------------------------------------- /koans/first_test.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | 3 | class TestSomething < Test::Unit::TestCase 4 | def test_assert 5 | assert true 6 | assert_equal 1, 1 7 | assert_equal 1, 1.0 8 | end 9 | end 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require 'rake/clean' 5 | require 'rake/testtask' 6 | 7 | task :default => :test 8 | 9 | task :test do 10 | ruby 'path_to_enlightenment.rb' 11 | end 12 | 13 | -------------------------------------------------------------------------------- /src/first_test.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | 3 | class TestSomething < Test::Unit::TestCase 4 | def test_assert 5 | assert true 6 | assert_equal 1, 1 7 | assert_equal 1, 1.0 8 | end 9 | end 10 | 11 | 12 | -------------------------------------------------------------------------------- /koans/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require 'rake/clean' 5 | require 'rake/testtask' 6 | 7 | task :default => :test 8 | 9 | task :test do 10 | ruby 'path_to_enlightenment.rb' 11 | end 12 | 13 | -------------------------------------------------------------------------------- /rakelib/run.rake: -------------------------------------------------------------------------------- 1 | RUBIES = ENV['KOAN_RUBIES'] || %w(ruby-1.8.7-p299,ruby-1.9.2-p0,jruby-1.5.2,jruby-head) 2 | 3 | task :runall do 4 | chdir('src') do 5 | ENV['SIMPLE_KOAN_OUTPUT'] = 'true' 6 | sh "rvm #{RUBIES} path_to_enlightenment.rb" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /koans/about_extra_credit.rb: -------------------------------------------------------------------------------- 1 | # EXTRA CREDIT: 2 | # 3 | # Create a program that will play the Greed Game. 4 | # Rules for the game are in GREED_RULES.TXT. 5 | # 6 | # You already have a DiceSet class and score function you can use. 7 | # Write a player class and a Game class to complete the project. This 8 | # is a free form assignment, so approach it however you desire. 9 | -------------------------------------------------------------------------------- /src/about_extra_credit.rb: -------------------------------------------------------------------------------- 1 | # EXTRA CREDIT: 2 | # 3 | # Create a program that will play the Greed Game. 4 | # Rules for the game are in GREED_RULES.TXT. 5 | # 6 | # You already have a DiceSet class and score function you can use. 7 | # Write a player class and a Game class to complete the project. This 8 | # is a free form assignment, so approach it however you desire. 9 | -------------------------------------------------------------------------------- /koans/triangle.rb: -------------------------------------------------------------------------------- 1 | # Triangle Project Code. 2 | 3 | # Triangle analyzes the lengths of the sides of a triangle 4 | # (represented by a, b and c) and returns the type of triangle. 5 | # 6 | # It returns: 7 | # :equilateral if all sides are equal 8 | # :isosceles if exactly 2 sides are equal 9 | # :scalene if no sides are equal 10 | # 11 | # The tests for this method can be found in 12 | # about_triangle_project.rb 13 | # and 14 | # about_triangle_project_2.rb 15 | # 16 | def triangle(a, b, c) 17 | # WRITE THIS CODE 18 | end 19 | 20 | # Error class used in part 2. No need to change this code. 21 | class TriangleError < StandardError 22 | end 23 | -------------------------------------------------------------------------------- /koans/about_triangle_project_2.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # You need to write the triangle method in the file 'triangle.rb' 4 | require 'triangle.rb' 5 | 6 | class AboutTriangleProject2 < EdgeCase::Koan 7 | # The first assignment did not talk about how to handle errors. 8 | # Let's handle that part now. 9 | def test_illegal_triangles_throw_exceptions 10 | assert_raise(TriangleError) do triangle(0, 0, 0) end 11 | assert_raise(TriangleError) do triangle(3, 4, -5) end 12 | assert_raise(TriangleError) do triangle(1, 1, 3) end 13 | assert_raise(TriangleError) do triangle(2, 4, 2) end 14 | # HINT: for tips, see http://stackoverflow.com/questions/3834203/ruby-koan-151-raising-exceptions 15 | end 16 | end 17 | 18 | -------------------------------------------------------------------------------- /src/about_triangle_project_2.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # You need to write the triangle method in the file 'triangle.rb' 4 | require 'triangle.rb' 5 | 6 | class AboutTriangleProject2 < EdgeCase::Koan 7 | # The first assignment did not talk about how to handle errors. 8 | # Let's handle that part now. 9 | def test_illegal_triangles_throw_exceptions 10 | assert_raise(TriangleError) do triangle(0, 0, 0) end 11 | assert_raise(TriangleError) do triangle(3, 4, -5) end 12 | assert_raise(TriangleError) do triangle(1, 1, 3) end 13 | assert_raise(TriangleError) do triangle(2, 4, 2) end 14 | # HINT: for tips, see http://stackoverflow.com/questions/3834203/ruby-koan-151-raising-exceptions 15 | end 16 | end 17 | 18 | -------------------------------------------------------------------------------- /src/triangle.rb: -------------------------------------------------------------------------------- 1 | # Triangle Project Code. 2 | 3 | # Triangle analyzes the lengths of the sides of a triangle 4 | # (represented by a, b and c) and returns the type of triangle. 5 | # 6 | # It returns: 7 | # :equilateral if all sides are equal 8 | # :isosceles if exactly 2 sides are equal 9 | # :scalene if no sides are equal 10 | # 11 | # The tests for this method can be found in 12 | # about_triangle_project.rb 13 | # and 14 | # about_triangle_project_2.rb 15 | # 16 | def triangle(a, b, c) 17 | # WRITE THIS CODE 18 | #-- 19 | a, b, c = [a, b, c].sort 20 | fail TriangleError if (a+b) <= c 21 | sides = [a, b, c].uniq 22 | [nil, :equilateral, :isosceles, :scalene][sides.size] 23 | #++ 24 | end 25 | 26 | # Error class used in part 2. No need to change this code. 27 | class TriangleError < StandardError 28 | end 29 | -------------------------------------------------------------------------------- /koans/about_true_and_false.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutTrueAndFalse < EdgeCase::Koan 4 | def truth_value(condition) 5 | if condition 6 | :true_stuff 7 | else 8 | :false_stuff 9 | end 10 | end 11 | 12 | def test_true_is_treated_as_true 13 | assert_equal __, truth_value(true) 14 | end 15 | 16 | def test_false_is_treated_as_false 17 | assert_equal __, truth_value(false) 18 | end 19 | 20 | def test_nil_is_treated_as_false_too 21 | assert_equal __, truth_value(nil) 22 | end 23 | 24 | def test_everything_else_is_treated_as_true 25 | assert_equal __, truth_value(1) 26 | assert_equal __, truth_value(0) 27 | assert_equal __, truth_value([]) 28 | assert_equal __, truth_value({}) 29 | assert_equal __, truth_value("Strings") 30 | assert_equal __, truth_value("") 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /koans/about_triangle_project.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # You need to write the triangle method in the file 'triangle.rb' 4 | require 'triangle.rb' 5 | 6 | class AboutTriangleProject < EdgeCase::Koan 7 | def test_equilateral_triangles_have_equal_sides 8 | assert_equal :equilateral, triangle(2, 2, 2) 9 | assert_equal :equilateral, triangle(10, 10, 10) 10 | end 11 | 12 | def test_isosceles_triangles_have_exactly_two_sides_equal 13 | assert_equal :isosceles, triangle(3, 4, 4) 14 | assert_equal :isosceles, triangle(4, 3, 4) 15 | assert_equal :isosceles, triangle(4, 4, 3) 16 | assert_equal :isosceles, triangle(10, 10, 2) 17 | end 18 | 19 | def test_scalene_triangles_have_no_equal_sides 20 | assert_equal :scalene, triangle(3, 4, 5) 21 | assert_equal :scalene, triangle(10, 11, 12) 22 | assert_equal :scalene, triangle(5, 4, 2) 23 | end 24 | end 25 | 26 | -------------------------------------------------------------------------------- /src/about_triangle_project.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # You need to write the triangle method in the file 'triangle.rb' 4 | require 'triangle.rb' 5 | 6 | class AboutTriangleProject < EdgeCase::Koan 7 | def test_equilateral_triangles_have_equal_sides 8 | assert_equal :equilateral, triangle(2, 2, 2) 9 | assert_equal :equilateral, triangle(10, 10, 10) 10 | end 11 | 12 | def test_isosceles_triangles_have_exactly_two_sides_equal 13 | assert_equal :isosceles, triangle(3, 4, 4) 14 | assert_equal :isosceles, triangle(4, 3, 4) 15 | assert_equal :isosceles, triangle(4, 4, 3) 16 | assert_equal :isosceles, triangle(10, 10, 2) 17 | end 18 | 19 | def test_scalene_triangles_have_no_equal_sides 20 | assert_equal :scalene, triangle(3, 4, 5) 21 | assert_equal :scalene, triangle(10, 11, 12) 22 | assert_equal :scalene, triangle(5, 4, 2) 23 | end 24 | end 25 | 26 | -------------------------------------------------------------------------------- /src/about_true_and_false.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutTrueAndFalse < EdgeCase::Koan 4 | def truth_value(condition) 5 | if condition 6 | :true_stuff 7 | else 8 | :false_stuff 9 | end 10 | end 11 | 12 | def test_true_is_treated_as_true 13 | assert_equal __(:true_stuff), truth_value(true) 14 | end 15 | 16 | def test_false_is_treated_as_false 17 | assert_equal __(:false_stuff), truth_value(false) 18 | end 19 | 20 | def test_nil_is_treated_as_false_too 21 | assert_equal __(:false_stuff), truth_value(nil) 22 | end 23 | 24 | def test_everything_else_is_treated_as_true 25 | assert_equal __(:true_stuff), truth_value(1) 26 | assert_equal __(:true_stuff), truth_value(0) 27 | assert_equal __(:true_stuff), truth_value([]) 28 | assert_equal __(:true_stuff), truth_value({}) 29 | assert_equal __(:true_stuff), truth_value("Strings") 30 | assert_equal __(:true_stuff), truth_value("") 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /koans/about_open_classes.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutOpenClasses < EdgeCase::Koan 4 | class Dog 5 | def bark 6 | "WOOF" 7 | end 8 | end 9 | 10 | def test_as_defined_dogs_do_bark 11 | fido = Dog.new 12 | assert_equal __, fido.bark 13 | end 14 | 15 | # ------------------------------------------------------------------ 16 | 17 | # Open the existing Dog class and add a new method. 18 | class Dog 19 | def wag 20 | "HAPPY" 21 | end 22 | end 23 | 24 | def test_after_reopening_dogs_can_both_wag_and_bark 25 | fido = Dog.new 26 | assert_equal __, fido.wag 27 | assert_equal __, fido.bark 28 | end 29 | 30 | # ------------------------------------------------------------------ 31 | 32 | class ::Integer 33 | def even? 34 | (self % 2) == 0 35 | end 36 | end 37 | 38 | def test_even_existing_built_in_classes_can_be_reopened 39 | assert_equal __, 1.even? 40 | assert_equal __, 2.even? 41 | end 42 | 43 | # NOTE: To understand why we need the :: before Integer, you need to 44 | # become enlightened about scope. 45 | end 46 | -------------------------------------------------------------------------------- /koans/about_asserts.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 5 | 6 | class AboutAsserts < EdgeCase::Koan 7 | 8 | # We shall contemplate truth by testing reality, via asserts. 9 | def test_assert_truth 10 | assert true # This should be true 11 | end 12 | 13 | # Enlightenment may be more easily achieved with appropriate 14 | # messages. 15 | def test_assert_with_message 16 | assert true, "This should be true -- Please fix this" 17 | end 18 | 19 | # To understand reality, we must compare our expectations against 20 | # reality. 21 | def test_assert_equality 22 | expected_value = 2 23 | actual_value = 1 + 1 24 | 25 | assert expected_value == actual_value 26 | end 27 | 28 | # Some ways of asserting equality are better than others. 29 | def test_a_better_way_of_asserting_equality 30 | expected_value = 2 31 | actual_value = 1 + 1 32 | 33 | assert_equal expected_value, actual_value 34 | end 35 | 36 | # Sometimes we will ask you to fill in the values 37 | def test_fill_in_values 38 | assert_equal 2, 1 + 1 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /rakelib/checks.rake: -------------------------------------------------------------------------------- 1 | namespace "check" do 2 | 3 | desc "Check that the require files match the about_* files" 4 | task :abouts do 5 | about_files = Dir['src/about_*.rb'].size 6 | about_requires = `grep require src/path_to_enlightenment.rb | wc -l`.to_i 7 | puts "Checking path_to_enlightenment completeness" 8 | puts "# of about files: #{about_files}" 9 | puts "# of about requires: #{about_requires}" 10 | if about_files > about_requires 11 | puts "*** There seems to be requires missing in the path to enlightenment" 12 | else 13 | puts "OK" 14 | end 15 | puts 16 | end 17 | 18 | desc "Check that asserts have __ replacements" 19 | task :asserts do 20 | puts "Checking for asserts missing the replacement text:" 21 | begin 22 | sh "egrep -n 'assert( |_)' src/about_*.rb | egrep -v '__|_n_|project|about_assert' | egrep -v ' *#'" 23 | puts 24 | puts "Examine the above lines for missing __ replacements" 25 | rescue RuntimeError => ex 26 | puts "OK" 27 | end 28 | puts 29 | end 30 | end 31 | 32 | desc "Run some simple consistency checks" 33 | task :check => ["check:abouts", "check:asserts"] 34 | -------------------------------------------------------------------------------- /src/about_open_classes.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutOpenClasses < EdgeCase::Koan 4 | class Dog 5 | def bark 6 | "WOOF" 7 | end 8 | end 9 | 10 | def test_as_defined_dogs_do_bark 11 | fido = Dog.new 12 | assert_equal __("WOOF"), fido.bark 13 | end 14 | 15 | # ------------------------------------------------------------------ 16 | 17 | # Open the existing Dog class and add a new method. 18 | class Dog 19 | def wag 20 | "HAPPY" 21 | end 22 | end 23 | 24 | def test_after_reopening_dogs_can_both_wag_and_bark 25 | fido = Dog.new 26 | assert_equal __("HAPPY"), fido.wag 27 | assert_equal __("WOOF"), fido.bark 28 | end 29 | 30 | # ------------------------------------------------------------------ 31 | 32 | class ::Integer 33 | def even? 34 | (self % 2) == 0 35 | end 36 | end 37 | 38 | def test_even_existing_built_in_classes_can_be_reopened 39 | assert_equal __(false), 1.even? 40 | assert_equal __(true), 2.even? 41 | end 42 | 43 | # NOTE: To understand why we need the :: before Integer, you need to 44 | # become enlightened about scope. 45 | end 46 | -------------------------------------------------------------------------------- /koans/path_to_enlightenment.rb: -------------------------------------------------------------------------------- 1 | # The path to Ruby Enlightenment starts with the following: 2 | 3 | $LOAD_PATH << File.dirname(__FILE__) 4 | 5 | require 'about_asserts' 6 | require 'about_nil' 7 | require 'about_objects' 8 | require 'about_arrays' 9 | require 'about_array_assignment' 10 | require 'about_hashes' 11 | require 'about_strings' 12 | require 'about_symbols' 13 | require 'about_regular_expressions' 14 | require 'about_methods' 15 | require 'about_constants' 16 | require 'about_control_statements' 17 | require 'about_true_and_false' 18 | require 'about_triangle_project' 19 | require 'about_exceptions' 20 | require 'about_triangle_project_2' 21 | require 'about_iteration' 22 | require 'about_blocks' 23 | require 'about_sandwich_code' 24 | require 'about_scoring_project' 25 | require 'about_classes' 26 | require 'about_open_classes' 27 | require 'about_dice_project' 28 | require 'about_inheritance' 29 | require 'about_modules' 30 | require 'about_scope' 31 | require 'about_class_methods' 32 | require 'about_message_passing' 33 | require 'about_proxy_object_project' 34 | require 'about_to_str' 35 | in_ruby_version("jruby") do 36 | require 'about_java_interop' 37 | end 38 | require 'about_extra_credit' 39 | -------------------------------------------------------------------------------- /src/path_to_enlightenment.rb: -------------------------------------------------------------------------------- 1 | # The path to Ruby Enlightenment starts with the following: 2 | 3 | $LOAD_PATH << File.dirname(__FILE__) 4 | 5 | require 'about_asserts' 6 | require 'about_nil' 7 | require 'about_objects' 8 | require 'about_arrays' 9 | require 'about_array_assignment' 10 | require 'about_hashes' 11 | require 'about_strings' 12 | require 'about_symbols' 13 | require 'about_regular_expressions' 14 | require 'about_methods' 15 | require 'about_constants' 16 | require 'about_control_statements' 17 | require 'about_true_and_false' 18 | require 'about_triangle_project' 19 | require 'about_exceptions' 20 | require 'about_triangle_project_2' 21 | require 'about_iteration' 22 | require 'about_blocks' 23 | require 'about_sandwich_code' 24 | require 'about_scoring_project' 25 | require 'about_classes' 26 | require 'about_open_classes' 27 | require 'about_dice_project' 28 | require 'about_inheritance' 29 | require 'about_modules' 30 | require 'about_scope' 31 | require 'about_class_methods' 32 | require 'about_message_passing' 33 | require 'about_proxy_object_project' 34 | require 'about_to_str' 35 | in_ruby_version("jruby") do 36 | require 'about_java_interop' 37 | end 38 | require 'about_extra_credit' 39 | -------------------------------------------------------------------------------- /koans/about_nil.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutNil < EdgeCase::Koan 4 | def test_nil_is_an_object 5 | assert_equal true, nil.is_a?(Object), "Unlike NULL in other languages" 6 | end 7 | 8 | def test_you_dont_get_null_pointer_errors_when_calling_methods_on_nil 9 | # What happens when you call a method that doesn't exist. The 10 | # following begin/rescue/end code block captures the exception and 11 | # makes some assertions about it. 12 | begin 13 | nil.some_method_nil_doesnt_know_about 14 | rescue Exception => ex 15 | # What exception has been caught? 16 | assert_equal NoMethodError, ex.class 17 | 18 | # What message was attached to the exception? 19 | # (HINT: replace __ with part of the error message.) 20 | assert_match(/undefined method/, ex.message) 21 | end 22 | end 23 | 24 | def test_nil_has_a_few_methods_defined_on_it 25 | assert_equal true, nil.nil? 26 | assert_equal "", nil.to_s 27 | assert_equal "nil", nil.inspect 28 | 29 | # THINK ABOUT IT: 30 | # 31 | # Is it better to use 32 | # obj.nil? 33 | # or 34 | # obj == nil 35 | # Why? 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /src/about_nil.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutNil < EdgeCase::Koan 4 | def test_nil_is_an_object 5 | assert_equal __(true), nil.is_a?(Object), "Unlike NULL in other languages" 6 | end 7 | 8 | def test_you_dont_get_null_pointer_errors_when_calling_methods_on_nil 9 | # What happens when you call a method that doesn't exist. The 10 | # following begin/rescue/end code block captures the exception and 11 | # makes some assertions about it. 12 | begin 13 | nil.some_method_nil_doesnt_know_about 14 | rescue Exception => ex 15 | # What exception has been caught? 16 | assert_equal __(NoMethodError), ex.class 17 | 18 | # What message was attached to the exception? 19 | # (HINT: replace __ with part of the error message.) 20 | assert_match(/#{__("undefined method")}/, ex.message) 21 | end 22 | end 23 | 24 | def test_nil_has_a_few_methods_defined_on_it 25 | assert_equal __(true), nil.nil? 26 | assert_equal __(""), nil.to_s 27 | assert_equal __("nil"), nil.inspect 28 | 29 | # THINK ABOUT IT: 30 | # 31 | # Is it better to use 32 | # obj.nil? 33 | # or 34 | # obj == nil 35 | # Why? 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /koans/array_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ArrayTest < EdgeCase::TestCase 4 | 5 | def test_basic_arrays 6 | food = [:peanut, :button, :and, :jelly] 7 | assert_equal __, food[0] 8 | assert_equal __, food.size 9 | end 10 | 11 | def test_array_access 12 | food = [:peanut, :button, :and, :jelly] 13 | assert_equal __, food.first 14 | assert_equal __, food.last 15 | assert_equal __, food[0] 16 | assert_equal __, food[2] 17 | assert_equal __, food[(food.size() - 1)] 18 | end 19 | 20 | def test_arrays_with_other_objects 21 | food = [:peanut, :button, :and, :jelly, 1, nil] 22 | assert_equal __, food.size 23 | assert_equal __, food.last 24 | assert_equal __, food[5] 25 | end 26 | 27 | def test_adding_to_an_array_with_shovel_shovel 28 | food = [:peanut, :button, :and, :jelly] 29 | food << 'sandwich' 30 | assert_equal __, food.size 31 | assert_equal __, food.first 32 | end 33 | 34 | def test_adding_to_an_array_with_push 35 | food = [:peanut, :button, :and, :jelly] 36 | food.push('sandwich') 37 | assert_equal __, food.last 38 | end 39 | 40 | def test_adding_to_an_array_with_unshift 41 | food = [:peanut, :button, :and, :jelly] 42 | food.unshift('a') 43 | assert_equal __, food.first 44 | end 45 | 46 | end 47 | 48 | -------------------------------------------------------------------------------- /src/array_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ArrayTest < EdgeCase::TestCase 4 | 5 | def test_basic_arrays 6 | food = [:peanut, :button, :and, :jelly] 7 | assert_equal __, food[0] 8 | assert_equal __, food.size 9 | end 10 | 11 | def test_array_access 12 | food = [:peanut, :button, :and, :jelly] 13 | assert_equal __, food.first 14 | assert_equal __, food.last 15 | assert_equal __, food[0] 16 | assert_equal __, food[2] 17 | assert_equal __, food[(food.size() - 1)] 18 | end 19 | 20 | def test_arrays_with_other_objects 21 | food = [:peanut, :button, :and, :jelly, 1, nil] 22 | assert_equal __, food.size 23 | assert_equal __, food.last 24 | assert_equal __, food[5] 25 | end 26 | 27 | def test_adding_to_an_array_with_shovel_shovel 28 | food = [:peanut, :button, :and, :jelly] 29 | food << 'sandwich' 30 | assert_equal __, food.size 31 | assert_equal __, food.first 32 | end 33 | 34 | def test_adding_to_an_array_with_push 35 | food = [:peanut, :button, :and, :jelly] 36 | food.push('sandwich') 37 | assert_equal __, food.last 38 | end 39 | 40 | def test_adding_to_an_array_with_unshift 41 | food = [:peanut, :button, :and, :jelly] 42 | food.unshift('a') 43 | assert_equal __, food.first 44 | end 45 | 46 | end 47 | 48 | -------------------------------------------------------------------------------- /koans/about_modules.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutModules < EdgeCase::Koan 4 | module Nameable 5 | def set_name(new_name) 6 | @name = new_name 7 | end 8 | 9 | def here 10 | :in_module 11 | end 12 | end 13 | 14 | def test_cant_instantiate_modules 15 | assert_raise(___) do 16 | Nameable.new 17 | end 18 | end 19 | 20 | # ------------------------------------------------------------------ 21 | 22 | class Dog 23 | include Nameable 24 | 25 | attr_reader :name 26 | 27 | def initialize 28 | @name = "Fido" 29 | end 30 | 31 | def bark 32 | "WOOF" 33 | end 34 | 35 | def here 36 | :in_object 37 | end 38 | end 39 | 40 | def test_normal_methods_are_available_in_the_object 41 | fido = Dog.new 42 | assert_equal __, fido.bark 43 | end 44 | 45 | def test_module_methods_are_also_available_in_the_object 46 | fido = Dog.new 47 | assert_nothing_raised(Exception) do 48 | fido.set_name("Rover") 49 | end 50 | end 51 | 52 | def test_module_methods_can_affect_instance_variables_in_the_object 53 | fido = Dog.new 54 | assert_equal __, fido.name 55 | fido.set_name("Rover") 56 | assert_equal __, fido.name 57 | end 58 | 59 | def test_classes_can_override_module_methods 60 | fido = Dog.new 61 | assert_equal __, fido.here 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /src/about_asserts.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 5 | 6 | class AboutAsserts < EdgeCase::Koan 7 | 8 | # We shall contemplate truth by testing reality, via asserts. 9 | def test_assert_truth 10 | #-- 11 | assert true # This should be true 12 | if false 13 | #++ 14 | assert false # This should be true 15 | #-- 16 | end 17 | #++ 18 | end 19 | 20 | # Enlightenment may be more easily achieved with appropriate 21 | # messages. 22 | def test_assert_with_message 23 | #-- 24 | assert true, "This should be true -- Please fix this" 25 | if false 26 | #++ 27 | assert false, "This should be true -- Please fix this" 28 | #-- 29 | end 30 | #++ 31 | end 32 | 33 | # To understand reality, we must compare our expectations against 34 | # reality. 35 | def test_assert_equality 36 | expected_value = __(2) 37 | actual_value = 1 + 1 38 | 39 | assert expected_value == actual_value 40 | end 41 | 42 | # Some ways of asserting equality are better than others. 43 | def test_a_better_way_of_asserting_equality 44 | expected_value = __(2) 45 | actual_value = 1 + 1 46 | 47 | assert_equal expected_value, actual_value 48 | end 49 | 50 | # Sometimes we will ask you to fill in the values 51 | def test_fill_in_values 52 | assert_equal __(2), 1 + 1 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /src/about_modules.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutModules < EdgeCase::Koan 4 | module Nameable 5 | def set_name(new_name) 6 | @name = new_name 7 | end 8 | 9 | def here 10 | :in_module 11 | end 12 | end 13 | 14 | def test_cant_instantiate_modules 15 | assert_raise(___(NoMethodError)) do 16 | Nameable.new 17 | end 18 | end 19 | 20 | # ------------------------------------------------------------------ 21 | 22 | class Dog 23 | include Nameable 24 | 25 | attr_reader :name 26 | 27 | def initialize 28 | @name = "Fido" 29 | end 30 | 31 | def bark 32 | "WOOF" 33 | end 34 | 35 | def here 36 | :in_object 37 | end 38 | end 39 | 40 | def test_normal_methods_are_available_in_the_object 41 | fido = Dog.new 42 | assert_equal __("WOOF"), fido.bark 43 | end 44 | 45 | def test_module_methods_are_also_available_in_the_object 46 | fido = Dog.new 47 | assert_nothing_raised(Exception) do # __ 48 | fido.set_name("Rover") 49 | end 50 | end 51 | 52 | def test_module_methods_can_affect_instance_variables_in_the_object 53 | fido = Dog.new 54 | assert_equal __("Fido"), fido.name 55 | fido.set_name("Rover") 56 | assert_equal __("Rover"), fido.name 57 | end 58 | 59 | def test_classes_can_override_module_methods 60 | fido = Dog.new 61 | assert_equal __(:in_object), fido.here 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /koans/about_to_str.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutToStr < EdgeCase::Koan 4 | 5 | class CanNotBeTreatedAsString 6 | def to_s 7 | "non-string-like" 8 | end 9 | end 10 | 11 | def test_to_s_returns_a_string_representation 12 | not_like_a_string = CanNotBeTreatedAsString.new 13 | assert_equal __, not_like_a_string.to_s 14 | end 15 | 16 | def test_normally_objects_cannot_be_used_where_strings_are_expected 17 | assert_raise(___) do 18 | File.exist?(CanNotBeTreatedAsString.new) 19 | end 20 | end 21 | 22 | # ------------------------------------------------------------------ 23 | 24 | class CanBeTreatedAsString 25 | def to_s 26 | "string-like" 27 | end 28 | 29 | def to_str 30 | to_s 31 | end 32 | end 33 | 34 | def test_to_str_also_returns_a_string_representation 35 | like_a_string = CanBeTreatedAsString.new 36 | assert_equal __, like_a_string.to_str 37 | end 38 | 39 | def test_to_str_allows_objects_to_be_treated_as_strings 40 | assert_equal __, File.exist?(CanBeTreatedAsString.new) 41 | end 42 | 43 | # ------------------------------------------------------------------ 44 | 45 | def acts_like_a_string?(string) 46 | string = string.to_str if string.respond_to?(:to_str) 47 | string.is_a?(String) 48 | end 49 | 50 | def test_user_defined_code_can_check_for_to_str 51 | assert_equal __, acts_like_a_string?(CanNotBeTreatedAsString.new) 52 | assert_equal __, acts_like_a_string?(CanBeTreatedAsString.new) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /src/about_to_str.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutToStr < EdgeCase::Koan 4 | 5 | class CanNotBeTreatedAsString 6 | def to_s 7 | "non-string-like" 8 | end 9 | end 10 | 11 | def test_to_s_returns_a_string_representation 12 | not_like_a_string = CanNotBeTreatedAsString.new 13 | assert_equal __("non-string-like"), not_like_a_string.to_s 14 | end 15 | 16 | def test_normally_objects_cannot_be_used_where_strings_are_expected 17 | assert_raise(___(TypeError)) do 18 | File.exist?(CanNotBeTreatedAsString.new) 19 | end 20 | end 21 | 22 | # ------------------------------------------------------------------ 23 | 24 | class CanBeTreatedAsString 25 | def to_s 26 | "string-like" 27 | end 28 | 29 | def to_str 30 | to_s 31 | end 32 | end 33 | 34 | def test_to_str_also_returns_a_string_representation 35 | like_a_string = CanBeTreatedAsString.new 36 | assert_equal __("string-like"), like_a_string.to_str 37 | end 38 | 39 | def test_to_str_allows_objects_to_be_treated_as_strings 40 | assert_equal __(false), File.exist?(CanBeTreatedAsString.new) 41 | end 42 | 43 | # ------------------------------------------------------------------ 44 | 45 | def acts_like_a_string?(string) 46 | string = string.to_str if string.respond_to?(:to_str) 47 | string.is_a?(String) 48 | end 49 | 50 | def test_user_defined_code_can_check_for_to_str 51 | assert_equal __(false), acts_like_a_string?(CanNotBeTreatedAsString.new) 52 | assert_equal __(true), acts_like_a_string?(CanBeTreatedAsString.new) 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /koans/about_array_assignment.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutArrayAssignment < EdgeCase::Koan 4 | def test_non_parallel_assignment 5 | names = ["John", "Smith"] 6 | assert_equal ["John", "Smith"], names 7 | end 8 | 9 | def test_parallel_assignments 10 | first_name, last_name = ["John", "Smith"] 11 | assert_equal "John", first_name 12 | assert_equal "Smith", last_name 13 | end 14 | 15 | def test_parallel_assignments_with_extra_values 16 | first_name, last_name = ["John", "Smith", "III"] 17 | assert_equal "John", first_name 18 | assert_equal "Smith", last_name 19 | end 20 | 21 | def test_parallel_assignments_with_splat_operator 22 | first_name, *last_name = ["John", "Smith", "III"] 23 | assert_equal "John", first_name 24 | assert_equal ["Smith", "III"], last_name 25 | end 26 | 27 | def test_parallel_assignments_with_too_few_variables 28 | first_name, last_name = ["Cher"] 29 | assert_equal "Cher", first_name 30 | assert_equal nil, last_name 31 | end 32 | 33 | def test_parallel_assignments_with_subarrays 34 | first_name, last_name = [["Willie", "Rae"], "Johnson"] 35 | assert_equal ["Willie", "Rae"], first_name 36 | assert_equal "Johnson", last_name 37 | end 38 | 39 | def test_parallel_assignment_with_one_variable 40 | first_name, = ["John", "Smith"] 41 | assert_equal "John", first_name 42 | end 43 | 44 | def test_swapping_with_parallel_assignment 45 | first_name = "Roy" 46 | last_name = "Rob" 47 | first_name, last_name = last_name, first_name 48 | assert_equal "Rob", first_name 49 | assert_equal "Roy", last_name 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /koans/about_objects.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutObjects < EdgeCase::Koan 4 | def test_everything_is_an_object 5 | assert_equal true, 1.is_a?(Object) 6 | assert_equal true, 1.5.is_a?(Object) 7 | assert_equal true, "string".is_a?(Object) 8 | assert_equal true, nil.is_a?(Object) 9 | assert_equal true, Object.is_a?(Object) 10 | end 11 | 12 | def test_objects_can_be_converted_to_strings 13 | assert_equal "123", 123.to_s 14 | assert_equal "", nil.to_s 15 | end 16 | 17 | def test_objects_can_be_inspected 18 | assert_equal '123', 123.inspect 19 | assert_equal 'nil', nil.inspect 20 | end 21 | 22 | def test_every_object_has_an_id 23 | obj = Object.new 24 | assert_equal Fixnum, obj.object_id.class 25 | end 26 | 27 | def test_every_object_has_different_id 28 | obj = Object.new 29 | another_obj = Object.new 30 | assert_equal true, obj.object_id != another_obj.object_id 31 | end 32 | 33 | def test_some_system_objects_always_have_the_same_id 34 | assert_equal 0, false.object_id 35 | assert_equal 2, true.object_id 36 | assert_equal 4, nil.object_id 37 | end 38 | 39 | def test_small_integers_have_fixed_ids 40 | assert_equal 1, 0.object_id 41 | assert_equal 3, 1.object_id 42 | assert_equal 5, 2.object_id 43 | assert_equal 201, 100.object_id 44 | 45 | # THINK ABOUT IT: 46 | # What pattern do the object IDs for small integers follow? 47 | end 48 | 49 | def test_clone_creates_a_different_object 50 | obj = Object.new 51 | copy = obj.clone 52 | 53 | assert_equal true, obj != copy 54 | assert_equal true, obj.object_id != copy.object_id 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /koans/about_dice_project.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # Implement a DiceSet Class here: 4 | # 5 | # class DiceSet 6 | # code ... 7 | # end 8 | 9 | class AboutDiceProject < EdgeCase::Koan 10 | def test_can_create_a_dice_set 11 | dice = DiceSet.new 12 | assert_not_nil dice 13 | end 14 | 15 | def test_rolling_the_dice_returns_a_set_of_integers_between_1_and_6 16 | dice = DiceSet.new 17 | 18 | dice.roll(5) 19 | assert dice.values.is_a?(Array), "should be an array" 20 | assert_equal 5, dice.values.size 21 | dice.values.each do |value| 22 | assert value >= 1 && value <= 6, "value #{value} must be between 1 and 6" 23 | end 24 | end 25 | 26 | def test_dice_values_do_not_change_unless_explicitly_rolled 27 | dice = DiceSet.new 28 | dice.roll(5) 29 | first_time = dice.values 30 | second_time = dice.values 31 | assert_equal first_time, second_time 32 | end 33 | 34 | def test_dice_values_should_change_between_rolls 35 | dice = DiceSet.new 36 | 37 | dice.roll(5) 38 | first_time = dice.values 39 | 40 | dice.roll(5) 41 | second_time = dice.values 42 | 43 | assert_not_equal first_time, second_time, 44 | "Two rolls should not be equal" 45 | 46 | # THINK ABOUT IT: 47 | # 48 | # If the rolls are random, then it is possible (although not 49 | # likely) that two consecutive rolls are equal. What would be a 50 | # better way to test this. 51 | end 52 | 53 | def test_you_can_roll_different_numbers_of_dice 54 | dice = DiceSet.new 55 | 56 | dice.roll(3) 57 | assert_equal 3, dice.values.size 58 | 59 | dice.roll(1) 60 | assert_equal 1, dice.values.size 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /src/about_array_assignment.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutArrayAssignment < EdgeCase::Koan 4 | def test_non_parallel_assignment 5 | names = ["John", "Smith"] 6 | assert_equal __(["John", "Smith"]), names 7 | end 8 | 9 | def test_parallel_assignments 10 | first_name, last_name = ["John", "Smith"] 11 | assert_equal __("John"), first_name 12 | assert_equal __("Smith"), last_name 13 | end 14 | 15 | def test_parallel_assignments_with_extra_values 16 | first_name, last_name = ["John", "Smith", "III"] 17 | assert_equal __("John"), first_name 18 | assert_equal __("Smith"), last_name 19 | end 20 | 21 | def test_parallel_assignments_with_splat_operator 22 | first_name, *last_name = ["John", "Smith", "III"] 23 | assert_equal __("John"), first_name 24 | assert_equal __(["Smith","III"]), last_name 25 | end 26 | 27 | def test_parallel_assignments_with_too_few_variables 28 | first_name, last_name = ["Cher"] 29 | assert_equal __("Cher"), first_name 30 | assert_equal __(nil), last_name 31 | end 32 | 33 | def test_parallel_assignments_with_subarrays 34 | first_name, last_name = [["Willie", "Rae"], "Johnson"] 35 | assert_equal __(["Willie", "Rae"]), first_name 36 | assert_equal __("Johnson"), last_name 37 | end 38 | 39 | def test_parallel_assignment_with_one_variable 40 | first_name, = ["John", "Smith"] 41 | assert_equal __("John"), first_name 42 | end 43 | 44 | def test_swapping_with_parallel_assignment 45 | first_name = "Roy" 46 | last_name = "Rob" 47 | first_name, last_name = last_name, first_name 48 | assert_equal __('Rob'), first_name 49 | assert_equal __('Roy'), last_name 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /src/about_objects.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutObjects < EdgeCase::Koan 4 | def test_everything_is_an_object 5 | assert_equal __(true), 1.is_a?(Object) 6 | assert_equal __(true), 1.5.is_a?(Object) 7 | assert_equal __(true), "string".is_a?(Object) 8 | assert_equal __(true), nil.is_a?(Object) 9 | assert_equal __(true), Object.is_a?(Object) 10 | end 11 | 12 | def test_objects_can_be_converted_to_strings 13 | assert_equal __("123"), 123.to_s 14 | assert_equal __(""), nil.to_s 15 | end 16 | 17 | def test_objects_can_be_inspected 18 | assert_equal __("123"), 123.inspect 19 | assert_equal __("nil"), nil.inspect 20 | end 21 | 22 | def test_every_object_has_an_id 23 | obj = Object.new 24 | assert_equal __(Fixnum), obj.object_id.class 25 | end 26 | 27 | def test_every_object_has_different_id 28 | obj = Object.new 29 | another_obj = Object.new 30 | assert_equal __(true), obj.object_id != another_obj.object_id 31 | end 32 | 33 | def test_some_system_objects_always_have_the_same_id 34 | assert_equal __(0), false.object_id 35 | assert_equal __(2), true.object_id 36 | assert_equal __(4), nil.object_id 37 | end 38 | 39 | def test_small_integers_have_fixed_ids 40 | assert_equal __(1), 0.object_id 41 | assert_equal __(3), 1.object_id 42 | assert_equal __(5), 2.object_id 43 | assert_equal __(201), 100.object_id 44 | 45 | # THINK ABOUT IT: 46 | # What pattern do the object IDs for small integers follow? 47 | end 48 | 49 | def test_clone_creates_a_different_object 50 | obj = Object.new 51 | copy = obj.clone 52 | 53 | assert_equal __(true), obj != copy 54 | assert_equal __(true), obj.object_id != copy.object_id 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /src/about_dice_project.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # Implement a DiceSet Class here: 4 | # 5 | # class DiceSet 6 | # code ... 7 | # end 8 | 9 | #-- 10 | class DiceSet 11 | attr_reader :values 12 | def roll(n) 13 | @values = (1..n).map { rand(6) + 1 } 14 | end 15 | end 16 | 17 | #++ 18 | class AboutDiceProject < EdgeCase::Koan 19 | def test_can_create_a_dice_set 20 | dice = DiceSet.new 21 | assert_not_nil dice 22 | end 23 | 24 | def test_rolling_the_dice_returns_a_set_of_integers_between_1_and_6 25 | dice = DiceSet.new 26 | 27 | dice.roll(5) 28 | assert dice.values.is_a?(Array), "should be an array" 29 | assert_equal 5, dice.values.size 30 | dice.values.each do |value| 31 | assert value >= 1 && value <= 6, "value #{value} must be between 1 and 6" 32 | end 33 | end 34 | 35 | def test_dice_values_do_not_change_unless_explicitly_rolled 36 | dice = DiceSet.new 37 | dice.roll(5) 38 | first_time = dice.values 39 | second_time = dice.values 40 | assert_equal first_time, second_time 41 | end 42 | 43 | def test_dice_values_should_change_between_rolls 44 | dice = DiceSet.new 45 | 46 | dice.roll(5) 47 | first_time = dice.values 48 | 49 | dice.roll(5) 50 | second_time = dice.values 51 | 52 | assert_not_equal first_time, second_time, 53 | "Two rolls should not be equal" 54 | 55 | # THINK ABOUT IT: 56 | # 57 | # If the rolls are random, then it is possible (although not 58 | # likely) that two consecutive rolls are equal. What would be a 59 | # better way to test this. 60 | end 61 | 62 | def test_you_can_roll_different_numbers_of_dice 63 | dice = DiceSet.new 64 | 65 | dice.roll(3) 66 | assert_equal 3, dice.values.size 67 | 68 | dice.roll(1) 69 | assert_equal 1, dice.values.size 70 | end 71 | 72 | end 73 | -------------------------------------------------------------------------------- /koans/about_exceptions.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutExceptions < EdgeCase::Koan 4 | 5 | class MySpecialError < RuntimeError 6 | end 7 | 8 | def test_exceptions_inherit_from_Exception 9 | assert_equal __, MySpecialError.ancestors[1] 10 | assert_equal __, MySpecialError.ancestors[2] 11 | assert_equal __, MySpecialError.ancestors[3] 12 | assert_equal __, MySpecialError.ancestors[4] 13 | end 14 | 15 | def test_rescue_clause 16 | result = nil 17 | begin 18 | fail "Oops" 19 | rescue StandardError => ex 20 | result = :exception_handled 21 | end 22 | 23 | assert_equal __, result 24 | 25 | assert_equal __, ex.is_a?(StandardError), "Should be a Standard Error" 26 | assert_equal __, ex.is_a?(RuntimeError), "Should be a Runtime Error" 27 | 28 | assert RuntimeError.ancestors.include?(StandardError), 29 | "RuntimeError is a subclass of StandardError" 30 | 31 | assert_equal __, ex.message 32 | end 33 | 34 | def test_raising_a_particular_error 35 | result = nil 36 | begin 37 | # 'raise' and 'fail' are synonyms 38 | raise MySpecialError, "My Message" 39 | rescue MySpecialError => ex 40 | result = :exception_handled 41 | end 42 | 43 | assert_equal __, result 44 | assert_equal __, ex.message 45 | end 46 | 47 | def test_ensure_clause 48 | result = nil 49 | begin 50 | fail "Oops" 51 | rescue StandardError => ex 52 | # no code here 53 | ensure 54 | result = :always_run 55 | end 56 | 57 | assert_equal __, result 58 | end 59 | 60 | # Sometimes, we must know about the unknown 61 | def test_asserting_an_error_is_raised 62 | # A do-end is a block, a topic to explore more later 63 | assert_raise(___) do 64 | raise MySpecialError.new("New instances can be raised directly.") 65 | end 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /koans/about_inheritance.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutInheritance < EdgeCase::Koan 4 | class Dog 5 | attr_reader :name 6 | 7 | def initialize(name) 8 | @name = name 9 | end 10 | 11 | def bark 12 | "WOOF" 13 | end 14 | end 15 | 16 | class Chihuahua < Dog 17 | def wag 18 | :happy 19 | end 20 | 21 | def bark 22 | "yip" 23 | end 24 | end 25 | 26 | def test_subclasses_have_the_parent_as_an_ancestor 27 | assert_equal __, Chihuahua.ancestors.include?(Dog) 28 | end 29 | 30 | def test_all_classes_ultimately_inherit_from_object 31 | assert_equal __, Chihuahua.ancestors.include?(Object) 32 | end 33 | 34 | def test_subclasses_inherit_behavior_from_parent_class 35 | chico = Chihuahua.new("Chico") 36 | assert_equal __, chico.name 37 | end 38 | 39 | def test_subclasses_add_new_behavior 40 | chico = Chihuahua.new("Chico") 41 | assert_equal __, chico.wag 42 | 43 | assert_raise(___) do 44 | fido = Dog.new("Fido") 45 | fido.wag 46 | end 47 | end 48 | 49 | def test_subclasses_can_modify_existing_behavior 50 | chico = Chihuahua.new("Chico") 51 | assert_equal __, chico.bark 52 | 53 | fido = Dog.new("Fido") 54 | assert_equal __, fido.bark 55 | end 56 | 57 | # ------------------------------------------------------------------ 58 | 59 | class BullDog < Dog 60 | def bark 61 | super + ", GROWL" 62 | end 63 | end 64 | 65 | def test_subclasses_can_invoke_parent_behavior_via_super 66 | ralph = BullDog.new("Ralph") 67 | assert_equal __, ralph.bark 68 | end 69 | 70 | # ------------------------------------------------------------------ 71 | 72 | class GreatDane < Dog 73 | def growl 74 | super.bark + ", GROWL" 75 | end 76 | end 77 | 78 | def test_super_does_not_work_cross_method 79 | george = GreatDane.new("George") 80 | assert_raise(___) do 81 | george.growl 82 | end 83 | end 84 | 85 | end 86 | -------------------------------------------------------------------------------- /src/about_exceptions.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutExceptions < EdgeCase::Koan 4 | 5 | class MySpecialError < RuntimeError 6 | end 7 | 8 | def test_exceptions_inherit_from_Exception 9 | assert_equal __(RuntimeError), MySpecialError.ancestors[1] 10 | assert_equal __(StandardError), MySpecialError.ancestors[2] 11 | assert_equal __(Exception), MySpecialError.ancestors[3] 12 | assert_equal __(Object), MySpecialError.ancestors[4] 13 | end 14 | 15 | def test_rescue_clause 16 | result = nil 17 | begin 18 | fail "Oops" 19 | rescue StandardError => ex 20 | result = :exception_handled 21 | end 22 | 23 | assert_equal __(:exception_handled), result 24 | 25 | assert_equal __(true), ex.is_a?(StandardError), "Should be a Standard Error" 26 | assert_equal __(true), ex.is_a?(RuntimeError), "Should be a Runtime Error" 27 | 28 | assert RuntimeError.ancestors.include?(StandardError), # __ 29 | "RuntimeError is a subclass of StandardError" 30 | 31 | assert_equal __("Oops"), ex.message 32 | end 33 | 34 | def test_raising_a_particular_error 35 | result = nil 36 | begin 37 | # 'raise' and 'fail' are synonyms 38 | raise MySpecialError, "My Message" 39 | rescue MySpecialError => ex 40 | result = :exception_handled 41 | end 42 | 43 | assert_equal __(:exception_handled), result 44 | assert_equal __("My Message"), ex.message 45 | end 46 | 47 | def test_ensure_clause 48 | result = nil 49 | begin 50 | fail "Oops" 51 | rescue StandardError => ex 52 | # no code here 53 | ensure 54 | result = :always_run 55 | end 56 | 57 | assert_equal __(:always_run), result 58 | end 59 | 60 | # Sometimes, we must know about the unknown 61 | def test_asserting_an_error_is_raised # __ 62 | # A do-end is a block, a topic to explore more later 63 | assert_raise(___(MySpecialError)) do 64 | raise MySpecialError.new("New instances can be raised directly.") 65 | end 66 | end 67 | 68 | end 69 | -------------------------------------------------------------------------------- /src/about_inheritance.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutInheritance < EdgeCase::Koan 4 | class Dog 5 | attr_reader :name 6 | 7 | def initialize(name) 8 | @name = name 9 | end 10 | 11 | def bark 12 | "WOOF" 13 | end 14 | end 15 | 16 | class Chihuahua < Dog 17 | def wag 18 | :happy 19 | end 20 | 21 | def bark 22 | "yip" 23 | end 24 | end 25 | 26 | def test_subclasses_have_the_parent_as_an_ancestor 27 | assert_equal __(true), Chihuahua.ancestors.include?(Dog) 28 | end 29 | 30 | def test_all_classes_ultimately_inherit_from_object 31 | assert_equal __(true), Chihuahua.ancestors.include?(Object) 32 | end 33 | 34 | def test_subclasses_inherit_behavior_from_parent_class 35 | chico = Chihuahua.new("Chico") 36 | assert_equal __("Chico"), chico.name 37 | end 38 | 39 | def test_subclasses_add_new_behavior 40 | chico = Chihuahua.new("Chico") 41 | assert_equal __(:happy), chico.wag 42 | 43 | assert_raise(___(NoMethodError)) do 44 | fido = Dog.new("Fido") 45 | fido.wag 46 | end 47 | end 48 | 49 | def test_subclasses_can_modify_existing_behavior 50 | chico = Chihuahua.new("Chico") 51 | assert_equal __("yip"), chico.bark 52 | 53 | fido = Dog.new("Fido") 54 | assert_equal __("WOOF"), fido.bark 55 | end 56 | 57 | # ------------------------------------------------------------------ 58 | 59 | class BullDog < Dog 60 | def bark 61 | super + ", GROWL" 62 | end 63 | end 64 | 65 | def test_subclasses_can_invoke_parent_behavior_via_super 66 | ralph = BullDog.new("Ralph") 67 | assert_equal __("WOOF, GROWL"), ralph.bark 68 | end 69 | 70 | # ------------------------------------------------------------------ 71 | 72 | class GreatDane < Dog 73 | def growl 74 | super.bark + ", GROWL" 75 | end 76 | end 77 | 78 | def test_super_does_not_work_cross_method 79 | george = GreatDane.new("George") 80 | assert_raise(___(NoMethodError)) do 81 | george.growl 82 | end 83 | end 84 | 85 | end 86 | -------------------------------------------------------------------------------- /koans/about_scope.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutScope < EdgeCase::Koan 4 | module Jims 5 | class Dog 6 | def identify 7 | :jims_dog 8 | end 9 | end 10 | end 11 | 12 | module Joes 13 | class Dog 14 | def identify 15 | :joes_dog 16 | end 17 | end 18 | end 19 | 20 | def test_dog_is_not_available_in_the_current_scope 21 | assert_raise(___) do 22 | fido = Dog.new 23 | end 24 | end 25 | 26 | def test_you_can_reference_nested_classes_using_the_scope_operator 27 | fido = Jims::Dog.new 28 | rover = Joes::Dog.new 29 | assert_equal __, fido.identify 30 | assert_equal __, rover.identify 31 | 32 | assert_equal __, fido.class != rover.class 33 | assert_equal __, Jims::Dog != Joes::Dog 34 | end 35 | 36 | # ------------------------------------------------------------------ 37 | 38 | class String 39 | end 40 | 41 | def test_bare_bones_class_names_assume_the_current_scope 42 | assert_equal __, AboutScope::String == String 43 | end 44 | 45 | def test_nested_string_is_not_the_same_as_the_system_string 46 | assert_equal __, String == "HI".class 47 | end 48 | 49 | def test_use_the_prefix_scope_operator_to_force_the_global_scope 50 | assert_equal __, ::String == "HI".class 51 | end 52 | 53 | # ------------------------------------------------------------------ 54 | 55 | PI = 3.1416 56 | 57 | def test_constants_are_defined_with_an_initial_uppercase_letter 58 | assert_equal __, PI 59 | end 60 | 61 | # ------------------------------------------------------------------ 62 | 63 | MyString = ::String 64 | 65 | def test_class_names_are_just_constants 66 | assert_equal __, MyString == ::String 67 | assert_equal __, MyString == "HI".class 68 | end 69 | 70 | def test_constants_can_be_looked_up_explicitly 71 | assert_equal __, PI == AboutScope.const_get("PI") 72 | assert_equal __, MyString == AboutScope.const_get("MyString") 73 | end 74 | 75 | def test_you_can_get_a_list_of_constants_for_any_class_or_module 76 | assert_equal __, Jims.constants 77 | assert Object.constants.size > _n_ 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /koans/about_scoring_project.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # Greed is a dice game where you roll up to five dice to accumulate 4 | # points. The following "score" function will be used to calculate the 5 | # score of a single roll of the dice. 6 | # 7 | # A greed roll is scored as follows: 8 | # 9 | # * A set of three ones is 1000 points 10 | # 11 | # * A set of three numbers (other than ones) is worth 100 times the 12 | # number. (e.g. three fives is 500 points). 13 | # 14 | # * A one (that is not part of a set of three) is worth 100 points. 15 | # 16 | # * A five (that is not part of a set of three) is worth 50 points. 17 | # 18 | # * Everything else is worth 0 points. 19 | # 20 | # 21 | # Examples: 22 | # 23 | # score([1,1,1,5,1]) => 1150 points 24 | # score([2,3,4,6,2]) => 0 points 25 | # score([3,4,5,3,3]) => 350 points 26 | # score([1,5,1,2,4]) => 250 points 27 | # 28 | # More scoring examples are given in the tests below: 29 | # 30 | # Your goal is to write the score method. 31 | 32 | def score(dice) 33 | # You need to write this method 34 | end 35 | 36 | class AboutScoringProject < EdgeCase::Koan 37 | def test_score_of_an_empty_list_is_zero 38 | assert_equal 0, score([]) 39 | end 40 | 41 | def test_score_of_a_single_roll_of_5_is_50 42 | assert_equal 50, score([5]) 43 | end 44 | 45 | def test_score_of_a_single_roll_of_1_is_100 46 | assert_equal 100, score([1]) 47 | end 48 | 49 | def test_score_of_multiple_1s_and_5s_is_the_sum_of_individual_scores 50 | assert_equal 300, score([1,5,5,1]) 51 | end 52 | 53 | def test_score_of_single_2s_3s_4s_and_6s_are_zero 54 | assert_equal 0, score([2,3,4,6]) 55 | end 56 | 57 | def test_score_of_a_triple_1_is_1000 58 | assert_equal 1000, score([1,1,1]) 59 | end 60 | 61 | def test_score_of_other_triples_is_100x 62 | assert_equal 200, score([2,2,2]) 63 | assert_equal 300, score([3,3,3]) 64 | assert_equal 400, score([4,4,4]) 65 | assert_equal 500, score([5,5,5]) 66 | assert_equal 600, score([6,6,6]) 67 | end 68 | 69 | def test_score_of_mixed_is_sum 70 | assert_equal 250, score([2,5,2,2,3]) 71 | assert_equal 550, score([5,5,5,5]) 72 | end 73 | 74 | end 75 | -------------------------------------------------------------------------------- /src/about_scope.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutScope < EdgeCase::Koan 4 | module Jims 5 | class Dog 6 | def identify 7 | :jims_dog 8 | end 9 | end 10 | end 11 | 12 | module Joes 13 | class Dog 14 | def identify 15 | :joes_dog 16 | end 17 | end 18 | end 19 | 20 | def test_dog_is_not_available_in_the_current_scope 21 | assert_raise(___(NameError)) do 22 | fido = Dog.new 23 | end 24 | end 25 | 26 | def test_you_can_reference_nested_classes_using_the_scope_operator 27 | fido = Jims::Dog.new 28 | rover = Joes::Dog.new 29 | assert_equal __(:jims_dog), fido.identify 30 | assert_equal __(:joes_dog), rover.identify 31 | 32 | assert_equal __(true), fido.class != rover.class 33 | assert_equal __(true), Jims::Dog != Joes::Dog 34 | end 35 | 36 | # ------------------------------------------------------------------ 37 | 38 | class String 39 | end 40 | 41 | def test_bare_bones_class_names_assume_the_current_scope 42 | assert_equal __(true), AboutScope::String == String 43 | end 44 | 45 | def test_nested_string_is_not_the_same_as_the_system_string 46 | assert_equal __(false), String == "HI".class 47 | end 48 | 49 | def test_use_the_prefix_scope_operator_to_force_the_global_scope 50 | assert_equal __(true), ::String == "HI".class 51 | end 52 | 53 | # ------------------------------------------------------------------ 54 | 55 | PI = 3.1416 56 | 57 | def test_constants_are_defined_with_an_initial_uppercase_letter 58 | assert_equal __(3.1416), PI 59 | end 60 | 61 | # ------------------------------------------------------------------ 62 | 63 | MyString = ::String 64 | 65 | def test_class_names_are_just_constants 66 | assert_equal __(true), MyString == ::String 67 | assert_equal __(true), MyString == "HI".class 68 | end 69 | 70 | def test_constants_can_be_looked_up_explicitly 71 | assert_equal __(true), PI == AboutScope.const_get("PI") 72 | assert_equal __(true), MyString == AboutScope.const_get("MyString") 73 | end 74 | 75 | def test_you_can_get_a_list_of_constants_for_any_class_or_module 76 | assert_equal __(["Dog"], [:Dog]), Jims.constants 77 | assert Object.constants.size > _n_(10) 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /koans/about_arrays.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutArrays < EdgeCase::Koan 4 | def test_creating_arrays 5 | empty_array = Array.new 6 | assert_equal Array, empty_array.class 7 | assert_equal 0, empty_array.size 8 | end 9 | 10 | def test_array_literals 11 | array = Array.new 12 | assert_equal [], array 13 | 14 | array[0] = 1 15 | assert_equal [1], array 16 | 17 | array[1] = 2 18 | assert_equal [1, 2], array 19 | 20 | array << 333 21 | assert_equal [1,2,333], array 22 | end 23 | 24 | def test_accessing_array_elements 25 | array = [:peanut, :butter, :and, :jelly] 26 | 27 | assert_equal :peanut, array[0] 28 | assert_equal :peanut, array.first 29 | assert_equal :jelly, array[3] 30 | assert_equal :jelly, array.last 31 | assert_equal :jelly, array[-1] 32 | assert_equal :butter, array[-3] 33 | end 34 | 35 | def test_slicing_arrays 36 | array = [:peanut, :butter, :and, :jelly] 37 | 38 | assert_equal [:peanut], array[0,1] 39 | assert_equal [:peanut, :butter], array[0,2] 40 | assert_equal [:and, :jelly], array[2,2] 41 | assert_equal [:and, :jelly], array[2,20] 42 | assert_equal [], array[4,0] 43 | assert_equal [], array[4,100] 44 | assert_equal nil, array[5,0] 45 | end 46 | 47 | def test_arrays_and_ranges 48 | assert_equal Range, (1..5).class 49 | assert_not_equal [2,3,4,5], (1..5) 50 | assert_equal [1,2,3,4,5], (1..5).to_a 51 | assert_equal [1,2,3,4], (1...5).to_a 52 | end 53 | 54 | def test_slicing_with_ranges 55 | array = [:peanut, :butter, :and, :jelly] 56 | 57 | assert_equal [:peanut, :butter, :and], array[0..2] 58 | assert_equal [:peanut, :butter], array[0...2] 59 | assert_equal [:and, :jelly], array[2..-1] 60 | end 61 | 62 | def test_pushing_and_popping_arrays 63 | array = [1,2] 64 | array.push(:last) 65 | 66 | assert_equal [1,2, :last], array 67 | 68 | popped_value = array.pop 69 | assert_equal :last, popped_value 70 | assert_equal [1,2], array 71 | end 72 | 73 | def test_shifting_arrays 74 | array = [1,2] 75 | array.unshift(:first) 76 | 77 | assert_equal [:first, 1,2,], array 78 | 79 | shifted_value = array.shift 80 | assert_equal :first, shifted_value 81 | assert_equal [1,2], array 82 | end 83 | 84 | end 85 | -------------------------------------------------------------------------------- /koans/about_constants.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | C = "top level" 4 | 5 | class AboutConstants < EdgeCase::Koan 6 | 7 | C = "nested" 8 | 9 | def test_nested_constants_may_also_be_referenced_with_relative_paths 10 | assert_equal __, C 11 | end 12 | 13 | def test_top_level_constants_are_referenced_by_double_colons 14 | assert_equal __, ::C 15 | end 16 | 17 | def test_nested_constants_are_referenced_by_their_complete_path 18 | assert_equal __, AboutConstants::C 19 | assert_equal __, ::AboutConstants::C 20 | end 21 | 22 | # ------------------------------------------------------------------ 23 | 24 | class Animal 25 | LEGS = 4 26 | def legs_in_animal 27 | LEGS 28 | end 29 | 30 | class NestedAnimal 31 | def legs_in_nested_animal 32 | LEGS 33 | end 34 | end 35 | end 36 | 37 | def test_nested_classes_inherit_constants_from_enclosing_classes 38 | assert_equal __, Animal::NestedAnimal.new.legs_in_nested_animal 39 | end 40 | 41 | # ------------------------------------------------------------------ 42 | 43 | class Reptile < Animal 44 | def legs_in_reptile 45 | LEGS 46 | end 47 | end 48 | 49 | def test_subclasses_inherit_constants_from_parent_classes 50 | assert_equal __, Reptile.new.legs_in_reptile 51 | end 52 | 53 | # ------------------------------------------------------------------ 54 | 55 | class MyAnimals 56 | LEGS = 2 57 | 58 | class Bird < Animal 59 | def legs_in_bird 60 | LEGS 61 | end 62 | end 63 | end 64 | 65 | def test_who_wins_with_both_nested_and_inherited_constants 66 | assert_equal __, MyAnimals::Bird.new.legs_in_bird 67 | end 68 | 69 | # QUESTION: Which has precedence: The constant in the lexical scope, 70 | # or the constant from the inheritance hierarchy? 71 | 72 | # ------------------------------------------------------------------ 73 | 74 | class MyAnimals::Oyster < Animal 75 | def legs_in_oyster 76 | LEGS 77 | end 78 | end 79 | 80 | def test_who_wins_with_explicit_scoping_on_class_definition 81 | assert_equal __, MyAnimals::Oyster.new.legs_in_oyster 82 | end 83 | 84 | # QUESTION: Now which has precedence: The constant in the lexical 85 | # scope, or the constant from the inheritance hierarchy? Why is it 86 | # different than the previous answer? 87 | end 88 | -------------------------------------------------------------------------------- /src/about_constants.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | C = "top level" 4 | 5 | class AboutConstants < EdgeCase::Koan 6 | 7 | C = "nested" 8 | 9 | def test_nested_constants_may_also_be_referenced_with_relative_paths 10 | assert_equal __("nested"), C 11 | end 12 | 13 | def test_top_level_constants_are_referenced_by_double_colons 14 | assert_equal __("top level"), ::C 15 | end 16 | 17 | def test_nested_constants_are_referenced_by_their_complete_path 18 | assert_equal __("nested"), AboutConstants::C 19 | assert_equal __("nested"), ::AboutConstants::C 20 | end 21 | 22 | # ------------------------------------------------------------------ 23 | 24 | class Animal 25 | LEGS = 4 26 | def legs_in_animal 27 | LEGS 28 | end 29 | 30 | class NestedAnimal 31 | def legs_in_nested_animal 32 | LEGS 33 | end 34 | end 35 | end 36 | 37 | def test_nested_classes_inherit_constants_from_enclosing_classes 38 | assert_equal __(4), Animal::NestedAnimal.new.legs_in_nested_animal 39 | end 40 | 41 | # ------------------------------------------------------------------ 42 | 43 | class Reptile < Animal 44 | def legs_in_reptile 45 | LEGS 46 | end 47 | end 48 | 49 | def test_subclasses_inherit_constants_from_parent_classes 50 | assert_equal __(4), Reptile.new.legs_in_reptile 51 | end 52 | 53 | # ------------------------------------------------------------------ 54 | 55 | class MyAnimals 56 | LEGS = 2 57 | 58 | class Bird < Animal 59 | def legs_in_bird 60 | LEGS 61 | end 62 | end 63 | end 64 | 65 | def test_who_wins_with_both_nested_and_inherited_constants 66 | assert_equal __(2), MyAnimals::Bird.new.legs_in_bird 67 | end 68 | 69 | # QUESTION: Which has precedence: The constant in the lexical scope, 70 | # or the constant from the inheritance hierarchy? 71 | 72 | # ------------------------------------------------------------------ 73 | 74 | class MyAnimals::Oyster < Animal 75 | def legs_in_oyster 76 | LEGS 77 | end 78 | end 79 | 80 | def test_who_wins_with_explicit_scoping_on_class_definition 81 | assert_equal __(4), MyAnimals::Oyster.new.legs_in_oyster 82 | end 83 | 84 | # QUESTION: Now which has precedence: The constant in the lexical 85 | # scope, or the constant from the inheritance hierarchy? Why is it 86 | # different than the previous answer? 87 | end 88 | -------------------------------------------------------------------------------- /src/about_arrays.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutArrays < EdgeCase::Koan 4 | def test_creating_arrays 5 | empty_array = Array.new 6 | assert_equal __(Array), empty_array.class 7 | assert_equal __(0), empty_array.size 8 | end 9 | 10 | def test_array_literals 11 | array = Array.new 12 | assert_equal [], array # __ 13 | 14 | array[0] = 1 15 | assert_equal [1], array # __ 16 | 17 | array[1] = 2 18 | assert_equal [1, __(2)], array 19 | 20 | array << 333 21 | assert_equal __([1, 2, 333]), array 22 | end 23 | 24 | def test_accessing_array_elements 25 | array = [:peanut, :butter, :and, :jelly] 26 | 27 | assert_equal __(:peanut), array[0] 28 | assert_equal __(:peanut), array.first 29 | assert_equal __(:jelly), array[3] 30 | assert_equal __(:jelly), array.last 31 | assert_equal __(:jelly), array[-1] 32 | assert_equal __(:butter), array[-3] 33 | end 34 | 35 | def test_slicing_arrays 36 | array = [:peanut, :butter, :and, :jelly] 37 | 38 | assert_equal __([:peanut]), array[0,1] 39 | assert_equal __([:peanut, :butter]), array[0,2] 40 | assert_equal __([:and, :jelly]), array[2,2] 41 | assert_equal __([:and, :jelly]), array[2,20] 42 | assert_equal __([]), array[4,0] 43 | assert_equal __([]), array[4,100] 44 | assert_equal __(nil), array[5,0] 45 | end 46 | 47 | def test_arrays_and_ranges 48 | assert_equal __(Range), (1..5).class 49 | assert_not_equal [1,2,3,4,5], (1..5) # __ 50 | assert_equal __([1,2,3,4,5]), (1..5).to_a 51 | assert_equal __([1,2,3,4]), (1...5).to_a 52 | end 53 | 54 | def test_slicing_with_ranges 55 | array = [:peanut, :butter, :and, :jelly] 56 | 57 | assert_equal __([:peanut, :butter, :and]), array[0..2] 58 | assert_equal __([:peanut, :butter]), array[0...2] 59 | assert_equal __([:and, :jelly]), array[2..-1] 60 | end 61 | 62 | def test_pushing_and_popping_arrays 63 | array = [1,2] 64 | array.push(:last) 65 | 66 | assert_equal __([1, 2, :last]), array 67 | 68 | popped_value = array.pop 69 | assert_equal __(:last), popped_value 70 | assert_equal __([1, 2]), array 71 | end 72 | 73 | def test_shifting_arrays 74 | array = [1,2] 75 | array.unshift(:first) 76 | 77 | assert_equal __([:first, 1, 2]), array 78 | 79 | shifted_value = array.shift 80 | assert_equal __(:first), shifted_value 81 | assert_equal __([1, 2]), array 82 | end 83 | 84 | end 85 | -------------------------------------------------------------------------------- /src/GREED_RULES.txt: -------------------------------------------------------------------------------- 1 | = Playing Greed 2 | 3 | Greed is a dice game played among 2 or more players, using 5 4 | six-sided dice. 5 | 6 | == Playing Greed 7 | 8 | Each player takes a turn consisting of one or more rolls of the dice. 9 | On the first roll of the game, a player rolls all five dice which are 10 | scored according to the following: 11 | 12 | Three 1's => 1000 points 13 | Three 6's => 600 points 14 | Three 5's => 500 points 15 | Three 4's => 400 points 16 | Three 3's => 300 points 17 | Three 2's => 200 points 18 | One 1 => 100 points 19 | One 5 => 50 points 20 | 21 | A single die can only be counted once in each roll. For example, 22 | a "5" can only count as part of a triplet (contributing to the 500 23 | points) or as a single 50 points, but not both in the same roll. 24 | 25 | Example Scoring 26 | 27 | Throw Score 28 | --------- ------------------ 29 | 5 1 3 4 1 50 + 2 * 100 = 250 30 | 1 1 1 3 1 1000 + 100 = 1100 31 | 2 4 4 5 4 400 + 50 = 450 32 | 33 | The dice not contributing to the score are called the non-scoring 34 | dice. "3" and "4" are non-scoring dice in the first example. "3" is 35 | a non-scoring die in the second, and "2" is a non-score die in the 36 | final example. 37 | 38 | After a player rolls and the score is calculated, the scoring dice are 39 | removed and the player has the option of rolling again using only the 40 | non-scoring dice. If all of the thrown dice are scoring, then the 41 | player may roll all 5 dice in the next roll. 42 | 43 | The player may continue to roll as long as each roll scores points. If 44 | a roll has zero points, then the player loses not only their turn, but 45 | also accumulated score for that turn. If a player decides to stop 46 | rolling before rolling a zero-point roll, then the accumulated points 47 | for the turn is added to his total score. 48 | 49 | == Getting "In The Game" 50 | 51 | Before a player is allowed to accumulate points, they must get at 52 | least 300 points in a single turn. Once they have achieved 300 points 53 | in a single turn, the points earned in that turn and each following 54 | turn will be counted toward their total score. 55 | 56 | == End Game 57 | 58 | Once a player reaches 3000 (or more) points, the game enters the final 59 | round where each of the other players gets one more turn. The winner 60 | is the player with the highest score after the final round. 61 | 62 | == References 63 | 64 | Greed is described on Wikipedia at 65 | http://en.wikipedia.org/wiki/Greed_(dice_game), however the rules are 66 | a bit different from the rules given here. 67 | -------------------------------------------------------------------------------- /koans/GREED_RULES.txt: -------------------------------------------------------------------------------- 1 | = Playing Greed 2 | 3 | Greed is a dice game played among 2 or more players, using 5 4 | six-sided dice. 5 | 6 | == Playing Greed 7 | 8 | Each player takes a turn consisting of one or more rolls of the dice. 9 | On the first roll of the game, a player rolls all five dice which are 10 | scored according to the following: 11 | 12 | Three 1's => 1000 points 13 | Three 6's => 600 points 14 | Three 5's => 500 points 15 | Three 4's => 400 points 16 | Three 3's => 300 points 17 | Three 2's => 200 points 18 | One 1 => 100 points 19 | One 5 => 50 points 20 | 21 | A single die can only be counted once in each roll. For example, 22 | a "5" can only count as part of a triplet (contributing to the 500 23 | points) or as a single 50 points, but not both in the same roll. 24 | 25 | Example Scoring 26 | 27 | Throw Score 28 | --------- ------------------ 29 | 5 1 3 4 1 50 + 2 * 100 = 250 30 | 1 1 1 3 1 1000 + 100 = 1100 31 | 2 4 4 5 4 400 + 50 = 450 32 | 33 | The dice not contributing to the score are called the non-scoring 34 | dice. "3" and "4" are non-scoring dice in the first example. "3" is 35 | a non-scoring die in the second, and "2" is a non-score die in the 36 | final example. 37 | 38 | After a player rolls and the score is calculated, the scoring dice are 39 | removed and the player has the option of rolling again using only the 40 | non-scoring dice. If all of the thrown dice are scoring, then the 41 | player may roll all 5 dice in the next roll. 42 | 43 | The player may continue to roll as long as each roll scores points. If 44 | a roll has zero points, then the player loses not only their turn, but 45 | also accumulated score for that turn. If a player decides to stop 46 | rolling before rolling a zero-point roll, then the accumulated points 47 | for the turn is added to his total score. 48 | 49 | == Getting "In The Game" 50 | 51 | Before a player is allowed to accumulate points, they must get at 52 | least 300 points in a single turn. Once they have achieved 300 points 53 | in a single turn, the points earned in that turn and each following 54 | turn will be counted toward their total score. 55 | 56 | == End Game 57 | 58 | Once a player reaches 3000 (or more) points, the game enters the final 59 | round where each of the other players gets one more turn. The winner 60 | is the player with the highest score after the final round. 61 | 62 | == References 63 | 64 | Greed is described on Wikipedia at 65 | http://en.wikipedia.org/wiki/Greed_(dice_game), however the rules are 66 | a bit different from the rules given here. 67 | -------------------------------------------------------------------------------- /src/about_scoring_project.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # Greed is a dice game where you roll up to five dice to accumulate 4 | # points. The following "score" function will be used to calculate the 5 | # score of a single roll of the dice. 6 | # 7 | # A greed roll is scored as follows: 8 | # 9 | # * A set of three ones is 1000 points 10 | # 11 | # * A set of three numbers (other than ones) is worth 100 times the 12 | # number. (e.g. three fives is 500 points). 13 | # 14 | # * A one (that is not part of a set of three) is worth 100 points. 15 | # 16 | # * A five (that is not part of a set of three) is worth 50 points. 17 | # 18 | # * Everything else is worth 0 points. 19 | # 20 | # 21 | # Examples: 22 | # 23 | # score([1,1,1,5,1]) => 1150 points 24 | # score([2,3,4,6,2]) => 0 points 25 | # score([3,4,5,3,3]) => 350 points 26 | # score([1,5,1,2,4]) => 250 points 27 | # 28 | # More scoring examples are given in the tests below: 29 | # 30 | # Your goal is to write the score method. 31 | 32 | def score(dice) 33 | # You need to write this method 34 | #-- 35 | result = 0 36 | (1..6).each do |face| 37 | count = dice.select { |n| n == face }.size 38 | while count > 0 39 | if count >= 3 40 | result += (face == 1) ? 1000 : 100 * face 41 | count -= 3 42 | elsif face == 5 43 | result += count * 50 44 | count = 0 45 | elsif face == 1 46 | result += count * 100 47 | count = 0 48 | else 49 | count = 0 50 | end 51 | end 52 | end 53 | result 54 | #++ 55 | end 56 | 57 | class AboutScoringProject < EdgeCase::Koan 58 | def test_score_of_an_empty_list_is_zero 59 | assert_equal 0, score([]) 60 | end 61 | 62 | def test_score_of_a_single_roll_of_5_is_50 63 | assert_equal 50, score([5]) 64 | end 65 | 66 | def test_score_of_a_single_roll_of_1_is_100 67 | assert_equal 100, score([1]) 68 | end 69 | 70 | def test_score_of_multiple_1s_and_5s_is_the_sum_of_individual_scores 71 | assert_equal 300, score([1,5,5,1]) 72 | end 73 | 74 | def test_score_of_single_2s_3s_4s_and_6s_are_zero 75 | assert_equal 0, score([2,3,4,6]) 76 | end 77 | 78 | def test_score_of_a_triple_1_is_1000 79 | assert_equal 1000, score([1,1,1]) 80 | end 81 | 82 | def test_score_of_other_triples_is_100x 83 | assert_equal 200, score([2,2,2]) 84 | assert_equal 300, score([3,3,3]) 85 | assert_equal 400, score([4,4,4]) 86 | assert_equal 500, score([5,5,5]) 87 | assert_equal 600, score([6,6,6]) 88 | end 89 | 90 | def test_score_of_mixed_is_sum 91 | assert_equal 250, score([2,5,2,2,3]) 92 | assert_equal 550, score([5,5,5,5]) 93 | end 94 | 95 | end 96 | -------------------------------------------------------------------------------- /koans/about_blocks.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutBlocks < EdgeCase::Koan 4 | def method_with_block 5 | result = yield 6 | result 7 | end 8 | 9 | def test_methods_can_take_blocks 10 | yielded_result = method_with_block { 1 + 2 } 11 | assert_equal __, yielded_result 12 | end 13 | 14 | def test_blocks_can_be_defined_with_do_end_too 15 | yielded_result = method_with_block do 1 + 2 end 16 | assert_equal __, yielded_result 17 | end 18 | 19 | # ------------------------------------------------------------------ 20 | 21 | def method_with_block_arguments 22 | yield("Jim") 23 | end 24 | 25 | def test_blocks_can_take_arguments 26 | result = method_with_block_arguments do |argument| 27 | assert_equal __, argument 28 | end 29 | end 30 | 31 | # ------------------------------------------------------------------ 32 | 33 | def many_yields 34 | yield(:peanut) 35 | yield(:butter) 36 | yield(:and) 37 | yield(:jelly) 38 | end 39 | 40 | def test_methods_can_call_yield_many_times 41 | result = [] 42 | many_yields { |item| result << item } 43 | assert_equal __, result 44 | end 45 | 46 | # ------------------------------------------------------------------ 47 | 48 | def yield_tester 49 | if block_given? 50 | yield 51 | else 52 | :no_block 53 | end 54 | end 55 | 56 | def test_methods_can_see_if_they_have_been_called_with_a_block 57 | assert_equal __, yield_tester { :with_block } 58 | assert_equal __, yield_tester 59 | end 60 | 61 | # ------------------------------------------------------------------ 62 | 63 | def test_block_can_affect_variables_in_the_code_where_they_are_created 64 | value = :initial_value 65 | method_with_block { value = :modified_in_a_block } 66 | assert_equal __, value 67 | end 68 | 69 | def test_blocks_can_be_assigned_to_variables_and_called_explicitly 70 | add_one = lambda { |n| n + 1 } 71 | assert_equal __, add_one.call(10) 72 | 73 | # Alternative calling sequence 74 | assert_equal __, add_one[10] 75 | end 76 | 77 | def test_stand_alone_blocks_can_be_passed_to_methods_expecting_blocks 78 | make_upper = lambda { |n| n.upcase } 79 | result = method_with_block_arguments(&make_upper) 80 | assert_equal __, result 81 | end 82 | 83 | # ------------------------------------------------------------------ 84 | 85 | def method_with_explicit_block(&block) 86 | block.call(10) 87 | end 88 | 89 | def test_methods_can_take_an_explicit_block_argument 90 | assert_equal __, method_with_explicit_block { |n| n * 2 } 91 | 92 | add_one = lambda { |n| n + 1 } 93 | assert_equal __, method_with_explicit_block(&add_one) 94 | end 95 | 96 | end 97 | -------------------------------------------------------------------------------- /src/about_blocks.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutBlocks < EdgeCase::Koan 4 | def method_with_block 5 | result = yield 6 | result 7 | end 8 | 9 | def test_methods_can_take_blocks 10 | yielded_result = method_with_block { 1 + 2 } 11 | assert_equal __(3), yielded_result 12 | end 13 | 14 | def test_blocks_can_be_defined_with_do_end_too 15 | yielded_result = method_with_block do 1 + 2 end 16 | assert_equal __(3), yielded_result 17 | end 18 | 19 | # ------------------------------------------------------------------ 20 | 21 | def method_with_block_arguments 22 | yield("Jim") 23 | end 24 | 25 | def test_blocks_can_take_arguments 26 | result = method_with_block_arguments do |argument| 27 | assert_equal __("Jim"), argument 28 | end 29 | end 30 | 31 | # ------------------------------------------------------------------ 32 | 33 | def many_yields 34 | yield(:peanut) 35 | yield(:butter) 36 | yield(:and) 37 | yield(:jelly) 38 | end 39 | 40 | def test_methods_can_call_yield_many_times 41 | result = [] 42 | many_yields { |item| result << item } 43 | assert_equal __([:peanut, :butter, :and, :jelly]), result 44 | end 45 | 46 | # ------------------------------------------------------------------ 47 | 48 | def yield_tester 49 | if block_given? 50 | yield 51 | else 52 | :no_block 53 | end 54 | end 55 | 56 | def test_methods_can_see_if_they_have_been_called_with_a_block 57 | assert_equal __(:with_block), yield_tester { :with_block } 58 | assert_equal __(:no_block), yield_tester 59 | end 60 | 61 | # ------------------------------------------------------------------ 62 | 63 | def test_block_can_affect_variables_in_the_code_where_they_are_created 64 | value = :initial_value 65 | method_with_block { value = :modified_in_a_block } 66 | assert_equal __(:modified_in_a_block), value 67 | end 68 | 69 | def test_blocks_can_be_assigned_to_variables_and_called_explicitly 70 | add_one = lambda { |n| n + 1 } 71 | assert_equal __(11), add_one.call(10) 72 | 73 | # Alternative calling sequence 74 | assert_equal __(11), add_one[10] 75 | end 76 | 77 | def test_stand_alone_blocks_can_be_passed_to_methods_expecting_blocks 78 | make_upper = lambda { |n| n.upcase } 79 | result = method_with_block_arguments(&make_upper) 80 | assert_equal __("JIM"), result 81 | end 82 | 83 | # ------------------------------------------------------------------ 84 | 85 | def method_with_explicit_block(&block) 86 | block.call(10) 87 | end 88 | 89 | def test_methods_can_take_an_explicit_block_argument 90 | assert_equal __(20), method_with_explicit_block { |n| n * 2 } 91 | 92 | add_one = lambda { |n| n + 1 } 93 | assert_equal __(11), method_with_explicit_block(&add_one) 94 | end 95 | 96 | end 97 | -------------------------------------------------------------------------------- /koans/about_symbols.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutSymbols < EdgeCase::Koan 4 | def test_symbols_are_symbols 5 | symbol = :ruby 6 | assert_equal __, symbol.is_a?(Symbol) 7 | end 8 | 9 | def test_symbols_can_be_compared 10 | symbol1 = :a_symbol 11 | symbol2 = :a_symbol 12 | symbol3 = :something_else 13 | 14 | assert_equal __, symbol1 == symbol2 15 | assert_equal __, symbol1 == symbol3 16 | end 17 | 18 | def test_identical_symbols_are_a_single_internal_object 19 | symbol1 = :a_symbol 20 | symbol2 = :a_symbol 21 | 22 | assert_equal __, symbol1 == symbol2 23 | assert_equal __, symbol1.object_id == symbol2.object_id 24 | end 25 | 26 | def test_method_names_become_symbols 27 | symbols_as_strings = Symbol.all_symbols.map { |x| x.to_s } 28 | assert_equal __, symbols_as_strings.include?("test_method_names_become_symbols") 29 | end 30 | 31 | # THINK ABOUT IT: 32 | # 33 | # Why do we convert the list of symbols to strings and then compare 34 | # against the string value rather than against symbols? 35 | 36 | in_ruby_version("mri") do 37 | RubyConstant = "What is the sound of one hand clapping?" 38 | def test_constants_become_symbols 39 | all_symbols = Symbol.all_symbols 40 | 41 | assert_equal __, all_symbols.include?(__) 42 | end 43 | end 44 | 45 | def test_symbols_can_be_made_from_strings 46 | string = "catsAndDogs" 47 | assert_equal __, string.to_sym 48 | end 49 | 50 | def test_symbols_with_spaces_can_be_built 51 | symbol = :"cats and dogs" 52 | 53 | assert_equal symbol, __.to_sym 54 | end 55 | 56 | def test_symbols_with_interpolation_can_be_built 57 | value = "and" 58 | symbol = :"cats #{value} dogs" 59 | 60 | assert_equal symbol, __.to_sym 61 | end 62 | 63 | def test_to_s_is_called_on_interpolated_symbols 64 | symbol = :cats 65 | string = "It is raining #{symbol} and dogs." 66 | 67 | assert_equal __, string 68 | end 69 | 70 | def test_symbols_are_not_strings 71 | symbol = :ruby 72 | assert_equal __, symbol.is_a?(String) 73 | assert_equal __, symbol.eql?("ruby") 74 | end 75 | 76 | def test_symbols_do_not_have_string_methods 77 | symbol = :not_a_string 78 | assert_equal __, symbol.respond_to?(:each_char) 79 | assert_equal __, symbol.respond_to?(:reverse) 80 | end 81 | 82 | # It's important to realize that symbols are not "immutable 83 | # strings", though they are immutable. None of the 84 | # interesting string operations are available on symbols. 85 | 86 | def test_symbols_cannot_be_concatenated 87 | # Exceptions will be pondered further down the path 88 | assert_raise(___) do 89 | :cats + :dogs 90 | end 91 | end 92 | 93 | def test_symbols_can_be_dynamically_created 94 | assert_equal __, ("cats" + "dogs").to_sym 95 | end 96 | 97 | # THINK ABOUT IT: 98 | # 99 | # Why is it not a good idea to dynamically create a lot of symbols? 100 | end 101 | -------------------------------------------------------------------------------- /koans/about_sandwich_code.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutSandwichCode < EdgeCase::Koan 4 | 5 | def count_lines(file_name) 6 | file = open(file_name) 7 | count = 0 8 | while line = file.gets 9 | count += 1 10 | end 11 | count 12 | ensure 13 | file.close if file 14 | end 15 | 16 | def test_counting_lines 17 | assert_equal __, count_lines("example_file.txt") 18 | end 19 | 20 | # ------------------------------------------------------------------ 21 | 22 | def find_line(file_name) 23 | file = open(file_name) 24 | while line = file.gets 25 | return line if line.match(/e/) 26 | end 27 | ensure 28 | file.close if file 29 | end 30 | 31 | def test_finding_lines 32 | assert_equal __, find_line("example_file.txt") 33 | end 34 | 35 | # ------------------------------------------------------------------ 36 | # THINK ABOUT IT: 37 | # 38 | # The count_lines and find_line are similar, and yet different. 39 | # They both follow the pattern of "sandwich code". 40 | # 41 | # Sandwich code is code that comes in three parts: (1) the top slice 42 | # of bread, (2) the meat, and (3) the bottom slice of bread. The 43 | # bread part of the sandwich almost always goes together, but 44 | # the meat part changes all the time. 45 | # 46 | # Because the changing part of the sandwich code is in the middle, 47 | # abstracting the top and bottom bread slices to a library can be 48 | # difficult in many languages. 49 | # 50 | # (Aside for C++ programmers: The idiom of capturing allocated 51 | # pointers in a smart pointer constructor is an attempt to deal with 52 | # the problem of sandwich code for resource allocation.) 53 | # 54 | # Consider the following code: 55 | # 56 | 57 | def file_sandwich(file_name) 58 | file = open(file_name) 59 | yield(file) 60 | ensure 61 | file.close if file 62 | end 63 | 64 | # Now we write: 65 | 66 | def count_lines2(file_name) 67 | file_sandwich(file_name) do |file| 68 | count = 0 69 | while line = file.gets 70 | count += 1 71 | end 72 | count 73 | end 74 | end 75 | 76 | def test_counting_lines2 77 | assert_equal __, count_lines2("example_file.txt") 78 | end 79 | 80 | # ------------------------------------------------------------------ 81 | 82 | def find_line2(file_name) 83 | # Rewrite find_line using the file_sandwich library function. 84 | end 85 | 86 | def test_finding_lines2 87 | assert_equal __, find_line2("example_file.txt") 88 | end 89 | 90 | # ------------------------------------------------------------------ 91 | 92 | def count_lines3(file_name) 93 | open(file_name) do |file| 94 | count = 0 95 | while line = file.gets 96 | count += 1 97 | end 98 | count 99 | end 100 | end 101 | 102 | def test_open_handles_the_file_sandwich_when_given_a_block 103 | assert_equal __, count_lines3("example_file.txt") 104 | end 105 | 106 | end 107 | -------------------------------------------------------------------------------- /src/about_symbols.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutSymbols < EdgeCase::Koan 4 | def test_symbols_are_symbols 5 | symbol = :ruby 6 | assert_equal __(true), symbol.is_a?(Symbol) 7 | end 8 | 9 | def test_symbols_can_be_compared 10 | symbol1 = :a_symbol 11 | symbol2 = :a_symbol 12 | symbol3 = :something_else 13 | 14 | assert_equal __(true), symbol1 == symbol2 15 | assert_equal __(false), symbol1 == symbol3 16 | end 17 | 18 | def test_identical_symbols_are_a_single_internal_object 19 | symbol1 = :a_symbol 20 | symbol2 = :a_symbol 21 | 22 | assert_equal __(true), symbol1 == symbol2 23 | assert_equal __(true), symbol1.object_id == symbol2.object_id 24 | end 25 | 26 | def test_method_names_become_symbols 27 | symbols_as_strings = Symbol.all_symbols.map { |x| x.to_s } 28 | assert_equal __(true), symbols_as_strings.include?("test_method_names_become_symbols") 29 | end 30 | 31 | # THINK ABOUT IT: 32 | # 33 | # Why do we convert the list of symbols to strings and then compare 34 | # against the string value rather than against symbols? 35 | 36 | in_ruby_version("mri") do 37 | RubyConstant = "What is the sound of one hand clapping?" 38 | def test_constants_become_symbols 39 | all_symbols = Symbol.all_symbols 40 | 41 | assert_equal __(true), all_symbols.include?(__(:RubyConstant)) 42 | end 43 | end 44 | 45 | def test_symbols_can_be_made_from_strings 46 | string = "catsAndDogs" 47 | assert_equal __(:catsAndDogs), string.to_sym 48 | end 49 | 50 | def test_symbols_with_spaces_can_be_built 51 | symbol = :"cats and dogs" 52 | 53 | assert_equal symbol, __("cats and dogs").to_sym 54 | end 55 | 56 | def test_symbols_with_interpolation_can_be_built 57 | value = "and" 58 | symbol = :"cats #{value} dogs" 59 | 60 | assert_equal symbol, __("cats and dogs").to_sym 61 | end 62 | 63 | def test_to_s_is_called_on_interpolated_symbols 64 | symbol = :cats 65 | string = "It is raining #{symbol} and dogs." 66 | 67 | assert_equal __('It is raining cats and dogs.'), string 68 | end 69 | 70 | def test_symbols_are_not_strings 71 | symbol = :ruby 72 | assert_equal __(false), symbol.is_a?(String) 73 | assert_equal __(false), symbol.eql?("ruby") 74 | end 75 | 76 | def test_symbols_do_not_have_string_methods 77 | symbol = :not_a_string 78 | assert_equal __(false), symbol.respond_to?(:each_char) 79 | assert_equal __(false), symbol.respond_to?(:reverse) 80 | end 81 | 82 | # It's important to realize that symbols are not "immutable 83 | # strings", though they are immutable. None of the 84 | # interesting string operations are available on symbols. 85 | 86 | def test_symbols_cannot_be_concatenated 87 | # Exceptions will be pondered further down the path 88 | assert_raise(___(NoMethodError)) do 89 | :cats + :dogs 90 | end 91 | end 92 | 93 | def test_symbols_can_be_dynamically_created 94 | assert_equal __(:catsdogs), ("cats" + "dogs").to_sym 95 | end 96 | 97 | # THINK ABOUT IT: 98 | # 99 | # Why is it not a good idea to dynamically create a lot of symbols? 100 | end 101 | -------------------------------------------------------------------------------- /src/about_sandwich_code.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutSandwichCode < EdgeCase::Koan 4 | 5 | def count_lines(file_name) 6 | file = open(file_name) 7 | count = 0 8 | while line = file.gets 9 | count += 1 10 | end 11 | count 12 | ensure 13 | file.close if file 14 | end 15 | 16 | def test_counting_lines 17 | assert_equal __(4), count_lines("example_file.txt") 18 | end 19 | 20 | # ------------------------------------------------------------------ 21 | 22 | def find_line(file_name) 23 | file = open(file_name) 24 | while line = file.gets 25 | return line if line.match(/e/) 26 | end 27 | ensure 28 | file.close if file 29 | end 30 | 31 | def test_finding_lines 32 | assert_equal __("test\n"), find_line("example_file.txt") 33 | end 34 | 35 | # ------------------------------------------------------------------ 36 | # THINK ABOUT IT: 37 | # 38 | # The count_lines and find_line are similar, and yet different. 39 | # They both follow the pattern of "sandwich code". 40 | # 41 | # Sandwich code is code that comes in three parts: (1) the top slice 42 | # of bread, (2) the meat, and (3) the bottom slice of bread. The 43 | # bread part of the sandwich almost always goes together, but 44 | # the meat part changes all the time. 45 | # 46 | # Because the changing part of the sandwich code is in the middle, 47 | # abstracting the top and bottom bread slices to a library can be 48 | # difficult in many languages. 49 | # 50 | # (Aside for C++ programmers: The idiom of capturing allocated 51 | # pointers in a smart pointer constructor is an attempt to deal with 52 | # the problem of sandwich code for resource allocation.) 53 | # 54 | # Consider the following code: 55 | # 56 | 57 | def file_sandwich(file_name) 58 | file = open(file_name) 59 | yield(file) 60 | ensure 61 | file.close if file 62 | end 63 | 64 | # Now we write: 65 | 66 | def count_lines2(file_name) 67 | file_sandwich(file_name) do |file| 68 | count = 0 69 | while line = file.gets 70 | count += 1 71 | end 72 | count 73 | end 74 | end 75 | 76 | def test_counting_lines2 77 | assert_equal __(4), count_lines2("example_file.txt") 78 | end 79 | 80 | # ------------------------------------------------------------------ 81 | 82 | def find_line2(file_name) 83 | # Rewrite find_line using the file_sandwich library function. 84 | #-- 85 | file_sandwich(file_name) do |file| 86 | file.each do |line| 87 | return line if line =~ /e/ 88 | end 89 | end 90 | #++ 91 | end 92 | 93 | def test_finding_lines2 94 | assert_equal __("test\n"), find_line2("example_file.txt") 95 | end 96 | 97 | # ------------------------------------------------------------------ 98 | 99 | def count_lines3(file_name) 100 | open(file_name) do |file| 101 | count = 0 102 | while line = file.gets 103 | count += 1 104 | end 105 | count 106 | end 107 | end 108 | 109 | def test_open_handles_the_file_sandwich_when_given_a_block 110 | assert_equal __(4), count_lines3("example_file.txt") 111 | end 112 | 113 | end 114 | -------------------------------------------------------------------------------- /koans/about_control_statements.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutControlStatements < EdgeCase::Koan 4 | 5 | def test_if_then_else_statements 6 | if true 7 | result = :true_value 8 | else 9 | result = :false_value 10 | end 11 | assert_equal __, result 12 | end 13 | 14 | def test_if_then_statements 15 | result = :default_value 16 | if true 17 | result = :true_value 18 | end 19 | assert_equal __, result 20 | end 21 | 22 | def test_if_statements_return_values 23 | value = if true 24 | :true_value 25 | else 26 | :false_value 27 | end 28 | assert_equal __, value 29 | 30 | value = if false 31 | :true_value 32 | else 33 | :false_value 34 | end 35 | assert_equal __, value 36 | 37 | # NOTE: Actually, EVERY statement in Ruby will return a value, not 38 | # just if statements. 39 | end 40 | 41 | def test_if_statements_with_no_else_with_false_condition_return_value 42 | value = if false 43 | :true_value 44 | end 45 | assert_equal __, value 46 | end 47 | 48 | def test_condition_operators 49 | assert_equal __, (true ? :true_value : :false_value) 50 | assert_equal __, (false ? :true_value : :false_value) 51 | end 52 | 53 | def test_if_statement_modifiers 54 | result = :default_value 55 | result = :true_value if true 56 | 57 | assert_equal __, result 58 | end 59 | 60 | def test_unless_statement 61 | result = :default_value 62 | unless false # same as saying 'if !false', which evaluates as 'if true' 63 | result = :false_value 64 | end 65 | assert_equal __, result 66 | end 67 | 68 | def test_unless_statement_evaluate_true 69 | result = :default_value 70 | unless true # same as saying 'if !true', which evaluates as 'if false' 71 | result = :true_value 72 | end 73 | assert_equal __, result 74 | end 75 | 76 | def test_unless_statement_modifier 77 | result = :default_value 78 | result = :false_value unless false 79 | 80 | assert_equal __, result 81 | end 82 | 83 | def test_while_statement 84 | i = 1 85 | result = 1 86 | while i <= 10 87 | result = result * i 88 | i += 1 89 | end 90 | assert_equal __, result 91 | end 92 | 93 | def test_break_statement 94 | i = 1 95 | result = 1 96 | while true 97 | break unless i <= 10 98 | result = result * i 99 | i += 1 100 | end 101 | assert_equal __, result 102 | end 103 | 104 | def test_break_statement_returns_values 105 | i = 1 106 | result = while i <= 10 107 | break i if i % 2 == 0 108 | i += 1 109 | end 110 | 111 | assert_equal __, result 112 | end 113 | 114 | def test_next_statement 115 | i = 0 116 | result = [] 117 | while i < 10 118 | i += 1 119 | next if (i % 2) == 0 120 | result << i 121 | end 122 | assert_equal __, result 123 | end 124 | 125 | def test_for_statement 126 | array = ["fish", "and", "chips"] 127 | result = [] 128 | for item in array 129 | result << item.upcase 130 | end 131 | assert_equal [__, __, __], result 132 | end 133 | 134 | end 135 | -------------------------------------------------------------------------------- /koans/about_hashes.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutHashes < EdgeCase::Koan 4 | def test_creating_hashes 5 | empty_hash = Hash.new 6 | assert_equal __, empty_hash.class 7 | assert_equal({}, empty_hash) 8 | assert_equal __, empty_hash.size 9 | end 10 | 11 | def test_hash_literals 12 | hash = { :one => "uno", :two => "dos" } 13 | assert_equal __, hash.size 14 | end 15 | 16 | def test_accessing_hashes 17 | hash = { :one => "uno", :two => "dos" } 18 | assert_equal __, hash[:one] 19 | assert_equal __, hash[:two] 20 | assert_equal __, hash[:doesnt_exist] 21 | end 22 | 23 | def test_accessing_hashes_with_fetch 24 | hash = { :one => "uno" } 25 | assert_equal __, hash.fetch(:one) 26 | assert_raise(___) do 27 | hash.fetch(:doesnt_exist) 28 | end 29 | 30 | # THINK ABOUT IT: 31 | # 32 | # Why might you want to use #fetch instead of #[] when accessing hash keys? 33 | end 34 | 35 | def test_changing_hashes 36 | hash = { :one => "uno", :two => "dos" } 37 | hash[:one] = "eins" 38 | 39 | expected = { :one => __, :two => "dos" } 40 | assert_equal __, expected == hash 41 | 42 | # Bonus Question: Why was "expected" broken out into a variable 43 | # rather than used as a literal? 44 | end 45 | 46 | def test_hash_is_unordered 47 | hash1 = { :one => "uno", :two => "dos" } 48 | hash2 = { :two => "dos", :one => "uno" } 49 | 50 | assert_equal __, hash1 == hash2 51 | end 52 | 53 | def test_hash_keys 54 | hash = { :one => "uno", :two => "dos" } 55 | assert_equal __, hash.keys.size 56 | assert_equal __, hash.keys.include?(:one) 57 | assert_equal __, hash.keys.include?(:two) 58 | assert_equal __, hash.keys.class 59 | end 60 | 61 | def test_hash_values 62 | hash = { :one => "uno", :two => "dos" } 63 | assert_equal __, hash.values.size 64 | assert_equal __, hash.values.include?("uno") 65 | assert_equal __, hash.values.include?("dos") 66 | assert_equal __, hash.values.class 67 | end 68 | 69 | def test_combining_hashes 70 | hash = { "jim" => 53, "amy" => 20, "dan" => 23 } 71 | new_hash = hash.merge({ "jim" => 54, "jenny" => 26 }) 72 | 73 | assert_equal __, hash != new_hash 74 | 75 | expected = { "jim" => __, "amy" => 20, "dan" => 23, "jenny" => __ } 76 | assert_equal __, expected == new_hash 77 | end 78 | 79 | def test_default_value 80 | hash1 = Hash.new 81 | hash1[:one] = 1 82 | 83 | assert_equal __, hash1[:one] 84 | assert_equal __, hash1[:two] 85 | 86 | hash2 = Hash.new("dos") 87 | hash2[:one] = 1 88 | 89 | assert_equal __, hash2[:one] 90 | assert_equal __, hash2[:two] 91 | end 92 | 93 | def test_default_value_is_the_same_object 94 | hash = Hash.new([]) 95 | 96 | hash[:one] << "uno" 97 | hash[:two] << "dos" 98 | 99 | assert_equal __, hash[:one] 100 | assert_equal __, hash[:two] 101 | assert_equal __, hash[:three] 102 | 103 | assert_equal __, hash[:one].object_id == hash[:two].object_id 104 | end 105 | 106 | def test_default_value_with_block 107 | hash = Hash.new {|hash, key| hash[key] = [] } 108 | 109 | hash[:one] << "uno" 110 | hash[:two] << "dos" 111 | 112 | assert_equal __, hash[:one] 113 | assert_equal __, hash[:two] 114 | assert_equal __, hash[:three] 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /src/about_control_statements.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutControlStatements < EdgeCase::Koan 4 | 5 | def test_if_then_else_statements 6 | if true 7 | result = :true_value 8 | else 9 | result = :false_value 10 | end 11 | assert_equal __(:true_value), result 12 | end 13 | 14 | def test_if_then_statements 15 | result = :default_value 16 | if true 17 | result = :true_value 18 | end 19 | assert_equal __(:true_value), result 20 | end 21 | 22 | def test_if_statements_return_values 23 | value = if true 24 | :true_value 25 | else 26 | :false_value 27 | end 28 | assert_equal __(:true_value), value 29 | 30 | value = if false 31 | :true_value 32 | else 33 | :false_value 34 | end 35 | assert_equal __(:false_value), value 36 | 37 | # NOTE: Actually, EVERY statement in Ruby will return a value, not 38 | # just if statements. 39 | end 40 | 41 | def test_if_statements_with_no_else_with_false_condition_return_value 42 | value = if false 43 | :true_value 44 | end 45 | assert_equal __(nil), value 46 | end 47 | 48 | def test_condition_operators 49 | assert_equal __(:true_value), (true ? :true_value : :false_value) 50 | assert_equal __(:false_value), (false ? :true_value : :false_value) 51 | end 52 | 53 | def test_if_statement_modifiers 54 | result = :default_value 55 | result = :true_value if true 56 | 57 | assert_equal __(:true_value), result 58 | end 59 | 60 | def test_unless_statement 61 | result = :default_value 62 | unless false # same as saying 'if !false', which evaluates as 'if true' 63 | result = :false_value 64 | end 65 | assert_equal __(:false_value), result 66 | end 67 | 68 | def test_unless_statement_evaluate_true 69 | result = :default_value 70 | unless true # same as saying 'if !true', which evaluates as 'if false' 71 | result = :true_value 72 | end 73 | assert_equal __(:default_value), result 74 | end 75 | 76 | def test_unless_statement_modifier 77 | result = :default_value 78 | result = :false_value unless false 79 | 80 | assert_equal __(:false_value), result 81 | end 82 | 83 | def test_while_statement 84 | i = 1 85 | result = 1 86 | while i <= 10 87 | result = result * i 88 | i += 1 89 | end 90 | assert_equal __(3628800), result 91 | end 92 | 93 | def test_break_statement 94 | i = 1 95 | result = 1 96 | while true 97 | break unless i <= 10 98 | result = result * i 99 | i += 1 100 | end 101 | assert_equal __(3628800), result 102 | end 103 | 104 | def test_break_statement_returns_values 105 | i = 1 106 | result = while i <= 10 107 | break i if i % 2 == 0 108 | i += 1 109 | end 110 | 111 | assert_equal __(2), result 112 | end 113 | 114 | def test_next_statement 115 | i = 0 116 | result = [] 117 | while i < 10 118 | i += 1 119 | next if (i % 2) == 0 120 | result << i 121 | end 122 | assert_equal __([1, 3, 5, 7, 9]), result 123 | end 124 | 125 | def test_for_statement 126 | array = ["fish", "and", "chips"] 127 | result = [] 128 | for item in array 129 | result << item.upcase 130 | end 131 | assert_equal [__("FISH"), __("AND"), __("CHIPS")], result 132 | end 133 | 134 | end 135 | -------------------------------------------------------------------------------- /src/about_hashes.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutHashes < EdgeCase::Koan 4 | def test_creating_hashes 5 | empty_hash = Hash.new 6 | assert_equal __(Hash), empty_hash.class 7 | assert_equal({}, empty_hash) # __ 8 | assert_equal __(0), empty_hash.size 9 | end 10 | 11 | def test_hash_literals 12 | hash = { :one => "uno", :two => "dos" } 13 | assert_equal __(2), hash.size 14 | end 15 | 16 | def test_accessing_hashes 17 | hash = { :one => "uno", :two => "dos" } 18 | assert_equal __("uno"), hash[:one] 19 | assert_equal __("dos"), hash[:two] 20 | assert_equal __(nil), hash[:doesnt_exist] 21 | end 22 | 23 | def test_accessing_hashes_with_fetch 24 | hash = { :one => "uno" } 25 | assert_equal __("uno"), hash.fetch(:one) 26 | assert_raise(___(IndexError, KeyError)) do 27 | hash.fetch(:doesnt_exist) 28 | end 29 | 30 | # THINK ABOUT IT: 31 | # 32 | # Why might you want to use #fetch instead of #[] when accessing hash keys? 33 | end 34 | 35 | def test_changing_hashes 36 | hash = { :one => "uno", :two => "dos" } 37 | hash[:one] = "eins" 38 | 39 | expected = { :one => __("eins"), :two => "dos" } 40 | assert_equal __(true), expected == hash 41 | 42 | # Bonus Question: Why was "expected" broken out into a variable 43 | # rather than used as a literal? 44 | end 45 | 46 | def test_hash_is_unordered 47 | hash1 = { :one => "uno", :two => "dos" } 48 | hash2 = { :two => "dos", :one => "uno" } 49 | 50 | assert_equal __(true), hash1 == hash2 51 | end 52 | 53 | def test_hash_keys 54 | hash = { :one => "uno", :two => "dos" } 55 | assert_equal __(2), hash.keys.size 56 | assert_equal __(true), hash.keys.include?(:one) 57 | assert_equal __(true), hash.keys.include?(:two) 58 | assert_equal __(Array), hash.keys.class 59 | end 60 | 61 | def test_hash_values 62 | hash = { :one => "uno", :two => "dos" } 63 | assert_equal __(2), hash.values.size 64 | assert_equal __(true), hash.values.include?("uno") 65 | assert_equal __(true), hash.values.include?("dos") 66 | assert_equal __(Array), hash.values.class 67 | end 68 | 69 | def test_combining_hashes 70 | hash = { "jim" => 53, "amy" => 20, "dan" => 23 } 71 | new_hash = hash.merge({ "jim" => 54, "jenny" => 26 }) 72 | 73 | assert_equal __(true), hash != new_hash 74 | 75 | expected = { "jim" => __(54), "amy" => 20, "dan" => 23, "jenny" => __(26) } 76 | assert_equal __(true), expected == new_hash 77 | end 78 | 79 | def test_default_value 80 | hash1 = Hash.new 81 | hash1[:one] = 1 82 | 83 | assert_equal __(1), hash1[:one] 84 | assert_equal __(nil), hash1[:two] 85 | 86 | hash2 = Hash.new("dos") 87 | hash2[:one] = 1 88 | 89 | assert_equal __(1), hash2[:one] 90 | assert_equal __("dos"), hash2[:two] 91 | end 92 | 93 | def test_default_value_is_the_same_object 94 | hash = Hash.new([]) 95 | 96 | hash[:one] << "uno" 97 | hash[:two] << "dos" 98 | 99 | assert_equal __(["uno", "dos"]), hash[:one] 100 | assert_equal __(["uno", "dos"]), hash[:two] 101 | assert_equal __(["uno", "dos"]), hash[:three] 102 | 103 | assert_equal __(true), hash[:one].object_id == hash[:two].object_id 104 | end 105 | 106 | def test_default_value_with_block 107 | hash = Hash.new {|hash, key| hash[key] = [] } 108 | 109 | hash[:one] << "uno" 110 | hash[:two] << "dos" 111 | 112 | assert_equal __(["uno"]), hash[:one] 113 | assert_equal __(["dos"]), hash[:two] 114 | assert_equal __([]), hash[:three] 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /koans/about_iteration.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutIteration < EdgeCase::Koan 4 | 5 | # -- An Aside ------------------------------------------------------ 6 | # Ruby 1.8 stores names as strings. Ruby 1.9 stores names as 7 | # symbols. So we use a version dependent method "as_name" to convert 8 | # to the right format in the koans. We will use "as_name" whenever 9 | # comparing to lists of methods. 10 | 11 | in_ruby_version("1.8") do 12 | def as_name(name) 13 | name.to_s 14 | end 15 | end 16 | 17 | in_ruby_version("1.9") do 18 | def as_name(name) 19 | name.to_sym 20 | end 21 | end 22 | 23 | # Ok, now back to the Koans. 24 | # ------------------------------------------------------------------- 25 | 26 | def test_each_is_a_method_on_arrays 27 | assert_equal __, [].methods.include?(as_name(:each)) 28 | end 29 | 30 | def test_iterating_with_each 31 | array = [1, 2, 3] 32 | sum = 0 33 | array.each do |item| 34 | sum += item 35 | end 36 | assert_equal __, sum 37 | end 38 | 39 | def test_each_can_use_curly_brace_blocks_too 40 | array = [1, 2, 3] 41 | sum = 0 42 | array.each { |item| 43 | sum += item 44 | } 45 | assert_equal __, sum 46 | end 47 | 48 | def test_break_works_with_each_style_iterations 49 | array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 50 | sum = 0 51 | array.each { |item| 52 | break if item > 3 53 | sum += item 54 | } 55 | assert_equal __, sum 56 | end 57 | 58 | def test_collect_transforms_elements_of_an_array 59 | array = [1, 2, 3] 60 | new_array = array.collect { |item| item + 10 } 61 | assert_equal __, new_array 62 | 63 | # NOTE: 'map' is another name for the 'collect' operation 64 | another_array = array.map { |item| item + 10 } 65 | assert_equal __, another_array 66 | end 67 | 68 | def test_select_selects_certain_items_from_an_array 69 | array = [1, 2, 3, 4, 5, 6] 70 | 71 | even_numbers = array.select { |item| (item % 2) == 0 } 72 | assert_equal __, even_numbers 73 | 74 | # NOTE: 'find_all' is another name for the 'select' operation 75 | more_even_numbers = array.find_all { |item| (item % 2) == 0 } 76 | assert_equal __, more_even_numbers 77 | end 78 | 79 | def test_find_locates_the_first_element_matching_a_criteria 80 | array = ["Jim", "Bill", "Clarence", "Doug", "Eli"] 81 | 82 | assert_equal __, array.find { |item| item.size > 4 } 83 | end 84 | 85 | def test_inject_will_blow_your_mind 86 | result = [2, 3, 4].inject(0) { |sum, item| sum + item } 87 | assert_equal __, result 88 | 89 | result2 = [2, 3, 4].inject(1) { |sum, item| sum * item } 90 | assert_equal __, result2 91 | 92 | # Extra Credit: 93 | # Describe in your own words what inject does. 94 | end 95 | 96 | def test_all_iteration_methods_work_on_any_collection_not_just_arrays 97 | # Ranges act like a collection 98 | result = (1..3).map { |item| item + 10 } 99 | assert_equal __, result 100 | 101 | # Files act like a collection of lines 102 | File.open("example_file.txt") do |file| 103 | upcase_lines = file.map { |line| line.strip.upcase } 104 | assert_equal __, upcase_lines 105 | end 106 | 107 | # NOTE: You can create your own collections that work with each, 108 | # map, select, etc. 109 | end 110 | 111 | # Bonus Question: In the previous koan, we saw the construct: 112 | # 113 | # File.open(filename) do |file| 114 | # # code to read 'file' 115 | # end 116 | # 117 | # Why did we do it that way instead of the following? 118 | # 119 | # file = File.open(filename) 120 | # # code to read 'file' 121 | # 122 | # When you get to the "AboutSandwichCode" koan, recheck your answer. 123 | 124 | end 125 | -------------------------------------------------------------------------------- /src/about_iteration.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutIteration < EdgeCase::Koan 4 | 5 | # -- An Aside ------------------------------------------------------ 6 | # Ruby 1.8 stores names as strings. Ruby 1.9 stores names as 7 | # symbols. So we use a version dependent method "as_name" to convert 8 | # to the right format in the koans. We will use "as_name" whenever 9 | # comparing to lists of methods. 10 | 11 | in_ruby_version("1.8") do 12 | def as_name(name) 13 | name.to_s 14 | end 15 | end 16 | 17 | in_ruby_version("1.9") do 18 | def as_name(name) 19 | name.to_sym 20 | end 21 | end 22 | 23 | # Ok, now back to the Koans. 24 | # ------------------------------------------------------------------- 25 | 26 | def test_each_is_a_method_on_arrays 27 | assert_equal __(true), [].methods.include?(as_name(:each)) 28 | end 29 | 30 | def test_iterating_with_each 31 | array = [1, 2, 3] 32 | sum = 0 33 | array.each do |item| 34 | sum += item 35 | end 36 | assert_equal __(6), sum 37 | end 38 | 39 | def test_each_can_use_curly_brace_blocks_too 40 | array = [1, 2, 3] 41 | sum = 0 42 | array.each { |item| 43 | sum += item 44 | } 45 | assert_equal __(6), sum 46 | end 47 | 48 | def test_break_works_with_each_style_iterations 49 | array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 50 | sum = 0 51 | array.each { |item| 52 | break if item > 3 53 | sum += item 54 | } 55 | assert_equal __(6), sum 56 | end 57 | 58 | def test_collect_transforms_elements_of_an_array 59 | array = [1, 2, 3] 60 | new_array = array.collect { |item| item + 10 } 61 | assert_equal __([11, 12, 13]), new_array 62 | 63 | # NOTE: 'map' is another name for the 'collect' operation 64 | another_array = array.map { |item| item + 10 } 65 | assert_equal __([11, 12, 13]), another_array 66 | end 67 | 68 | def test_select_selects_certain_items_from_an_array 69 | array = [1, 2, 3, 4, 5, 6] 70 | 71 | even_numbers = array.select { |item| (item % 2) == 0 } 72 | assert_equal __([2, 4, 6]), even_numbers 73 | 74 | # NOTE: 'find_all' is another name for the 'select' operation 75 | more_even_numbers = array.find_all { |item| (item % 2) == 0 } 76 | assert_equal __([2, 4, 6]), more_even_numbers 77 | end 78 | 79 | def test_find_locates_the_first_element_matching_a_criteria 80 | array = ["Jim", "Bill", "Clarence", "Doug", "Eli"] 81 | 82 | assert_equal __("Clarence"), array.find { |item| item.size > 4 } 83 | end 84 | 85 | def test_inject_will_blow_your_mind 86 | result = [2, 3, 4].inject(0) { |sum, item| sum + item } 87 | assert_equal __(9), result 88 | 89 | result2 = [2, 3, 4].inject(1) { |sum, item| sum * item } 90 | assert_equal __(24), result2 91 | 92 | # Extra Credit: 93 | # Describe in your own words what inject does. 94 | end 95 | 96 | def test_all_iteration_methods_work_on_any_collection_not_just_arrays 97 | # Ranges act like a collection 98 | result = (1..3).map { |item| item + 10 } 99 | assert_equal __([11, 12, 13]), result 100 | 101 | # Files act like a collection of lines 102 | File.open("example_file.txt") do |file| 103 | upcase_lines = file.map { |line| line.strip.upcase } 104 | assert_equal __(["THIS", "IS", "A", "TEST"]), upcase_lines 105 | end 106 | 107 | # NOTE: You can create your own collections that work with each, 108 | # map, select, etc. 109 | end 110 | 111 | # Bonus Question: In the previous koan, we saw the construct: 112 | # 113 | # File.open(filename) do |file| 114 | # # code to read 'file' 115 | # end 116 | # 117 | # Why did we do it that way instead of the following? 118 | # 119 | # file = File.open(filename) 120 | # # code to read 'file' 121 | # 122 | # When you get to the "AboutSandwichCode" koan, recheck your answer. 123 | 124 | end 125 | -------------------------------------------------------------------------------- /koans/about_proxy_object_project.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # Project: Create a Proxy Class 4 | # 5 | # In this assignment, create a proxy class (one is started for you 6 | # below). You should be able to initialize the proxy object with any 7 | # object. Any messages sent to the proxy object should be forwarded 8 | # to the target object. As each message is sent, the proxy should 9 | # record the name of the method sent. 10 | # 11 | # The proxy class is started for you. You will need to add a method 12 | # missing handler and any other supporting methods. The specification 13 | # of the Proxy class is given in the AboutProxyObjectProject koan. 14 | 15 | class Proxy 16 | def initialize(target_object) 17 | @object = target_object 18 | # ADD MORE CODE HERE 19 | end 20 | 21 | # WRITE CODE HERE 22 | end 23 | 24 | # The proxy object should pass the following Koan: 25 | # 26 | class AboutProxyObjectProject < EdgeCase::Koan 27 | def test_proxy_method_returns_wrapped_object 28 | # NOTE: The Television class is defined below 29 | tv = Proxy.new(Television.new) 30 | 31 | # HINT: Proxy class is defined above, may need tweaking... 32 | 33 | assert tv.instance_of?(Proxy) 34 | end 35 | 36 | def test_tv_methods_still_perform_their_function 37 | tv = Proxy.new(Television.new) 38 | 39 | tv.channel = 10 40 | tv.power 41 | 42 | assert_equal 10, tv.channel 43 | assert tv.on? 44 | end 45 | 46 | def test_proxy_records_messages_sent_to_tv 47 | tv = Proxy.new(Television.new) 48 | 49 | tv.power 50 | tv.channel = 10 51 | 52 | assert_equal [:power, :channel=], tv.messages 53 | end 54 | 55 | def test_proxy_handles_invalid_messages 56 | tv = Proxy.new(Television.new) 57 | 58 | assert_raise(NoMethodError) do 59 | tv.no_such_method 60 | end 61 | end 62 | 63 | def test_proxy_reports_methods_have_been_called 64 | tv = Proxy.new(Television.new) 65 | 66 | tv.power 67 | tv.power 68 | 69 | assert tv.called?(:power) 70 | assert ! tv.called?(:channel) 71 | end 72 | 73 | def test_proxy_counts_method_calls 74 | tv = Proxy.new(Television.new) 75 | 76 | tv.power 77 | tv.channel = 48 78 | tv.power 79 | 80 | assert_equal 2, tv.number_of_times_called(:power) 81 | assert_equal 1, tv.number_of_times_called(:channel=) 82 | assert_equal 0, tv.number_of_times_called(:on?) 83 | end 84 | 85 | def test_proxy_can_record_more_than_just_tv_objects 86 | proxy = Proxy.new("Code Mash 2009") 87 | 88 | proxy.upcase! 89 | result = proxy.split 90 | 91 | assert_equal ["CODE", "MASH", "2009"], result 92 | assert_equal [:upcase!, :split], proxy.messages 93 | end 94 | end 95 | 96 | 97 | # ==================================================================== 98 | # The following code is to support the testing of the Proxy class. No 99 | # changes should be necessary to anything below this comment. 100 | 101 | # Example class using in the proxy testing above. 102 | class Television 103 | attr_accessor :channel 104 | 105 | def power 106 | if @power == :on 107 | @power = :off 108 | else 109 | @power = :on 110 | end 111 | end 112 | 113 | def on? 114 | @power == :on 115 | end 116 | end 117 | 118 | # Tests for the Television class. All of theses tests should pass. 119 | class TelevisionTest < EdgeCase::Koan 120 | def test_it_turns_on 121 | tv = Television.new 122 | 123 | tv.power 124 | assert tv.on? 125 | end 126 | 127 | def test_it_also_turns_off 128 | tv = Television.new 129 | 130 | tv.power 131 | tv.power 132 | 133 | assert ! tv.on? 134 | end 135 | 136 | def test_edge_case_on_off 137 | tv = Television.new 138 | 139 | tv.power 140 | tv.power 141 | tv.power 142 | 143 | assert tv.on? 144 | 145 | tv.power 146 | 147 | assert ! tv.on? 148 | end 149 | 150 | def test_can_set_the_channel 151 | tv = Television.new 152 | 153 | tv.channel = 11 154 | assert_equal 11, tv.channel 155 | end 156 | end 157 | -------------------------------------------------------------------------------- /koans/about_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | def my_global_method(a,b) 4 | a + b 5 | end 6 | 7 | class AboutMethods < EdgeCase::Koan 8 | 9 | def test_calling_global_methods 10 | assert_equal __, my_global_method(2,3) 11 | end 12 | 13 | def test_calling_global_methods_without_parentheses 14 | result = my_global_method 2, 3 15 | assert_equal __, result 16 | end 17 | 18 | # (NOTE: We are Using eval below because the example code is 19 | # considered to be syntactically invalid). 20 | def test_sometimes_missing_parentheses_are_ambiguous 21 | eval "assert_equal 5, my_global_method 2, 3" # ENABLE CHECK 22 | # 23 | # Ruby doesn't know if you mean: 24 | # 25 | # assert_equal(5, my_global_method(2), 3) 26 | # or 27 | # assert_equal(5, my_global_method(2, 3)) 28 | # 29 | # Rewrite the eval string to continue. 30 | # 31 | end 32 | 33 | # NOTE: wrong number of argument is not a SYNTAX error, but a 34 | # runtime error. 35 | def test_calling_global_methods_with_wrong_number_of_arguments 36 | exception = assert_raise(___) do 37 | my_global_method 38 | end 39 | assert_match(/__/, exception.message) 40 | 41 | exception = assert_raise(___) do 42 | my_global_method(1,2,3) 43 | end 44 | assert_match(/__/, exception.message) 45 | end 46 | 47 | # ------------------------------------------------------------------ 48 | 49 | def method_with_defaults(a, b=:default_value) 50 | [a, b] 51 | end 52 | 53 | def test_calling_with_default_values 54 | assert_equal [1, __], method_with_defaults(1) 55 | assert_equal [1, __], method_with_defaults(1, 2) 56 | end 57 | 58 | # ------------------------------------------------------------------ 59 | 60 | def method_with_var_args(*args) 61 | args 62 | end 63 | 64 | def test_calling_with_variable_arguments 65 | assert_equal __, method_with_var_args.class 66 | assert_equal __, method_with_var_args 67 | assert_equal __, method_with_var_args(:one) 68 | assert_equal __, method_with_var_args(:one, :two) 69 | end 70 | 71 | # ------------------------------------------------------------------ 72 | 73 | def method_with_explicit_return 74 | :a_non_return_value 75 | return :return_value 76 | :another_non_return_value 77 | end 78 | 79 | def test_method_with_explicit_return 80 | assert_equal __, method_with_explicit_return 81 | end 82 | 83 | # ------------------------------------------------------------------ 84 | 85 | def method_without_explicit_return 86 | :a_non_return_value 87 | :return_value 88 | end 89 | 90 | def test_method_without_explicit_return 91 | assert_equal __, method_without_explicit_return 92 | end 93 | 94 | # ------------------------------------------------------------------ 95 | 96 | def my_method_in_the_same_class(a, b) 97 | a * b 98 | end 99 | 100 | def test_calling_methods_in_same_class 101 | assert_equal __, my_method_in_the_same_class(3,4) 102 | end 103 | 104 | def test_calling_methods_in_same_class_with_explicit_receiver 105 | assert_equal __, self.my_method_in_the_same_class(3,4) 106 | end 107 | 108 | # ------------------------------------------------------------------ 109 | 110 | def my_private_method 111 | "a secret" 112 | end 113 | private :my_private_method 114 | 115 | def test_calling_private_methods_without_receiver 116 | assert_equal __, my_private_method 117 | end 118 | 119 | def test_calling_private_methods_with_an_explicit_receiver 120 | exception = assert_raise(___) do 121 | self.my_private_method 122 | end 123 | assert_match /__/, exception.message 124 | end 125 | 126 | # ------------------------------------------------------------------ 127 | 128 | class Dog 129 | def name 130 | "Fido" 131 | end 132 | 133 | private 134 | 135 | def tail 136 | "tail" 137 | end 138 | end 139 | 140 | def test_calling_methods_in_other_objects_require_explicit_receiver 141 | rover = Dog.new 142 | assert_equal __, rover.name 143 | end 144 | 145 | def test_calling_private_methods_in_other_objects 146 | rover = Dog.new 147 | assert_raise(___) do 148 | rover.tail 149 | end 150 | end 151 | end 152 | -------------------------------------------------------------------------------- /koans/about_class_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutClassMethods < EdgeCase::Koan 4 | class Dog 5 | end 6 | 7 | def test_objects_are_objects 8 | fido = Dog.new 9 | assert_equal __, fido.is_a?(Object) 10 | end 11 | 12 | def test_classes_are_classes 13 | assert_equal __, Dog.is_a?(Class) 14 | end 15 | 16 | def test_classes_are_objects_too 17 | assert_equal __, Dog.is_a?(Object) 18 | end 19 | 20 | def test_objects_have_methods 21 | fido = Dog.new 22 | assert fido.methods.size > _n_ 23 | end 24 | 25 | def test_classes_have_methods 26 | assert Dog.methods.size > _n_ 27 | end 28 | 29 | def test_you_can_define_methods_on_individual_objects 30 | fido = Dog.new 31 | def fido.wag 32 | :fidos_wag 33 | end 34 | assert_equal __, fido.wag 35 | end 36 | 37 | def test_other_objects_are_not_affected_by_these_singleton_methods 38 | fido = Dog.new 39 | rover = Dog.new 40 | def fido.wag 41 | :fidos_wag 42 | end 43 | 44 | assert_raise(___) do 45 | rover.wag 46 | end 47 | end 48 | 49 | # ------------------------------------------------------------------ 50 | 51 | class Dog2 52 | def wag 53 | :instance_level_wag 54 | end 55 | end 56 | 57 | def Dog2.wag 58 | :class_level_wag 59 | end 60 | 61 | def test_since_classes_are_objects_you_can_define_singleton_methods_on_them_too 62 | assert_equal __, Dog2.wag 63 | end 64 | 65 | def test_class_methods_are_independent_of_instance_methods 66 | fido = Dog2.new 67 | assert_equal __, fido.wag 68 | assert_equal __, Dog2.wag 69 | end 70 | 71 | # ------------------------------------------------------------------ 72 | 73 | class Dog 74 | attr_accessor :name 75 | end 76 | 77 | def Dog.name 78 | @name 79 | end 80 | 81 | def test_classes_and_instances_do_not_share_instance_variables 82 | fido = Dog.new 83 | fido.name = "Fido" 84 | assert_equal __, fido.name 85 | assert_equal __, Dog.name 86 | end 87 | 88 | # ------------------------------------------------------------------ 89 | 90 | class Dog 91 | def Dog.a_class_method 92 | :dogs_class_method 93 | end 94 | end 95 | 96 | def test_you_can_define_class_methods_inside_the_class 97 | assert_equal __, Dog.a_class_method 98 | end 99 | 100 | # ------------------------------------------------------------------ 101 | 102 | LastExpressionInClassStatement = class Dog 103 | 21 104 | end 105 | 106 | def test_class_statements_return_the_value_of_their_last_expression 107 | assert_equal __, LastExpressionInClassStatement 108 | end 109 | 110 | # ------------------------------------------------------------------ 111 | 112 | SelfInsideOfClassStatement = class Dog 113 | self 114 | end 115 | 116 | def test_self_while_inside_class_is_class_object_not_instance 117 | assert_equal __, Dog == SelfInsideOfClassStatement 118 | end 119 | 120 | # ------------------------------------------------------------------ 121 | 122 | class Dog 123 | def self.class_method2 124 | :another_way_to_write_class_methods 125 | end 126 | end 127 | 128 | def test_you_can_use_self_instead_of_an_explicit_reference_to_dog 129 | assert_equal __, Dog.class_method2 130 | end 131 | 132 | # ------------------------------------------------------------------ 133 | 134 | class Dog 135 | class << self 136 | def another_class_method 137 | :still_another_way 138 | end 139 | end 140 | end 141 | 142 | def test_heres_still_another_way_to_write_class_methods 143 | assert_equal __, Dog.another_class_method 144 | end 145 | 146 | # THINK ABOUT IT: 147 | # 148 | # The two major ways to write class methods are: 149 | # class Demo 150 | # def self.method 151 | # end 152 | # 153 | # class << self 154 | # def class_methods 155 | # end 156 | # end 157 | # end 158 | # 159 | # Which do you prefer and why? 160 | # Are there times you might prefer one over the other? 161 | 162 | # ------------------------------------------------------------------ 163 | 164 | def test_heres_an_easy_way_to_call_class_methods_from_instance_methods 165 | fido = Dog.new 166 | assert_equal __, fido.class.another_class_method 167 | end 168 | 169 | end 170 | -------------------------------------------------------------------------------- /src/about_proxy_object_project.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | # Project: Create a Proxy Class 4 | # 5 | # In this assignment, create a proxy class (one is started for you 6 | # below). You should be able to initialize the proxy object with any 7 | # object. Any messages sent to the proxy object should be forwarded 8 | # to the target object. As each message is sent, the proxy should 9 | # record the name of the method sent. 10 | # 11 | # The proxy class is started for you. You will need to add a method 12 | # missing handler and any other supporting methods. The specification 13 | # of the Proxy class is given in the AboutProxyObjectProject koan. 14 | 15 | class Proxy 16 | def initialize(target_object) 17 | @object = target_object 18 | # ADD MORE CODE HERE 19 | #-- 20 | @messages = [] 21 | #++ 22 | end 23 | 24 | # WRITE CODE HERE 25 | #-- 26 | attr_reader :messages 27 | 28 | def method_missing(sym, *args, &block) 29 | @messages << sym 30 | @object.send(sym, *args, &block) 31 | end 32 | 33 | def called?(method) 34 | @messages.include?(method) 35 | end 36 | 37 | def number_of_times_called(method) 38 | @messages.select { |m| m == method }.size 39 | end 40 | #++ 41 | end 42 | 43 | # The proxy object should pass the following Koan: 44 | # 45 | class AboutProxyObjectProject < EdgeCase::Koan 46 | def test_proxy_method_returns_wrapped_object 47 | # NOTE: The Television class is defined below 48 | tv = Proxy.new(Television.new) 49 | 50 | # HINT: Proxy class is defined above, may need tweaking... 51 | 52 | assert tv.instance_of?(Proxy) 53 | end 54 | 55 | def test_tv_methods_still_perform_their_function 56 | tv = Proxy.new(Television.new) 57 | 58 | tv.channel = 10 59 | tv.power 60 | 61 | assert_equal 10, tv.channel 62 | assert tv.on? 63 | end 64 | 65 | def test_proxy_records_messages_sent_to_tv 66 | tv = Proxy.new(Television.new) 67 | 68 | tv.power 69 | tv.channel = 10 70 | 71 | assert_equal [:power, :channel=], tv.messages 72 | end 73 | 74 | def test_proxy_handles_invalid_messages 75 | tv = Proxy.new(Television.new) 76 | 77 | assert_raise(NoMethodError) do 78 | tv.no_such_method 79 | end 80 | end 81 | 82 | def test_proxy_reports_methods_have_been_called 83 | tv = Proxy.new(Television.new) 84 | 85 | tv.power 86 | tv.power 87 | 88 | assert tv.called?(:power) 89 | assert ! tv.called?(:channel) 90 | end 91 | 92 | def test_proxy_counts_method_calls 93 | tv = Proxy.new(Television.new) 94 | 95 | tv.power 96 | tv.channel = 48 97 | tv.power 98 | 99 | assert_equal 2, tv.number_of_times_called(:power) 100 | assert_equal 1, tv.number_of_times_called(:channel=) 101 | assert_equal 0, tv.number_of_times_called(:on?) 102 | end 103 | 104 | def test_proxy_can_record_more_than_just_tv_objects 105 | proxy = Proxy.new("Code Mash 2009") 106 | 107 | proxy.upcase! 108 | result = proxy.split 109 | 110 | assert_equal ["CODE", "MASH", "2009"], result 111 | assert_equal [:upcase!, :split], proxy.messages 112 | end 113 | end 114 | 115 | 116 | # ==================================================================== 117 | # The following code is to support the testing of the Proxy class. No 118 | # changes should be necessary to anything below this comment. 119 | 120 | # Example class using in the proxy testing above. 121 | class Television 122 | attr_accessor :channel 123 | 124 | def power 125 | if @power == :on 126 | @power = :off 127 | else 128 | @power = :on 129 | end 130 | end 131 | 132 | def on? 133 | @power == :on 134 | end 135 | end 136 | 137 | # Tests for the Television class. All of theses tests should pass. 138 | class TelevisionTest < EdgeCase::Koan 139 | def test_it_turns_on 140 | tv = Television.new 141 | 142 | tv.power 143 | assert tv.on? 144 | end 145 | 146 | def test_it_also_turns_off 147 | tv = Television.new 148 | 149 | tv.power 150 | tv.power 151 | 152 | assert ! tv.on? 153 | end 154 | 155 | def test_edge_case_on_off 156 | tv = Television.new 157 | 158 | tv.power 159 | tv.power 160 | tv.power 161 | 162 | assert tv.on? 163 | 164 | tv.power 165 | 166 | assert ! tv.on? 167 | end 168 | 169 | def test_can_set_the_channel 170 | tv = Television.new 171 | 172 | tv.channel = 11 173 | assert_equal 11, tv.channel 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /src/about_class_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutClassMethods < EdgeCase::Koan 4 | class Dog 5 | end 6 | 7 | def test_objects_are_objects 8 | fido = Dog.new 9 | assert_equal __(true), fido.is_a?(Object) 10 | end 11 | 12 | def test_classes_are_classes 13 | assert_equal __(true), Dog.is_a?(Class) 14 | end 15 | 16 | def test_classes_are_objects_too 17 | assert_equal __(true), Dog.is_a?(Object) 18 | end 19 | 20 | def test_objects_have_methods 21 | fido = Dog.new 22 | assert fido.methods.size > _n_(30) 23 | end 24 | 25 | def test_classes_have_methods 26 | assert Dog.methods.size > _n_(40) 27 | end 28 | 29 | def test_you_can_define_methods_on_individual_objects 30 | fido = Dog.new 31 | def fido.wag 32 | :fidos_wag 33 | end 34 | assert_equal __(:fidos_wag), fido.wag 35 | end 36 | 37 | def test_other_objects_are_not_affected_by_these_singleton_methods 38 | fido = Dog.new 39 | rover = Dog.new 40 | def fido.wag 41 | :fidos_wag 42 | end 43 | 44 | assert_raise(___(NoMethodError)) do 45 | rover.wag 46 | end 47 | end 48 | 49 | # ------------------------------------------------------------------ 50 | 51 | class Dog2 52 | def wag 53 | :instance_level_wag 54 | end 55 | end 56 | 57 | def Dog2.wag 58 | :class_level_wag 59 | end 60 | 61 | def test_since_classes_are_objects_you_can_define_singleton_methods_on_them_too 62 | assert_equal __(:class_level_wag), Dog2.wag 63 | end 64 | 65 | def test_class_methods_are_independent_of_instance_methods 66 | fido = Dog2.new 67 | assert_equal __(:instance_level_wag), fido.wag 68 | assert_equal __(:class_level_wag), Dog2.wag 69 | end 70 | 71 | # ------------------------------------------------------------------ 72 | 73 | class Dog 74 | attr_accessor :name 75 | end 76 | 77 | def Dog.name 78 | @name 79 | end 80 | 81 | def test_classes_and_instances_do_not_share_instance_variables 82 | fido = Dog.new 83 | fido.name = "Fido" 84 | assert_equal __("Fido"), fido.name 85 | assert_equal __(nil), Dog.name 86 | end 87 | 88 | # ------------------------------------------------------------------ 89 | 90 | class Dog 91 | def Dog.a_class_method 92 | :dogs_class_method 93 | end 94 | end 95 | 96 | def test_you_can_define_class_methods_inside_the_class 97 | assert_equal __(:dogs_class_method), Dog.a_class_method 98 | end 99 | 100 | # ------------------------------------------------------------------ 101 | 102 | LastExpressionInClassStatement = class Dog 103 | 21 104 | end 105 | 106 | def test_class_statements_return_the_value_of_their_last_expression 107 | assert_equal __(21), LastExpressionInClassStatement 108 | end 109 | 110 | # ------------------------------------------------------------------ 111 | 112 | SelfInsideOfClassStatement = class Dog 113 | self 114 | end 115 | 116 | def test_self_while_inside_class_is_class_object_not_instance 117 | assert_equal __(true), Dog == SelfInsideOfClassStatement 118 | end 119 | 120 | # ------------------------------------------------------------------ 121 | 122 | class Dog 123 | def self.class_method2 124 | :another_way_to_write_class_methods 125 | end 126 | end 127 | 128 | def test_you_can_use_self_instead_of_an_explicit_reference_to_dog 129 | assert_equal __(:another_way_to_write_class_methods), Dog.class_method2 130 | end 131 | 132 | # ------------------------------------------------------------------ 133 | 134 | class Dog 135 | class << self 136 | def another_class_method 137 | :still_another_way 138 | end 139 | end 140 | end 141 | 142 | def test_heres_still_another_way_to_write_class_methods 143 | assert_equal __(:still_another_way), Dog.another_class_method 144 | end 145 | 146 | # THINK ABOUT IT: 147 | # 148 | # The two major ways to write class methods are: 149 | # class Demo 150 | # def self.method 151 | # end 152 | # 153 | # class << self 154 | # def class_methods 155 | # end 156 | # end 157 | # end 158 | # 159 | # Which do you prefer and why? 160 | # Are there times you might prefer one over the other? 161 | 162 | # ------------------------------------------------------------------ 163 | 164 | def test_heres_an_easy_way_to_call_class_methods_from_instance_methods 165 | fido = Dog.new 166 | assert_equal __(:still_another_way), fido.class.another_class_method 167 | end 168 | 169 | end 170 | -------------------------------------------------------------------------------- /koans/about_java_interop.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | include Java 4 | 5 | # Concepts 6 | # * Pull in a java class 7 | # * calling a method, Camel vs snake 8 | # * Resolving module/class name conflicts 9 | # * Showing what gets returned 10 | # * Ruby Strings VS Java Strings 11 | # * Calling custom java class 12 | # * Calling Ruby from java??? 13 | 14 | class AboutJavaInterop < EdgeCase::Koan 15 | def test_using_a_java_library_class 16 | java_array = java.util.ArrayList.new 17 | assert_equal __, java_array.class 18 | end 19 | 20 | def test_java_class_can_be_referenced_using_both_ruby_and_java_like_syntax 21 | assert_equal __, Java::JavaUtil::ArrayList == java.util.ArrayList 22 | end 23 | 24 | def test_include_class_includes_class_in_module_scope 25 | assert_nil defined?(TreeSet) 26 | include_class "java.util.TreeSet" 27 | assert_equal __, defined?(TreeSet) 28 | end 29 | 30 | # THINK ABOUT IT: 31 | # 32 | # What if we use: 33 | # 34 | # include_class "java.lang.String" 35 | # 36 | # What would be the value of the String constant after this 37 | # include_class is run? Would it be useful to provide a way of 38 | # aliasing java classes to different names? 39 | 40 | JString = java.lang.String 41 | def test_also_java_class_can_be_given_ruby_aliases 42 | java_string = JString.new("A Java String") 43 | assert_equal __, java_string.class 44 | assert_equal __, JString 45 | end 46 | 47 | def test_can_directly_call_java_methods_on_java_objects 48 | java_string = JString.new("A Java String") 49 | assert_equal __, java_string.toLowerCase 50 | end 51 | 52 | def test_jruby_provides_snake_case_versions_of_java_methods 53 | java_string = JString.new("A Java String") 54 | assert_equal __, java_string.to_lower_case 55 | end 56 | 57 | def test_jruby_provides_question_mark_versions_of_boolean_methods 58 | java_string = JString.new("A Java String") 59 | assert_equal __, java_string.endsWith("String") 60 | assert_equal __, java_string.ends_with("String") 61 | assert_equal __, java_string.ends_with?("String") 62 | end 63 | 64 | def test_java_string_are_not_ruby_strings 65 | ruby_string = "A Java String" 66 | java_string = java.lang.String.new(ruby_string) 67 | assert_equal __, java_string.is_a?(java.lang.String) 68 | assert_equal __, java_string.is_a?(String) 69 | end 70 | 71 | def test_java_strings_can_be_compared_to_ruby_strings_maybe 72 | ruby_string = "A Java String" 73 | java_string = java.lang.String.new(ruby_string) 74 | assert_equal __, ruby_string == java_string 75 | assert_equal __, java_string == ruby_string 76 | 77 | # THINK ABOUT IT: 78 | # 79 | # Is there any possible way for this to be more wrong? 80 | # 81 | # SERIOUSLY, THINK ABOUT IT: 82 | # 83 | # Why do you suppose that Ruby and Java strings compare like that? 84 | # 85 | # ADVANCED THINK ABOUT IT: 86 | # 87 | # Is there a way to make Ruby/Java string comparisons commutative? 88 | # How would you do it? 89 | end 90 | 91 | def test_however_most_methods_returning_strings_return_ruby_strings 92 | java_array = java.util.ArrayList.new 93 | assert_equal __, java_array.toString 94 | assert_equal __, java_array.toString.is_a?(String) 95 | assert_equal __, java_array.toString.is_a?(java.lang.String) 96 | end 97 | 98 | def test_some_ruby_objects_can_be_coerced_to_java 99 | assert_equal __, "ruby string".to_java.class 100 | assert_equal __, 1.to_java.class 101 | assert_equal __, 9.32.to_java.class 102 | assert_equal __, false.to_java.class 103 | end 104 | 105 | def test_some_ruby_objects_are_not_coerced_to_what_you_might_expect 106 | assert_equal __, [].to_java.class == Java::JavaUtil::ArrayList 107 | assert_equal __, {}.to_java.class == Java::JavaUtil::HashMap 108 | assert_equal __, Object.new.to_java.class == Java::JavaLang::Object 109 | end 110 | 111 | def test_java_collections_are_enumerable 112 | java_array = java.util.ArrayList.new 113 | java_array << "one" << "two" << "three" 114 | assert_equal __, java_array.map { |item| item.upcase } 115 | end 116 | 117 | # ------------------------------------------------------------------ 118 | 119 | # Open the Java ArrayList class and add a new method. 120 | class Java::JavaUtil::ArrayList 121 | def multiply_all 122 | result = 1 123 | each do |item| 124 | result *= item 125 | end 126 | result 127 | end 128 | end 129 | 130 | def test_java_class_are_open_from_ruby 131 | java_array = java.util.ArrayList.new 132 | java_array.add_all([1,2,3,4,5]) 133 | 134 | assert_equal __, java_array.multiply_all 135 | end 136 | 137 | end 138 | -------------------------------------------------------------------------------- /src/about_methods.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | def my_global_method(a,b) 4 | a + b 5 | end 6 | 7 | class AboutMethods < EdgeCase::Koan 8 | 9 | def test_calling_global_methods 10 | assert_equal __(5), my_global_method(2,3) 11 | end 12 | 13 | def test_calling_global_methods_without_parentheses 14 | result = my_global_method 2, 3 15 | assert_equal __(5), result 16 | end 17 | 18 | # (NOTE: We are Using eval below because the example code is 19 | # considered to be syntactically invalid). 20 | def test_sometimes_missing_parentheses_are_ambiguous 21 | #-- 22 | eval "assert_equal 5, my_global_method(2, 3)" # REMOVE CHECK # __ 23 | if false 24 | #++ 25 | eval "assert_equal 5, my_global_method 2, 3" # ENABLE CHECK # __ 26 | #-- 27 | end 28 | #++ 29 | # 30 | # Ruby doesn't know if you mean: 31 | # 32 | # assert_equal(5, my_global_method(2), 3) 33 | # or 34 | # assert_equal(5, my_global_method(2, 3)) 35 | # 36 | # Rewrite the eval string to continue. 37 | # 38 | end 39 | 40 | # NOTE: wrong number of argument is not a SYNTAX error, but a 41 | # runtime error. 42 | def test_calling_global_methods_with_wrong_number_of_arguments 43 | exception = assert_raise(___(ArgumentError)) do 44 | my_global_method 45 | end 46 | #-- 47 | pattern = "wrong (number|#) of arguments" 48 | #++ 49 | assert_match(/#{__(pattern)}/, exception.message) 50 | 51 | exception = assert_raise(___(ArgumentError)) do 52 | my_global_method(1,2,3) 53 | end 54 | assert_match(/#{__(pattern)}/, exception.message) 55 | end 56 | 57 | # ------------------------------------------------------------------ 58 | 59 | def method_with_defaults(a, b=:default_value) 60 | [a, b] 61 | end 62 | 63 | def test_calling_with_default_values 64 | assert_equal [1, __(:default_value)], method_with_defaults(1) 65 | assert_equal [1, __(2)], method_with_defaults(1, 2) 66 | end 67 | 68 | # ------------------------------------------------------------------ 69 | 70 | def method_with_var_args(*args) 71 | args 72 | end 73 | 74 | def test_calling_with_variable_arguments 75 | assert_equal __(Array), method_with_var_args.class 76 | assert_equal __([]), method_with_var_args 77 | assert_equal __([:one]), method_with_var_args(:one) 78 | assert_equal __([:one, :two]), method_with_var_args(:one, :two) 79 | end 80 | 81 | # ------------------------------------------------------------------ 82 | 83 | def method_with_explicit_return 84 | :a_non_return_value 85 | return :return_value 86 | :another_non_return_value 87 | end 88 | 89 | def test_method_with_explicit_return 90 | assert_equal __(:return_value), method_with_explicit_return 91 | end 92 | 93 | # ------------------------------------------------------------------ 94 | 95 | def method_without_explicit_return 96 | :a_non_return_value 97 | :return_value 98 | end 99 | 100 | def test_method_without_explicit_return 101 | assert_equal __(:return_value), method_without_explicit_return 102 | end 103 | 104 | # ------------------------------------------------------------------ 105 | 106 | def my_method_in_the_same_class(a, b) 107 | a * b 108 | end 109 | 110 | def test_calling_methods_in_same_class 111 | assert_equal __(12), my_method_in_the_same_class(3,4) 112 | end 113 | 114 | def test_calling_methods_in_same_class_with_explicit_receiver 115 | assert_equal __(12), self.my_method_in_the_same_class(3,4) 116 | end 117 | 118 | # ------------------------------------------------------------------ 119 | 120 | def my_private_method 121 | "a secret" 122 | end 123 | private :my_private_method 124 | 125 | def test_calling_private_methods_without_receiver 126 | assert_equal __("a secret"), my_private_method 127 | end 128 | 129 | def test_calling_private_methods_with_an_explicit_receiver 130 | exception = assert_raise(___(NoMethodError)) do 131 | self.my_private_method 132 | end 133 | assert_match /#{__("private method `my_private_method' called ")}/, exception.message 134 | end 135 | 136 | # ------------------------------------------------------------------ 137 | 138 | class Dog 139 | def name 140 | "Fido" 141 | end 142 | 143 | private 144 | 145 | def tail 146 | "tail" 147 | end 148 | end 149 | 150 | def test_calling_methods_in_other_objects_require_explicit_receiver 151 | rover = Dog.new 152 | assert_equal __("Fido"), rover.name 153 | end 154 | 155 | def test_calling_private_methods_in_other_objects 156 | rover = Dog.new 157 | assert_raise(___(NoMethodError)) do 158 | rover.tail 159 | end 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /koans/about_classes.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutClasses < EdgeCase::Koan 4 | class Dog 5 | end 6 | 7 | def test_instances_of_classes_can_be_created_with_new 8 | fido = Dog.new 9 | assert_equal __, fido.class 10 | end 11 | 12 | # ------------------------------------------------------------------ 13 | 14 | class Dog2 15 | def set_name(a_name) 16 | @name = a_name 17 | end 18 | end 19 | 20 | def test_instance_variables_can_be_set_by_assigning_to_them 21 | fido = Dog2.new 22 | assert_equal __, fido.instance_variables 23 | 24 | fido.set_name("Fido") 25 | assert_equal __, fido.instance_variables 26 | end 27 | 28 | def test_instance_variables_cannot_be_accessed_outside_the_class 29 | fido = Dog2.new 30 | fido.set_name("Fido") 31 | 32 | assert_raise(___) do 33 | fido.name 34 | end 35 | 36 | assert_raise(___) do 37 | eval "fido.@name" 38 | # NOTE: Using eval because the above line is a syntax error. 39 | end 40 | end 41 | 42 | def test_you_can_politely_ask_for_instance_variable_values 43 | fido = Dog2.new 44 | fido.set_name("Fido") 45 | 46 | assert_equal __, fido.instance_variable_get("@name") 47 | end 48 | 49 | def test_you_can_rip_the_value_out_using_instance_eval 50 | fido = Dog2.new 51 | fido.set_name("Fido") 52 | 53 | assert_equal __, fido.instance_eval("@name") # string version 54 | assert_equal __, fido.instance_eval { @name } # block version 55 | end 56 | 57 | # ------------------------------------------------------------------ 58 | 59 | class Dog3 60 | def set_name(a_name) 61 | @name = a_name 62 | end 63 | def name 64 | @name 65 | end 66 | end 67 | 68 | def test_you_can_create_accessor_methods_to_return_instance_variables 69 | fido = Dog3.new 70 | fido.set_name("Fido") 71 | 72 | assert_equal __, fido.name 73 | end 74 | 75 | # ------------------------------------------------------------------ 76 | 77 | class Dog4 78 | attr_reader :name 79 | 80 | def set_name(a_name) 81 | @name = a_name 82 | end 83 | end 84 | 85 | 86 | def test_attr_reader_will_automatically_define_an_accessor 87 | fido = Dog4.new 88 | fido.set_name("Fido") 89 | 90 | assert_equal __, fido.name 91 | end 92 | 93 | # ------------------------------------------------------------------ 94 | 95 | class Dog5 96 | attr_accessor :name 97 | end 98 | 99 | 100 | def test_attr_accessor_will_automatically_define_both_read_and_write_accessors 101 | fido = Dog5.new 102 | 103 | fido.name = "Fido" 104 | assert_equal __, fido.name 105 | end 106 | 107 | # ------------------------------------------------------------------ 108 | 109 | class Dog6 110 | attr_reader :name 111 | def initialize(initial_name) 112 | @name = initial_name 113 | end 114 | end 115 | 116 | def test_initialize_provides_initial_values_for_instance_variables 117 | fido = Dog6.new("Fido") 118 | assert_equal __, fido.name 119 | end 120 | 121 | def test_args_to_new_must_match_initialize 122 | assert_raise(___) do 123 | Dog6.new 124 | end 125 | # THINK ABOUT IT: 126 | # Why is this so? 127 | end 128 | 129 | def test_different_objects_have_different_instance_variables 130 | fido = Dog6.new("Fido") 131 | rover = Dog6.new("Rover") 132 | 133 | assert_equal __, rover.name != fido.name 134 | end 135 | 136 | # ------------------------------------------------------------------ 137 | 138 | class Dog7 139 | attr_reader :name 140 | 141 | def initialize(initial_name) 142 | @name = initial_name 143 | end 144 | 145 | def get_self 146 | self 147 | end 148 | 149 | def to_s 150 | __ 151 | end 152 | 153 | def inspect 154 | "" 155 | end 156 | end 157 | 158 | def test_inside_a_method_self_refers_to_the_containing_object 159 | fido = Dog7.new("Fido") 160 | 161 | fidos_self = fido.get_self 162 | assert_equal __, fidos_self 163 | end 164 | 165 | def test_to_s_provides_a_string_version_of_the_object 166 | fido = Dog7.new("Fido") 167 | assert_equal __, fido.to_s 168 | end 169 | 170 | def test_to_s_is_used_in_string_interpolation 171 | fido = Dog7.new("Fido") 172 | assert_equal __, "My dog is #{fido}" 173 | end 174 | 175 | def test_inspect_provides_a_more_complete_string_version 176 | fido = Dog7.new("Fido") 177 | assert_equal __, fido.inspect 178 | end 179 | 180 | def test_all_objects_support_to_s_and_inspect 181 | array = [1,2,3] 182 | 183 | assert_equal __, array.to_s 184 | assert_equal __, array.inspect 185 | 186 | assert_equal __, "STRING".to_s 187 | assert_equal __, "STRING".inspect 188 | end 189 | 190 | end 191 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- ruby -*- 3 | 4 | require 'rake/clean' 5 | begin 6 | require 'rdoc/task' 7 | rescue LoadError => ex 8 | # No rdoc task availble. 9 | end 10 | 11 | SRC_DIR = 'src' 12 | PROB_DIR = 'koans' 13 | DIST_DIR = 'dist' 14 | 15 | SRC_FILES = FileList["#{SRC_DIR}/*"] 16 | KOAN_FILES = SRC_FILES.pathmap("#{PROB_DIR}/%f") 17 | 18 | today = Time.now.strftime("%Y-%m-%d") 19 | TAR_FILE = "#{DIST_DIR}/rubykoans-#{today}.tgz" 20 | ZIP_FILE = "#{DIST_DIR}/rubykoans-#{today}.zip" 21 | 22 | CLEAN.include("**/*.rbc") 23 | CLOBBER.include(DIST_DIR) 24 | 25 | module Koans 26 | extend Rake::DSL if defined?(Rake::DSL) 27 | 28 | # Remove solution info from source 29 | # __(a,b) => __ 30 | # _n_(number) => __ 31 | # # __ => 32 | def Koans.remove_solution(line) 33 | line = line.gsub(/\b____\([^\)]+\)/, "____") 34 | line = line.gsub(/\b___\([^\)]+\)/, "___") 35 | line = line.gsub(/\b__\([^\)]+\)/, "__") 36 | line = line.gsub(/\b_n_\([^\)]+\)/, "_n_") 37 | line = line.gsub(%r(/\#\{__\}/), "/__/") 38 | line = line.gsub(/\s*#\s*__\s*$/, '') 39 | line 40 | end 41 | 42 | def Koans.make_koan_file(infile, outfile) 43 | if infile =~ /edgecase/ 44 | cp infile, outfile 45 | else 46 | open(infile) do |ins| 47 | open(outfile, "w") do |outs| 48 | state = :copy 49 | ins.each do |line| 50 | state = :skip if line =~ /^ *#--/ 51 | case state 52 | when :copy 53 | outs.puts remove_solution(line) 54 | else 55 | # do nothing 56 | end 57 | state = :copy if line =~ /^ *#\+\+/ 58 | end 59 | end 60 | end 61 | end 62 | end 63 | end 64 | 65 | module RubyImpls 66 | # Calculate the list of relevant Ruby implementations. 67 | def self.find_ruby_impls 68 | rubys = `rvm list`.gsub(/=>/,'').split(/\n/).map { |x| x.strip }.reject { |x| x.empty? || x =~ /^rvm/ }.sort 69 | expected.map { |impl| 70 | last = rubys.grep(Regexp.new(Regexp.quote(impl))).last 71 | last ? last.split.first : nil 72 | }.compact 73 | end 74 | 75 | # Return a (cached) list of relevant Ruby implementations. 76 | def self.list 77 | @list ||= find_ruby_impls 78 | end 79 | 80 | # List of expected ruby implementations. 81 | def self.expected 82 | %w(ruby-1.8.7 ruby-1.9.2 jruby ree) 83 | end 84 | end 85 | 86 | task :default => :walk_the_path 87 | 88 | task :walk_the_path do 89 | cd PROB_DIR 90 | ruby 'path_to_enlightenment.rb' 91 | end 92 | 93 | if defined?(Rake::RDocTask) 94 | Rake::RDocTask.new do |rd| 95 | rd.main = "README.rdoc" 96 | rd.rdoc_files.include("README.rdoc", "${PROB_DIR}/*.rb") 97 | end 98 | end 99 | 100 | directory DIST_DIR 101 | directory PROB_DIR 102 | 103 | file ZIP_FILE => KOAN_FILES + [DIST_DIR] do 104 | sh "zip #{ZIP_FILE} #{PROB_DIR}/*" 105 | end 106 | 107 | file TAR_FILE => KOAN_FILES + [DIST_DIR] do 108 | sh "tar zcvf #{TAR_FILE} #{PROB_DIR}" 109 | end 110 | 111 | desc "Create packaged files for distribution" 112 | task :package => [TAR_FILE, ZIP_FILE] 113 | 114 | desc "Upload the package files to the web server" 115 | task :upload => [TAR_FILE, ZIP_FILE] do 116 | sh "scp #{TAR_FILE} linode:sites/onestepback.org/download" 117 | sh "scp #{ZIP_FILE} linode:sites/onestepback.org/download" 118 | end 119 | 120 | desc "Generate the Koans from the source files from scratch." 121 | task :regen => [:clobber_koans, :gen] 122 | 123 | desc "Generate the Koans from the changed source files." 124 | task :gen => KOAN_FILES + [PROB_DIR + "/README.rdoc"] 125 | task :clobber_koans do 126 | rm_r PROB_DIR 127 | end 128 | 129 | file PROB_DIR + "/README.rdoc" => "README.rdoc" do |t| 130 | cp "README.rdoc", t.name 131 | end 132 | 133 | SRC_FILES.each do |koan_src| 134 | file koan_src.pathmap("#{PROB_DIR}/%f") => [PROB_DIR, koan_src] do |t| 135 | Koans.make_koan_file koan_src, t.name 136 | end 137 | end 138 | 139 | task :run do 140 | puts 'koans' 141 | Dir.chdir("${SRC_DIR}") do 142 | puts "in #{Dir.pwd}" 143 | sh "ruby path_to_enlightenment.rb" 144 | end 145 | end 146 | 147 | 148 | desc "Pre-checkin tests (=> run_all)" 149 | task :cruise => :run_all 150 | 151 | desc "Run the completed koans againts a list of relevant Ruby Implementations" 152 | task :run_all do 153 | results = [] 154 | RubyImpls.list.each do |impl| 155 | puts "=" * 40 156 | puts "On Ruby #{impl}" 157 | sh ". rvm #{impl}; rake run" 158 | results << [impl, "RAN"] 159 | puts 160 | end 161 | puts "=" * 40 162 | puts "Summary:" 163 | puts 164 | results.each do |impl, res| 165 | puts "#{impl} => RAN" 166 | end 167 | puts 168 | RubyImpls.expected.each do |requested_impl| 169 | impl_pattern = Regexp.new(Regexp.quote(requested_impl)) 170 | puts "No Results for #{requested_impl}" unless results.detect { |x| x.first =~ impl_pattern } 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /src/about_java_interop.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | include Java 4 | 5 | # Concepts 6 | # * Pull in a java class 7 | # * calling a method, Camel vs snake 8 | # * Resolving module/class name conflicts 9 | # * Showing what gets returned 10 | # * Ruby Strings VS Java Strings 11 | # * Calling custom java class 12 | # * Calling Ruby from java??? 13 | 14 | class AboutJavaInterop < EdgeCase::Koan 15 | def test_using_a_java_library_class 16 | java_array = java.util.ArrayList.new 17 | assert_equal __(Java::JavaUtil::ArrayList), java_array.class 18 | end 19 | 20 | def test_java_class_can_be_referenced_using_both_ruby_and_java_like_syntax 21 | assert_equal __(true), Java::JavaUtil::ArrayList == java.util.ArrayList 22 | end 23 | 24 | def test_include_class_includes_class_in_module_scope 25 | assert_nil defined?(TreeSet) # __ 26 | include_class "java.util.TreeSet" 27 | assert_equal __("constant"), defined?(TreeSet) 28 | end 29 | 30 | # THINK ABOUT IT: 31 | # 32 | # What if we use: 33 | # 34 | # include_class "java.lang.String" 35 | # 36 | # What would be the value of the String constant after this 37 | # include_class is run? Would it be useful to provide a way of 38 | # aliasing java classes to different names? 39 | 40 | JString = java.lang.String 41 | def test_also_java_class_can_be_given_ruby_aliases 42 | java_string = JString.new("A Java String") 43 | assert_equal __(java.lang.String), java_string.class 44 | assert_equal __(java.lang.String), JString 45 | end 46 | 47 | def test_can_directly_call_java_methods_on_java_objects 48 | java_string = JString.new("A Java String") 49 | assert_equal __("a java string"), java_string.toLowerCase 50 | end 51 | 52 | def test_jruby_provides_snake_case_versions_of_java_methods 53 | java_string = JString.new("A Java String") 54 | assert_equal __("a java string"), java_string.to_lower_case 55 | end 56 | 57 | def test_jruby_provides_question_mark_versions_of_boolean_methods 58 | java_string = JString.new("A Java String") 59 | assert_equal __(true), java_string.endsWith("String") 60 | assert_equal __(true), java_string.ends_with("String") 61 | assert_equal __(true), java_string.ends_with?("String") 62 | end 63 | 64 | def test_java_string_are_not_ruby_strings 65 | ruby_string = "A Java String" 66 | java_string = java.lang.String.new(ruby_string) 67 | assert_equal __(true), java_string.is_a?(java.lang.String) 68 | assert_equal __(false), java_string.is_a?(String) 69 | end 70 | 71 | def test_java_strings_can_be_compared_to_ruby_strings_maybe 72 | ruby_string = "A Java String" 73 | java_string = java.lang.String.new(ruby_string) 74 | assert_equal __(false), ruby_string == java_string 75 | assert_equal __(true), java_string == ruby_string 76 | 77 | # THINK ABOUT IT: 78 | # 79 | # Is there any possible way for this to be more wrong? 80 | # 81 | # SERIOUSLY, THINK ABOUT IT: 82 | # 83 | # Why do you suppose that Ruby and Java strings compare like that? 84 | # 85 | # ADVANCED THINK ABOUT IT: 86 | # 87 | # Is there a way to make Ruby/Java string comparisons commutative? 88 | # How would you do it? 89 | end 90 | 91 | def test_however_most_methods_returning_strings_return_ruby_strings 92 | java_array = java.util.ArrayList.new 93 | assert_equal __("[]"), java_array.toString 94 | assert_equal __(true), java_array.toString.is_a?(String) 95 | assert_equal __(false), java_array.toString.is_a?(java.lang.String) 96 | end 97 | 98 | def test_some_ruby_objects_can_be_coerced_to_java 99 | assert_equal __(Java::JavaLang::String), "ruby string".to_java.class 100 | assert_equal __(Java::JavaLang::Long), 1.to_java.class 101 | assert_equal __(Java::JavaLang::Double), 9.32.to_java.class 102 | assert_equal __(Java::JavaLang::Boolean), false.to_java.class 103 | end 104 | 105 | def test_some_ruby_objects_are_not_coerced_to_what_you_might_expect 106 | assert_equal __(false), [].to_java.class == Java::JavaUtil::ArrayList 107 | assert_equal __(false), {}.to_java.class == Java::JavaUtil::HashMap 108 | assert_equal __(false), Object.new.to_java.class == Java::JavaLang::Object 109 | end 110 | 111 | def test_java_collections_are_enumerable 112 | java_array = java.util.ArrayList.new 113 | java_array << "one" << "two" << "three" 114 | assert_equal __(["ONE", "TWO", "THREE"]), java_array.map { |item| item.upcase } 115 | end 116 | 117 | # ------------------------------------------------------------------ 118 | 119 | # Open the Java ArrayList class and add a new method. 120 | class Java::JavaUtil::ArrayList 121 | def multiply_all 122 | result = 1 123 | each do |item| 124 | result *= item 125 | end 126 | result 127 | end 128 | end 129 | 130 | def test_java_class_are_open_from_ruby 131 | java_array = java.util.ArrayList.new 132 | java_array.add_all([1,2,3,4,5]) 133 | 134 | assert_equal __(120), java_array.multiply_all 135 | end 136 | 137 | end 138 | -------------------------------------------------------------------------------- /src/about_classes.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutClasses < EdgeCase::Koan 4 | class Dog 5 | end 6 | 7 | def test_instances_of_classes_can_be_created_with_new 8 | fido = Dog.new 9 | assert_equal __(Dog), fido.class 10 | end 11 | 12 | # ------------------------------------------------------------------ 13 | 14 | class Dog2 15 | def set_name(a_name) 16 | @name = a_name 17 | end 18 | end 19 | 20 | def test_instance_variables_can_be_set_by_assigning_to_them 21 | fido = Dog2.new 22 | assert_equal __([]), fido.instance_variables 23 | 24 | fido.set_name("Fido") 25 | assert_equal __(["@name"], [:@name]), fido.instance_variables 26 | end 27 | 28 | def test_instance_variables_cannot_be_accessed_outside_the_class 29 | fido = Dog2.new 30 | fido.set_name("Fido") 31 | 32 | assert_raise(___(NoMethodError)) do 33 | fido.name 34 | end 35 | 36 | assert_raise(___(SyntaxError)) do 37 | eval "fido.@name" 38 | # NOTE: Using eval because the above line is a syntax error. 39 | end 40 | end 41 | 42 | def test_you_can_politely_ask_for_instance_variable_values 43 | fido = Dog2.new 44 | fido.set_name("Fido") 45 | 46 | assert_equal __("Fido"), fido.instance_variable_get("@name") 47 | end 48 | 49 | def test_you_can_rip_the_value_out_using_instance_eval 50 | fido = Dog2.new 51 | fido.set_name("Fido") 52 | 53 | assert_equal __("Fido"), fido.instance_eval("@name") # string version 54 | assert_equal __("Fido"), fido.instance_eval { @name } # block version 55 | end 56 | 57 | # ------------------------------------------------------------------ 58 | 59 | class Dog3 60 | def set_name(a_name) 61 | @name = a_name 62 | end 63 | def name 64 | @name 65 | end 66 | end 67 | 68 | def test_you_can_create_accessor_methods_to_return_instance_variables 69 | fido = Dog3.new 70 | fido.set_name("Fido") 71 | 72 | assert_equal __("Fido"), fido.name 73 | end 74 | 75 | # ------------------------------------------------------------------ 76 | 77 | class Dog4 78 | attr_reader :name 79 | 80 | def set_name(a_name) 81 | @name = a_name 82 | end 83 | end 84 | 85 | 86 | def test_attr_reader_will_automatically_define_an_accessor 87 | fido = Dog4.new 88 | fido.set_name("Fido") 89 | 90 | assert_equal __("Fido"), fido.name 91 | end 92 | 93 | # ------------------------------------------------------------------ 94 | 95 | class Dog5 96 | attr_accessor :name 97 | end 98 | 99 | 100 | def test_attr_accessor_will_automatically_define_both_read_and_write_accessors 101 | fido = Dog5.new 102 | 103 | fido.name = "Fido" 104 | assert_equal __("Fido"), fido.name 105 | end 106 | 107 | # ------------------------------------------------------------------ 108 | 109 | class Dog6 110 | attr_reader :name 111 | def initialize(initial_name) 112 | @name = initial_name 113 | end 114 | end 115 | 116 | def test_initialize_provides_initial_values_for_instance_variables 117 | fido = Dog6.new("Fido") 118 | assert_equal __("Fido"), fido.name 119 | end 120 | 121 | def test_args_to_new_must_match_initialize 122 | assert_raise(___(ArgumentError)) do 123 | Dog6.new 124 | end 125 | # THINK ABOUT IT: 126 | # Why is this so? 127 | end 128 | 129 | def test_different_objects_have_different_instance_variables 130 | fido = Dog6.new("Fido") 131 | rover = Dog6.new("Rover") 132 | 133 | assert_equal __(true), rover.name != fido.name 134 | end 135 | 136 | # ------------------------------------------------------------------ 137 | 138 | class Dog7 139 | attr_reader :name 140 | 141 | def initialize(initial_name) 142 | @name = initial_name 143 | end 144 | 145 | def get_self 146 | self 147 | end 148 | 149 | def to_s 150 | __(@name) 151 | end 152 | 153 | def inspect 154 | "" 155 | end 156 | end 157 | 158 | def test_inside_a_method_self_refers_to_the_containing_object 159 | fido = Dog7.new("Fido") 160 | 161 | fidos_self = fido.get_self 162 | assert_equal __(fido), fidos_self 163 | end 164 | 165 | def test_to_s_provides_a_string_version_of_the_object 166 | fido = Dog7.new("Fido") 167 | assert_equal __("Fido"), fido.to_s 168 | end 169 | 170 | def test_to_s_is_used_in_string_interpolation 171 | fido = Dog7.new("Fido") 172 | assert_equal __("My dog is Fido"), "My dog is #{fido}" 173 | end 174 | 175 | def test_inspect_provides_a_more_complete_string_version 176 | fido = Dog7.new("Fido") 177 | assert_equal __(""), fido.inspect 178 | end 179 | 180 | def test_all_objects_support_to_s_and_inspect 181 | array = [1,2,3] 182 | 183 | assert_equal __("123", "[1, 2, 3]"), array.to_s 184 | assert_equal __("[1, 2, 3]"), array.inspect 185 | 186 | assert_equal __("STRING"), "STRING".to_s 187 | assert_equal __('"STRING"'), "STRING".inspect 188 | end 189 | 190 | end 191 | -------------------------------------------------------------------------------- /koans/about_message_passing.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutMessagePassing < EdgeCase::Koan 4 | 5 | class MessageCatcher 6 | def caught? 7 | true 8 | end 9 | end 10 | 11 | def test_methods_can_be_called_directly 12 | mc = MessageCatcher.new 13 | 14 | assert mc.caught? 15 | end 16 | 17 | def test_methods_can_be_invoked_by_sending_the_message 18 | mc = MessageCatcher.new 19 | 20 | assert mc.send(:caught?) 21 | end 22 | 23 | def test_methods_can_be_invoked_more_dynamically 24 | mc = MessageCatcher.new 25 | 26 | assert mc.send("caught?") 27 | assert mc.send("caught" + __ ) # What do you need to add to the first string? 28 | assert mc.send("CAUGHT?".____ ) # What would you need to do to the string? 29 | end 30 | 31 | def test_send_with_underscores_will_also_send_messages 32 | mc = MessageCatcher.new 33 | 34 | assert_equal __, mc.__send__(:caught?) 35 | 36 | # THINK ABOUT IT: 37 | # 38 | # Why does Ruby provide both send and __send__ ? 39 | end 40 | 41 | def test_classes_can_be_asked_if_they_know_how_to_respond 42 | mc = MessageCatcher.new 43 | 44 | assert_equal __, mc.respond_to?(:caught?) 45 | assert_equal __, mc.respond_to?(:does_not_exist) 46 | end 47 | 48 | # ------------------------------------------------------------------ 49 | 50 | class MessageCatcher 51 | def add_a_payload(*args) 52 | args 53 | end 54 | end 55 | 56 | def test_sending_a_message_with_arguments 57 | mc = MessageCatcher.new 58 | 59 | assert_equal __, mc.add_a_payload 60 | assert_equal __, mc.send(:add_a_payload) 61 | 62 | assert_equal __, mc.add_a_payload(3, 4, nil, 6) 63 | assert_equal __, mc.send(:add_a_payload, 3, 4, nil, 6) 64 | end 65 | 66 | # ------------------------------------------------------------------ 67 | 68 | class TypicalObject 69 | end 70 | 71 | def test_sending_undefined_messages_to_a_typical_object_results_in_errors 72 | typical = TypicalObject.new 73 | 74 | exception = assert_raise(___) do 75 | typical.foobar 76 | end 77 | assert_match(/foobar/, exception.message) 78 | end 79 | 80 | def test_calling_method_missing_causes_the_no_method_error 81 | typical = TypicalObject.new 82 | 83 | exception = assert_raise(___) do 84 | typical.method_missing(:foobar) 85 | end 86 | assert_match(/foobar/, exception.message) 87 | 88 | # THINK ABOUT IT: 89 | # 90 | # If the method :method_missing causes the NoMethodError, then 91 | # what would happen if we redefine method_missing? 92 | # 93 | # NOTE: 94 | # 95 | # In Ruby 1.8 the method_missing method is public and can be 96 | # called as shown above. However, in Ruby 1.9 the method_missing 97 | # method is private. We explicitly made it public in the testing 98 | # framework so this example works in both versions of Ruby. Just 99 | # keep in mind you can't call method_missing like that in Ruby 100 | # 1.9. normally. 101 | # 102 | # Thanks. We now return you to your regularly scheduled Ruby 103 | # Koans. 104 | end 105 | 106 | # ------------------------------------------------------------------ 107 | 108 | class AllMessageCatcher 109 | def method_missing(method_name, *args, &block) 110 | "Someone called #{method_name} with <#{args.join(", ")}>" 111 | end 112 | end 113 | 114 | def test_all_messages_are_caught 115 | catcher = AllMessageCatcher.new 116 | 117 | assert_equal __, catcher.foobar 118 | assert_equal __, catcher.foobaz(1) 119 | assert_equal __, catcher.sum(1,2,3,4,5,6) 120 | end 121 | 122 | def test_catching_messages_makes_respond_to_lie 123 | catcher = AllMessageCatcher.new 124 | 125 | assert_nothing_raised(NoMethodError) do 126 | catcher.any_method 127 | end 128 | assert_equal __, catcher.respond_to?(:any_method) 129 | end 130 | 131 | # ------------------------------------------------------------------ 132 | 133 | class WellBehavedFooCatcher 134 | def method_missing(method_name, *args, &block) 135 | if method_name.to_s[0,3] == "foo" 136 | "Foo to you too" 137 | else 138 | super(method_name, *args, &block) 139 | end 140 | end 141 | end 142 | 143 | def test_foo_method_are_caught 144 | catcher = WellBehavedFooCatcher.new 145 | 146 | assert_equal __, catcher.foo_bar 147 | assert_equal __, catcher.foo_baz 148 | end 149 | 150 | def test_non_foo_messages_are_treated_normally 151 | catcher = WellBehavedFooCatcher.new 152 | 153 | assert_raise(___) do 154 | catcher.normal_undefined_method 155 | end 156 | end 157 | 158 | # ------------------------------------------------------------------ 159 | 160 | # (note: just reopening class from above) 161 | class WellBehavedFooCatcher 162 | def respond_to?(method_name) 163 | if method_name.to_s[0,3] == "foo" 164 | true 165 | else 166 | super(method_name) 167 | end 168 | end 169 | end 170 | 171 | def test_explicitly_implementing_respond_to_lets_objects_tell_the_truth 172 | catcher = WellBehavedFooCatcher.new 173 | 174 | assert_equal __, catcher.respond_to?(:foo_bar) 175 | assert_equal __, catcher.respond_to?(:something_else) 176 | end 177 | 178 | end 179 | -------------------------------------------------------------------------------- /koans/about_regular_expressions.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 3 | 4 | class AboutRegularExpressions < EdgeCase::Koan 5 | def test_a_pattern_is_a_regular_expression 6 | assert_equal __, /pattern/.class 7 | end 8 | 9 | def test_a_regexp_can_search_a_string_for_matching_content 10 | assert_equal __, "some matching content"[/match/] 11 | end 12 | 13 | def test_a_failed_match_returns_nil 14 | assert_equal __, "some matching content"[/missing/] 15 | end 16 | 17 | # ------------------------------------------------------------------ 18 | 19 | def test_question_mark_means_optional 20 | assert_equal __, "abbcccddddeeeee"[/ab?/] 21 | assert_equal __, "abbcccddddeeeee"[/az?/] 22 | end 23 | 24 | def test_plus_means_one_or_more 25 | assert_equal __, "abbcccddddeeeee"[/bc+/] 26 | end 27 | 28 | def test_asterisk_means_zero_or_more 29 | assert_equal __, "abbcccddddeeeee"[/ab*/] 30 | assert_equal __, "abbcccddddeeeee"[/az*/] 31 | assert_equal __, "abbcccddddeeeee"[/z*/] 32 | 33 | # THINK ABOUT IT: 34 | # 35 | # When would * fail to match? 36 | end 37 | 38 | # THINK ABOUT IT: 39 | # 40 | # We say that the repetition operators above are "greedy." 41 | # 42 | # Why? 43 | 44 | # ------------------------------------------------------------------ 45 | 46 | def test_the_left_most_match_wins 47 | assert_equal __, "abbccc az"[/az*/] 48 | end 49 | 50 | # ------------------------------------------------------------------ 51 | 52 | def test_character_classes_give_options_for_a_character 53 | animals = ["cat", "bat", "rat", "zat"] 54 | assert_equal __, animals.select { |a| a[/[cbr]at/] } 55 | end 56 | 57 | def test_slash_d_is_a_shortcut_for_a_digit_character_class 58 | assert_equal __, "the number is 42"[/[0123456789]+/] 59 | assert_equal __, "the number is 42"[/\d+/] 60 | end 61 | 62 | def test_character_classes_can_include_ranges 63 | assert_equal __, "the number is 42"[/[0-9]+/] 64 | end 65 | 66 | def test_slash_s_is_a_shortcut_for_a_whitespace_character_class 67 | assert_equal __, "space: \t\n"[/\s+/] 68 | end 69 | 70 | def test_slash_w_is_a_shortcut_for_a_word_character_class 71 | # NOTE: This is more like how a programmer might define a word. 72 | assert_equal __, "variable_1 = 42"[/[a-zA-Z0-9_]+/] 73 | assert_equal __, "variable_1 = 42"[/\w+/] 74 | end 75 | 76 | def test_period_is_a_shortcut_for_any_non_newline_character 77 | assert_equal __, "abc\n123"[/a.+/] 78 | end 79 | 80 | def test_a_character_class_can_be_negated 81 | assert_equal __, "the number is 42"[/[^0-9]+/] 82 | end 83 | 84 | def test_shortcut_character_classes_are_negated_with_capitals 85 | assert_equal __, "the number is 42"[/\D+/] 86 | assert_equal __, "space: \t\n"[/\S+/] 87 | # ... a programmer would most likely do 88 | assert_equal __, "variable_1 = 42"[/[^a-zA-Z0-9_]+/] 89 | assert_equal __, "variable_1 = 42"[/\W+/] 90 | end 91 | 92 | # ------------------------------------------------------------------ 93 | 94 | def test_slash_a_anchors_to_the_start_of_the_string 95 | assert_equal __, "start end"[/\Astart/] 96 | assert_equal __, "start end"[/\Aend/] 97 | end 98 | 99 | def test_slash_z_anchors_to_the_end_of_the_string 100 | assert_equal __, "start end"[/end\z/] 101 | assert_equal __, "start end"[/start\z/] 102 | end 103 | 104 | def test_caret_anchors_to_the_start_of_lines 105 | assert_equal __, "num 42\n2 lines"[/^\d+/] 106 | end 107 | 108 | def test_dollar_sign_anchors_to_the_end_of_lines 109 | assert_equal __, "2 lines\nnum 42"[/\d+$/] 110 | end 111 | 112 | def test_slash_b_anchors_to_a_word_boundary 113 | assert_equal __, "bovine vines"[/\bvine./] 114 | end 115 | 116 | # ------------------------------------------------------------------ 117 | 118 | def test_parentheses_group_contents 119 | assert_equal __, "ahahaha"[/(ha)+/] 120 | end 121 | 122 | # ------------------------------------------------------------------ 123 | 124 | def test_parentheses_also_capture_matched_content_by_number 125 | assert_equal __, "Gray, James"[/(\w+), (\w+)/, 1] 126 | assert_equal __, "Gray, James"[/(\w+), (\w+)/, 2] 127 | end 128 | 129 | def test_variables_can_also_be_used_to_access_captures 130 | assert_equal __, "Name: Gray, James"[/(\w+), (\w+)/] 131 | assert_equal __, $1 132 | assert_equal __, $2 133 | end 134 | 135 | # ------------------------------------------------------------------ 136 | 137 | def test_a_vertical_pipe_means_or 138 | grays = /(James|Dana|Summer) Gray/ 139 | assert_equal __, "James Gray"[grays] 140 | assert_equal __, "Summer Gray"[grays, 1] 141 | assert_equal __, "Jim Gray"[grays, 1] 142 | end 143 | 144 | # THINK ABOUT IT: 145 | # 146 | # Explain the difference between a character class ([...]) and alternation (|). 147 | 148 | # ------------------------------------------------------------------ 149 | 150 | def test_scan_is_like_find_all 151 | assert_equal __, "one two-three".scan(/\w+/) 152 | end 153 | 154 | def test_sub_is_like_find_and_replace 155 | assert_equal __, "one two-three".sub(/(t\w*)/) { $1[0, 1] } 156 | end 157 | 158 | def test_gsub_is_like_find_and_replace_all 159 | assert_equal __, "one two-three".gsub(/(t\w*)/) { $1[0, 1] } 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /src/about_message_passing.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutMessagePassing < EdgeCase::Koan 4 | 5 | class MessageCatcher 6 | def caught? 7 | true 8 | end 9 | end 10 | 11 | def test_methods_can_be_called_directly 12 | mc = MessageCatcher.new 13 | 14 | assert mc.caught? # __ 15 | end 16 | 17 | def test_methods_can_be_invoked_by_sending_the_message 18 | mc = MessageCatcher.new 19 | 20 | assert mc.send(:caught?) # __ 21 | end 22 | 23 | def test_methods_can_be_invoked_more_dynamically 24 | mc = MessageCatcher.new 25 | 26 | assert mc.send("caught?") # __ 27 | assert mc.send("caught" + __("?") ) # What do you need to add to the first string? 28 | assert mc.send("CAUGHT?".____(:downcase) ) # What would you need to do to the string? 29 | end 30 | 31 | def test_send_with_underscores_will_also_send_messages 32 | mc = MessageCatcher.new 33 | 34 | assert_equal __(true), mc.__send__(:caught?) 35 | 36 | # THINK ABOUT IT: 37 | # 38 | # Why does Ruby provide both send and __send__ ? 39 | end 40 | 41 | def test_classes_can_be_asked_if_they_know_how_to_respond 42 | mc = MessageCatcher.new 43 | 44 | assert_equal __(true), mc.respond_to?(:caught?) 45 | assert_equal __(false), mc.respond_to?(:does_not_exist) 46 | end 47 | 48 | # ------------------------------------------------------------------ 49 | 50 | class MessageCatcher 51 | def add_a_payload(*args) 52 | args 53 | end 54 | end 55 | 56 | def test_sending_a_message_with_arguments 57 | mc = MessageCatcher.new 58 | 59 | assert_equal __([]), mc.add_a_payload 60 | assert_equal __([]), mc.send(:add_a_payload) 61 | 62 | assert_equal __([3, 4, nil, 6]), mc.add_a_payload(3, 4, nil, 6) 63 | assert_equal __([3, 4, nil, 6]), mc.send(:add_a_payload, 3, 4, nil, 6) 64 | end 65 | 66 | # ------------------------------------------------------------------ 67 | 68 | class TypicalObject 69 | end 70 | 71 | def test_sending_undefined_messages_to_a_typical_object_results_in_errors 72 | typical = TypicalObject.new 73 | 74 | exception = assert_raise(___(NoMethodError)) do 75 | typical.foobar 76 | end 77 | assert_match(/foobar/, exception.message) # __ 78 | end 79 | 80 | def test_calling_method_missing_causes_the_no_method_error 81 | typical = TypicalObject.new 82 | 83 | exception = assert_raise(___(NoMethodError)) do 84 | typical.method_missing(:foobar) 85 | end 86 | assert_match(/foobar/, exception.message) # __ 87 | 88 | # THINK ABOUT IT: 89 | # 90 | # If the method :method_missing causes the NoMethodError, then 91 | # what would happen if we redefine method_missing? 92 | # 93 | # NOTE: 94 | # 95 | # In Ruby 1.8 the method_missing method is public and can be 96 | # called as shown above. However, in Ruby 1.9 the method_missing 97 | # method is private. We explicitly made it public in the testing 98 | # framework so this example works in both versions of Ruby. Just 99 | # keep in mind you can't call method_missing like that in Ruby 100 | # 1.9. normally. 101 | # 102 | # Thanks. We now return you to your regularly scheduled Ruby 103 | # Koans. 104 | end 105 | 106 | # ------------------------------------------------------------------ 107 | 108 | class AllMessageCatcher 109 | def method_missing(method_name, *args, &block) 110 | "Someone called #{method_name} with <#{args.join(", ")}>" 111 | end 112 | end 113 | 114 | def test_all_messages_are_caught 115 | catcher = AllMessageCatcher.new 116 | 117 | assert_equal __("Someone called foobar with <>"), catcher.foobar 118 | assert_equal __("Someone called foobaz with <1>"), catcher.foobaz(1) 119 | assert_equal __("Someone called sum with <1, 2, 3, 4, 5, 6>"), catcher.sum(1,2,3,4,5,6) 120 | end 121 | 122 | def test_catching_messages_makes_respond_to_lie 123 | catcher = AllMessageCatcher.new 124 | 125 | assert_nothing_raised(NoMethodError) do # __ 126 | catcher.any_method 127 | end 128 | assert_equal __(false), catcher.respond_to?(:any_method) 129 | end 130 | 131 | # ------------------------------------------------------------------ 132 | 133 | class WellBehavedFooCatcher 134 | def method_missing(method_name, *args, &block) 135 | if method_name.to_s[0,3] == "foo" 136 | "Foo to you too" 137 | else 138 | super(method_name, *args, &block) 139 | end 140 | end 141 | end 142 | 143 | def test_foo_method_are_caught 144 | catcher = WellBehavedFooCatcher.new 145 | 146 | assert_equal __("Foo to you too"), catcher.foo_bar 147 | assert_equal __("Foo to you too"), catcher.foo_baz 148 | end 149 | 150 | def test_non_foo_messages_are_treated_normally 151 | catcher = WellBehavedFooCatcher.new 152 | 153 | assert_raise(___(NoMethodError)) do 154 | catcher.normal_undefined_method 155 | end 156 | end 157 | 158 | # ------------------------------------------------------------------ 159 | 160 | # (note: just reopening class from above) 161 | class WellBehavedFooCatcher 162 | def respond_to?(method_name) 163 | if method_name.to_s[0,3] == "foo" 164 | true 165 | else 166 | super(method_name) 167 | end 168 | end 169 | end 170 | 171 | def test_explicitly_implementing_respond_to_lets_objects_tell_the_truth 172 | catcher = WellBehavedFooCatcher.new 173 | 174 | assert_equal __(true), catcher.respond_to?(:foo_bar) 175 | assert_equal __(false), catcher.respond_to?(:something_else) 176 | end 177 | 178 | end 179 | -------------------------------------------------------------------------------- /src/about_regular_expressions.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 3 | 4 | class AboutRegularExpressions < EdgeCase::Koan 5 | def test_a_pattern_is_a_regular_expression 6 | assert_equal __(Regexp), /pattern/.class 7 | end 8 | 9 | def test_a_regexp_can_search_a_string_for_matching_content 10 | assert_equal __("match"), "some matching content"[/match/] 11 | end 12 | 13 | def test_a_failed_match_returns_nil 14 | assert_equal __(nil), "some matching content"[/missing/] 15 | end 16 | 17 | # ------------------------------------------------------------------ 18 | 19 | def test_question_mark_means_optional 20 | assert_equal __("ab"), "abbcccddddeeeee"[/ab?/] 21 | assert_equal __("a"), "abbcccddddeeeee"[/az?/] 22 | end 23 | 24 | def test_plus_means_one_or_more 25 | assert_equal __("bccc"), "abbcccddddeeeee"[/bc+/] 26 | end 27 | 28 | def test_asterisk_means_zero_or_more 29 | assert_equal __("abb"), "abbcccddddeeeee"[/ab*/] 30 | assert_equal __("a"), "abbcccddddeeeee"[/az*/] 31 | assert_equal __(""), "abbcccddddeeeee"[/z*/] 32 | 33 | # THINK ABOUT IT: 34 | # 35 | # When would * fail to match? 36 | end 37 | 38 | # THINK ABOUT IT: 39 | # 40 | # We say that the repetition operators above are "greedy." 41 | # 42 | # Why? 43 | 44 | # ------------------------------------------------------------------ 45 | 46 | def test_the_left_most_match_wins 47 | assert_equal __("a"), "abbccc az"[/az*/] 48 | end 49 | 50 | # ------------------------------------------------------------------ 51 | 52 | def test_character_classes_give_options_for_a_character 53 | animals = ["cat", "bat", "rat", "zat"] 54 | assert_equal __(["cat", "bat", "rat"]), animals.select { |a| a[/[cbr]at/] } 55 | end 56 | 57 | def test_slash_d_is_a_shortcut_for_a_digit_character_class 58 | assert_equal __("42"), "the number is 42"[/[0123456789]+/] 59 | assert_equal __("42"), "the number is 42"[/\d+/] 60 | end 61 | 62 | def test_character_classes_can_include_ranges 63 | assert_equal __("42"), "the number is 42"[/[0-9]+/] 64 | end 65 | 66 | def test_slash_s_is_a_shortcut_for_a_whitespace_character_class 67 | assert_equal __(" \t\n"), "space: \t\n"[/\s+/] 68 | end 69 | 70 | def test_slash_w_is_a_shortcut_for_a_word_character_class 71 | # NOTE: This is more like how a programmer might define a word. 72 | assert_equal __("variable_1"), "variable_1 = 42"[/[a-zA-Z0-9_]+/] 73 | assert_equal __("variable_1"), "variable_1 = 42"[/\w+/] 74 | end 75 | 76 | def test_period_is_a_shortcut_for_any_non_newline_character 77 | assert_equal __("abc"), "abc\n123"[/a.+/] 78 | end 79 | 80 | def test_a_character_class_can_be_negated 81 | assert_equal __("the number is "), "the number is 42"[/[^0-9]+/] 82 | end 83 | 84 | def test_shortcut_character_classes_are_negated_with_capitals 85 | assert_equal __("the number is "), "the number is 42"[/\D+/] 86 | assert_equal __("space:"), "space: \t\n"[/\S+/] 87 | # ... a programmer would most likely do 88 | assert_equal __(" = "), "variable_1 = 42"[/[^a-zA-Z0-9_]+/] 89 | assert_equal __(" = "), "variable_1 = 42"[/\W+/] 90 | end 91 | 92 | # ------------------------------------------------------------------ 93 | 94 | def test_slash_a_anchors_to_the_start_of_the_string 95 | assert_equal __("start"), "start end"[/\Astart/] 96 | assert_equal __(nil), "start end"[/\Aend/] 97 | end 98 | 99 | def test_slash_z_anchors_to_the_end_of_the_string 100 | assert_equal __("end"), "start end"[/end\z/] 101 | assert_equal __(nil), "start end"[/start\z/] 102 | end 103 | 104 | def test_caret_anchors_to_the_start_of_lines 105 | assert_equal __("2"), "num 42\n2 lines"[/^\d+/] 106 | end 107 | 108 | def test_dollar_sign_anchors_to_the_end_of_lines 109 | assert_equal __("42"), "2 lines\nnum 42"[/\d+$/] 110 | end 111 | 112 | def test_slash_b_anchors_to_a_word_boundary 113 | assert_equal __("vines"), "bovine vines"[/\bvine./] 114 | end 115 | 116 | # ------------------------------------------------------------------ 117 | 118 | def test_parentheses_group_contents 119 | assert_equal __("hahaha"), "ahahaha"[/(ha)+/] 120 | end 121 | 122 | # ------------------------------------------------------------------ 123 | 124 | def test_parentheses_also_capture_matched_content_by_number 125 | assert_equal __("Gray"), "Gray, James"[/(\w+), (\w+)/, 1] 126 | assert_equal __("James"), "Gray, James"[/(\w+), (\w+)/, 2] 127 | end 128 | 129 | def test_variables_can_also_be_used_to_access_captures 130 | assert_equal __("Gray, James"), "Name: Gray, James"[/(\w+), (\w+)/] 131 | assert_equal __("Gray"), $1 132 | assert_equal __("James"), $2 133 | end 134 | 135 | # ------------------------------------------------------------------ 136 | 137 | def test_a_vertical_pipe_means_or 138 | grays = /(James|Dana|Summer) Gray/ 139 | assert_equal __("James Gray"), "James Gray"[grays] 140 | assert_equal __("Summer"), "Summer Gray"[grays, 1] 141 | assert_equal __(nil), "Jim Gray"[grays, 1] 142 | end 143 | 144 | # THINK ABOUT IT: 145 | # 146 | # Explain the difference between a character class ([...]) and alternation (|). 147 | 148 | # ------------------------------------------------------------------ 149 | 150 | def test_scan_is_like_find_all 151 | assert_equal __(["one", "two", "three"]), "one two-three".scan(/\w+/) 152 | end 153 | 154 | def test_sub_is_like_find_and_replace 155 | assert_equal __("one t-three"), "one two-three".sub(/(t\w*)/) { $1[0, 1] } 156 | end 157 | 158 | def test_gsub_is_like_find_and_replace_all 159 | assert_equal __("one t-t"), "one two-three".gsub(/(t\w*)/) { $1[0, 1] } 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /koans/about_strings.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path(File.dirname(__FILE__) + '/edgecase') 2 | 3 | class AboutStrings < EdgeCase::Koan 4 | def test_double_quoted_strings_are_strings 5 | string = "Hello, World" 6 | assert_equal __, string.is_a?(String) 7 | end 8 | 9 | def test_single_quoted_strings_are_also_strings 10 | string = 'Goodbye, World' 11 | assert_equal __, string.is_a?(String) 12 | end 13 | 14 | def test_use_single_quotes_to_create_string_with_double_quotes 15 | string = 'He said, "Go Away."' 16 | assert_equal __, string 17 | end 18 | 19 | def test_use_double_quotes_to_create_strings_with_single_quotes 20 | string = "Don't" 21 | assert_equal __, string 22 | end 23 | 24 | def test_use_backslash_for_those_hard_cases 25 | a = "He said, \"Don't\"" 26 | b = 'He said, "Don\'t"' 27 | assert_equal __, a == b 28 | end 29 | 30 | def test_use_flexible_quoting_to_handle_really_hard_cases 31 | a = %(flexible quotes can handle both ' and " characters) 32 | b = %!flexible quotes can handle both ' and " characters! 33 | c = %{flexible quotes can handle both ' and " characters} 34 | assert_equal __, a == b 35 | assert_equal __, a == c 36 | end 37 | 38 | def test_flexible_quotes_can_handle_multiple_lines 39 | long_string = %{ 40 | It was the best of times, 41 | It was the worst of times. 42 | } 43 | assert_equal __, long_string.length 44 | assert_equal __, long_string.lines.count 45 | end 46 | 47 | def test_here_documents_can_also_handle_multiple_lines 48 | long_string = < is not true. 92 | 93 | Please meditate on the following code: 94 | ./about_asserts.rb:10:in `test_assert_truth' 95 | path_to_enlightenment.rb:38:in `each_with_index' 96 | path_to_enlightenment.rb:38 97 | 98 | mountains are merely mountains 99 | your path thus far [X_________________________________________________] 0/280 100 | 101 | You have come to your first stage. If you notice it is telling you where to look for 102 | the first solution: 103 | 104 | Please meditate on the following code: 105 | ./about_asserts.rb:10:in `test_assert_truth' 106 | path_to_enlightenment.rb:38:in `each_with_index' 107 | path_to_enlightenment.rb:38 108 | 109 | We then open up the about_asserts.rb file and look at the first test: 110 | 111 | # We shall contemplate truth by testing reality, via asserts. 112 | def test_assert_truth 113 | assert false # This should be true 114 | end 115 | 116 | We then change the +false+ to +true+ and run the test again. After you are 117 | done, think about what you are learning. In this case, ignore everything except 118 | the method name (+test_assert_truth+) and the parts inside the method (everything 119 | before the +end+). 120 | 121 | In this case the goal is for you to see that if you pass a value to the +assert+ 122 | method, it will either ensure it is +true+ and continue on, or fail if in fact 123 | the statement is +false+. 124 | 125 | == Inspiration 126 | 127 | A special thanks to Mike Clark and Ara Howard for inspiring this 128 | project. Mike Clark wrote an excellent blog post about learning Ruby 129 | through unit testing. This sparked an idea that has taken a bit to 130 | solidify, that of bringing new rubyists into the community through 131 | testing. Ara Howard then gave us the idea for the Koans in his ruby 132 | quiz entry on Meta Koans (a must for any rubyist wanting to improve 133 | their skills). Also, "The Little Lisper" taught us all the value of 134 | the short questions/simple answers style of learning. 135 | 136 | Mike Clark's post :: http://www.clarkware.com/cgi/blosxom/2005/03/18 137 | Meta Koans :: http://rubyquiz.com/quiz67.html 138 | The Little Lisper :: http://www.amazon.com/Little-LISPer-Third-Daniel-Friedman/dp/0023397632 139 | 140 | == Other Resources 141 | 142 | The Ruby Language :: http://ruby-lang.org 143 | Try Ruby in your browser :: http://tryruby.org 144 | 145 | Dave Thomas' introduction to Ruby Programming Ruby (the Pick Axe) :: http://pragprog.com/titles/ruby/programming-ruby 146 | 147 | Brian Marick's fantastic guide for beginners Everyday Scripting with Ruby :: http://pragprog.com/titles/bmsft/everyday-scripting-with-ruby 148 | 149 | = Other stuff 150 | 151 | Author :: Jim Weirich 152 | Author :: Joe O'Brien 153 | Issue Tracker :: http://www.pivotaltracker.com/projects/48111 154 | Requires :: Ruby 1.8.x or later and Rake (any recent version) 155 | 156 | = License 157 | 158 | http://i.creativecommons.org/l/by-nc-sa/3.0/88x31.png 159 | 160 | RubyKoans is released under a Creative Commons, 161 | Attribution-NonCommercial-ShareAlike, Version 3.0 162 | (http://creativecommons.org/licenses/by-nc-sa/3.0/) License. 163 | -------------------------------------------------------------------------------- /koans/README.rdoc: -------------------------------------------------------------------------------- 1 | = EdgeCase Ruby Koans 2 | 3 | The Ruby Koans walk you along the path to enlightenment in order to learn Ruby. 4 | The goal is to learn the Ruby language, syntax, structure, and some common 5 | functions and libraries. We also teach you culture. Testing is not just something we 6 | pay lip service to, but something we live. It is essential in your quest to learn 7 | and do great things in the language. 8 | 9 | == The Structure 10 | 11 | The koans are broken out into areas by file, hashes are covered in about_hashes.rb, 12 | modules are introduced in about_modules.rb, etc. They are presented in order in the 13 | path_to_enlightenment.rb file. 14 | 15 | Each koan builds up your knowledge of Ruby and builds upon itself. It will stop at 16 | the first place you need to correct. 17 | 18 | Some koans simply need to have the correct answer substituted for an incorrect one. 19 | Some, however, require you to supply your own answer. If you see the method +__+ (a 20 | double underscore) listed, it is a hint to you to supply your own code in order to 21 | make it work correctly. 22 | 23 | == Installing Ruby 24 | 25 | If you do not have Ruby setup, please visit http://ruby-lang.org/en/downloads/ for 26 | operating specific instructions. In order to run this you need ruby and rake 27 | installed. To check the installations simply type: 28 | 29 | *nix platforms from any terminal window: 30 | 31 | [~] $ ruby --version 32 | [~] $ rake --version 33 | 34 | Windows from the command prompt (cmd.exe) 35 | 36 | c:\ruby --version 37 | c:\rake --version 38 | 39 | If you don't have rake installed, just run `gem install rake` 40 | 41 | Any response for Ruby with a version number greater than 1.8 is fine (should be 42 | around 1.8.6 or more). Any version of rake will do. 43 | 44 | == Generating the Koans 45 | 46 | A fresh checkout will not include the koans, you will need to generate 47 | them. 48 | 49 | [ruby_koans] $ rake gen # generates the koans directory 50 | 51 | If you need to regenerate the koans, thus wiping your current `koans`, 52 | 53 | [ruby_koans] $ rake regen # regenerates the koans directory, wiping the original 54 | 55 | == The Path To Enlightenment 56 | 57 | You can run the tests through rake or by calling the file itself (rake is the 58 | recommended way to run them as we might build more functionality into this task). 59 | 60 | *nix platforms, from the koans directory 61 | 62 | [ruby_koans] $ rake # runs the default target :walk_the_path 63 | [ruby_koans] $ ruby path_to_enlightenment.rb # simply call the file directly 64 | 65 | Windows is the same thing 66 | 67 | c:\ruby_koans\rake # runs the default target :walk_the_path 68 | c:\ruby_koans\ruby path_to_enlightenment.rb # simply call the file directly 69 | 70 | === Red, Green, Refactor 71 | 72 | In test-driven development the mantra has always been, red, green, refactor. Write a 73 | failing test and run it (red), make the test pass (green), then refactor it (that is 74 | look at the code and see if you can make it any better). In this case you will need 75 | to run the koan and see it fail (red), make the test pass (green), then take a 76 | moment and reflect upon the test to see what it is teaching you and improve the 77 | code to better communicate its intent (refactor). 78 | 79 | The very first time you run it you will see the following output: 80 | 81 | [ ruby_koans ] $ rake 82 | (in /Users/person/dev/ruby_koans) 83 | /usr/bin/ruby1.8 path_to_enlightenment.rb 84 | 85 | AboutAsserts#test_assert_truth has damaged your karma. 86 | 87 | The Master says: 88 | You have not yet reached enlightenment. 89 | 90 | The answers you seek... 91 | is not true. 92 | 93 | Please meditate on the following code: 94 | ./about_asserts.rb:10:in `test_assert_truth' 95 | path_to_enlightenment.rb:38:in `each_with_index' 96 | path_to_enlightenment.rb:38 97 | 98 | mountains are merely mountains 99 | your path thus far [X_________________________________________________] 0/280 100 | 101 | You have come to your first stage. If you notice it is telling you where to look for 102 | the first solution: 103 | 104 | Please meditate on the following code: 105 | ./about_asserts.rb:10:in `test_assert_truth' 106 | path_to_enlightenment.rb:38:in `each_with_index' 107 | path_to_enlightenment.rb:38 108 | 109 | We then open up the about_asserts.rb file and look at the first test: 110 | 111 | # We shall contemplate truth by testing reality, via asserts. 112 | def test_assert_truth 113 | assert false # This should be true 114 | end 115 | 116 | We then change the +false+ to +true+ and run the test again. After you are 117 | done, think about what you are learning. In this case, ignore everything except 118 | the method name (+test_assert_truth+) and the parts inside the method (everything 119 | before the +end+). 120 | 121 | In this case the goal is for you to see that if you pass a value to the +assert+ 122 | method, it will either ensure it is +true+ and continue on, or fail if in fact 123 | the statement is +false+. 124 | 125 | == Inspiration 126 | 127 | A special thanks to Mike Clark and Ara Howard for inspiring this 128 | project. Mike Clark wrote an excellent blog post about learning Ruby 129 | through unit testing. This sparked an idea that has taken a bit to 130 | solidify, that of bringing new rubyists into the community through 131 | testing. Ara Howard then gave us the idea for the Koans in his ruby 132 | quiz entry on Meta Koans (a must for any rubyist wanting to improve 133 | their skills). Also, "The Little Lisper" taught us all the value of 134 | the short questions/simple answers style of learning. 135 | 136 | Mike Clark's post :: http://www.clarkware.com/cgi/blosxom/2005/03/18 137 | Meta Koans :: http://rubyquiz.com/quiz67.html 138 | The Little Lisper :: http://www.amazon.com/Little-LISPer-Third-Daniel-Friedman/dp/0023397632 139 | 140 | == Other Resources 141 | 142 | The Ruby Language :: http://ruby-lang.org 143 | Try Ruby in your browser :: http://tryruby.org 144 | 145 | Dave Thomas' introduction to Ruby Programming Ruby (the Pick Axe) :: http://pragprog.com/titles/ruby/programming-ruby 146 | 147 | Brian Marick's fantastic guide for beginners Everyday Scripting with Ruby :: http://pragprog.com/titles/bmsft/everyday-scripting-with-ruby 148 | 149 | = Other stuff 150 | 151 | Author :: Jim Weirich 152 | Author :: Joe O'Brien 153 | Issue Tracker :: http://www.pivotaltracker.com/projects/48111 154 | Requires :: Ruby 1.8.x or later and Rake (any recent version) 155 | 156 | = License 157 | 158 | http://i.creativecommons.org/l/by-nc-sa/3.0/88x31.png 159 | 160 | RubyKoans is released under a Creative Commons, 161 | Attribution-NonCommercial-ShareAlike, Version 3.0 162 | (http://creativecommons.org/licenses/by-nc-sa/3.0/) License. 163 | --------------------------------------------------------------------------------