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

Adapters: Reasonable Metaprogramming in Ruby

Adapters: Reasonable Metaprogramming in Ruby

Implementing an adapter using method_missing is an opportunity to learn about Ruby's method lookup and the contracts that metaprogramming should not break. No prior experience required!

Gonzalo Bulnes Guilpain

February 02, 2018
Tweet

More Decks by Gonzalo Bulnes Guilpain

Other Decks in Programming

Transcript

  1. 1. Method lookup in Ruby 2. Introducing metaprogramming 3. What’s

    in an adapter? e.g. Dredd::Rack 4. Dive into method_missing 5. Abstraction
  2. ~/dev/dredd-rack$ irb -Ilib irb:001> require ‘dredd/rack/runner’ => true irb:002> dredd

    = Dredd::Rack::Runner.new => #<Dredd::Rack::Runner:0x007f9d9285f7c8 ...> irb:003> dredd.object_id # for example => 70157372423140 1. Create a new instance of Dredd::Rack::Runner 2. Send the object_id message to our runner object 3. Get the expected value!
  3. “One step to the right, then all the way up.”

    Metaprogramming Ruby 2 by Paolo Perrotta
  4. #<Dredd::Rack::Runner :0x007f9d9285f7c8> dredd.method_missing(:make_coffee, :long, :black) 1. Method lookup unsuccessful 2.

    Ruby notifies the object sending a new message 3. Message received Which means... 4. Method lookup round 2!
  5. - Right from the standard Ruby library - usually we

    define methods using def, with names chosen at write time - we can also use define_method, and choose the names at load time - or even at runtime!
  6. class Recipe def initialize @ingredients = [] end def list_ingredients

    @ingredients.map{ |ingredient| ingredient.name } end # Metaprogramming! Yay! define_method(:add_ingredient) do |ingredient| @ingredients << ingredient end end
  7. $ dredd api-blueprint.apib http://localhost:3000 \ --level=info --color --sorted # FORMAT:

    1A7 # GET /message + Response 200 (text/plain) Hello World! $ dredd api-blueprint.apib http://lo PASS /hello-world PASS /question/1/answers FAIL /wip PASS /status
  8. # Wouldn’t it be great to create and configure a

    runner ONCE... dredd = Dredd::Rack::Runner.new.level(:info).color!.sorted! # And run Dredd without worrying about configuring it or # restarting the application server? dredd.run $ dredd api-blueprint.apib http://localhost:3000 \ --level=info --color --sorted
  9. Dredd::Rack is an adapter It adapts Dredd for use with

    Rack apps* * Ruby web apps, e.g. Rails, Sinatra, Grape apps...
  10. # Part of the application code, alongside the specs dredd

    = Dredd::Rack::Runner.new.level(:info).color!.sorted! dredd.run $ # Written manually to the command line $ dredd api-blueprint.apib http://localhost:3000 \ --level=info --color --sorted
  11. - a “runner” instance allows to run Dredd - it

    provides methods to set Dredd options - the configuration is readable code - that is stored close to the app code
  12. class Dredd::Rack::Runner def initialize @command_options = [] end def command

    ([@dredd_command, @paths_to_blueprints, @api_endpoint] + \ @command_options).join(' ') end def run raise InvalidCommandError.new(command) unless command_valid? start_server! unless api_remote? Kernel.system(command) end # ... end
  13. $ dredd --help [...] Options: --dry-run, -y Do not run

    any real HTTP transaction... --hookfiles, -f Specifies a pattern to match files... --language, -a Language of hookfiles. Possible options... --sandbox, -b Load and run non trusted hooks code... --server, -g Run API backend server command and kill... --server-wait Set delay time in seconds between running... --init, -i Run interactive configuration. ... --custom, -j Pass custom key-value configuration data... --names, -n Only list names of requests... --only, -x Run only specified transaction name. ... --reporter, -r Output additional report format. ... --output, -o Specifies output file when using... [...] --version Show version number. class Dredd::Rack::Runner def dry_run! @command_options << ‘--dry-run’ end # ... def names! @command_options << ‘--names’ end def only(arg) @command_options += [‘--only’, arg] end # ... # many many more... end
  14. 1. method name matches an option? 2. yes: do what

    the method would do 3. no: raise the usual NoMethodError
  15. class Dredd::Rack::Runner def initialize @command_options = [] end # ...

    def method_missing(name, *args) ... end # and that replaces ALL the “option” methods! end
  16. class Dredd::Rack::Runner # ... def method_missing # ... end def

    respond_to_missing?(method, include_private=false) OPTIONS_NAMES.include?(method.to_sym) || super end end
  17. $ dredd --help [...] Options: --dry-run, -y Do not run

    any real HTTP transaction... --hookfiles, -f Specifies a pattern to match files... --language, -a Language of hookfiles. Possible options... --sandbox, -b Load and run non trusted hooks code... --server, -g Run API backend server command and kill... --server-wait Set delay time in seconds between running... --init, -i Run interactive configuration. ... --custom, -j Pass custom key-value configuration data... --names, -n Only list names of requests... --only, -x Run only specified transaction name. ... --reporter, -r Output additional report format. ... --output, -o Specifies output file when using... [...] --version Show version number.
  18. class Dredd::Rack::Runner NEGATABLE_BOOLEAN_OPTIONS = [:details!, :color!, ...] META_OPTIONS = [:help,

    :version] BOOLEAN_OPTIONS = NEGATABLE_BOOLEAN_OPTIONS + META_OPTIONS SINGLE_ARGUMENT_OPTIONS = [:language, :only, :output, ...] OPTIONS = BOOLEAN_OPTIONS + SINGLE_ARGUMENT_OPTIONS end
  19. Takeaways - Method lookup in Ruby (it’s a thing) -

    Metaprogramming is a collection of techniques. Some of them are simple. - Adapters’ are good candidates for automation - Metaprogramming should be reasonable - Abstraction is reasonable, can be a criterion
  20. The copyright of the following artworks [1] belongs to their

    authors: The Trouble is... by JMFenner https:/ /www.redbubble.com/people/jmfenner/works/25293292-the-trouble-is Nighthawks Pastiche by sweetmans https:/ /www.redbubble.com/people/sweetmans/works/20602742-nighthawks-pastiche DJ by Ali Gulec https:/ /www.redbubble.com/people/aligulec/works/8367544-dj Nosferatu by JoJo Sesames https:/ /www.redbubble.com/people/jojoseames/works/16629789-nosferatu The optimist / / rose tinted glasses by lauragraves https:/ /www.redbubble.com/people/lauragraves/works/23478426-the-optimist-rose-tinted-glasses Lion by LOVATTO https:/ /www.redbubble.com/people/lovatto/works/22502675-lion [1] Only detail views are displayed in this presentation, find the originals on Redbubble!
  21. Come talk to me, I’m a nice person ; )

    You can read this presentation again on SpeakerDeck. Copyright (C) 2017 Gonzalo Bulnes Guilpain Except where otherwise stated, this presentation is published under the term of the Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA). All logos and trademarks belong to their respective authors. Artworks displayed in this presentation belong to their authors, see the corresponding credits page for details. The code shown here is part of Dredd::Rack. Dredd::Rack is free software, see details at https:/ /github.com/gonzalo-bulnes/dredd-rack. Want to become a better programmer? Join the Recurse Center! More at https:/ /www.recurse.com/faq