gexp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +29 -0
  5. data/Rakefile +1 -0
  6. data/gexp.gemspec +32 -0
  7. data/lib/gexp.rb +16 -0
  8. data/lib/gexp/command.rb +50 -0
  9. data/lib/gexp/command/object.rb +37 -0
  10. data/lib/gexp/command/stack.rb +50 -0
  11. data/lib/gexp/handler.rb +25 -0
  12. data/lib/gexp/handler/caller.rb +8 -0
  13. data/lib/gexp/handler/check.rb +9 -0
  14. data/lib/gexp/handler/check/item.rb +34 -0
  15. data/lib/gexp/handler/check/resources.rb +56 -0
  16. data/lib/gexp/handler/modify.rb +9 -0
  17. data/lib/gexp/handler/modify/resources.rb +81 -0
  18. data/lib/gexp/handler/producer.rb +61 -0
  19. data/lib/gexp/handler/transition.rb +44 -0
  20. data/lib/gexp/handler/transition/builder.rb +33 -0
  21. data/lib/gexp/mongoid.rb +4 -0
  22. data/lib/gexp/mongoid/transaction.rb +266 -0
  23. data/lib/gexp/object.rb +39 -0
  24. data/lib/gexp/receiver.rb +33 -0
  25. data/lib/gexp/state_definition.rb +4 -0
  26. data/lib/gexp/state_definition/state_machine.rb +90 -0
  27. data/lib/gexp/version.rb +3 -0
  28. data/spec/gexp/command/object_spec.rb +84 -0
  29. data/spec/gexp/command/stack_spec.rb +74 -0
  30. data/spec/gexp/command_spec.rb +59 -0
  31. data/spec/gexp/handler/check/item_spec.rb +45 -0
  32. data/spec/gexp/handler/check/resources_spec.rb +81 -0
  33. data/spec/gexp/handler/check_spec.rb +5 -0
  34. data/spec/gexp/handler/modify/resources_spec.rb +96 -0
  35. data/spec/gexp/handler/modify_spec.rb +6 -0
  36. data/spec/gexp/handler/producer_spec.rb +85 -0
  37. data/spec/gexp/handler/transition/builder_spec.rb +122 -0
  38. data/spec/gexp/handler/transition_spec.rb +14 -0
  39. data/spec/gexp/mongoid/transaction_spec.rb +210 -0
  40. data/spec/gexp/state_definition/state_machine_spec.rb +48 -0
  41. data/spec/spec_helper.rb +10 -0
  42. 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
@@ -0,0 +1,4 @@
1
+ module Gexp
2
+ module Mongoid
3
+ end
4
+ end
@@ -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
@@ -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