Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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/

Akira Matsuda

December 07, 2012
Tweet

More Decks by Akira Matsuda

Other Decks in Programming

Transcript

  1. 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 ...
  2. 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)
  3. 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
  4. 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
  5. @shugomaeda Lives in "the Ruby City" Matz-e Matz's boss at

    NaCl http://www.netlab.jp/ Ruby Association http://www.ruby.or.jp/
  6. 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
  7. 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
  8. 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!!
  9. 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!!
  10. 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
  11. using lambda / Proc? module Foo refine String do def

    say; puts "#{self}!!"; end end; end -> { using Foo 'hello'.say }.call #=> hello!! 'world'.say #=> world!!
  12. 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
  13. 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
  14. 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
  15. 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"
  16. 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.
  17. Restrictions only allowed at top level not available in subclasses

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

    not for library / framework authors, but for script writers (?)
  19. OMG

  20. 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
  21. 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
  22. 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
  23. 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]
  24. "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
  25. 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
  26. 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 ...
  27. @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
  28. 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
  29. "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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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]
  36. 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
  37. 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
  38. "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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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'}
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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"}
  49. 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)
  50. Why wasn't the Ruby Encoding system like this in 1.9?

    (I guess) In order to "teach" us the way Encoding works
  51. 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
  52. 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
  53. end