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,310 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'petra/proxies/abstract_proxy'
|
4
|
+
require 'petra/proxies/method_handlers'
|
5
|
+
|
6
|
+
module Petra
|
7
|
+
module Proxies
|
8
|
+
#
|
9
|
+
# To avoid messing with the methods defined by ActiveRecord or similar,
|
10
|
+
# the programmer should use these proxy objects (object.petra.*) which handle
|
11
|
+
# actions on a different level.
|
12
|
+
#
|
13
|
+
# This class is the base proxy class which can be extended to cover
|
14
|
+
# certain behaviours that would be too complex to be put inside the configuration.
|
15
|
+
#
|
16
|
+
class ObjectProxy < AbstractProxy
|
17
|
+
include Comparable
|
18
|
+
|
19
|
+
CLASS_NAMES = %w[Object].freeze
|
20
|
+
|
21
|
+
delegate :to_s, to: :proxied_object
|
22
|
+
|
23
|
+
#
|
24
|
+
# Do not create new proxies for already proxied objects.
|
25
|
+
# Instead, return the current proxy object
|
26
|
+
#
|
27
|
+
def petra(*)
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
# Creepy!
|
32
|
+
def new(*args)
|
33
|
+
class_method!
|
34
|
+
proxied_object.new(*args).petra
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Access the proxied object publicly from each petra proxy
|
39
|
+
# TODO: This should not leave the proxy!
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# user = User.petra.first
|
43
|
+
# user.unproxied.first_name
|
44
|
+
#
|
45
|
+
def unproxied
|
46
|
+
proxied_object
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Checks whether the given attribute was altered during the current transaction.
|
51
|
+
# Note that an attribute counts as `altered` even if it was reset to its original
|
52
|
+
# value in a later transaction step.
|
53
|
+
#
|
54
|
+
# @deprecated
|
55
|
+
#
|
56
|
+
# TODO: Check for dynamic attribute readers?
|
57
|
+
#
|
58
|
+
def __original_attribute?(attribute_name)
|
59
|
+
!transaction.attribute_value?(self, attribute: attribute_name.to_s)
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Catch all methods which are not defined on this proxy object as they
|
64
|
+
# are most likely meant to go to the proxied object
|
65
|
+
#
|
66
|
+
# Also checks a few special cases like attribute reads/changes.
|
67
|
+
# Please note that a method may be e.g. a persistence method AND an attribute writer
|
68
|
+
# (for normal objects, every attribute write would be persisted to memory), so
|
69
|
+
# we have to execute all matching handlers in a queue.
|
70
|
+
#
|
71
|
+
# rubocop:disable Style/MethodMissing
|
72
|
+
def method_missing(meth, *args, &block)
|
73
|
+
# If no transaction is currently running, we proxy everything
|
74
|
+
# to the original object.
|
75
|
+
unless Petra.transaction_running?
|
76
|
+
Petra.logger.info "No transaction running, proxying #{meth} to original object."
|
77
|
+
return unproxied.public_send(meth, *args, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
# As calling a superclass method in ruby does not cause method calls within this method
|
81
|
+
# to be called within the superclass context, the correct (= the child class') attribute
|
82
|
+
# detectors are run.
|
83
|
+
result = __handlers.execute_missing_queue(meth, *args, block: block) do |queue|
|
84
|
+
queue << :handle_attribute_change if __attribute_writer?(meth)
|
85
|
+
queue << :handle_attribute_read if __attribute_reader?(meth)
|
86
|
+
queue << :handle_dynamic_attribute_read if __dynamic_attribute_reader?(meth)
|
87
|
+
queue << :handle_object_persistence if __persistence_method?(meth)
|
88
|
+
end
|
89
|
+
|
90
|
+
Petra.logger.debug "#{object_class_or_self}##{meth}(#{args.map(&:inspect).join(', ')}) => #{result.inspect}"
|
91
|
+
|
92
|
+
result
|
93
|
+
rescue SystemStackError => e
|
94
|
+
exception = ArgumentError.new("Method '#{meth}' lead to a SystemStackError due to `method_missing`")
|
95
|
+
exception.set_backtrace(e.backtrace.uniq)
|
96
|
+
raise exception
|
97
|
+
end
|
98
|
+
# rubocop:enable Style/MethodMissing
|
99
|
+
|
100
|
+
#
|
101
|
+
# It is necessary to forward #respond_to? queries to
|
102
|
+
# the proxied object as otherwise certain calls, especially from
|
103
|
+
# the Rails framework itself will fail.
|
104
|
+
# Hidden methods are ignored.
|
105
|
+
#
|
106
|
+
def respond_to_missing?(meth, *)
|
107
|
+
proxied_object.respond_to?(meth)
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Generates an ID for the proxied object based on the class configuration.
|
112
|
+
# New objects (= objects which were generated within this transaction) receive
|
113
|
+
# an artificial ID
|
114
|
+
#
|
115
|
+
def __object_id
|
116
|
+
@__object_id ||= if __new?
|
117
|
+
transaction.objects.next_id
|
118
|
+
else
|
119
|
+
object_config(:id_method, proc_expected: true, base: proxied_object)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
#
|
124
|
+
# Generates a unique object key based on the proxied object's class and id
|
125
|
+
#
|
126
|
+
# @return [String] the generated object key
|
127
|
+
#
|
128
|
+
def __object_key
|
129
|
+
[proxied_object.class, __object_id].map(&:to_s).join('/')
|
130
|
+
end
|
131
|
+
|
132
|
+
#
|
133
|
+
# Generates a unique attribute key based on the proxied object's class, id and a given attribute
|
134
|
+
#
|
135
|
+
# @param [String, Symbol] attribute
|
136
|
+
#
|
137
|
+
# @return [String] the generated attribute key
|
138
|
+
#
|
139
|
+
def __attribute_key(attribute)
|
140
|
+
[proxied_object.class, __object_id, attribute].map(&:to_s).join('/')
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# @return [Boolean] +true+ if the proxied object did not exist before the transaction started
|
145
|
+
#
|
146
|
+
def __new?
|
147
|
+
transaction.objects.new?(self)
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# @return [Boolean] +true+ if the proxied object existed before the transaction started
|
152
|
+
#
|
153
|
+
def __existing?
|
154
|
+
transaction.objects.existing?(self)
|
155
|
+
end
|
156
|
+
|
157
|
+
#
|
158
|
+
# @return [Boolean] +true+ if the proxied object was created (= initialized + persisted) during
|
159
|
+
# the current transaction
|
160
|
+
#
|
161
|
+
def __created?
|
162
|
+
transaction.objects.created?(self)
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# @return [Boolean] +true+ if the proxied object was destroyed during the transaction
|
167
|
+
#
|
168
|
+
def __destroyed?
|
169
|
+
transaction.objects.destroyed?(self)
|
170
|
+
end
|
171
|
+
|
172
|
+
#
|
173
|
+
# Very simple spaceship operator based on the object key
|
174
|
+
# TODO: See if this causes problems when ID-ordering is expected.
|
175
|
+
# For existing objects that shouldn't be the case in most situations as
|
176
|
+
# a collection mostly contains only objects of one kind
|
177
|
+
#
|
178
|
+
def <=>(other)
|
179
|
+
__object_key <=> other.__object_key
|
180
|
+
end
|
181
|
+
|
182
|
+
protected
|
183
|
+
|
184
|
+
#----------------------------------------------------------------
|
185
|
+
# Method Group Detectors
|
186
|
+
#----------------------------------------------------------------
|
187
|
+
|
188
|
+
#
|
189
|
+
# Checks whether the given method name is part of the configured attribute reader
|
190
|
+
# methods within the currently proxied class
|
191
|
+
#
|
192
|
+
def __attribute_reader?(method_name)
|
193
|
+
object_config(:attribute_reader?, method_name.to_s)
|
194
|
+
end
|
195
|
+
|
196
|
+
#
|
197
|
+
# @see #attribute_reader?
|
198
|
+
#
|
199
|
+
def __attribute_writer?(method_name)
|
200
|
+
object_config(:attribute_writer?, method_name.to_s)
|
201
|
+
end
|
202
|
+
|
203
|
+
#
|
204
|
+
# @see #attribute_reader?
|
205
|
+
#
|
206
|
+
# Currently, classes may not use dynamic attribute readers
|
207
|
+
#
|
208
|
+
def __dynamic_attribute_reader?(method_name)
|
209
|
+
!class_proxy? && object_config(:dynamic_attribute_reader?, method_name.to_s)
|
210
|
+
end
|
211
|
+
|
212
|
+
#
|
213
|
+
# @return [Boolean] +true+ if the given method would persist the
|
214
|
+
# proxied object
|
215
|
+
#
|
216
|
+
def __persistence_method?(method_name)
|
217
|
+
!class_proxy? && object_config(:persistence_method?, method_name.to_s)
|
218
|
+
end
|
219
|
+
|
220
|
+
#
|
221
|
+
# @return [Boolean] +true+ if the given method name is a "destructor" of the
|
222
|
+
# proxied object
|
223
|
+
#
|
224
|
+
def __destruction_method?(method_name)
|
225
|
+
!class_proxy? && object_config(:destruction_method?, method_name.to_s)
|
226
|
+
end
|
227
|
+
|
228
|
+
#
|
229
|
+
# Sets the given attribute to the given value using the default setter
|
230
|
+
# function `name=`. This function is just a convenience method and does not
|
231
|
+
# manage the actual write set. Please take a look at #handle_attribute_change instead.
|
232
|
+
#
|
233
|
+
# @param [String, Symbol] attribute
|
234
|
+
# The attribute name. The proxied object is expected to have a corresponding public setter method
|
235
|
+
#
|
236
|
+
# @param [Object] new_value
|
237
|
+
#
|
238
|
+
def __set_attribute(attribute, new_value)
|
239
|
+
public_send("#{attribute}=", new_value)
|
240
|
+
end
|
241
|
+
|
242
|
+
def __handlers
|
243
|
+
@__handlers ||= Petra::Proxies::MethodHandlers.new(self, binding)
|
244
|
+
end
|
245
|
+
|
246
|
+
def initialize(object, inherited = false, object_id: nil)
|
247
|
+
@obj = object
|
248
|
+
@inherited = inherited
|
249
|
+
@__object_id = object_id
|
250
|
+
end
|
251
|
+
|
252
|
+
#
|
253
|
+
# @return [Object] the proxied object
|
254
|
+
#
|
255
|
+
def proxied_object
|
256
|
+
@obj
|
257
|
+
end
|
258
|
+
|
259
|
+
#
|
260
|
+
# @return [Boolean] +true+ if the proxied object is a class
|
261
|
+
#
|
262
|
+
def class_proxy?
|
263
|
+
proxied_object.is_a?(Class)
|
264
|
+
end
|
265
|
+
|
266
|
+
#
|
267
|
+
# @return [Class] the proxied object if it is a class itself, otherwise
|
268
|
+
# the proxied object's class.
|
269
|
+
#
|
270
|
+
def object_class_or_self
|
271
|
+
class_proxy? ? proxied_object : proxied_object.class
|
272
|
+
end
|
273
|
+
|
274
|
+
#
|
275
|
+
# @return [Boolean] +true+ if the proxied object is a +klass+
|
276
|
+
#
|
277
|
+
def for_class?(klass)
|
278
|
+
proxied_object.is_a?(klass)
|
279
|
+
end
|
280
|
+
|
281
|
+
#
|
282
|
+
# Performs possible type casts on a value which is about to be set
|
283
|
+
# for an attribute. For general ObjectProxy instances, this is simply the identity
|
284
|
+
# function, but it might be overridden in more specialized proxies.
|
285
|
+
#
|
286
|
+
def __type_cast_attribute_value(_attribute, value)
|
287
|
+
value
|
288
|
+
end
|
289
|
+
|
290
|
+
#
|
291
|
+
# Raises an exception if proxied object isn't a class.
|
292
|
+
# Currently, there is no way to specify which methods are class and instance methods
|
293
|
+
# in a specialized proxy, so this at least tells the developer that he did something wrong.
|
294
|
+
#
|
295
|
+
def class_method!
|
296
|
+
return if class_proxy?
|
297
|
+
fail Petra::PetraError, 'This method is meant to be used as a singleton method, not an instance method.'
|
298
|
+
end
|
299
|
+
|
300
|
+
#
|
301
|
+
# @see #class_method!
|
302
|
+
#
|
303
|
+
def instance_method!
|
304
|
+
return if class_proxy?
|
305
|
+
fail Petra::PetraError, 'This method is meant to be used as an instance method only!'
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Util
|
5
|
+
module Debug
|
6
|
+
STRING_COLORS = {light_gray: 90,
|
7
|
+
yellow: 33,
|
8
|
+
green: 32,
|
9
|
+
red: 31,
|
10
|
+
purple: 35,
|
11
|
+
cyan: 36,
|
12
|
+
blue: 34}.freeze
|
13
|
+
|
14
|
+
FORMATS = {default: 0,
|
15
|
+
bold: 1,
|
16
|
+
underline: 4}.freeze
|
17
|
+
|
18
|
+
%i[debug info warn error].each do |level|
|
19
|
+
define_method level do |message, color = :light_gray, format = :default|
|
20
|
+
log(message, level: level, color: color, format: format)
|
21
|
+
end
|
22
|
+
|
23
|
+
module_function level
|
24
|
+
end
|
25
|
+
|
26
|
+
def log(message, level: :debug, color: :light_gray, format: :default)
|
27
|
+
logger.send(level, 'Petra :: ' + colored_string(message, color, format))
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def logger
|
33
|
+
@logger ||= Logger.new(STDOUT).tap do |l|
|
34
|
+
l.level = "Logger::#{Petra.configuration.log_level.upcase}".constantize
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def colored_string(string, color, format)
|
39
|
+
"\e[#{Petra::Util::Debug::FORMATS[format]};#{Petra::Util::Debug::STRING_COLORS[color.to_sym]}m#{string}\e[0m"
|
40
|
+
end
|
41
|
+
|
42
|
+
module_function :log, :logger, :colored_string
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Util
|
5
|
+
module ExtendedAttributeAccessors
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
extend ClassMethods
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# TODO: Keep track of available accessors on class level
|
14
|
+
# TODO: Keep track of the actual values on instance (or class instance) level
|
15
|
+
|
16
|
+
def extended_attr_accessor(name, **options)
|
17
|
+
singleton = options.fetch(:singleton, false)
|
18
|
+
methods_method, definer_method = :instance_methods, :define_method
|
19
|
+
methods_method, definer_method = :singleton_methods, :define_singleton_method if singleton
|
20
|
+
|
21
|
+
unless send(methods_method).include?(:extended_attribute_accessors)
|
22
|
+
send(definer_method, :__extended_attribute_accessors__) do |group = :general|
|
23
|
+
(@extended_attribute_accessors ||= {})[group.to_sym] ||= {}
|
24
|
+
end
|
25
|
+
|
26
|
+
send(definer_method, :extended_attribute_accessors) do |group = :general, only: nil|
|
27
|
+
result = __extended_attribute_accessors__(group)
|
28
|
+
return result unless only
|
29
|
+
|
30
|
+
result.each_with_object({}) do |(k, v), h|
|
31
|
+
h[k] = v.slice(Array(only).map(:to_sym))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
group = options.fetch(:group, :general)
|
37
|
+
|
38
|
+
send(definer_method, name) do
|
39
|
+
accessor = extended_attribute_accessors(group)[name.to_sym] || {}
|
40
|
+
accessor[:value] || accessor[:default]
|
41
|
+
end
|
42
|
+
|
43
|
+
define_method("#{name}=") do |value|
|
44
|
+
self[name] = value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Util
|
5
|
+
module FieldAccessors
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(ClassMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def field_accessor(name)
|
12
|
+
define_method(name) do
|
13
|
+
self[name]
|
14
|
+
end
|
15
|
+
|
16
|
+
define_method("#{name}=") do |value|
|
17
|
+
self[name] = value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def fields
|
23
|
+
@fields ||= {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](key)
|
27
|
+
fields[key.to_s]
|
28
|
+
end
|
29
|
+
|
30
|
+
def []=(key, value)
|
31
|
+
fields[key.to_s] = value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Petra
|
4
|
+
module Util
|
5
|
+
#
|
6
|
+
# Helper module to add register functionality to a class
|
7
|
+
# This means that other classes may register themselves under a certain name
|
8
|
+
#
|
9
|
+
module Registrable
|
10
|
+
def self.included(base)
|
11
|
+
base.extend ClassMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
#
|
16
|
+
# Generates helper methods from the given name.
|
17
|
+
#
|
18
|
+
# @example Type register
|
19
|
+
# acts_as_register(:type)
|
20
|
+
# => registered_types
|
21
|
+
# => register_type(type)
|
22
|
+
# => registered_type(type) #=> value
|
23
|
+
# => registered_type?(type) #=> true/false
|
24
|
+
#
|
25
|
+
def acts_as_register(name)
|
26
|
+
name = name.to_s
|
27
|
+
|
28
|
+
define_singleton_method("registered_#{name.pluralize}") do
|
29
|
+
@registered_components ||= {}
|
30
|
+
@registered_components[name.to_s] ||= {}
|
31
|
+
end
|
32
|
+
|
33
|
+
define_singleton_method("registered_#{name}") do |key|
|
34
|
+
send("registered_#{name.pluralize}")[key.to_s]
|
35
|
+
end
|
36
|
+
|
37
|
+
define_singleton_method("register_#{name}") do |key, value|
|
38
|
+
send("registered_#{name.pluralize}")[key.to_s] = value
|
39
|
+
end
|
40
|
+
|
41
|
+
define_singleton_method("registered_#{name}?") do |key|
|
42
|
+
send("registered_#{name.pluralize}").key?(key.to_s)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/petra.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/all'
|
4
|
+
require 'method_source'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
require 'petra/core_ext'
|
8
|
+
require 'petra/exceptions'
|
9
|
+
require 'petra/configuration/base'
|
10
|
+
require 'petra/configuration/class_configurator'
|
11
|
+
require 'petra/util/debug'
|
12
|
+
require 'petra/persistence_adapters/file_adapter'
|
13
|
+
|
14
|
+
require 'petra/proxies/enumerable_proxy'
|
15
|
+
require 'petra/proxies/object_proxy'
|
16
|
+
|
17
|
+
require 'petra/components/transaction_manager'
|
18
|
+
|
19
|
+
require 'petra/components/entries/attribute_change'
|
20
|
+
require 'petra/components/entries/attribute_change_veto'
|
21
|
+
require 'petra/components/entries/attribute_read'
|
22
|
+
require 'petra/components/entries/object_destruction'
|
23
|
+
require 'petra/components/entries/object_initialization'
|
24
|
+
require 'petra/components/entries/object_persistence'
|
25
|
+
require 'petra/components/entries/read_integrity_override'
|
26
|
+
|
27
|
+
require 'forwardable'
|
28
|
+
|
29
|
+
module Petra
|
30
|
+
extend SingleForwardable
|
31
|
+
|
32
|
+
def self.root
|
33
|
+
Pathname.new(File.dirname(__FILE__))
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# @return [Petra::Configuration::Base] petra's configuration instance
|
38
|
+
#
|
39
|
+
def self.configuration
|
40
|
+
@configuration ||= Petra::Configuration::Base.new
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Executes the given block in the context of petra's configuration instance
|
45
|
+
#
|
46
|
+
def self.configure(&proc)
|
47
|
+
configuration.instance_eval(&proc) if block_given?
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Forward transaction handling to the TransactionManager class
|
52
|
+
#
|
53
|
+
# @see Petra::Components::TransactionManager#with_transaction
|
54
|
+
#
|
55
|
+
def self.transaction(identifier: nil, &block)
|
56
|
+
Petra::Components::TransactionManager.with_transaction(identifier: identifier || SecureRandom.uuid, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# @return [Boolean] +true+ if a transaction is currently running
|
61
|
+
#
|
62
|
+
def self.transaction_running?
|
63
|
+
Petra::Components::TransactionManager.instance?
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Attempts to commit the currently active transaction
|
68
|
+
#
|
69
|
+
def self.commit!
|
70
|
+
transaction_manager.commit_transaction
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# @return [Petra::Components::TransactionManager, NilClass]
|
75
|
+
#
|
76
|
+
def self.transaction_manager
|
77
|
+
Petra::Components::TransactionManager.instance
|
78
|
+
end
|
79
|
+
|
80
|
+
def_delegator :transaction_manager, :current_transaction
|
81
|
+
|
82
|
+
#
|
83
|
+
# Logs the given +message+
|
84
|
+
#
|
85
|
+
def self.logger
|
86
|
+
Petra::Util::Debug
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.rails?
|
90
|
+
defined?(Rails)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Extend the Object class to add the `petra` proxy generator
|
95
|
+
Object.class_eval do
|
96
|
+
include Petra::CoreExt::Object
|
97
|
+
end
|
98
|
+
|
99
|
+
# Register Persistence Adapters
|
100
|
+
Petra::PersistenceAdapters::Adapter.register_adapter(:file, Petra::PersistenceAdapters::FileAdapter)
|
data/petra.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'petra/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'petra_core'
|
9
|
+
spec.version = Petra::VERSION
|
10
|
+
spec.authors = ['Stefan Exner']
|
11
|
+
spec.email = ['stex@sterex.de']
|
12
|
+
|
13
|
+
spec.summary = 'Proof-Of-Concept for temporarily persisted transactions in Ruby'
|
14
|
+
spec.homepage = 'https://github.com/stex/petra'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
|
21
|
+
spec.bindir = 'exe'
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.required_ruby_version = '~> 2.5'
|
26
|
+
|
27
|
+
spec.add_dependency 'activesupport', '~> 4.2'
|
28
|
+
spec.add_dependency 'method_source', '~> 0.9.0'
|
29
|
+
|
30
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
31
|
+
spec.add_development_dependency 'faker', '~> 1.8'
|
32
|
+
spec.add_development_dependency 'pry', '~> 0.11'
|
33
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
34
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
35
|
+
spec.add_development_dependency 'rubocop', '~> 0.53.0'
|
36
|
+
end
|