petra_core 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/.rubocop.yml +83 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +74 -0
- data/MIT-LICENSE +20 -0
- data/README.md +726 -0
- data/Rakefile +8 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/examples/continuation_error.rb +125 -0
- data/examples/dining_philosophers.rb +138 -0
- data/examples/showcase.rb +54 -0
- data/lib/petra/components/entries/attribute_change.rb +29 -0
- data/lib/petra/components/entries/attribute_change_veto.rb +37 -0
- data/lib/petra/components/entries/attribute_read.rb +20 -0
- data/lib/petra/components/entries/object_destruction.rb +22 -0
- data/lib/petra/components/entries/object_initialization.rb +19 -0
- data/lib/petra/components/entries/object_persistence.rb +26 -0
- data/lib/petra/components/entries/read_integrity_override.rb +42 -0
- data/lib/petra/components/entry_set.rb +87 -0
- data/lib/petra/components/log_entry.rb +342 -0
- data/lib/petra/components/proxy_cache.rb +209 -0
- data/lib/petra/components/section.rb +543 -0
- data/lib/petra/components/transaction.rb +405 -0
- data/lib/petra/components/transaction_manager.rb +214 -0
- data/lib/petra/configuration/base.rb +132 -0
- data/lib/petra/configuration/class_configurator.rb +309 -0
- data/lib/petra/configuration/configurator.rb +67 -0
- data/lib/petra/core_ext.rb +27 -0
- data/lib/petra/exceptions.rb +181 -0
- data/lib/petra/persistence_adapters/adapter.rb +154 -0
- data/lib/petra/persistence_adapters/file_adapter.rb +239 -0
- data/lib/petra/proxies/abstract_proxy.rb +149 -0
- data/lib/petra/proxies/enumerable_proxy.rb +44 -0
- data/lib/petra/proxies/handlers/attribute_read_handler.rb +45 -0
- data/lib/petra/proxies/handlers/missing_method_handler.rb +47 -0
- data/lib/petra/proxies/method_handlers.rb +213 -0
- data/lib/petra/proxies/module_proxy.rb +12 -0
- data/lib/petra/proxies/object_proxy.rb +310 -0
- data/lib/petra/util/debug.rb +45 -0
- data/lib/petra/util/extended_attribute_accessors.rb +51 -0
- data/lib/petra/util/field_accessors.rb +35 -0
- data/lib/petra/util/registrable.rb +48 -0
- data/lib/petra/util/test_helpers.rb +9 -0
- data/lib/petra/version.rb +5 -0
- data/lib/petra.rb +100 -0
- data/lib/tasks/petra_tasks.rake +5 -0
- data/petra.gemspec +36 -0
- metadata +208 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Configuration
|
5
|
+
class Base
|
6
|
+
|
7
|
+
DEFAULTS = {
|
8
|
+
persistence_adapter_name: 'file',
|
9
|
+
log_level: 'debug',
|
10
|
+
instant_read_integrity_fail: true
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
#----------------------------------------------------------------
|
14
|
+
# Configuration Keys
|
15
|
+
#----------------------------------------------------------------
|
16
|
+
|
17
|
+
#
|
18
|
+
# Configures whether a read integrity error will be automatically
|
19
|
+
# detected whenever an attribute is read.
|
20
|
+
# If this is set to +false+, the read values will only be checked during
|
21
|
+
# the commit phase.
|
22
|
+
#
|
23
|
+
def instant_read_integrity_fail(new_value = nil)
|
24
|
+
if !new_value.nil?
|
25
|
+
__configuration_hash[:instant_read_integrity_fail] = new_value
|
26
|
+
else
|
27
|
+
__config_or_default(:instant_read_integrity_fail)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
alias instantly_fail_on_read_integrity_errors instant_read_integrity_fail
|
32
|
+
|
33
|
+
#
|
34
|
+
# Sets the adapter to be used as transaction persistence adapter.
|
35
|
+
# An adapter has to be registered before it may be used (see Adapter)
|
36
|
+
#
|
37
|
+
# @return [Class] the persistence adapter class used for storing transaction values.
|
38
|
+
# Defaults to use to the cache adapter
|
39
|
+
#
|
40
|
+
def persistence_adapter(name = nil)
|
41
|
+
if name
|
42
|
+
unless Petra::PersistenceAdapters::Adapter.registered_adapter?(name)
|
43
|
+
fail Petra::ConfigurationError,
|
44
|
+
"The given adapter `#{name}` hasn't been registered. " \
|
45
|
+
"Valid adapters are: #{Petra::PersistenceAdapters::Adapter.registered_adapters.keys.inspect}"
|
46
|
+
end
|
47
|
+
__configuration_hash[:persistence_adapter_name] = name
|
48
|
+
else
|
49
|
+
Petra::PersistenceAdapters::Adapter[__config_or_default(:persistence_adapter_name)]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# The log level for petra. Only messages which are greater or equal to this level
|
55
|
+
# will be shown in the output
|
56
|
+
#
|
57
|
+
def log_level(new_value = nil)
|
58
|
+
if new_value
|
59
|
+
__configuration_hash[:log_level] = new_value.to_s
|
60
|
+
else
|
61
|
+
__config_or_default(:log_level).to_sym
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
#----------------------------------------------------------------
|
66
|
+
# Helper Methods
|
67
|
+
#----------------------------------------------------------------
|
68
|
+
|
69
|
+
#
|
70
|
+
# A shortcut method to set +proxy_instances+ for multiple classes at once
|
71
|
+
# without having to +configure_class+ for each one.
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# proxy_class_instances 'Array', 'Enumerator', Hash
|
75
|
+
#
|
76
|
+
def proxy_class_instances(*class_names)
|
77
|
+
class_names.each do |klass|
|
78
|
+
configure_class(klass) do
|
79
|
+
proxy_instances true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Executes the given block in the context of a ClassConfigurator to
|
86
|
+
# configure petra's behaviour for a certain model/class
|
87
|
+
#
|
88
|
+
def configure_class(class_name, &proc)
|
89
|
+
configurator = class_configurator(class_name)
|
90
|
+
configurator.instance_eval(&proc)
|
91
|
+
configurator.__persist!
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Builds a ClassConfigurator for the given class or class name.
|
96
|
+
#
|
97
|
+
# @example Request the configuration for a certain model
|
98
|
+
# Notifications::Notificator.configuration.model_configurator(Subscription).__value(:recipients)
|
99
|
+
#
|
100
|
+
def class_configurator(class_name)
|
101
|
+
ClassConfigurator.for_class(class_name)
|
102
|
+
end
|
103
|
+
|
104
|
+
alias [] class_configurator
|
105
|
+
|
106
|
+
#
|
107
|
+
# @return [Hash] the complete configuration or one of its sub-namespaces.
|
108
|
+
# If a namespace does not exists yet, it will be initialized with an empty hash
|
109
|
+
#
|
110
|
+
# @example Retrieve the {:something => {:completely => {:different => 5}}} namespace
|
111
|
+
# __configuration_hash(:something, :completely)
|
112
|
+
# #=> {:different => 5}
|
113
|
+
#
|
114
|
+
def __configuration_hash(*sub_keys)
|
115
|
+
sub_keys.inject(@configuration ||= {}) do |h, k|
|
116
|
+
h[k] ||= {}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
#
|
123
|
+
# @return [Object] a base configuration value (non-namespaced) or
|
124
|
+
# the default value (see DEFAULTS) if none was set yet.
|
125
|
+
#
|
126
|
+
def __config_or_default(name)
|
127
|
+
__configuration_hash.fetch(name.to_sym, DEFAULTS[name.to_sym])
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,309 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'petra/configuration/configurator'
|
4
|
+
|
5
|
+
module Petra
|
6
|
+
module Configuration
|
7
|
+
class ClassConfigurator < Configurator
|
8
|
+
|
9
|
+
DEFAULTS = {
|
10
|
+
proxy_instances: false,
|
11
|
+
mixin_module_proxies: true,
|
12
|
+
use_specialized_proxy: true,
|
13
|
+
id_method: :object_id,
|
14
|
+
lookup_method: ->(id) { ObjectSpace._id2ref(id.to_i) },
|
15
|
+
init_method: :new,
|
16
|
+
attribute_reader?: false,
|
17
|
+
attribute_writer?: ->(name) { /=$/.match(name) },
|
18
|
+
dynamic_attribute_reader?: false,
|
19
|
+
persistence_method?: false,
|
20
|
+
destruction_method?: false
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
#
|
24
|
+
# @param [String] class_name
|
25
|
+
# The name of the class to be configured.
|
26
|
+
# .new() should not be called manually, use #for_class instead
|
27
|
+
# which accepts different input types.
|
28
|
+
#
|
29
|
+
def initialize(class_name)
|
30
|
+
@class_name = class_name
|
31
|
+
super()
|
32
|
+
end
|
33
|
+
|
34
|
+
#----------------------------------------------------------------
|
35
|
+
# Configuration Keys
|
36
|
+
#----------------------------------------------------------------
|
37
|
+
|
38
|
+
# TODO: LIST:
|
39
|
+
# - Rails' routes like configurators for method handlers:
|
40
|
+
# - Allow attribute writers without parameters. These will need a value the
|
41
|
+
# write set entry is set to. An example here would be `lock!` which sets `locked = true` internally
|
42
|
+
# - Methods should get an option which will set their generated log entries
|
43
|
+
# to not be executed. This is necessary if e.g. a setter method is also a persistence method
|
44
|
+
# and would re-set the attribute - fine in many places, but think about a mutex.
|
45
|
+
# example:
|
46
|
+
# attribute_writers do
|
47
|
+
# lock! :locked => true
|
48
|
+
# - Usage of method_missing with (**options)?
|
49
|
+
#
|
50
|
+
# Sets whether instances of this class should be wrapped in an ObjectProxy
|
51
|
+
# if this is not directly done by the programmer, e.g. as return value
|
52
|
+
# from an already proxied object.
|
53
|
+
#
|
54
|
+
base_config :proxy_instances
|
55
|
+
|
56
|
+
#
|
57
|
+
# Sets whether ObjectProxies should be extended with possibly existing
|
58
|
+
# ModuleProxy modules. This is used mainly for +Enumerable+, but you may want
|
59
|
+
# to define your own helper modules.
|
60
|
+
#
|
61
|
+
base_config :mixin_module_proxies
|
62
|
+
|
63
|
+
#
|
64
|
+
# Some classes have specialized proxy classes.
|
65
|
+
# If this setting is set to +false+, they will not be used in favour of ObjectProxy
|
66
|
+
#
|
67
|
+
base_config :use_specialized_proxy
|
68
|
+
|
69
|
+
#
|
70
|
+
# Sets the method to be used to determine the unique ID of an Object.
|
71
|
+
# The ID is needed to identify an object when reloading it within a transaction,
|
72
|
+
# so basically a key for our read set.
|
73
|
+
#
|
74
|
+
# If a block is given, the (:base) object is yielded to it, otherwise,
|
75
|
+
# the given method name is assumed to be an instance method in the configured class
|
76
|
+
#
|
77
|
+
base_config :id_method
|
78
|
+
|
79
|
+
#
|
80
|
+
# Sets the method to be used to load an object with a certain unique ID
|
81
|
+
# (see +:id_method+).
|
82
|
+
#
|
83
|
+
# If a block is given, the identifier is yielded to it, otherwise,
|
84
|
+
# the given method name is assumed to be a class method accepting
|
85
|
+
# a string identifier in the configured class
|
86
|
+
#
|
87
|
+
base_config :lookup_method
|
88
|
+
|
89
|
+
#
|
90
|
+
# Method to initialize a new instance of the proxied class, e.g. `:new` for basic objects
|
91
|
+
#
|
92
|
+
base_config :init_method
|
93
|
+
|
94
|
+
#
|
95
|
+
# Expects the value (or return value of a block) to be a boolean value
|
96
|
+
# depending on whether a method name given as argument is an attribute reader
|
97
|
+
#
|
98
|
+
base_config :attribute_reader?
|
99
|
+
|
100
|
+
#
|
101
|
+
# Expects the value (or return value of a block) to be a boolean value
|
102
|
+
# depending on whether a method name given as argument is an attribute reader
|
103
|
+
#
|
104
|
+
base_config :attribute_writer?
|
105
|
+
|
106
|
+
#
|
107
|
+
# Sometimes it might be necessary to use helper methods to combine multiple attributes,
|
108
|
+
# e.g. `#name` for `"#{first_name} #{last_name}"`.
|
109
|
+
# As calling `#name` would usually be passed to the proxied objects and
|
110
|
+
# executed within the object's context instead of the proxy, these methods
|
111
|
+
# can be flagged as combined/dynamic attribute readers and will be executed within
|
112
|
+
# the proxy's binding.
|
113
|
+
# The function is expected to return a boolean value.
|
114
|
+
#
|
115
|
+
base_config :dynamic_attribute_reader?
|
116
|
+
|
117
|
+
#
|
118
|
+
# Expects the value (or return value of a block) to be a boolean value
|
119
|
+
# depending on whether a method name given as argument is a method that will persist
|
120
|
+
# the current instance.
|
121
|
+
# For normal ruby objects this would be every attribute setter (as it would be persisted in
|
122
|
+
# the process memory), for e.g. ActiveRecord::Base instances, this is only done by update/save/...
|
123
|
+
#
|
124
|
+
base_config :persistence_method?
|
125
|
+
|
126
|
+
#
|
127
|
+
# Expects the value (or return value of a block) to be a boolean value and
|
128
|
+
# be +true+ if the given method is a "destructor" of the configured class.
|
129
|
+
# This can't be easily said for plain ruby objects, but some classes
|
130
|
+
# may implement an own destruction behaviour (e.g. ActiveRecord)
|
131
|
+
#
|
132
|
+
base_config :destruction_method?
|
133
|
+
|
134
|
+
#----------------------------------------------------------------
|
135
|
+
# Helper Methods
|
136
|
+
#----------------------------------------------------------------
|
137
|
+
|
138
|
+
#
|
139
|
+
# Builds a new instance for the given class name.
|
140
|
+
# If a configuration for this class already exists, it is loaded and
|
141
|
+
# can be retrieved through the corresponding getter methods
|
142
|
+
#
|
143
|
+
# @param [String, Symbol, Class] klass
|
144
|
+
# The class (name) which will be used to initialize the configurator
|
145
|
+
# and load a possibly already existing configuration
|
146
|
+
#
|
147
|
+
def self.for_class(klass)
|
148
|
+
new(klass.to_s)
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# Returns the value for a certain configuration key.
|
153
|
+
# If the configuration value is a proc, it will be called
|
154
|
+
# with the given +*args+.
|
155
|
+
#
|
156
|
+
# If no custom configuration was set for the given +name+, the default
|
157
|
+
# value is returned instead.
|
158
|
+
#
|
159
|
+
# @param [Boolean] proc_expected
|
160
|
+
# If set to +true+, the value is expected to be either a Proc object
|
161
|
+
# or a String/Symbol which is assumed to be a method name.
|
162
|
+
# If the value is something else, an Exception is thrown
|
163
|
+
#
|
164
|
+
# @param [Object] base
|
165
|
+
# The base object which is used in case +mandatory_proc+ is set to +true+.
|
166
|
+
# If the fetched value is a String or Symbol, it will be used as method
|
167
|
+
# name in a call based on the +base+ object with +*args* as actual parameters, e.g.
|
168
|
+
# base.send(:some_fetched_value, *args)
|
169
|
+
#
|
170
|
+
def __value(key, *args, proc_expected: false, base: nil)
|
171
|
+
v = __configuration.fetch(key.to_sym, DEFAULTS[key.to_sym])
|
172
|
+
|
173
|
+
# As the setting blocks are saved as Proc objects (which are run
|
174
|
+
# in their textual scope) and not lambdas (which are run in their caller's scope),
|
175
|
+
# Ruby does not allow using the `return` keyword while being inside the
|
176
|
+
# block as method the proc was defined in might have already been returned.
|
177
|
+
#
|
178
|
+
# When configuring petra using blocks, it is advised to use `next`
|
179
|
+
# instead of `return` (which will jump back to the correct position),
|
180
|
+
# a workaround is to rescue from possible LocalJumpErrors and simply
|
181
|
+
# use their exit value.
|
182
|
+
begin
|
183
|
+
case v
|
184
|
+
when Proc
|
185
|
+
# see #__send_to_base
|
186
|
+
return v.call(*[*args, base][0, v.arity]) if proc_expected
|
187
|
+
v.call(*(args[0, v.arity]))
|
188
|
+
when String, Symbol
|
189
|
+
return __send_to_base(base, method: v, args: args, key: key) if proc_expected
|
190
|
+
v
|
191
|
+
else
|
192
|
+
__fail_for_key key, 'Value has to be either a Proc or a method name (Symbol/String)' if proc_expected
|
193
|
+
v
|
194
|
+
end
|
195
|
+
rescue LocalJumpError => e
|
196
|
+
e.exit_value
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# Tests whether this class configuration has a custom setting for the given key.
|
202
|
+
#
|
203
|
+
# @return [TrueClass, FalseClass] +true+ if there is a custom setting
|
204
|
+
#
|
205
|
+
def __value?(key)
|
206
|
+
__configuration.key?(key.to_sym)
|
207
|
+
end
|
208
|
+
|
209
|
+
#
|
210
|
+
# Much like #__value, but it searches for settings
|
211
|
+
# with the given name in the current class' ancestors if
|
212
|
+
# itself does not have a custom value set.
|
213
|
+
#
|
214
|
+
def __inherited_value(key, *args)
|
215
|
+
configurator = self
|
216
|
+
|
217
|
+
# Search for a custom configuration in the current class and its superclasses
|
218
|
+
# until we either reach Object (the lowest level ignoring BasicObject) or
|
219
|
+
# found a custom setting.
|
220
|
+
until (klass = configurator.send(:configured_class)) == Object || configurator.__value?(key)
|
221
|
+
configurator = Petra.configuration.class_configurator(klass.superclass)
|
222
|
+
end
|
223
|
+
|
224
|
+
# By now, we have either reached the Object level or found a value.
|
225
|
+
# In either case, we are save to retrieve it.
|
226
|
+
configurator.__value(key, *args)
|
227
|
+
end
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
#
|
232
|
+
# Raises a Petra::ConfigurationError with information about the key that caused it and a message
|
233
|
+
#
|
234
|
+
def __fail_for_key(key, message)
|
235
|
+
fail Petra::ConfigurationError,
|
236
|
+
"The configuration '#{key}' for class '#{@class_name}' seems to be incorrect: #{message}"
|
237
|
+
end
|
238
|
+
|
239
|
+
#
|
240
|
+
# Tries to .send() the given +method+ to the +base+ object.
|
241
|
+
# Exceptions are raised when no base was given or the given base does not respond to the given method.
|
242
|
+
#
|
243
|
+
def __send_to_base(base, method:, key:, args: [])
|
244
|
+
fail ArgumentError, "No base object to send ':#{method}' to was given" unless base
|
245
|
+
|
246
|
+
unless base.respond_to?(method.to_sym)
|
247
|
+
if base.is_a?(Class)
|
248
|
+
__fail_for_key key, ":#{method} was expected to be a class method in #{base}"
|
249
|
+
else
|
250
|
+
__fail_for_key key, ":#{method} was expected to be an instance method in #{base.class}"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# It might happen that the given method name does not accept all of the given
|
255
|
+
# arguments, most likely because they are not needed to make the necessary
|
256
|
+
# decisions anyway.
|
257
|
+
# Therefore, only the correct amount of arguments is passed to the function, e.g.
|
258
|
+
# args[0,2] for a method with arity 2
|
259
|
+
base.send(method.to_sym, *__args_for_arity(base, method, args))
|
260
|
+
end
|
261
|
+
|
262
|
+
#
|
263
|
+
# Takes as many elements from +args+ as the given method accepts
|
264
|
+
# If a method with variable arguments is given (def something(*args)),
|
265
|
+
# all arguments are returned
|
266
|
+
#
|
267
|
+
def __args_for_arity(base, method, args)
|
268
|
+
arity = base.method(method.to_sym).arity
|
269
|
+
arity >= 0 ? args[0, arity] : args
|
270
|
+
end
|
271
|
+
|
272
|
+
#
|
273
|
+
# @return [Class] the class which is configured by this ClassConfigurator
|
274
|
+
#
|
275
|
+
# Even though ruby class should only contain module separators (::) and camel case words,
|
276
|
+
# there might be (framework) class names which do not comply to this.
|
277
|
+
# An example would be ActiveRecord's Relation class which seems to be specific
|
278
|
+
# for each model class it is used on.
|
279
|
+
#
|
280
|
+
# Example: User.all #=> <User::ActiveRecord_Relation...>
|
281
|
+
#
|
282
|
+
# Therefore, we first try to camelize the given class name and if that
|
283
|
+
# does not lead us to a valid constant name, we try to pass in the
|
284
|
+
# @class_name as is and raise possible errors.
|
285
|
+
#
|
286
|
+
def configured_class
|
287
|
+
@class_name.camelize.safe_constantize || @class_name.constantize
|
288
|
+
end
|
289
|
+
|
290
|
+
#
|
291
|
+
# @return [Array<Symbol, String>] the namespaces which will be used
|
292
|
+
# when merging this class configuration into the main configuration hash
|
293
|
+
#
|
294
|
+
def __namespaces
|
295
|
+
[:models, @class_name]
|
296
|
+
end
|
297
|
+
|
298
|
+
#
|
299
|
+
# Removes options from the given arguments if the last element is a Hash
|
300
|
+
#
|
301
|
+
# @return [Hash] the options extracted from the given arguments or an empty
|
302
|
+
# hash if there were no options given
|
303
|
+
#
|
304
|
+
def extract_options!(args)
|
305
|
+
args.last.is_a?(Hash) ? args.pop : {}
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Configuration
|
5
|
+
class Configurator
|
6
|
+
|
7
|
+
#
|
8
|
+
# Generates a very basic configuration method which accepts a
|
9
|
+
# block or input value (see +options+)
|
10
|
+
#
|
11
|
+
# @param [Object] name
|
12
|
+
# The configuration's name which will become the method name
|
13
|
+
#
|
14
|
+
# @param [Boolean] accept_value
|
15
|
+
# If set to +true+, the resulting method accepts not only a block,
|
16
|
+
# but also a direct value.
|
17
|
+
# If both, a value and a block are given, the block takes precedence
|
18
|
+
#
|
19
|
+
def self.base_config(name, accept_value: true)
|
20
|
+
if accept_value
|
21
|
+
define_method name do |value = nil, &proc|
|
22
|
+
if proc
|
23
|
+
__configuration[name.to_sym] = proc
|
24
|
+
elsif !value.nil?
|
25
|
+
__configuration[name.to_sym] = value
|
26
|
+
else
|
27
|
+
fail ArgumentError, 'Either a value or a configuration block have to be given.'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
else
|
31
|
+
define_method name do |&proc|
|
32
|
+
fail(ArgumentError, 'A configuration block has to be given.') unless proc
|
33
|
+
__configuration[name.to_sym] = proc
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
@options = Petra.configuration.__configuration_hash(*__namespaces).deep_dup
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Persists the new configuration values in the global configuration,
|
44
|
+
# meaning that it merges its options into the specific configuration hash
|
45
|
+
# under a certain key
|
46
|
+
#
|
47
|
+
def __persist!
|
48
|
+
Petra.configuration.__configuration_hash(*__namespaces).deep_merge!(__configuration)
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
#
|
54
|
+
# @return [Array<Symbol>] the current configuration options within an
|
55
|
+
# optional namespace chain, mainly to be merged into a global
|
56
|
+
#
|
57
|
+
def __namespaces
|
58
|
+
not_implemented
|
59
|
+
end
|
60
|
+
|
61
|
+
def __configuration
|
62
|
+
@options
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module CoreExt
|
5
|
+
module Object
|
6
|
+
#
|
7
|
+
# @return [Petra::ObjectProxy, Object] A proxy object to be used instead of the
|
8
|
+
# actual object in the transactions' contexts.
|
9
|
+
#
|
10
|
+
# Some objects are frozen by default (e.g. +nil+ or the shared instances of TrueClass and FalseClass),
|
11
|
+
# for these, the resulting object proxy is not cached
|
12
|
+
#
|
13
|
+
def petra(inherited: false, configuration_args: [])
|
14
|
+
# Do not proxy inherited objects if their configuration prohibits it.
|
15
|
+
if inherited && !Petra::Proxies::ObjectProxy.inherited_config_for(self, :proxy_instances, *configuration_args)
|
16
|
+
return self
|
17
|
+
end
|
18
|
+
|
19
|
+
if frozen?
|
20
|
+
Petra::Proxies::ObjectProxy.for(self, inherited: inherited)
|
21
|
+
else
|
22
|
+
@__petra_proxy ||= Petra::Proxies::ObjectProxy.for(self, inherited: inherited)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|