$30 off During Our Annual Pro Sale. View Details »

Why is nobody using Refinements?

lazyatom
November 16, 2015

Why is nobody using Refinements?

From RubyConf 2015; transcript will be at http://lazyatom/refinements

Refinements have been a feature in Ruby for several years now, added as a more structured alternative to the "scourge" of monkey patching, but it seems like almost nobody is using them. My question is this: why not? Are they a bad idea? Are they broken? Or do we just not understand them?

Let's figure out once and for all what refinements are good for, and what their limitations might be, so that future generations can either use them for glory, or rightfully ignore them forevermore.

lazyatom

November 16, 2015
Tweet

More Decks by lazyatom

Other Decks in Programming

Transcript

  1. Disclaimer This is not a sales pitch for refinements. I

    am not going to show you how they are amazing and will solve all your programming problems. They might be good… but also they might be bad. They might even be terrible! Using refinements might make your application implode, or make you smell really bad, or lower your credit rating, or make your cat regard you with an even stronger level of apathetic disdain. Please do not hold me responsible if use of refinements leaves you with an unexplainable rash, or an overwhelming sense of ennui. Other side effects may include: syntax itch, nil nausea, const-ipation, drowsiness, and global interpreter lockjaw.
  2. What are refinements? A mechanism to change the behaviour of

    an object in a limited and controlled way
  3. module Shouting refine String do def shout self.upcase + "!"

    end end end class Thing using Shouting "Hello".shout # => "HELLO!" end
  4. module Shouting refine String do def shout self.upcase + "!"

    end end end class Thing using Shouting "Hello".shout # => "HELLO!" end "Hello".shout # => NoMethodError: undefined method 'shout' for "Oh no":String
  5. module TexasTime refine Time do def to_s if hour >

    12 "Afternoon, y’all!" else super end end end end
  6. module TexasTime refine Time do def to_s if hour >

    12 "Afternoon, y’all!" else super end end end end class RubyConf using TexasTime Time.parse("12:00").to_s # => "2015-11-16 12:00:00" Time.parse("14:15").to_s # => "Afternoon, y’all!" end
  7. module TexasTime refine Time do def to_s if hour >

    12 "Afternoon, y’all!" else super end end end end class RubyConf using TexasTime Time.parse("12:00").to_s # => "2015-11-16 12:00:00" Time.parse("14:15").to_s # => "Afternoon, y’all!" end Time.parse("14:15").to_s # => "2015-11-16 14:15:00"
  8. class Thing using Shouting def run yield end end Thing.new.run

    do "hello".shout end # => NoMethodError
  9. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  10. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  11. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  12. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout
  13. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout # => “HELLO!”
  14. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout
  15. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  16. file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout Top

    Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  17. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout
  18. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting # => NoMethodError file: thing.rb class Thing using Shouting "hello".shout end "goodbye".shout
  19. file: thing.rb using Shouting class Thing "hello".shout end "goodbye".shout Top

    Level Parent none Refinements Shouting A Parent Top Level Refinements Shouting
  20. Top Level Parent none Refinements Shouting A Parent Top Level

    Refinements Shouting file: thing.rb using Shouting class Thing "hello".shout end "goodbye".shout
  21. Top Level Parent none Refinements Shouting A Parent Top Level

    Refinements Shouting file: thing.rb using Shouting class Thing "hello".shout end "goodbye".shout # => “HELLO!” # => “GOODBYE!”
  22. file: thing.rb class Thing using Shouting end class Thing "hello".shout

    end Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  23. file: thing.rb class Thing using Shouting end class Thing "hello".shout

    end Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting B Parent Top Level Refinements empty
  24. file: thing.rb class Thing using Shouting end class Thing "hello".shout

    end Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting B Parent Top Level Refinements empty
  25. file: thing.rb class Thing using Shouting end class Other <

    Thing "hello".shout end Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting B Parent Top Level Refinements empty
  26. file: thing.rb class Thing using Shouting def greet "hello".shout end

    end Thing.new.greet Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting
  27. file: thing.rb class Thing using Shouting def greet "hello".shout end

    end Thing.new.greet Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting Thing#greet Lexical scope A
  28. file: thing.rb class Thing using Shouting def greet "hello".shout end

    end Thing.new.greet Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting Thing#greet Lexical scope A
  29. file: thing.rb class Thing using Shouting def greet "hello".shout end

    end Thing.new.greet Top Level Parent none Refinements empty A Parent Top Level Refinements Shouting Thing#greet Lexical scope A
  30. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting Thing#greet Lexical scope A file: thing.rb class Thing using Shouting def greet "hello".shout end end Thing.new.greet
  31. Top Level Parent none Refinements empty A Parent Top Level

    Refinements Shouting Thing#greet Lexical scope A file: thing.rb class Thing using Shouting def greet "hello".shout end end Thing.new.greet # => “HELLO!”
  32. file: thing.rb class Thing using Shouting def run yield end

    end Thing.new.run do "hello".shout end # => NoMethodError
  33. file: thing.rb class Thing using Shouting def run yield end

    end Thing.new.run do "hello".shout end Top Level Refinements empty A Parent Top Level Refinements Shouting Thing#greet Lexical scope A
  34. file: thing.rb class Thing using Shouting def run yield end

    end Thing.new.run do "hello".shout end Top Level Refinements empty A Parent Top Level Refinements Shouting Thing#greet Lexical scope A Main#block Lexical scope Top level
  35. You get a new lexical scope when 1. Entering a

    different file 2. opening a class or module definition 3. running code from a string using “eval”
  36. Refinement principles 1. activated within current and nested scopes 2.

    scope hierarchy is not the same as class hierarchy 3. different files get different top level scopes 4.methods are evaluated using lexical scope at their definition 5. blocks are evaluated using lexical scope at their definition
  37. /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/ inflector/methods.rb:261:in `const_get': wrong constant name Admin/adminHelper (NameError) from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/

    inflector/methods.rb:261:in `block in constantize' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/ inflector/methods.rb:259:in `each' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/ inflector/methods.rb:259:in `inject' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/ inflector/methods.rb:259:in `constantize' from /app/.bundle/gems/ruby/2.1.0/gems/activesupport-4.2.1/lib/active_support/ core_ext/string/inflections.rb:66:in `constantize' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/ abstract_controller/helpers.rb:156:in `block in modules_for_helpers' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/ abstract_controller/helpers.rb:144:in `map!' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/ abstract_controller/helpers.rb:144:in `modules_for_helpers' from /app/.bundle/gems/ruby/2.1.0/gems/actionpack-4.2.1/lib/action_controller/ metal/helpers.rb:93:in `modules_for_helpers'
  38. # Ruby 1.9 "hello".chars # => #<Enumerator: "hello".each_char> # Ruby

    2.0+ "hello".chars # => ["h", "e", "l", "l", "o"]
  39. module OldChars refine String do def chars; each_char; end end

    end "hello".chars # => ["h", "e", "l", "l", “o"] using OldChars "hello".chars # => #<Enumerator: "hello".each_char>
  40. describe "Ruby" do it "should bring happiness" do developer =

    Developer.new(uses: "ruby") developer.should be_happy end end
  41. describe "Ruby" do it "should bring happiness" do developer =

    Developer.new(uses: "ruby") developer.should be_happy end end
  42. module RSpec refine Object do def should(expectation) expectation.satisfied_by?(self) end end

    end using RSpec 123.should eq 123 # => true false.should eq true # => false
  43. module UserAdmin refine User do def purge! user.associated_records.delete_all! user.delete! end

    end end # in app/controllers/admin_controller.rb class AdminController < ApplicationController using UserAdmin def purge_user User.find(params[:id]).purge! end end
  44. using Shouting "hello".shout # => “HELLO" "hello".respond_to?(:shout) # => false

    "hello".send(:shout) # => NoMethodError ["how", "are", "you"].map(&:shout) # => NoMethodError
  45. class Shouter def initialize(string) @string = string end def shout

    @string.upcase + "!" end end shouted_hello = Shouter.new("hello") shouted_hello.shout # => "HELLO!"
  46. I don’t know. 1. Lack of understanding 2. Adding using

    everywhere is a pain in the ass 3. Rails (and Ruby) doesn’t use them 4. Implementation quirks 5. They solve a problem that nobody has 6. Object-oriented design
  47. I don’t know. 1. Lack of understanding 2. Adding using

    everywhere is a pain in the ass 3. Rails (and Ruby) doesn’t use them 4. Implementation quirks 5. They solve a problem that nobody has 6. Object-oriented design 7. Because other people have told us that they are “bad”.