Slide 1

Slide 1 text

Use case of Re nements With Black Magic Tomohiro Hashidate (@joker1007)

Slide 2

Slide 2 text

Sorry, I talk in Japanese very fastly I will upload this slides later

Slide 3

Slide 3 text

self.inspect @joker1007 Repro inc. CTO Vimmer Ruby/Rails uentd/embulk Docker/ECS Bigquery/EMR/Hive/Presto

Slide 4

Slide 4 text

I am one of authors of パーフェクトRuby, Please check it!

Slide 5

Slide 5 text

I Re nements because.. Allow override builtin Classes Romantic

Slide 6

Slide 6 text

Introduce some use cases of Re nements

Slide 7

Slide 7 text

Case1 MonkeyPatch to google-api-client In past, google-api-client could not skip_serialization . It is not ef ciently in a case that response data size is enormous, especially Bigquery. Re nements enable to replace serialization implementation in speci c context. (It is already unnecessary now)

Slide 8

Slide 8 text

module QueryEngine::Bigquery class SimpleHashRepresentable def initialize(instance = {}) @instance = instance end def from_json(body, options) @instance.merge!(Oj.load(body)) end end module HashrizeGetJobQueryResults refine Google::Apis::BigqueryV2::BigqueryService do def get_job_query_results(*args) command = make_simple_command(:get, 'projects/{projectId command.response_representation = SimpleHashRepresentable command.response_class = Hash # Omit end end end end

Slide 9

Slide 9 text

Case2 Concern Module We want to send data from application to uentd. That data consists of various objects. I want to implement some helper methods in those classes. Re nements can implement helper methods that is called in concern module context only.

Slide 10

Slide 10 text

module FluentLoggable using(Module.new do refine Clip do def fluentd_tag "fluentd.tag.definition" end def fluentd_payload # constructing payload end def fluentd_timestamp created_at end # Other helpers end end) def post_to_fluentd # Publish to external context Fluent::Logger.post_with_time( fluentd_tag, fluentd_payload, fluentd_timestamp) end end

Slide 11

Slide 11 text

Case3 DSL I implemented this. def foo 1 | 2 | 3 5 | 8 | 13 0 | 0 | 0 end foo #=> Like [[1,2,3], [5,8,13], [0,0,0]] objects By Re nements How?

Slide 12

Slide 12 text

binding_ninja this gem passes binding of method caller implicity Lightweight alternative of binding_of_caller class Foo extend BindingNinja def foo(binding, arg) p binding p arg end auto_inject_binding :foo end Foo.new.foo(1) # => # => 1

Slide 13

Slide 13 text

binding_ninja impl static VALUE auto_inject_binding_invoke(int argc,VALUE *argv,VALUE self) { VALUE binding, args_ary; binding = rb_binding_new(); // Important args_ary = rb_ary_new_from_values(argc, argv); rb_ary_unshift(args_ary, binding); return rb_call_super(argc+1, RARRAY_CONST_PTR(args_ary)); } Create binding in C method call, returns caller context binding. Because Ruby level cfp is not changed.

Slide 14

Slide 14 text

And create and prepend module in C method call to wrap any methods. mod_name = rb_mod_name(mod); extensions = rb_ivar_get(rb_mBindingNinja, rb_intern("@auto_inject_binding_extensions")); ext_mod = rb_hash_aref(extensions, mod_name); if (ext_mod == Qnil) { ext_mod = rb_module_new(); rb_hash_aset(extensions, mod_name, ext_mod); } if (rb_mod_include_p(mod, ext_mod) == Qfalse) { rb_prepend_module(mod, ext_mod); } rb_define_method_id(ext_mod, SYM2ID(method_sym), auto_inject_binding_invoke, -1);

Slide 15

Slide 15 text

Compare to binding_of_caller Warming up -------------------------------------- plain 343.111k i/100ms binding_ninja 150.293k i/100ms binding_of_caller 7.002k i/100ms Calculating ------------------------------------- plain 7.046M (± 0.2%) i/s - 35.340M in binding_ninja 2.177M (± 0.4%) i/s - 10.971M in binding_of_caller 71.916k (± 1.1%) i/s - 364.104k in Comparison: plain: 7045866.1 i/s binding_ninja: 2176528.7 i/s - 3.24x slower binding_of_caller: 71916.4 i/s - 97.97x slower 30x faster and very simple code, but restricted. binding_ninja get only direct caller binding.

Slide 16

Slide 16 text

Back to Table Syntax DSL module TableSyntaxImplement extend BindingNinja auto_inject_binding def |(b, other) # Wrap method caller = b.receiver # Define instance variable in caller context! if caller.instance_variable_defined?(:@__table) table = caller.instance_variable_get(:@__table) else table = Table.new caller.instance_variable_set(:@__table, table) end row = Table::Row.new(self) table.add_row(row); row.add_param(other) table end end And refine Builtin classes. String , Integer , Nil etc.

Slide 17

Slide 17 text

This DSL is used by rspec-parameterized acutually

Slide 18

Slide 18 text

describe "plus" do using RSpec::Parameterized::TableSyntax where(:a, :b, :answer) do 1 | 2 | 3 5 | 8 | 13 0 | 0 | 0 end with_them do it "should do additions" do expect(a + b).to eq answer end end end RSpec ExampleGroup is actual Class de nition. It is Re nements friendly

Slide 19

Slide 19 text

Re nements is fun Shall we use Re nements? Thanks!!