Skip to content

Instantly share code, notes, and snippets.

@sanchojaf
Last active October 29, 2018 19:17
Show Gist options
  • Save sanchojaf/0df056bf21184bcf41b41d14fe8ef78d to your computer and use it in GitHub Desktop.
Save sanchojaf/0df056bf21184bcf41b41d14fe8ef78d to your computer and use it in GitHub Desktop.
Durable Decorator

Problem

Exist places in our repo where are copies of code from a gem with few changes of the original method.

If never happened an error is difficult to know when the original source changes.

That means that is possible to have old code in Pulse360 that not correspond with the current version of the original gem.

That is particularly important if we are doing a big upgrade, for example, bumping to a higher version of Rails.

One possible solution is t use Durable Decorator

Slogan: "Reducing stress and guilt while engaging in monkey patching since... 2013?"

Usage. Working with SHAs

Furthermore, we can hash the contents of a method as it exists at inspect-time and seal it by providing extra options to the decorator. If the method definition gets tampered with, the decorator will detect this at decoration-time and raise an error for your review.

Find the SHA of the method as currently loaded into memory, works with classes as well as modules:

DurableDecorator::Base.determine_sha('ExampleClass#instance_method')

Or for class (singleton) methods:

DurableDecorator::Base.determine_sha('ExampleClass.class_level_method')

Armed with this knowledge, we can enforce a strict mode:

DurableDecorator::Base.determine_sha('ExampleClass#no_param_method')
# => 'ba3114b2d46caa684b3f7ba38d6f74b2'

ExampleClass.class_eval do
  durably_decorate :string_method, mode: 'strict', sha: 'WRONG-SHA-123456' do
    original_string_method + " and new"
  end
end

DurableDecorator::TamperedDefinitionError: Method SHA mismatch, the definition has been tampered with

DurableDecorator may also decorate methods with params like so:

class ExampleClass
  def string_method(text)
    "original #{text}"
  end
end

ExampleClass.class_eval do
  durably_decorate :string_method, mode: 'strict', sha: 'ba3114b2d46caa684b3f7ba38d6f74b2' do |text|
    original_string_method(text) + " and new"
  end
end

instance = ExampleClass.new
instance.string_method('test')
# => "original test and new"

No more surprise monkey patching

Once you decorate the method and seal it with its SHA, if some gem tries to come in and overwrite your work BEFORE decorate-time, DurableDecorator will warn you. Similarly, expect to see an exception bubble up if the definition of the original method has changed and requires a review and a re-hash.

The usefulness is for gem consumers, and their application-level specs.

Problems

Currently, dealing with default parameter values is problematic due to how Ruby answer inquiries about methods' arity. If the method you are overriding has default values, consider appending a splat argument and manually parsing the contents. Here is an example:

class ExampleClass
  def string_method arg1, args2 = [], arg3 = {}
    "original"
  end
end

ExampleClass.class_eval do
  durably_decorate :string_method do |arg1, *args|
    arg2 = args[0] || []
    arg3 = args[1] || {}

    original_string_method + " and new"
  end
end

Using in Test mode (Kevin's idea)

durable_decorator gem verifies the monkey-patched code and declares the implied dependency where it didn't before.

Here an example of the general pattern.

describe 'our monkeypatch' do
  let(:monkeypatched_method) { Foo.instance_method(:bar) }

  it 'should patch intended implementation' do
    original_method = monkeypatched_method.super_method
    expected_implementation_sha = DurableDecorator::Util.method_sha(original_method)
    expect(expected_implementation_sha).to eq('1234567890abcdef')
  end
end

In this PR 7347 are two examples.

notice to add a test for durable_decorator/lib/extensions/activerecord/query_form_alias we create a similar file path in the test folder, ending in _test.rb:

test/durable_decorator/lib/extensions/activerecord/query_form_alias_test.rb

Our current alternative solution

Example:

https://github.com/PulseSoftwareInc/pulse360/blob/master/lib/extensions/activerecord/pluck_arel.rb#L1-L9

begin
 Gem::Dependency.new('activerecord', '~> 4.1').to_spec
 Gem::Dependency.new('arel', '<= 6.0.4').to_spec
rescue Gem::LoadError
 fail Gem::LoadError, <<~MSG
   ActiveRecord and Arel patch to forward-port functionality from Rails 4.0
   Re-evaluate compatibility on change.
 MSG
end

Monkeypatch on Pulse360 with potential issues

References

https://github.com/jumph4x/durable_decorator

https://stackoverflow.com/questions/4470108/when-monkey-patching-a-method-can-you-call-the-overridden-method-from-the-new-i

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment