Class: Petra::Proxies::ObjectProxy

Inherits:
AbstractProxy show all
Includes:
Comparable
Defined in:
lib/petra/proxies/object_proxy.rb

Overview

To avoid messing with the methods defined by ActiveRecord or similar, the programmer should use these proxy objects (object.petra.*) which handle actions on a different level.

This class is the base proxy class which can be extended to cover certain behaviours that would be too complex to be put inside the configuration.

Constant Summary collapse

CLASS_NAMES =
%w[Object].freeze

Instance Method Summary collapse

Methods inherited from AbstractProxy

available_class_proxies, available_module_proxies, for, inherited_config_for, #mixin_module_proxies!, #object_config, #transaction

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args, &block) ⇒ Object

Catch all methods which are not defined on this proxy object as they are most likely meant to go to the proxied object

Also checks a few special cases like attribute reads/changes. Please note that a method may be e.g. a persistence method AND an attribute writer (for normal objects, every attribute write would be persisted to memory), so we have to execute all matching handlers in a queue.

rubocop:disable Style/MethodMissing



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/petra/proxies/object_proxy.rb', line 72

def method_missing(meth, *args, &block)
  # If no transaction is currently running, we proxy everything
  # to the original object.
  unless Petra.transaction_running?
    Petra.logger.info "No transaction running, proxying #{meth} to original object."
    return unproxied.public_send(meth, *args, &block)
  end

  # As calling a superclass method in ruby does not cause method calls within this method
  # to be called within the superclass context, the correct (= the child class') attribute
  # detectors are run.
  result = __handlers.execute_missing_queue(meth, *args, block: block) do |queue|
    queue << :handle_attribute_change if __attribute_writer?(meth)
    queue << :handle_attribute_read if __attribute_reader?(meth)
    queue << :handle_dynamic_attribute_read if __dynamic_attribute_reader?(meth)
    queue << :handle_object_persistence if __persistence_method?(meth)
  end

  Petra.logger.debug "#{object_class_or_self}##{meth}(#{args.map(&:inspect).join(', ')}) => #{result.inspect}"

  result
rescue SystemStackError => e
  exception = ArgumentError.new("Method '#{meth}' lead to a SystemStackError due to `method_missing`")
  exception.set_backtrace(e.backtrace.uniq)
  raise exception
end

Instance Method Details

#<=>(other) ⇒ Object

Very simple spaceship operator based on the object key TODO: See if this causes problems when ID-ordering is expected.

For existing objects that shouldn't be the case in most situations as
a collection mostly contains only objects of one kind


178
179
180
# File 'lib/petra/proxies/object_proxy.rb', line 178

def <=>(other)
  __object_key <=> other.__object_key
end

#__attribute_key(attribute) ⇒ String

Generates a unique attribute key based on the proxied object’s class, id and a given attribute

Parameters:

  • attribute (String, Symbol)

Returns:

  • (String)

    the generated attribute key



139
140
141
# File 'lib/petra/proxies/object_proxy.rb', line 139

def __attribute_key(attribute)
  [proxied_object.class, __object_id, attribute].map(&:to_s).join('/')
end

#__created?Boolean

Returns true if the proxied object was created (= initialized + persisted) during the current transaction.

Returns:

  • (Boolean)

    true if the proxied object was created (= initialized + persisted) during the current transaction



161
162
163
# File 'lib/petra/proxies/object_proxy.rb', line 161

def __created?
  transaction.objects.created?(self)
end

#__destroyed?Boolean

Returns true if the proxied object was destroyed during the transaction.

Returns:

  • (Boolean)

    true if the proxied object was destroyed during the transaction



168
169
170
# File 'lib/petra/proxies/object_proxy.rb', line 168

def __destroyed?
  transaction.objects.destroyed?(self)
end

#__existing?Boolean

Returns true if the proxied object existed before the transaction started.

Returns:

  • (Boolean)

    true if the proxied object existed before the transaction started



153
154
155
# File 'lib/petra/proxies/object_proxy.rb', line 153

def __existing?
  transaction.objects.existing?(self)
end

#__new?Boolean

Returns true if the proxied object did not exist before the transaction started.

Returns:

  • (Boolean)

    true if the proxied object did not exist before the transaction started



146
147
148
# File 'lib/petra/proxies/object_proxy.rb', line 146

def __new?
  transaction.objects.new?(self)
end

#__object_idObject

Generates an ID for the proxied object based on the class configuration. New objects (= objects which were generated within this transaction) receive an artificial ID



115
116
117
118
119
120
121
# File 'lib/petra/proxies/object_proxy.rb', line 115

def __object_id
  @__object_id ||= if __new?
                     transaction.objects.next_id
                   else
                     object_config(:id_method, proc_expected: true, base: proxied_object)
                   end
end

#__object_keyString

Generates a unique object key based on the proxied object’s class and id

Returns:

  • (String)

    the generated object key



128
129
130
# File 'lib/petra/proxies/object_proxy.rb', line 128

def __object_key
  [proxied_object.class, __object_id].map(&:to_s).join('/')
end

#__original_attribute?(attribute_name) ⇒ Boolean

Deprecated.

Checks whether the given attribute was altered during the current transaction. Note that an attribute counts as ‘altered` even if it was reset to its original value in a later transaction step.

TODO: Check for dynamic attribute readers?

Returns:

  • (Boolean)


58
59
60
# File 'lib/petra/proxies/object_proxy.rb', line 58

def __original_attribute?(attribute_name)
  !transaction.attribute_value?(self, attribute: attribute_name.to_s)
end

#new(*args) ⇒ Object

Creepy!



32
33
34
35
# File 'lib/petra/proxies/object_proxy.rb', line 32

def new(*args)
  class_method!
  proxied_object.new(*args).petra
end

#petraObject

Do not create new proxies for already proxied objects. Instead, return the current proxy object



27
28
29
# File 'lib/petra/proxies/object_proxy.rb', line 27

def petra(*)
  self
end

#respond_to_missing?(meth) ⇒ Boolean

It is necessary to forward #respond_to? queries to the proxied object as otherwise certain calls, especially from the Rails framework itself will fail. Hidden methods are ignored.

Returns:

  • (Boolean)


106
107
108
# File 'lib/petra/proxies/object_proxy.rb', line 106

def respond_to_missing?(meth, *)
  proxied_object.respond_to?(meth)
end

#unproxiedObject

Access the proxied object publicly from each petra proxy TODO: This should not leave the proxy!

Examples:

user = User.petra.first
user.unproxied.first_name


45
46
47
# File 'lib/petra/proxies/object_proxy.rb', line 45

def unproxied
  proxied_object
end