Share
Embed
Use case of Refinements With Black Magic
Rubykaigi 2017 LT
Introduce use case refinements and binding_ninja gem
Tomohiro Hashidate
September 19, 2017
Transcript
19 slides
-
Sorry, I talk in Japanese very fastly I will upload
this slides later -
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) -
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 -
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. -
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 -
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? -
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) # => <Binding of toplevel> # => 1 -
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. -
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); -
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. -
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. -
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