Hidden Gems of Ruby 1.9

9,503 views

Published on

  • This is a noob as i have not used FFI.
    why Fiddle when we have ruby-ffi?
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Hidden Gems of Ruby 1.9

  1. 1. HELLO!!!
  2. 2. ZOMG HAPPY SATURDAY!
  3. 3. PEW PEW PEW~!!!
  4. 4. Aaron Patterson
  5. 5. @tenderlove
  6. 6. google 'tenderlove' Might be NSFW
  7. 7. AT&T, AT&T logo and all AT&T related marks are trademarks of AT&T Intellectual Property and/or AT&T affiliated companies.
  8. 8. Enterprise Developer http://github.com/tenderlove/enterprise
  9. 9. ruby core committer
  10. 10. rails core committer
  11. 11. rails committer
  12. 12. My Failures
  13. 13. rails core committer
  14. 14. rails committer
  15. 15. Presenting Last
  16. 16. I like Ryan Davis
  17. 17. My Slides Suck (Sorry Shane)
  18. 18. minitest
  19. 19. RFC 2119 common.rspec
  20. 20. No Parens on Method Definitions
  21. 21. def foo bar, baz ... end
  22. 22. Ruby + JavaScript
  23. 23. Ruby + C
  24. 24. My Failures (as a presenter)
  25. 25. Fun
  26. 26. Practical
  27. 27. I am a nerd
  28. 28. I like boring things
  29. 29. 2 Presentations
  30. 30. Practical
  31. 31. Fun!
  32. 32. The Fun
  33. 33. Your Guide to Presentation Popularity!
  34. 34. Your Guide to Presentation Notoriety!
  35. 35. •Provocative Title • Risqué Photos •Ruby Code?
  36. 36. Provocative Title:
  37. 37. Use Ruby 1.9 like an Engineer
  38. 38. Use Ruby 1.9 like a SEXY Engineer
  39. 39. Risqué Photos
  40. 40. America's Next Top Model
  41. 41. America's Next Top Engineer
  42. 42. Confident
  43. 43. Elegant
  44. 44. Sultry
  45. 45. Sexy
  46. 46. Thoughtful
  47. 47. Fierce
  48. 48. Playful
  49. 49. Powerful
  50. 50. Provocative
  51. 51. Ruby Code?
  52. 52. protected def method_missing(method, *args, &block) if Array.method_defined?(method) to_a.send(method, *args, &block) elsif @klass.scopes[method] merge(@klass.send(method, *args, &block)) elsif @klass.respond_to?(method) scoping { @klass.send(method, *args, &block) } elsif arel.respond_to?(method) arel.send(method, *args, &block) elsif match = DynamicFinderMatch.match(method) attributes = match.attribute_names super unless @klass.send(:all_attributes_exists?, attributes) if match.finder? find_by_attributes(match, attributes, *args) elsif match.instantiator? find_or_instantiator_by_attributes(match, attributes, *args, &block) end else super end end private def references_eager_loaded_tables? # always convert table names to downcase as in Oracle quoted table names are in uppercase joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.map{ |t| t.downcase }.uniq (tables_in_string(to_sql) - joined_tables).any? end
  53. 53. TL;DR
  54. 54. The Practical
  55. 55. Hidden Gems of Ruby 1.9
  56. 56. Ruby 1.9 PSA
  57. 57. minitest
  58. 58. require 'minitest/autorun' class FooTest < MiniTest::Unit::TestCase WIN32 = true def test_foo assert_equal 'foo', 'foo' end def test_refutation refute_equal 'foo', 'bar' end def test_skip return skip if WIN32 assert_equal 'fun!', 'fun!' end end
  59. 59. require 'minitest/autorun'
  60. 60. Test::Unit::TestCase => MiniTest::Unit::TestCase
  61. 61. class FooTest < MiniTest::Unit::TestCase end
  62. 62. assert_not_* => refute_*
  63. 63. def test_refutation refute_equal 'foo', 'bar' end
  64. 64. skip
  65. 65. class FooTest < MiniTest::Unit::TestCase WIN32 = true def test_skip skip if WIN32 assert_equal 'fun!', 'fun!' end end
  66. 66. Loaded suite footest Started .S. Finished in 0.000682 seconds. 1) Skipped: test_skip(FooTest) [footest.rb:15]: Skipped, no message given 3 tests, 2 assertions, 0 failures, 0 errors, 1 skips
  67. 67. randomization
  68. 68. class FailTest < MiniTest::Unit::TestCase @@foos = %w{ hello } def test_equality assert_equal @@foos, %w{ hello } end def test_append @@foos << "world" assert_equal @@foos, %w{ hello world } end end
  69. 69. Test run options: --seed 31149 Loaded suite failtest Started .. Finished in 0.000604 seconds. 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips Test run options: --seed 31149
  70. 70. Test run options: --seed 29650 Loaded suite failtest Started .F Finished in 0.000637 seconds. 1) Failure: test_equality(FailTest) [failtest.rb:7]: Expected ["hello", "world"], not ["hello"]. 2 tests, 2 assertions, 1 failures, 0 errors, 0 skips Test run options: --seed 29650
  71. 71. Test run options: --seed 29650 Loaded suite failtest Started .F Finished in 0.000637 seconds. 1) Failure: test_equality(FailTest) [failtest.rb:7]: Expected ["hello", "world"], not ["hello"]. 2 tests, 2 assertions, 1 failures, 0 errors, 0 skips Test run options: --seed 29650
  72. 72. --seed 29650
  73. 73. -v
  74. 74. ruby failtest.rb --seed 29650 -v
  75. 75. Test run options: --seed 29650 --verbose Loaded suite failtest Started FailTest#test_append: 0.00 s: . FailTest#test_equality: 0.00 s: F Finished in 0.000735 seconds. 1) Failure: test_equality(FailTest) [failtest.rb:7]: Expected ["hello", "world"], not ["hello"]. 2 tests, 2 assertions, 1 failures, 0 errors, 0 skips Test run options: --seed 29650 --verbose
  76. 76. Test run options: --seed 29650 --verbose Loaded suite failtest Started FailTest#test_append: 0.00 s: . FailTest#test_equality: 0.00 s: F Finished in 0.000735 seconds. 1) Failure: test_equality(FailTest) [failtest.rb:7]: Expected ["hello", "world"], not ["hello"]. 2 tests, 2 assertions, 1 failures, 0 errors, 0 skips Test run options: --seed 29650 --verbose
  77. 77. Test Performance
  78. 78. class FooTest < MiniTest::Unit::TestCase def test_foo assert_equal 'foo', 'foo' end def test_refutation refute_equal 'foo', 'bar' end def test_slow sleep 10 end end
  79. 79. class FooTest < MiniTest::Unit::TestCase def test_foo assert_equal 'foo', 'foo' end def test_refutation refute_equal 'foo', 'bar' end def test_slow sleep 10 end end
  80. 80. Test run options: --seed 33095 --verbose Loaded suite footest Started FooTest#test_slow: 10.00 s: . FooTest#test_refutation: 0.00 s: . FooTest#test_foo: 0.00 s: . Finished in 10.001114 seconds. 3 tests, 2 assertions, 0 failures, 0 errors, 0 skips Test run options: --seed 33095 --verbose
  81. 81. Test run options: --seed 33095 --verbose Loaded suite footest Started FooTest#test_slow: 10.00 s: . FooTest#test_refutation: 0.00 s: . FooTest#test_foo: 0.00 s: . Finished in 10.001114 seconds. 3 tests, 2 assertions, 0 failures, 0 errors, 0 skips Test run options: --seed 33095 --verbose
  82. 82. With Rake:
  83. 83. rake test TESTSOPTS='-v'
  84. 84. rspec
  85. 85. describe 'Awesome' do describe 'Class' do it 'discovers something AMAZING' do (10 + 10).must_equal 20 end it 'matches something AMAZING' do "vuvuzela".must_match /vuvu/ end it 'raises something AMAZING' do lambda { raise }.must_raise(RuntimeError) end end end
  86. 86. rspec
  87. 87. minitest/spec
  88. 88. require 'minitest/spec' require 'minitest/autorun'
  89. 89. require 'minitest/spec' require 'minitest/autorun' describe 'Awesome' do describe 'Class' do it 'discovers something AMAZING' do (10 + 10).must_equal 20 end it 'matches something AMAZING' do "vuvuzela".must_match /vuvu/ end it 'must raise something' do lambda { raise }.must_raise(RuntimeError) end end end
  90. 90. ObjectSpace
  91. 91. ObjectSpace.each_object do |obj| p obj end
  92. 92. require 'objspace' • count_objects_size • memsize_of • count_nodes • count_tdata_objects
  93. 93. count_object_size require 'objspace' hash = {} ObjectSpace.count_objects_size(hash) p hash # => {:T_CLASS=>291520, :T_MODULE=>42512, :T_STRING=>26133, :T_REGEXP=>11501, :T_ARRAY=>5896, :T_HASH=>1088, :T_FILE=>9056, :T_DATA=>1144348, :TOTAL=>1532054}
  94. 94. count_object_size require 'objspace' hash = {} ObjectSpace.count_objects_size(hash) p hash # => {:T_CLASS=>291520, :T_MODULE=>42512, :T_STRING=>26133, :T_REGEXP=>11501, :T_ARRAY=>5896, :T_HASH=>1088, :T_FILE=>9056, :T_DATA=>1144348, :TOTAL=>1532054}
  95. 95. memsize_of require 'objspace' require 'fiddle' cl = Fiddle::Closure.new(0, [1]) p ObjectSpace.memsize_of(cl) # => 232
  96. 96. memsize_of require 'objspace' require 'fiddle' cl = Fiddle::Closure.new(0, [1]) p ObjectSpace.memsize_of(cl) # => 232
  97. 97. Implementation
  98. 98. struct rb_data_type_struct struct rb_data_type_struct { const char *wrap_struct_name; struct { void (*dmark)(void*); void (*dfree)(void*); size_t (*dsize)(const void *); void *reserved[2]; /* For future extension. This array *must* be filled with ZERO. */ } function; const rb_data_type_t *parent; void *data; /* This area can be used for any purpose by a programmer who define the type. */ };
  99. 99. struct rb_data_type_struct struct rb_data_type_struct { const char *wrap_struct_name; struct { void (*dmark)(void*); void (*dfree)(void*); size_t (*dsize)(const void *); void *reserved[2]; /* For future extension. This array *must* be filled with ZERO. */ } function; const rb_data_type_t *parent; void *data; /* This area can be used for any purpose by a programmer who define the type. */ };
  100. 100. static size_t my_memsize(const void *p) { return 10; } const rb_data_type_t my_data_type = { "my_extension", {NULL, NULL, my_memsize,}, }; static VALUE allocate(VALUE klass) { struct something * cif; return TypedData_Make_Struct( klass, something, &my_data_type, cif); }
  101. 101. count_nodes require 'objspace' p ObjectSpace.count_nodes #=> {:NODE_SCOPE=>50, :NODE_BLOCK=>168, :NODE_IF=>27, :NODE_ITER=>7, ... }
  102. 102. count_nodes require 'objspace' p ObjectSpace.count_nodes #=> {:NODE_SCOPE=>50, :NODE_BLOCK=>168, :NODE_IF=>27, :NODE_ITER=>7, ... }
  103. 103. count_nodes require 'objspace' 10.times do p ObjectSpace.count_nodes[:NODE_IF] eval 'if true; end' end
  104. 104. $ ruby objectspace.rb 27 28 29 30 31 32 33 34 35 36 $
  105. 105. count_tdata_objects require 'objspace' p ObjectSpace.count_tdata_objects # => {RubyVM::InstructionSequence=>64, false=>13, ... }
  106. 106. count_tdata_objects require 'objspace' p ObjectSpace.count_tdata_objects # => {RubyVM::InstructionSequence=>64, false=>13, ... }
  107. 107. count_tdata_objects require 'objspace' require 'fiddle' 10.times do Fiddle::Closure.new(0, [1]) p ObjectSpace.count_tdata_objects[Fiddle::Closure] end
  108. 108. $ ruby objectspace.rb 1 2 3 4 5 6 7 8 9 10 $
  109. 109. Fiddle
  110. 110. libffi wrapper
  111. 111. fiddle + dl
  112. 112. Fiddle • Function calls • Closure allocation
  113. 113. DL • dlopen() wrapper • memory management
  114. 114. Calling Functions
  115. 115. • Open dynamic library • Locate function pointer • Wrap function pointer • Call function
  116. 116. Wrapping "sin" require 'fiddle' libm = DL.dlopen('libm.dylib') function = Fiddle::Function.new( libm['sin'], [Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE ) puts function.call(90 * Math::PI / 180)
  117. 117. Wrapping "sin" require 'fiddle' libm = DL.dlopen('libm.dylib') function = Fiddle::Function.new( libm['sin'], [Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE ) puts function.call(90 * Math::PI / 180)
  118. 118. Wrapping "sin" require 'fiddle' libm = DL.dlopen('libm.dylib') function = Fiddle::Function.new( libm['sin'], [Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE ) puts function.call(90 * Math::PI / 180)
  119. 119. Wrapping "sin" require 'fiddle' libm = DL.dlopen('libm.dylib') function = Fiddle::Function.new( libm['sin'], [Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE ) puts function.call(90 * Math::PI / 180)
  120. 120. Wrapping "sin" require 'fiddle' libm = DL.dlopen('libm.dylib') function = Fiddle::Function.new( libm['sin'], [Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE ) puts function.call(90 * Math::PI / 180)
  121. 121. Creating Closures
  122. 122. double (func *)(double)
  123. 123. require 'fiddle' class MySin < Fiddle::Closure def call number Math.sin(number) end end function = MySin.new( Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE] ) puts function.call(90 * Math::PI / 180)
  124. 124. require 'fiddle' class MySin < Fiddle::Closure def call(number) Math.sin(number) end end function = MySin.new( Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE] ) puts function.call(90 * Math::PI / 180)
  125. 125. require 'fiddle' class MySin < Fiddle::Closure def call(number) Math.sin(number) end end function = MySin.new( Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE] ) puts function.call(90 * Math::PI / 180)
  126. 126. require 'fiddle' class MySin < Fiddle::Closure def call(number) Math.sin(number) end end function = MySin.new( Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE] ) puts function.call(90 * Math::PI / 180)
  127. 127. Using our Closure
  128. 128. class MySin < Fiddle::Closure def call number Math.sin(number) end end function = MySin.new( Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE] ) cfunc = Fiddle::Function.new( function, [Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE ) puts cfunc.call(90 * Math::PI / 180)
  129. 129. class MySin < Fiddle::Closure def call number Math.sin(number) end end function = MySin.new( Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE] ) cfunc = Fiddle::Function.new( function, [Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE ) puts cfunc.call(90 * Math::PI / 180)
  130. 130. Fiddle Masquerade
  131. 131. ruby-ffi code module Tidy extend FFI::Library ffi_lib "libtidy.dylib" attach_function :tidyFileExists, [:string], :int attach_function :tidyCreate, [], :pointer attach_function :tidyParseString, [:pointer, :string], :int attach_function :tidySaveStdout, [:pointer], :int end tdoc = Tidy.tidyCreate Tidy.tidyParseString tdoc, "<title>Foo</title" Tidy.tidySaveStdout tdoc
  132. 132. In terms of Fiddle
  133. 133. module FFI module Library TYPE_MAP = { :string => DL::TYPE_VOIDP, :pointer => DL::TYPE_VOIDP, } DL.constants.each do |const| next unless const.to_s =~ /^TYPE_/ name = const.to_s.split('_', 2).last.downcase.to_sym TYPE_MAP[name] = DL.const_get(const) end def ffi_lib(lib) @lib = DL::Handle.new lib end def attach_function(name, args, ret) func = Fiddle::Function.new( @lib[name.to_s], args.map { |x| TYPE_MAP[x] }, TYPE_MAP[ret] ) define_singleton_method(name) { |*args| func.call(*args) } end end end
  134. 134. ruby-ffi code module Tidy extend FFI::Library ffi_lib "libtidy.dylib" attach_function :tidyFileExists, [:string], :int attach_function :tidyCreate, [], :pointer attach_function :tidyParseString, [:pointer, :string], :int attach_function :tidySaveStdout, [:pointer], :int end tdoc = Tidy.tidyCreate Tidy.tidyParseString tdoc, "<title>Foo</title" Tidy.tidySaveStdout tdoc
  135. 135. Fiddle code module Tidy extend FFI::Library ffi_lib "libtidy.dylib" attach_function :tidyFileExists, [:string], :int attach_function :tidyCreate, [], :pointer attach_function :tidyParseString, [:pointer, :string], :int attach_function :tidySaveStdout, [:pointer], :int end tdoc = Tidy.tidyCreate Tidy.tidyParseString tdoc, "<title>Foo</title" Tidy.tidySaveStdout tdoc
  136. 136. Psych
  137. 137. YAML Parser • 1.9.2 and up • Wraps libyaml • Replaces Syck • Opt-in
  138. 138. Opt-in Process $ irb irb(main):001:0> require 'yaml' => true irb(main):002:0> YAML::ENGINE.syck? => true irb(main):003:0> YAML::ENGINE.yamler = 'psych' => "psych" irb(main):004:0> YAML::ENGINE.syck? => false irb(main):005:0>
  139. 139. require 'psych'
  140. 140. Parsing & Dumping require 'psych' Psych.load('--- hello world!') # => 'hello world!' Psych.dump('hello world!') # => '--- hello world!' 'hello world!'.to_yaml # => '--- hello world!'
  141. 141. JSON Psych.load("['hello', 'world!']n") # => ["hello", "world!"] Psych.to_json(%w{ hello world! }) # => "['hello', 'world!']n"
  142. 142. JSON Disclaimer
  143. 143. Evented Parsing
  144. 144. Evented Parsing class MyHandler < Psych::Handler def start_sequence(*args) puts "open [" end def end_sequence(*args) puts "close ]" end def scalar(value, anchor, tag, plain, quoted, style) puts value end end
  145. 145. Evented Parsing parser = Psych::Parser.new(MyHandler.new) parser.parse(StringIO.new("['foo', 'bar']"))
  146. 146. Evented Parsing $ ruby yml.rb open [ foo bar close ] $
  147. 147. Psych::Parser#parse(io_or_string)
  148. 148. Evented Emitting
  149. 149. (the hard way)
  150. 150. Psych::Emitter emitter = Psych::Emitter.new($stdout) emitter.start_stream(Psych::Parser::UTF8) emitter.start_document([], [], false) emitter.start_sequence(nil, nil, false, 1) 10.times { emitter.scalar('hello world', nil, nil, false, true, 1) } emitter.end_sequence emitter.end_document true emitter.end_stream
  151. 151. --- - 'hello world' - 'hello world' - 'hello world' - 'hello world' - 'hello world' - 'hello world' - 'hello world' - 'hello world' - 'hello world' - 'hello world'
  152. 152. Read Psych::Handler
  153. 153. Streamed Emitting
  154. 154. (the easy way)
  155. 155. Psych::Stream emitter = Psych::Stream.new($stdout) emitter.start emitter.push %w{ one two } emitter.push %w{ three four } emitter.finish
  156. 156. Psych::Stream emitter = Psych::Stream.new($stdout) emitter.start emitter.push %w{ one two } emitter.push %w{ three four } emitter.finish
  157. 157. Psych::Stream $ ruby yml.rb --- - one - two ... --- - three - four ...
  158. 158. Problem?
  159. 159. Streaming JSON
  160. 160. Psych::Stream emitter = Psych::Stream.new($stdout) emitter.start emitter.push %w{ one two } emitter.push %w{ three four } emitter.finish
  161. 161. Psych::JSON::Stream emitter = Psych::JSON::Stream.new($stdout) emitter.start emitter.push %w{ one two } emitter.push %w{ three four } emitter.finish
  162. 162. Psych::JSON::Stream emitter = Psych::JSON::Stream.new($stdout) emitter.start emitter.push %w{ one two } emitter.push %w{ three four } emitter.finish
  163. 163. Psych::JSON::Stream --- ['one', 'two'] ... --- ['three', 'four'] ...
  164. 164. Uses?
  165. 165. More Info
  166. 166. THE END
  167. 167. Questions?
  168. 168. Coverage
  169. 169. Methods • Coverage.start • Coverage.result
  170. 170. a.rb ### # It's my class! class Foo def initialize @thought = 0 10.times { @thought += 1 # thinking } end def rested? if @thought > 8 false else true end end end Foo.new.rested?
  171. 171. a.rb ### # It's my class! class Foo def initialize @thought = 0 10.times { @thought += 1 # thinking } end def rested? if @thought > 8 false else true end end end Foo.new.rested?
  172. 172. a.rb ### # It's my class! class Foo def initialize @thought = 0 10.times { @thought += 1 # thinking } end def rested? if @thought > 8 false else true end end end Foo.new.rested?
  173. 173. a.rb ### # It's my class! class Foo def initialize @thought = 0 10.times { @thought += 1 # thinking } end def rested? if @thought > 8 false else true end end end Foo.new.rested?
  174. 174. require 'coverage' Coverage.start require 'a' p Coverage.result
  175. 175. require 'coverage' Coverage.start require 'a' p Coverage.result
  176. 176. Coverage.result {"/Users/apatterson/git/code/a.rb"=>[nil, nil, 1, 1, 1, 1, 10, nil, nil, nil, 1, 1, 1, nil, 0, nil, nil, nil, nil, 1]}
  177. 177. We've Learned
  178. 178. We've Learned • Lines executed
  179. 179. We've Learned • Lines executed • # times a line was executed
  180. 180. We've Learned • Lines executed • # times a line was executed • Lines that can't be executed
  181. 181. We can deduce
  182. 182. We can deduce • Coverage
  183. 183. We can deduce • Coverage • Hotspots (code heatmap)
  184. 184. SimpleCov http://github.com/colszowka/simplecov
  185. 185. Read More Here! http://bit.ly/19coverage

×