gexp 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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/gexp.gemspec +32 -0
- data/lib/gexp.rb +16 -0
- data/lib/gexp/command.rb +50 -0
- data/lib/gexp/command/object.rb +37 -0
- data/lib/gexp/command/stack.rb +50 -0
- data/lib/gexp/handler.rb +25 -0
- data/lib/gexp/handler/caller.rb +8 -0
- data/lib/gexp/handler/check.rb +9 -0
- data/lib/gexp/handler/check/item.rb +34 -0
- data/lib/gexp/handler/check/resources.rb +56 -0
- data/lib/gexp/handler/modify.rb +9 -0
- data/lib/gexp/handler/modify/resources.rb +81 -0
- data/lib/gexp/handler/producer.rb +61 -0
- data/lib/gexp/handler/transition.rb +44 -0
- data/lib/gexp/handler/transition/builder.rb +33 -0
- data/lib/gexp/mongoid.rb +4 -0
- data/lib/gexp/mongoid/transaction.rb +266 -0
- data/lib/gexp/object.rb +39 -0
- data/lib/gexp/receiver.rb +33 -0
- data/lib/gexp/state_definition.rb +4 -0
- data/lib/gexp/state_definition/state_machine.rb +90 -0
- data/lib/gexp/version.rb +3 -0
- data/spec/gexp/command/object_spec.rb +84 -0
- data/spec/gexp/command/stack_spec.rb +74 -0
- data/spec/gexp/command_spec.rb +59 -0
- data/spec/gexp/handler/check/item_spec.rb +45 -0
- data/spec/gexp/handler/check/resources_spec.rb +81 -0
- data/spec/gexp/handler/check_spec.rb +5 -0
- data/spec/gexp/handler/modify/resources_spec.rb +96 -0
- data/spec/gexp/handler/modify_spec.rb +6 -0
- data/spec/gexp/handler/producer_spec.rb +85 -0
- data/spec/gexp/handler/transition/builder_spec.rb +122 -0
- data/spec/gexp/handler/transition_spec.rb +14 -0
- data/spec/gexp/mongoid/transaction_spec.rb +210 -0
- data/spec/gexp/state_definition/state_machine_spec.rb +48 -0
- data/spec/spec_helper.rb +10 -0
- metadata +210 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
module Gexp
|
2
|
+
class Handler
|
3
|
+
|
4
|
+
# Инкапсулирцует логику по созданию
|
5
|
+
# объектов-обработчиков команд на основании
|
6
|
+
# выражения-генератора полученного Builder'ом
|
7
|
+
# из конфигурационного файл объекта
|
8
|
+
class Producer
|
9
|
+
|
10
|
+
def self.namespaces
|
11
|
+
{
|
12
|
+
chekers: Gexp::Handler::Check,
|
13
|
+
modifiers: Gexp::Handler::Modify,
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
#
|
19
|
+
# params - handler params { klass, object, args }
|
20
|
+
# type - chekers|modifiers # TODO: избавиться от этого параметра
|
21
|
+
# objects - { object: <...>, subject: <...>, provider: <...> }
|
22
|
+
def initialize(params, type = nil, objects = {})
|
23
|
+
@params = params
|
24
|
+
@type = type
|
25
|
+
@objects = objects
|
26
|
+
|
27
|
+
if !for_klass? && !for_caller?
|
28
|
+
raise 'Can\'t define handler class'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def emit
|
33
|
+
args = @params.clone
|
34
|
+
|
35
|
+
if for_klass?
|
36
|
+
superclass = self.class.namespaces[@type]
|
37
|
+
subclass = args.shift.to_s.classify
|
38
|
+
klass = superclass.const_get(subclass)
|
39
|
+
else
|
40
|
+
klass = Gexp::Handler::Caller
|
41
|
+
end
|
42
|
+
|
43
|
+
obj_key = args.shift
|
44
|
+
object = @objects[obj_key]
|
45
|
+
params = args
|
46
|
+
handler = klass.new(object, params, @objects, @params)
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
def for_klass?
|
52
|
+
@params.size == 3
|
53
|
+
end
|
54
|
+
|
55
|
+
def for_caller?
|
56
|
+
@params.size == 2
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Gexp
|
2
|
+
class Handler
|
3
|
+
class Transition
|
4
|
+
|
5
|
+
attr_accessor :transition, :config, :object, :subject, :provider
|
6
|
+
|
7
|
+
def self.namespaces
|
8
|
+
@namespaces ||= {
|
9
|
+
checkers: Gexp::Handler::Check,
|
10
|
+
modifiers: Gexp::Handler::Modify,
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
# transition - объект транзакции от стейт машины
|
15
|
+
# object - объект у которого вызывается событие стейтмашины
|
16
|
+
# subject - объект кто вызывает событие (всегда User)
|
17
|
+
# provider - объект которому принадлежит object (иногда User иногда Friend)
|
18
|
+
#
|
19
|
+
# Выбор конечного объекта определяется конфигом object
|
20
|
+
def initialize(transition, object = nil, subject = nil, provider = nil)
|
21
|
+
@transition = transition
|
22
|
+
|
23
|
+
@object = object
|
24
|
+
@subject = subject
|
25
|
+
@provider = provider
|
26
|
+
|
27
|
+
@config = @object.config
|
28
|
+
end
|
29
|
+
|
30
|
+
def produce(params, type)
|
31
|
+
producer = Gexp::Handler::Producer.new params, type
|
32
|
+
producer.emit
|
33
|
+
end
|
34
|
+
|
35
|
+
def handlers(type = nil)
|
36
|
+
type = :checkers
|
37
|
+
(self.send(type) || []).map do |handler_params|
|
38
|
+
self.produce(handler_params, type)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Gexp
|
2
|
+
class Handler
|
3
|
+
class Transition
|
4
|
+
|
5
|
+
# Возвращает обработчики
|
6
|
+
# в зависимости от перехода FSM (StateMachine)
|
7
|
+
class Builder < self
|
8
|
+
|
9
|
+
def events(last_key)
|
10
|
+
event = @config[:events][@transition.event]
|
11
|
+
(event || {})[last_key] || []
|
12
|
+
end
|
13
|
+
|
14
|
+
def transitions(last_key)
|
15
|
+
from = @transition.from_name
|
16
|
+
to = @transition.to_name
|
17
|
+
from_branch = @config[:transitions][from] || {}
|
18
|
+
(from_branch[to] || {})[last_key] || []
|
19
|
+
end
|
20
|
+
|
21
|
+
def checkers
|
22
|
+
events(:check) + transitions(:check)
|
23
|
+
end
|
24
|
+
|
25
|
+
def modifiers
|
26
|
+
events(:modify) + transitions(:modify)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/gexp/mongoid.rb
ADDED
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
# Gexp::Mongoid::Transaction::Observer.started?
|
4
|
+
|
5
|
+
module Gexp
|
6
|
+
module Mongoid
|
7
|
+
module Transaction
|
8
|
+
|
9
|
+
SYSTEM_FIELDS = [
|
10
|
+
:_updated,
|
11
|
+
:_version,
|
12
|
+
:_transaction_id,
|
13
|
+
]
|
14
|
+
|
15
|
+
class Observer #TODO: Переделать на контекст
|
16
|
+
class << self
|
17
|
+
|
18
|
+
def runned
|
19
|
+
@runned
|
20
|
+
end
|
21
|
+
|
22
|
+
def started?
|
23
|
+
@runned ||= false
|
24
|
+
end
|
25
|
+
|
26
|
+
def start_transaction!(transaction)
|
27
|
+
@runned = transaction
|
28
|
+
end
|
29
|
+
|
30
|
+
def finish_transaction!
|
31
|
+
@runned = false
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Instance
|
38
|
+
|
39
|
+
attr_accessor :operation, :error
|
40
|
+
include ::Mongoid::Document
|
41
|
+
|
42
|
+
has_many :objects, class_name: 'Gexp::Mongoid::Transaction::Base'
|
43
|
+
|
44
|
+
def initialize(*args)
|
45
|
+
super(*args)
|
46
|
+
Observer.start_transaction!(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete
|
50
|
+
Observer.finish_transaction!
|
51
|
+
super
|
52
|
+
end
|
53
|
+
|
54
|
+
def commit
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
class << self
|
60
|
+
|
61
|
+
def with(*objects, &block)
|
62
|
+
|
63
|
+
begin
|
64
|
+
self.start_transaction(objects)
|
65
|
+
block.call self
|
66
|
+
Observer.finish_transaction!
|
67
|
+
self.transaction.objects.map(&:save)
|
68
|
+
self.end_transaction
|
69
|
+
rescue => e
|
70
|
+
raise
|
71
|
+
ensure
|
72
|
+
if @transaction
|
73
|
+
@transaction.delete unless @transaction.deleted?
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
def transaction
|
80
|
+
@transaction
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
def start_transaction(objects)
|
86
|
+
@transaction = Instance.create
|
87
|
+
objects.each { |obj|
|
88
|
+
obj.transaction ||= @transaction
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
def end_transaction
|
93
|
+
@transaction.delete
|
94
|
+
@transaction.objects.map(&:transaction_commit)
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
#extend ActiveSupport::Concern
|
100
|
+
|
101
|
+
class Base
|
102
|
+
|
103
|
+
class << self
|
104
|
+
def inherited(sub)
|
105
|
+
sub.instance_eval do
|
106
|
+
|
107
|
+
def field(name, *args)
|
108
|
+
super # вызов Mongoid::Document#field
|
109
|
+
|
110
|
+
return if SYSTEM_FIELDS.include? name.to_sym
|
111
|
+
|
112
|
+
field_setter = "#{name}=".to_sym
|
113
|
+
field_getter = "#{name}"
|
114
|
+
|
115
|
+
# переопределяем сеттер
|
116
|
+
define_method(field_setter) do |val|
|
117
|
+
|
118
|
+
if Observer.started?
|
119
|
+
self.transaction ||= Observer.runned
|
120
|
+
end
|
121
|
+
|
122
|
+
if self.new_record?
|
123
|
+
super(val)
|
124
|
+
else
|
125
|
+
if Observer.started?
|
126
|
+
# Создание грязной версии если еще нет
|
127
|
+
self.create_update
|
128
|
+
self._updated[name.to_s] = val
|
129
|
+
else
|
130
|
+
super(val)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
define_method(field_getter) do
|
136
|
+
# Если транзакция началась и текущий объект не _updated
|
137
|
+
if Observer.started? && self._updated
|
138
|
+
self._updated[name.to_s]
|
139
|
+
else
|
140
|
+
super()
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
include ::Mongoid::Document
|
150
|
+
|
151
|
+
field :_version, type: Integer, default: 0
|
152
|
+
field :_updated, type: Hash
|
153
|
+
|
154
|
+
belongs_to :transaction, class_name: 'Gexp::Mongoid::Transaction::Instance', foreign_key: :_transaction_id
|
155
|
+
|
156
|
+
after_initialize :check_dirty, :if => lambda { |o| not o.new_record? }
|
157
|
+
|
158
|
+
def can_save?
|
159
|
+
self.new_record? || (not Observer.started?)
|
160
|
+
end
|
161
|
+
|
162
|
+
def save
|
163
|
+
return super if can_save?
|
164
|
+
true
|
165
|
+
end
|
166
|
+
|
167
|
+
#def save!
|
168
|
+
# return super if can_save?
|
169
|
+
# true
|
170
|
+
#end
|
171
|
+
|
172
|
+
def create_update
|
173
|
+
self._updated ||= self.attributes.except('_id', '_type')
|
174
|
+
end
|
175
|
+
|
176
|
+
# Вызывается только тогда, когда все объекты сохраненны
|
177
|
+
def transaction_commit
|
178
|
+
if self._updated
|
179
|
+
self.updated_apply!
|
180
|
+
self.clear_updated!
|
181
|
+
self._version += 1
|
182
|
+
self._transaction_id = nil
|
183
|
+
self.save
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def clear_updated!
|
188
|
+
self._updated = nil
|
189
|
+
end
|
190
|
+
|
191
|
+
def updated_apply!
|
192
|
+
self.attributes = self.attributes.merge(self._updated)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Проверка объекта на незаконченную транзакцию
|
196
|
+
# если объект "грязный", то чистим его
|
197
|
+
def check_dirty
|
198
|
+
self.clear! if self.dirty?
|
199
|
+
end
|
200
|
+
|
201
|
+
def clear_commited!
|
202
|
+
self.updated_apply!
|
203
|
+
self.clear_updated!
|
204
|
+
self._version += 1
|
205
|
+
self._transaction_id = nil
|
206
|
+
end
|
207
|
+
|
208
|
+
def clear_uncommited!
|
209
|
+
_transaction_id = Observer.started? ? Observer.runned.id : nil
|
210
|
+
self._transaction_id = _transaction_id
|
211
|
+
self.clear_updated!
|
212
|
+
end
|
213
|
+
|
214
|
+
def clear!
|
215
|
+
if self.dirty_commited?
|
216
|
+
self.clear_commited!
|
217
|
+
elsif self.dirty_uncommited?
|
218
|
+
self.clear_uncommited!
|
219
|
+
end
|
220
|
+
|
221
|
+
self.save
|
222
|
+
end
|
223
|
+
|
224
|
+
def dirty?
|
225
|
+
self.dirty_commited? ||
|
226
|
+
self.dirty_uncommited?
|
227
|
+
end
|
228
|
+
|
229
|
+
# Чистый
|
230
|
+
def clear?
|
231
|
+
self._updated.nil? && self._transaction_id.nil?
|
232
|
+
end
|
233
|
+
|
234
|
+
# Существование у объекта транзакции
|
235
|
+
def exist_transaction?
|
236
|
+
return false unless self._transaction_id
|
237
|
+
Instance.find(self._transaction_id)
|
238
|
+
true
|
239
|
+
rescue ::Mongoid::Errors::DocumentNotFound
|
240
|
+
false
|
241
|
+
end
|
242
|
+
|
243
|
+
# Грязный закоммиченный
|
244
|
+
def dirty_commited?
|
245
|
+
self.update_exist? &&
|
246
|
+
self._transaction_id.present? &&
|
247
|
+
(not self.exist_transaction?)
|
248
|
+
end
|
249
|
+
|
250
|
+
# Грязный незакоммиченный
|
251
|
+
def dirty_uncommited?
|
252
|
+
self.update_exist? &&
|
253
|
+
self._transaction_id.present? &&
|
254
|
+
self.exist_transaction?
|
255
|
+
end
|
256
|
+
|
257
|
+
# Сущетвование не закоммиченной версии
|
258
|
+
def update_exist?
|
259
|
+
self._updated.present?
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
data/lib/gexp/object.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.join(APP_ROOT, 'lib', 'gexp', 'state_definition')
|
2
|
+
require File.join(APP_ROOT, 'lib', 'gexp', 'state_definition', 'state_machine')
|
3
|
+
|
4
|
+
module Gexp
|
5
|
+
module Object
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include ::Gexp::StateDefinition::StateMachine
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
@config_name = base.name.gsub('::', '.').underscore
|
11
|
+
@end_class_name = base.name.split('::').last
|
12
|
+
|
13
|
+
@config = Configuration.for(@config_name)
|
14
|
+
|
15
|
+
base.instance_variable_set("@config_name", @config_name)
|
16
|
+
base.instance_variable_set("@config", @config)
|
17
|
+
|
18
|
+
if @config.respond_to? :states
|
19
|
+
base.define_state_by @config.states.to_hash
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def config
|
24
|
+
self.class.config
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
|
29
|
+
def config_name
|
30
|
+
@config_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def config
|
34
|
+
@config
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|