Class: Petra::Proxies::MethodHandlers
- Inherits:
-
Object
- Object
- Petra::Proxies::MethodHandlers
- Defined in:
- lib/petra/proxies/method_handlers.rb
Overview
This class holds method handlers for certain method groups a proxy may encounter (readers, writers, etc). They are encapsulated in an own class instead of a module mixin to keep the proxy objects as small as possible, hopefully avoiding using the same method names as a proxied object
Class Method Summary collapse
-
.proxy_method(name, underscore_prefix = false) ⇒ Object
Helper method to call private (or public) methods on the associated proxy object.
-
.proxy_methods(*methods, underscore_prefix: false) ⇒ Object
Shortcut function to call ‘proxy_method` for multiple functions.
Instance Method Summary collapse
-
#execute_missing_queue(method_name, *args, block: nil) {|queue = []| ... } ⇒ Object
Yields an array and executes the given handlers afterwards.
-
#handle_attribute_change(method_name, *args) ⇒ Object
Logs changes made to attributes of the proxied object.
-
#handle_attribute_read(method_name, *args) ⇒ Object
Handles a getter method for the proxied object.
-
#handle_dynamic_attribute_read(method_name, *args) ⇒ Object
A “dynamic attribute” in this case is a method which usually formats one or multiple attributes and returns the result.
-
#handle_missing_method(method_name, *args, &block) ⇒ Object
Calls the given method on the proxied object and optionally wraps the result in another petra proxy.
-
#handle_object_destruction(method_name, *args) ⇒ Object
Handles calls to a method which destroys the proxied object.
-
#handle_object_persistence(method_name, *args) ⇒ Object
Handles calls to a method which persists the proxied object.
-
#initialize(proxy, proxy_binding) ⇒ MethodHandlers
constructor
A new instance of MethodHandlers.
-
#method_source_proc(method_name) ⇒ Object
Generates a new Proc object from the source code of a given instance method of the proxied object.
Constructor Details
#initialize(proxy, proxy_binding) ⇒ MethodHandlers
Returns a new instance of MethodHandlers.
13 14 15 16 |
# File 'lib/petra/proxies/method_handlers.rb', line 13 def initialize(proxy, proxy_binding) @proxy = proxy @proxy_binding = proxy_binding end |
Class Method Details
.proxy_method(name, underscore_prefix = false) ⇒ Object
Helper method to call private (or public) methods on the associated proxy object. It will define an own method in this class which can be used as if it would be called directly on the proxy.
30 31 32 33 34 35 36 37 38 |
# File 'lib/petra/proxies/method_handlers.rb', line 30 def self.proxy_method(name, underscore_prefix = false) define_method(name) do |*args| if underscore_prefix @proxy.send("__#{name}", *args) else @proxy.send(name, *args) end end end |
.proxy_methods(*methods, underscore_prefix: false) ⇒ Object
Shortcut function to call ‘proxy_method` for multiple functions
43 44 45 |
# File 'lib/petra/proxies/method_handlers.rb', line 43 def self.proxy_methods(*methods, underscore_prefix: false) methods.each { |m| proxy_method(m, underscore_prefix) } end |
Instance Method Details
#execute_missing_queue(method_name, *args, block: nil) {|queue = []| ... } ⇒ Object
Yields an array and executes the given handlers afterwards.
59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/petra/proxies/method_handlers.rb', line 59 def execute_missing_queue(method_name, *args, block: nil) yield queue = [] queue << :handle_missing_method if queue.empty? send(queue.first, method_name, *args).tap do queue[1..-1].each do |handler| if block send(handler, method_name, *args, &block) else send(handler, method_name, *args) end end end end |
#handle_attribute_change(method_name, *args) ⇒ Object
Logs changes made to attributes of the proxied object. This means that the attribute change is documented within the currently active transaction section and added to the temporary write set.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/petra/proxies/method_handlers.rb', line 107 def handle_attribute_change(method_name, *args) # Remove a possible "=" at the end of the setter method name attribute_name = method_name attribute_name = method_name[0..-2] if method_name =~ /^.*=$/ # As there might not be a corresponding getter, our fallback value for # the old attribute value is +nil+. TODO: See if this causes unexpected behaviour old_value = nil # To get the actual old value of an attribute reader, we have to # act as if it was requested externally by either serving it from the object # itself or the transaction's write set. # TODO: (Better) way to determine the reader method name, it might be a different one... old_value = handle_attribute_read(attribute_name) if attribute_reader?(attribute_name) # As we currently only handle simple setters, we expect the first given argument # to be the new attribute value. new_value = args.first # type_cast_attribute_value(attribute_name, args.first) transaction.log_attribute_change(@proxy, attribute: attribute_name, old_value: old_value, new_value: new_value, method: method_name.to_s) new_value end |
#handle_attribute_read(method_name, *args) ⇒ Object
Handles a getter method for the proxied object. As attribute changes are not actually forwarded to the actual object, we have to retrieve them from the transaction’s write set.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/petra/proxies/method_handlers.rb', line 139 def handle_attribute_read(method_name, *args) # We wrote this attribute before, so we have to serve its value # from the transaction's write set if transaction.attribute_value?(@proxy, attribute: method_name) # As we wrote this attribute before, we have the value we read back then on record. # Therefore, we may check if the value changed in the mean time which would invalidate # the transaction (most likely). transaction.verify_attribute_integrity!(@proxy, attribute: method_name) transaction.attribute_value(@proxy, attribute: method_name).tap do |result| Petra.logger.debug "Served value from write set: #{method_name} => #{result}", :yellow, :bold end elsif transaction.read_attribute_value?(@proxy, attribute: method_name) # If we didn't write the attribute before, we may at least have already read it. # In this case, we don't have to generate a new read log entry transaction.verify_attribute_integrity!(@proxy, attribute: method_name) # We also may simply return the last accepted read set value transaction.read_attribute_value(@proxy, attribute: method_name).tap do |result| Petra.logger.debug "Re-read attribute: #{method_name} => #{result}", :yellow, :bold end else proxied_object.send(method_name, *args).tap do |val| transaction.log_attribute_read(@proxy, attribute: method_name, value: val, method: method_name) end end end |
#handle_dynamic_attribute_read(method_name, *args) ⇒ Object
A “dynamic attribute” in this case is a method which usually formats one or multiple attributes and returns the result. An example would be ‘#first_name #last_name` within a user class. As methods which are no simple readers/writers are usually forwarded to the proxied object, we have to make sure that these methods are called in this proxy’s context, otherwise the used attribute readers would return the actual values, not the ones from our write set.
There is no particularly elegant way to achieve this as all forms of bind or instance_eval/exec would not set the correct self (or be incompatible), we generate a new proc from the method’s source code and call it within our own context. This should therefore be only used for dynamic attributes like the above example, more complex methods might cause serious problems.
98 99 100 |
# File 'lib/petra/proxies/method_handlers.rb', line 98 def handle_dynamic_attribute_read(method_name, *args) method_source_proc(method_name).call(*args) end |
#handle_missing_method(method_name, *args, &block) ⇒ Object
Calls the given method on the proxied object and optionally wraps the result in another petra proxy
78 79 80 81 82 |
# File 'lib/petra/proxies/method_handlers.rb', line 78 def handle_missing_method(method_name, *args, &block) proxied_object .public_send(method_name, *args, &block) .petra(inherited: true, configuration_args: [method_name.to_s]) end |
#handle_object_destruction(method_name, *args) ⇒ Object
Handles calls to a method which destroys the proxied object
186 187 188 189 |
# File 'lib/petra/proxies/method_handlers.rb', line 186 def handle_object_destruction(method_name, *args) transaction.log_object_destruction(@proxy, method: method_name, args: args) true end |
#handle_object_persistence(method_name, *args) ⇒ Object
Handles calls to a method which persists the proxied object. As we may not actually call the method on the proxied object, we may only log the persistence.
This is a very simple behaviour, so it makes sense to handle persistence methods differently in specialized object proxies (see ActiveRecordProxy)
TODO: Log parameters given to the persistence method so they can be used during the commit phase
177 178 179 180 181 |
# File 'lib/petra/proxies/method_handlers.rb', line 177 def handle_object_persistence(method_name, *args) transaction.log_object_persistence(@proxy, method: method_name, args: args) # TODO: Find a better return value for pure persistence calls true end |
#method_source_proc(method_name) ⇒ Object
Generates a new Proc object from the source code of a given instance method of the proxied object.
TODO: This does not work well with #unloadable, e.g. in Rails development environment TODO: method.parameters returns the required and optional parameters, these could be handed to the proc TODO: what happens with dynamically generated methods? is there a practical way to achieve this?
203 204 205 206 207 208 209 |
# File 'lib/petra/proxies/method_handlers.rb', line 203 def method_source_proc(method_name) method = proxied_object.method(method_name.to_sym) method_source = method.source.lines[1..-2].join proc do @proxy_binding.eval method_source end end |