hidden gems of ruby 1.9
TRANSCRIPT
HELLO!!!
ZOMG HAPPY SATURDAY!
PEW PEW PEW~!!!
Aaron Patterson
@tenderlove
google 'tenderlove'Might be NSFW
AT&T, AT&T logo and all AT&T related marks are trademarks of AT&T Intellectual Property and/or AT&T affiliated companies.
Enterprise Developer
http://github.com/tenderlove/enterprise
ruby core committer
rails corecommitter
railscommitter
My Failures
rails corecommitter
railscommitter
Presenting Last
I like Ryan Davis
My Slides Suck(Sorry Shane)
minitest
RFC 2119common.rspec
No Parens on Method Definitions
def foo bar, baz ...end
Ruby + JavaScript
Ruby + C
My Failures(as a presenter)
Fun
Practical
I am a nerd
I like boring things
2 Presentations
Practical
Fun!
The Fun
Your Guide to Presentation Popularity!
Your Guide to Presentation
Notoriety!
•Provocative Title
•Risqué Photos
•Ruby Code?
Provocative Title:
Use Ruby 1.9 like an Engineer
Use Ruby 1.9 like a SEXY Engineer
Risqué Photos
America's Next Top Model
America's Next Top Engineer
Confident
Elegant
Sultry
Sexy
Thoughtful
Fierce
Playful
Powerful
Provocative
Ruby Code?
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
TL;DR
The Practical
Hidden Gems of Ruby 1.9
Ruby 1.9 PSA
minitest
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!' endend
require 'minitest/autorun'
Test::Unit::TestCase=>
MiniTest::Unit::TestCase
class FooTest < MiniTest::Unit::TestCaseend
assert_not_*=>
refute_*
def test_refutation refute_equal 'foo', 'bar' end
skip
class FooTest < MiniTest::Unit::TestCase WIN32 = true
def test_skip skip if WIN32 assert_equal 'fun!', 'fun!' endend
Loaded suite footestStarted.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
randomization
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 } endend
Test run options: --seed 31149
Loaded suite failtestStarted..Finished in 0.000604 seconds.
2 tests, 2 assertions, 0 failures, 0 errors, 0 skips
Test run options: --seed 31149
Test run options: --seed 29650
Loaded suite failtestStarted.FFinished 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
Test run options: --seed 29650
Loaded suite failtestStarted.FFinished 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
--seed 29650
-v
ruby failtest.rb --seed 29650 -v
Test run options: --seed 29650 --verbose
Loaded suite failtestStartedFailTest#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
Test run options: --seed 29650 --verbose
Loaded suite failtestStartedFailTest#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
Test Performance
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 endend
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 endend
Test run options: --seed 33095 --verbose
Loaded suite footestStartedFooTest#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
Test run options: --seed 33095 --verbose
Loaded suite footestStartedFooTest#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
With Rake:
rake test TESTSOPTS='-v'
rspec
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 endend
rspec
minitest/spec
require 'minitest/spec'require 'minitest/autorun'
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 endend
ObjectSpace
ObjectSpace.each_object do |obj| p objend
require 'objspace'
• count_objects_size
•memsize_of
• count_nodes
• count_tdata_objects
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}
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}
memsize_of
require 'objspace'require 'fiddle'
cl = Fiddle::Closure.new(0, [1])p ObjectSpace.memsize_of(cl) # => 232
memsize_of
require 'objspace'require 'fiddle'
cl = Fiddle::Closure.new(0, [1])p ObjectSpace.memsize_of(cl) # => 232
Implementation
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. */};
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. */};
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);}
count_nodes
require 'objspace'
p ObjectSpace.count_nodes #=> {:NODE_SCOPE=>50, :NODE_BLOCK=>168, :NODE_IF=>27, :NODE_ITER=>7, ... }
count_nodes
require 'objspace'
p ObjectSpace.count_nodes #=> {:NODE_SCOPE=>50, :NODE_BLOCK=>168, :NODE_IF=>27, :NODE_ITER=>7, ... }
count_nodes
require 'objspace'
10.times do p ObjectSpace.count_nodes[:NODE_IF] eval 'if true; end'end
$ ruby objectspace.rb 27282930313233343536$
count_tdata_objects
require 'objspace'
p ObjectSpace.count_tdata_objects # => {RubyVM::InstructionSequence=>64, false=>13, ... }
count_tdata_objects
require 'objspace'
p ObjectSpace.count_tdata_objects # => {RubyVM::InstructionSequence=>64, false=>13, ... }
count_tdata_objects
require 'objspace'require 'fiddle'
10.times do Fiddle::Closure.new(0, [1]) p ObjectSpace.count_tdata_objects[Fiddle::Closure]end
$ ruby objectspace.rb 12345678910$
Fiddle
libffi wrapper
fiddle + dl
Fiddle
• Function calls
•Closure allocation
DL
• dlopen() wrapper
•memory management
Calling Functions
•Open dynamic library
• Locate function pointer
•Wrap function pointer
•Call function
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)
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)
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)
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)
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)
Wrapping "sin"
Creating Closures
double (func *)(double)
require 'fiddle'
class MySin < Fiddle::Closure def call number Math.sin(number) endend
function = MySin.new( Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE])
puts function.call(90 * Math::PI / 180)
require 'fiddle'
class MySin < Fiddle::Closure def call(number) Math.sin(number) endend
function = MySin.new( Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE])
puts function.call(90 * Math::PI / 180)
require 'fiddle'
class MySin < Fiddle::Closure def call(number) Math.sin(number) endend
function = MySin.new( Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE])
puts function.call(90 * Math::PI / 180)
require 'fiddle'
class MySin < Fiddle::Closure def call(number) Math.sin(number) endend
function = MySin.new( Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE])
puts function.call(90 * Math::PI / 180)
Using our Closure
class MySin < Fiddle::Closure def call number Math.sin(number) endend
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)
class MySin < Fiddle::Closure def call number Math.sin(number) endend
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)
Fiddle Masquerade
ruby-ffi codemodule 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], :intend
tdoc = Tidy.tidyCreateTidy.tidyParseString tdoc, "<title>Foo</title"Tidy.tidySaveStdout tdoc
In terms of Fiddle
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 endend
ruby-ffi codemodule 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], :intend
tdoc = Tidy.tidyCreateTidy.tidyParseString tdoc, "<title>Foo</title"Tidy.tidySaveStdout tdoc
Fiddle codemodule 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], :intend
tdoc = Tidy.tidyCreateTidy.tidyParseString tdoc, "<title>Foo</title"Tidy.tidySaveStdout tdoc
Psych
YAML Parser
• 1.9.2 and up
•Wraps libyaml
•Replaces Syck
•Opt-in
Opt-in Process
$ irbirb(main):001:0> require 'yaml'=> trueirb(main):002:0> YAML::ENGINE.syck?=> trueirb(main):003:0> YAML::ENGINE.yamler = 'psych'=> "psych"irb(main):004:0> YAML::ENGINE.syck?=> falseirb(main):005:0>
require 'psych'
Parsing & Dumping
require 'psych'
Psych.load('--- hello world!') # => 'hello world!'Psych.dump('hello world!') # => '--- hello world!''hello world!'.to_yaml # => '--- hello world!'
JSON
Psych.load("['hello', 'world!']\n") # => ["hello", "world!"]
Psych.to_json(%w{ hello world! }) # => "['hello', 'world!']\n"
JSON Disclaimer
Evented Parsing
Evented Parsingclass 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 endend
Evented Parsing
parser = Psych::Parser.new(MyHandler.new)parser.parse(StringIO.new("['foo', 'bar']"))
Evented Parsing
$ ruby yml.rb open [foobarclose ]$
Psych::Parser#parse(io_or_string)
Evented Emitting
(the hard way)
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_sequenceemitter.end_document trueemitter.end_stream
Psych::Emitter
---- 'hello world'- 'hello world'- 'hello world'- 'hello world'- 'hello world'- 'hello world'- 'hello world'- 'hello world'- 'hello world'- 'hello world'
ReadPsych::Handler
Streamed Emitting
(the easy way)
emitter = Psych::Stream.new($stdout)emitter.startemitter.push %w{ one two }emitter.push %w{ three four }emitter.finish
Psych::Stream
emitter = Psych::Stream.new($stdout)emitter.startemitter.push %w{ one two }emitter.push %w{ three four }emitter.finish
Psych::Stream
Psych::Stream
$ ruby yml.rb ---- one- two...---- three- four...
Problem?
Streaming JSON
emitter = Psych::Stream.new($stdout)emitter.startemitter.push %w{ one two }emitter.push %w{ three four }emitter.finish
Psych::Stream
emitter = Psych::JSON::Stream.new($stdout)emitter.startemitter.push %w{ one two }emitter.push %w{ three four }emitter.finish
Psych::JSON::Stream
emitter = Psych::JSON::Stream.new($stdout)emitter.startemitter.push %w{ one two }emitter.push %w{ three four }emitter.finish
Psych::JSON::Stream
--- ['one', 'two']...--- ['three', 'four']...
Psych::JSON::Stream
Uses?
More Info
THE END
Questions?
Coverage
Methods
•Coverage.start
•Coverage.result
#### 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 endend
Foo.new.rested?
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 endend
Foo.new.rested?
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 endend
Foo.new.rested?
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 endend
Foo.new.rested?
a.rb
require 'coverage'
Coverage.startrequire 'a'p Coverage.result
require 'coverage'
Coverage.startrequire 'a'p 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]}
Coverage.result
We've Learned
We've Learned
• Lines executed
We've Learned
• Lines executed
• # times a line was executed
We've Learned
• Lines executed
• # times a line was executed
• Lines that can't be executed
We can deduce
We can deduce
•Coverage
We can deduce
•Coverage
•Hotspots (code heatmap)
SimpleCovhttp://github.com/colszowka/simplecov
Read More Here!http://bit.ly/19coverage