Ruby 2.0: new features

Ruby 2.0: new features

Slides for RubyConf Taiwan 2012 talk "Ruby 2.0: new features" http://rubyconf.tw/2012/

76a777ff80f30bd3b390e275cce625bc?s=128

Akira Matsuda

December 07, 2012
Tweet

Transcript

  1. Ruby 2.0: New Features @a_matsuda

  2. whoami Akira Matsuda (দా ໌) GitHub: amatsuda Twitter: @a_matsuda

  3. None
  4. works (gems) kaminari active_decorator action_args erd hocus_pocus i18n_generators html5_validators gem-src

    traceroute interactive_rspec ...
  5. works (the most recent one) AR.where.not AR.where.like https://github.com/rails/rails/commit/de75af7

  6. Ruby 2.0

  7. begin

  8. Ruby 2.0

  9. Major version update

  10. Simultaneous major version updates in the Ruby ecosystem Ruby 2.0

    Rails 4.0 Rake 10.0 RubyGems 2.0 RDoc 4.0 RSpec 3.0 Sprockets 3.0 Capybara 2.0 Rubinius 2.0 ...
  11. Ruby version numbers 1993/02 Born 1995/12 The first public release

    (0.95) 1996/12 1.0 1997/08 1.1 1998/12 1.2 1999/08 1.4 2000/09 1.6 2003/08 1.8 2007/12 1.9.0 (unstable) 2010/08 1.9.2 (stable) 2011/10 1.9.3 (stable)
  12. Vaporware? 1 1.2 1.4 1.6 1.8 2 1995/12 1996/12 1997/08

    1998/12 1999/08 2000/09 2003/08 2007/12 2010/08 2011/10
  13. No! 2.0!! 1 1.2 1.4 1.6 1.8 2 1995/121996/121997/081998/121999/082000/092003/082007/122010/082011/102013/02 Title

  14. Why 2.0? '1.9'.succ Features Birthday

  15. '1.9'.succ

  16. '1.9'.succ '1.9'.succ #=> "2.0"

  17. Features

  18. Matz's slide for RubyConf 2003 http://www.rubyist.net/~matz/slides/rc2003/ mgp00009.html

  19. Optional Type? Optional Type!

  20. Happy 20th birthday! Ruby was "born" in 1993/2/24 Ruby will

    become 20 yrs old in 2013/2/24 Ruby is no more a teenager! Let's call it Ruby 2.0
  21. New Features

  22. New Features Refinements Module#prepend Enumerable#lazy Keyword Arguments

  23. Refinements

  24. @shugomaeda

  25. None
  26. @shugomaeda Lives in "the Ruby City" Matz-e Matz's boss at

    NaCl http://www.netlab.jp/ Ruby Association http://www.ruby.or.jp/
  27. What are Refinements? Somewhat limited monkey-patching

  28. background "Monkey-patching" is sometimes too much powerful

  29. Monkey Battles God vs ActiveSupport Mail vs ActiveSupport

  30. God vs ActiveSupport require 'active_support/all' p 3.days.since(Date.today) #=> Mon, 10

    Dec 2012 gem 'god', '0.13.0' require 'god' p 3.days.since(Date.today) #=> Tue, 08 Aug 2722
  31. The new methods Module#refine Kernel#using

  32. Module#refine

  33. Module#refine module Foo refine String do def say puts "#{self}!!"

    end end 'hello'.say end #=> hello!!
  34. Module#refine module Foo refine String do def say; puts "#{self}!!";

    end end 'hello'.say end #=> hello!! 'world'.say #=> NoMethodError: undefined method `say' for "world":String
  35. Kernel#using

  36. using Kernel#using module Foo refine String do def say; puts

    "#{self}!!"; end end end class Bar using Foo def hello 'hello'.say end end Bar.new.hello #=> hello!!
  37. using using in Module module Foo refine String do def

    say; puts "#{self}!!"; end end end module Bar using Foo end Bar.module_eval { 'hello'.say } #=> hello!!
  38. using anonymous Module module Foo refine String do def say;

    puts "#{self}!!"; end end end Module.new { using Foo }.module_eval { 'hello'.say } #=> hello!! 'world'.say #=> NoMethodError: undefined method `say' for "world":String
  39. using lambda / Proc? module Foo refine String do def

    say; puts "#{self}!!"; end end; end -> { using Foo 'hello'.say }.call #=> hello!! 'world'.say #=> world!!
  40. using lambda + Module module Foo refine String do def

    say; puts "#{self}!!"; end end; end Module.new { using Foo }.module_eval { -> { 'hello'.say } }.call #=> hello!! 'world'.say #=> undefined method `say' for "world":String
  41. example

  42. activerecord-refinements

  43. Feature User.where('age >= ?', 18)

  44. Feature User.where('age >= ?', 18) => User.where { :age >=

    18 }
  45. activerecord-refinements https://github.com/ amatsuda/activerecord- refinements % gem i activerecord- refinements

  46. activerecord-refinements/lib/ active_record/refinements.rb (1) module ActiveRecord::Refinements module WhereBlockSyntax refine Symbol do

    %i[== != =~ > >= < <=].each do |op| define_method(op) {|val| [self, op, val] } ennnnd
  47. activerecord-refinements/lib/ active_record/refinements.rb (2) module ActiveRecord::Refinements module QueryMethods def where(opts =

    nil, *rest, &block) if block col, op, val = Module.new { using ActiveRecord::Refinements::WhereBlockSyntax }.module_eval &block arel_node = case op when :!= table[col].not_eq val when :=~ table[col].matches val when :> table[col].gt val when ... # (snip) end clone.tap do |relation| relation.where_values += build_where(arel_node) end else super ennnnd
  48. activerecord-refinements/lib/ activerecord-refinements.rb module ActiveRecord::QueryMethods prepend ActiveRecord::Refinements::QueryMethods end

  49. activerecord-refinements/ spec/where_spec.rb describe 'Symbol enhancements' do describe '#!=' do subject

    { User.where { :name != 'nobu' }.to_sql } it { should =~ /WHERE \("users"."name" != 'nobu'\)/ } end describe '#>=' do subject { User.where { :age >= 18 }.to_sql } it { should =~ /WHERE \("users"."age" >= 18\)/ } end describe '#=~' do subject { User.where { :name =~ 'tender%' }.to_sql } it { should =~ /WHERE \("users"."name" LIKE 'tender%'\)/ } end context 'outside of where block' do it { expect { :omg > 1 }.to raise_error ArgumentError } end end
  50. None
  51. "Refinements and nested methods" ruby-core:50336 https://bugs.ruby- lang.org/issues/4085

  52. "Refining Ruby" http://blog.headius.com/ 2012/11/refining-ruby.html

  53. Charlie's worry module WeirdPlus refine String do def +(other) "#{self}

    plus #{other}" end end end class MyArray def initialize @ary = ['foo', 'bar', 'baz'] end def inject(accum, &block) @ary.each do |str| accum = WeirdPlus.module_exec(str, accum, &block) end accum end end def add_all(str_ary) str_ary.inject('') do |str, accum| accum + str end end add_all(MyArray.new) #=> " plus foo plus bar plus baz"
  54. Matz's new plan (as of today) "using" is only allowed

    at top level. refined methods are called only after "using". or within blocks given to "refine". if you pass the proc to "refine" e.g. refine(C,&b) refined methods may not be called from b. It's implementation dependent. refinements are not available in subclasses, nor in reopened classes/modules. refinements are not available from module_eval/class_eval.
  55. Restrictions only allowed at top level not available in subclasses

    not available in reopened classes/modules not available from module_eval
  56. Matz's intension let `refine` + `using` be lexical (file scoped)

    not for library / framework authors, but for script writers (?)
  57. Moved to ext/refinement https://github.com/ruby/ruby/commit/328e0ff

  58. OMG

  59. Module#prepend

  60. background

  61. Ruby < 2, Rails < 3

  62. alias_method_chain def alias_method_chain(target, feature) alias_method "#{target}_without_#{feature}", target alias_method target, "#{target}_with_#{feature}"

    end https://github.com/rails/rails/commit/794d93f by @jamis
  63. AMC class A def foo; puts 'foo'; end end class

    A def foo_with_bar foo_without_bar puts 'bar' end alias_method_chain :foo, :bar end A.new.foo A.new.foo_without_bar
  64. simple pagination ActiveRecord::FinderMethods.module_eval do def all_with_page(*args) if args.any? && (page

    = args.first.delete(:page)) limit(10). offset(10 * (page - 1)). all_without_page(*args) else all_without_page(*args) end end alias_method_chain :all, :page end
  65. Problem

  66. adding `baz` and calling foo "without_bar" class A def foo;

    puts 'foo'; end def foo_with_bar foo_without_bar puts 'bar' end alias_method_chain :foo, :bar def foo_with_baz foo_without_baz puts 'baz' end alias_method_chain :foo, :baz end
  67. AMC defines tons of messy public methods class A def

    foo; puts 'foo'; end def foo_with_bar foo_without_bar puts 'bar' end alias_method_chain :foo, :bar def foo_with_baz foo_without_baz puts 'baz' end alias_method_chain :foo, :baz end A.instance_methods.grep(/foo/) #=> [:foo, :foo_with_bar, :foo_without_bar, :foo_with_baz, :foo_without_baz]
  68. "without_bar" skips "baz" class A def foo; puts 'foo'; end

    def foo_with_bar foo_without_bar puts 'bar' end alias_method_chain :foo, :bar def foo_with_baz foo_without_baz puts 'baz' end alias_method_chain :foo, :baz end A.new.foo_without_bar #=> foo
  69. save_without_* gem 'activerecord', '<2.3' require 'active_record' ActiveRecord::Base.configurations = {'test' =>

    {:adapter => 'sqlite3', :database => ':memory:'}} ActiveRecord::Base.establish_connection('test') class User < ActiveRecord::Base validates_presence_of :name end class CreateAllTables < ActiveRecord::Migration def self.up create_table(:users) {|t| t.column :name, :string} end end CreateAllTables.up # p User.new.save_without_validation p User.new.save_without_dirty p User.new.save
  70. Rails 3’s solution use the power of Ruby Module super

  71. https://github.com/rails/ rails/commit/d916c62

  72. excerpt from d916c62 included do - alias_method_chain :save, :dirty ...

    end - def save_with_dirty(*args) - if status = save_without_dirty(*args) + def save(*) #:nodoc: + if status = super ...
  73. @wycats style module Bar def foo puts 'bar' super end;

    end module Baz def foo puts 'baz' end; end class A include Baz include Bar def foo puts 'foo' super end end A.new.foo #=> foo bar baz
  74. the original foo method class A def foo puts 'foo'

    super end end
  75. how can we extend methods that are not calling super?

    class A def foo puts 'foo' end end (do something...) A.new.foo #=> foo bar
  76. Module#prepend

  77. Module#prepend @wycats @n0kada (Nobu)

  78. Nobu

  79. "The Patch Monster" % git clone git://github.com/ruby/ruby % git shortlog

    -ns | grep -v svn | head -10 7233! nobu 3471" akr 2553" matz 1568" naruse 1502" usa 1412" eban 873" ko1 601" knu 561" drbrain 536" mame
  80. Who's creating Ruby? Matz: designing Ko1: implementing Nobu: fixing

  81. Who's creating Ruby? Matz: designing Ko1: implementing Nobu: fixing

  82. None
  83. AMC class A def foo puts 'foo' end end class

    A def foo_with_bar foo_without_bar puts ‘bar’ end alias_method_chain :foo, :bar end A.new.foo #=> foo bar
  84. Module#prepend class A def foo; puts 'foo'; end end module

    Bar def foo super puts 'bar' end end class A prepend Bar end A.new.foo #=> foo bar
  85. prepending multiple Modules class A; def foo; puts 'foo'; end;

    end module Bar def foo super puts 'bar' end end module Baz def foo super puts 'baz' end end class A prepend Bar prepend Baz end A.new.foo #=> foo bar baz
  86. simple pagination with Module#prepend module PrependPaginator def all(*args) if args.any?

    && (page = args.first.delete(:page)) self.limit_value = 10 self.offset_value = 10 * (page - 1) end super end end ActiveRecord::FinderMethods.send :prepend, PrependPaginator
  87. activerecord-refinements/lib/ activerecord-refinements.rb module ActiveRecord::Refinements module QueryMethods def where(opts = nil,

    *rest, &block) if block ... # (snip) else super ennnnd module ActiveRecord::QueryMethods prepend ActiveRecord::Refinements::QueryMethods end
  88. Enumerable#lazy

  89. @yhara

  90. @yhara Lives in "the Ruby City" Matz-e Matz's co-worker at

    NaCl http://www.netlab.jp/
  91. Ruby committers in NaCl @yukihiro_matz (since 15.years.ago) @shugomaeda @gotoyuzo @nari3

    @takaokouji @shyouhei (until October 2012)
  92. The proposal http://bugs.ruby- lang.org/issues/4890

  93. Enumerator [1, 2, 3].each => #<Enumerator: [1, 2, 3]:each>

  94. Enumerator::Lazy [1, 2, 3].lazy => #<Enumerator::Lazy: [1, 2, 3]>

  95. lazy evaluation (1..Float::INFINITY).lazy .select(&:even?) .take(20) .force #=> [2, 4, 6,

    8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40]
  96. Friday the 13th (until ETERNITY) require 'date' ETERNITY = Float::INFINITY

    puts (Date.today..ETERNITY). lazy. select {|d| (d.day == 13) && d.friday?}. take(5).force #=> 2013-09-13 2013-12-13 2014-06-13 2015-02-13 2015-03-13
  97. Ruby implementation of UNIX wc command (ARGV.length == 0 ?

    [["", STDIN]] : ARGV.lazy.map { |filename| [filename, File.open(filename)] }).map { |filename, file| "%4d %4d %4d %s\n" % [*file.lines.lazy.map { |line| [1, line.split.length, line.length] }.inject([0, 0, 0]) { |(lc, wc, cc), (l, w, c)| [wc + w, lc + l, cc + c] }, filename] }.each(&:display) by @shugomaeda http://shugomaeda.blogspot.com/2012/03/ enumerablelazy-and-its-benefits.html
  98. Keyword Arguments

  99. @mametter

  100. @mametter Quine artist Ruby 2.0 release manager

  101. "The Qlobe" http:// mamememo.blogspot.tw/ 2010/09/qlobe.html

  102. "The Qlobe" v=0000;eval$s=%q~d=%!^Lcf<LK8, _@7gj*LJ=c5nM)Tp1g0%Xv.,S[<>YoP 4ZojjV)O>qIH1/n[|2yE[>:ieC "%.#% :::##" 97N-A&Kj_K_><wS5rtWk@*a+Y5 yH?b[F^e7C/56j|pmRe+:)B "##%

    ::##########" O98(Zh)'Iof*nm.,$C5Nyt= PPu01Avw^<IiQ=5$'D-y? "##: ###############" g6`YT+qLw9k^ch|K'),tc 6ygIL8xI#LNz3v}T=4W "# #. .####:#######" lL27FZ0ij)7TQCI)P7u }RT5-iJbbG5P-DHB<. " ##### # :############" R,YvZ_rnv6ky-G+4U' $*are@b4U351Q-ug5 " #######################" 00x8RR%`Om7VDp4M5 PFixrPvl&<p[]1IJ " ############:#### %#####" EGgDt8Lm#;bc4zS^ y]0`_PstfUxOC(q " .#############:##% .## ." /,}.YOIFj(k&q_V zcaAi?]^lCVYp!; " %% .################. #. " ;s="v=%04o;ev"% (;v=(v-($*+[45, ":####: :##############% : " ])[n=0].to_i;)% 360)+"al$s=%q#{ "%######. ######### " ;;"%c"%126+$s<< 126}";d.gsub!(/ "##########. #######% " |\s|".*"/,"");; require"zlib"|| "########### :######. " ;d=d.unpack"C*" d.map{|c|n=(n|| ":#########: .######: . " )*90+(c-2)%91}; e=["%x"%n].pack " :#######% :###### #: " &&"H*";e=Zlib:: Inflate.inflate( " ######% .####% :: " &&e).unpack("b*" )[0];22.times{|y| " ####% %### " ;w=(Math.sqrt(1-( (y*2.0-21)/22)**(; " .###: .#% " ;2))*23).floor;(w* 2-1).times{|x|u=(e+ " %## " )[y*z=360,z]*2;u=u[ 90*x/w+v+90,90/w];s[( " #. " ;y*80)+120-w+x]=(""<< 32<<".:%#")[4*u.count(( " . " ;"0"))/u.size]}};;puts\ s+";_ The Qlobe#{" "*18+ ( "# :#######" ;"Copyright(C).Yusuke End\ oh, 2010")}";exit~;_ The Qlobe Copyright(C).Yusuke Endoh, 2010
  103. "௒ઈٕ޼ Ruby ϓϩάϥϛϯά" http:// www.slideshare.net/ mametter/ruby-esoteric- obfuscated-ruby- programming

  104. background

  105. Well known idiom using Hash def foo(args = {}) puts

    "a is #{args[:a]}, b is #{args[:b]}" end foo a: 1, b: 2 #=> a is 1, b is 2
  106. Patterns Default Values Multiple Hashes Splat Operator Assert Valid Keys

  107. Default Values

  108. Hash#merge def foo(args = {}) values = {a: 1, b:

    2}.merge args puts "a is #{values[:a]}, b is #{values[:b]}" end foo b: 4 #=> a is 1, b is 4
  109. Hash#reverse_merge (ActiveSupport) def foo(args = {}) values = args.reverse_merge a:

    1, b: 2 puts "a is #{values[:a]}, b is #{values[:b]}" end foo b: 4 #=> a is 1, b is 4
  110. multiple Hashes def create(attributes = {}, options = {}, &block)

    AR/associations/collection_association.rb def form_tag(url_for_options = {}, options = {}, &block) AV/helpers/form_tag_helper.rb def render(options = {}, locals = {}, &block) AV/helpers/rendering_helper.rb def button_to(name, options = {}, html_options = {}) AV/helpers/url_helper.rb def date_select(method, options = {}, html_options = {}) AV/helpers/date_helper.rb
  111. Passing values into the latter keywords Hash def button_to(name, options

    = {}, html_options = {}) <%= button_to 'New!', action: 'new', method: 'get' %> #=> options: {action: 'new', method: 'get'}, html_options: {} <%= button_to 'New!', {action: 'new'}, {method: 'get'} %> #=> options: {action: 'new'}, html_options: {method: 'get'}
  112. Splat Operator def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = {

    :terminator => "result == false", :scope => [:kind, :name], :only => [:before, :around, :after] }.merge(options) ... AMo/callbacks.rb
  113. extract_options! class Array def extract_options! if last.is_a?(Hash) && last.extractable_options? pop

    else {} end end end AS/core_ext/array/extract_options.rb
  114. assert_valid_keys VALID_FIND_OPTIONS = [:conditions, :include, :joins, :limit, :offset, :extend, :order,

    :select, :readonly, :group, :having, :from, :lock ] def apply_finder_options(options) ... options.assert_valid_keys(VALID_FIND_OPTIONS) AR/relation/spawn_methods.rb
  115. Implementation of assert_valid_keys def assert_valid_keys(*valid_keys) valid_keys.flatten! each_key do |k| raise

    ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k) end end AS/core_ext/hash/keys.rb
  116. using assert_valid_keys {name: 'amatsuda', job: 'Rubyist'} .assert_valid_keys(:name, :age) #=> ArgumentError:

    Unknown key: job
  117. Ruby 2.0 keyword arguments

  118. Defalut Values

  119. No need to merge or reverse_merge anymore! def foo(a: 1,

    b: 2) puts "a is #{a}, b is #{b}" end foo() #=> a is 1, b is 2 foo a: 3, b: 2 #=> a is 3, b is 2
  120. Multiple Hashes & Splat Operator

  121. The "rest" argument def button_to(name, controller: nil, action: nil, **html_options)

    puts "controller: #{controller}" puts "action: #{action}" puts "html_options: #{html_options}" end button_to 'a', action: 'new', method: 'get' #=> controller: (nil) action: new html_options: {:method=>"get"}
  122. assert_valid_keys

  123. We don't need assert_valid_keys anymore! def apply_finder_options(conditions: nil, include: nil,

    joins: nil, limit: nil, offset: nil, extend: nil, order: nil, select: nil, readonly: nil, group: nil, having: nil, from: nil, lock: nil) puts "order: #{order}" puts "limit: #{limit}" end apply_finder_options order: 'id', limit: 3, omg: 999 #=> unknown keyword: omg (ArgumentError)
  124. A bit more about 2.0 performance Encoding

  125. Significant performance improvement VM GC `require`

  126. VM: @_ko1

  127. None
  128. GC: @nari_en

  129. None
  130. Encoding

  131. Default scripting Encoding 1.9.x: US-ASCII 2.0: UTF-8

  132. Magic comment # encoding: utf-8 puts '͜Μʹͪ͸ɺੈքʂ'

  133. Magic comment # encoding: utf-8 puts '͜Μʹͪ͸ɺੈքʂ'

  134. Why wasn't the Ruby Encoding system like this in 1.9?

    (I guess) In order to "teach" us the way Encoding works
  135. @nalsh

  136. None
  137. ensure

  138. Ruby 2.0 is "100% compatible" powerful fast something special

  139. Ruby 2.0 is coming! 2.months.until 2.0 The next release would

    be "2.0.0-rc1" within a month or so
  140. Give it a try! tar.gz RVM rbenv

  141. try 2.0 if your_app.ruby_version == 1.9.3 you.should upgrade {|ruby| your_app

    << ruby.new_features! raise Issue.new unless ruby.compatible? } end
  142. try 2.0 if your_app.ruby_version == 1.9.3 you.should upgrade {|ruby| your_app

    << ruby.new_features! raise Issue.new unless ruby.compatible? } else die end
  143. end