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.
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