Hidden Gems of Ruby 1.9

Post on 11-May-2015

8.770 views 6 download

Tags:

Transcript of Hidden Gems of Ruby 1.9

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