lib/memo_wise.rb in memo_wise-1.8.0 vs lib/memo_wise.rb in memo_wise-1.9.0

- old
+ new

@@ -29,16 +29,16 @@ module MemoWise # Constructor to set up memoization state before # [calling the original](https://medium.com/@jeremy_96642/ruby-method-auditing-using-module-prepend-4f4e69aacd95) # constructor. # - # - **Q:** Why is [Module#prepend](https://ruby-doc.org/3.2.1/Module.html#method-i-prepend) + # - **Q:** Why is [Module#prepend](https://ruby-doc.org/3.2.2/Module.html#method-i-prepend) # important here # ([more info](https://medium.com/@leo_hetsch/ruby-modules-include-vs-prepend-vs-extend-f09837a5b073))? # - **A:** To set up *mutable state* inside the instance, even if the original # constructor will then call - # [Object#freeze](https://ruby-doc.org/3.2.1/Object.html#method-i-freeze). + # [Object#freeze](https://ruby-doc.org/3.2.2/Object.html#method-i-freeze). # # This approach supports memoization on frozen (immutable) objects -- for # example, classes created by the # [Values](https://github.com/tcrayford/Values) # [gem](https://rubygems.org/gems/values). @@ -76,18 +76,34 @@ MemoWise::InternalAPI.create_memo_wise_state!(self) super end HEREDOC + module CreateMemoWiseStateOnExtended + def extended(base) + MemoWise::InternalAPI.create_memo_wise_state!(base) + super + end + end + private_constant(:CreateMemoWiseStateOnExtended) + + module CreateMemoWiseStateOnInherited + def inherited(subclass) + MemoWise::InternalAPI.create_memo_wise_state!(subclass) + super + end + end + private_constant(:CreateMemoWiseStateOnInherited) + # @private # # Private setup method, called automatically by `prepend MemoWise` in a class. # # @param target [Class] # The `Class` into to prepend the MemoWise methods e.g. `memo_wise` # - # @see https://ruby-doc.org/3.2.1/Module.html#method-i-prepend + # @see https://ruby-doc.org/3.2.2/Module.html#method-i-prepend # # @example # class Example # prepend MemoWise # end @@ -98,11 +114,11 @@ # [calling the original](https://medium.com/@jeremy_96642/ruby-method-auditing-using-module-prepend-4f4e69aacd95) # allocator. # # This is necessary in addition to the `#initialize` method definition # above because - # [`Class#allocate`](https://ruby-doc.org/3.2.1/Class.html#method-i-allocate) + # [`Class#allocate`](https://ruby-doc.org/3.2.2/Class.html#method-i-allocate) # bypasses `#initialize`, and when it's used (e.g., # [in ActiveRecord](https://github.com/rails/rails/blob/a395c3a6af1e079740e7a28994d77c8baadd2a9d/activerecord/lib/active_record/persistence.rb#L411)) # we still need to be able to access MemoWise's instance variable. Despite # Ruby documentation indicating otherwise, `Class#new` does not call # `Class#allocate`, so we need to override both. @@ -134,13 +150,11 @@ # But a module/class extending another module with memo_wise # would not call `.allocate`/`#initialize` before calling methods # # On method call `@_memo_wise` would still be `nil` # causing error when fetching cache from `@_memo_wise` - def klass.extended(base) - MemoWise::InternalAPI.create_memo_wise_state!(base) - end + klass.singleton_class.prepend(CreateMemoWiseStateOnExtended) end when Hash unless method_name_or_hash.keys == [:self] raise ArgumentError, "`:self` is the only key allowed in memo_wise" @@ -157,16 +171,15 @@ klass = klass.singleton_class end # This ensures that a memoized method defined on a parent class can # still be used in a child class. - klass.module_eval <<~HEREDOC, __FILE__, __LINE__ + 1 - def inherited(subclass) - super - MemoWise::InternalAPI.create_memo_wise_state!(subclass) - end - HEREDOC + if klass.is_a?(Class) && !klass.singleton_class? + klass.singleton_class.prepend(CreateMemoWiseStateOnInherited) + else + klass.prepend(CreateMemoWiseStateOnInherited) + end raise ArgumentError, "#{method_name.inspect} must be a Symbol" unless method_name.is_a?(Symbol) visibility = MemoWise::InternalAPI.method_visibility(klass, method_name) original_memo_wised_name = MemoWise::InternalAPI.original_memo_wised_name(method_name) @@ -253,10 +266,10 @@ method_name, MemoWise.instance_method(method_name) ) end - # Override [Module#instance_method](https://ruby-doc.org/3.2.1/Module.html#method-i-instance_method) + # Override [Module#instance_method](https://ruby-doc.org/3.2.2/Module.html#method-i-instance_method) # to proxy the original `UnboundMethod#parameters` results. We want the # parameters to reflect the original method in order to support callers # who want to use Ruby reflection to process the method parameters, # because our overridden `#initialize` method, and in some cases the # generated memoized methods, will have a generic set of parameters