event_sourced_accounting 0.1.6 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -34,16 +34,28 @@ module ESA
34
34
 
35
35
  def self.produce_events(accountable)
36
36
  ruleset = Ruleset.extension_instance(accountable)
37
+
37
38
  if ruleset.present?
38
- last_event_time = accountable.esa_events.maximum(:time)
39
- unrecorded_events = ruleset.unrecorded_events_as_attributes(accountable)
40
- valid_events = unrecorded_events.select{|e| last_event_time.nil? or e[:time] >= last_event_time}
41
- accountable.esa_events.new(valid_events)
39
+ events = ruleset.addable_unrecorded_events_as_attributes(accountable)
40
+ accountable.esa_events.new(events)
42
41
  else
43
42
  []
44
43
  end
45
44
  end
46
45
 
46
+ def self.event_attrs_with_adjustment(ruleset, accountable, events)
47
+ if ruleset.is_adjustment_event_needed?(accountable)
48
+ events.unshift({
49
+ accountable: accountable,
50
+ ruleset: ruleset,
51
+ nature: :adjustment,
52
+ time: [events.map{|e| e[:time]}.min, Time.zone.now].compact.min,
53
+ })
54
+ else
55
+ events
56
+ end
57
+ end
58
+
47
59
  def self.process_event(event)
48
60
  flags_created = create_flags(event)
49
61
 
@@ -102,14 +114,7 @@ module ESA
102
114
  flag.adjusted = true
103
115
  flag.adjustment_time = event.time
104
116
 
105
- attrs = {
106
- :accountable => event.accountable,
107
- :nature => flag.nature,
108
- :state => flag.state,
109
- :event => event,
110
- }
111
-
112
- adjustment = event.accountable.esa_flags.new(attrs)
117
+ adjustment = initialize_adjustment_flag(event, flag)
113
118
  event.flags << adjustment
114
119
 
115
120
  [flag, adjustment]
@@ -119,6 +124,17 @@ module ESA
119
124
  end
120
125
  end
121
126
 
127
+ def self.initialize_adjustment_flag(event, flag)
128
+ attrs = {
129
+ :accountable => event.accountable,
130
+ :nature => flag.nature,
131
+ :state => flag.state,
132
+ :event => event,
133
+ }
134
+
135
+ event.accountable.esa_flags.new(attrs)
136
+ end
137
+
122
138
  def self.produce_flags_for_regular(event)
123
139
  if event.ruleset.present?
124
140
  existing_flags = event.flags.all
@@ -157,19 +173,18 @@ module ESA
157
173
  end
158
174
 
159
175
  def self.produce_transactions(flag)
160
- if flag.ruleset.present? and flag.transition.present? and flag.transition.in? [-1, 0, 1]
161
- existing_transactions = flag.transactions.all
162
- required_transactions = flag.ruleset.flag_transactions_as_attributes(flag)
163
-
164
- required_transactions.map do |attrs|
165
- transaction = existing_transactions.find{|tx| tx.matches_spec?(attrs)}
176
+ txs = flag.accountable.esa_transactions.new(missing_transactions(flag))
177
+ flag.transactions += txs
178
+ txs
179
+ end
166
180
 
167
- if transaction.nil?
168
- transaction = flag.accountable.esa_transactions.new(attrs)
169
- flag.transactions << transaction
170
- end
181
+ def self.missing_transactions(flag)
182
+ if flag.ruleset.present? and flag.transition.present? and flag.transition.in? [-1, 0, 1]
183
+ existing = flag.transactions.all
184
+ required = flag.ruleset.flag_transactions_as_attributes(flag)
171
185
 
172
- transaction
186
+ required.reject do |attrs|
187
+ existing.find{|tx| tx.matches_spec?(attrs)}.present?
173
188
  end
174
189
  else
175
190
  []
@@ -16,6 +16,7 @@ module ESA
16
16
  attr_accessor :context_freshness_threshold
17
17
  attr_accessor :context_providers
18
18
  attr_accessor :context_tree
19
+ attr_accessor :context_walk_ignore
19
20
 
20
21
  def initialize
21
22
  @base_classes = [ESA::Ruleset, ESA::Event, ESA::Flag, ESA::Transaction].freeze
@@ -34,25 +35,37 @@ module ESA
34
35
  'account' => ESA::ContextProviders::AccountContextProvider,
35
36
  'accountable' => ESA::ContextProviders::AccountableContextProvider,
36
37
  'accountable_type' => ESA::ContextProviders::AccountableTypeContextProvider,
37
- 'monthly' => [ESA::ContextProviders::DateContextProvider, {period: :month}],
38
- 'daily' => [ESA::ContextProviders::DateContextProvider, {period: :day}],
38
+ 'month' => [ESA::ContextProviders::DateContextProvider, {period: :month}],
39
+ 'date' => [ESA::ContextProviders::DateContextProvider, {period: :date}],
39
40
  }
40
41
 
41
42
  @context_tree = {
42
43
  'account' => {
43
- 'monthly' => {
44
- 'daily' => {},
44
+ 'month' => {
45
+ 'date' => {},
45
46
  },
46
47
  },
47
- 'monthly' => {
48
+ 'period' => {
48
49
  'account' => {
49
- 'daily' => {},
50
+ 'date' => {},
50
51
  },
51
52
  },
52
- 'daily' => {
53
+ 'year' => {
54
+ 'account' => {
55
+ 'date' => {},
56
+ },
57
+ },
58
+ 'month' => {
59
+ 'account' => {
60
+ 'date' => {},
61
+ },
62
+ },
63
+ 'date' => {
53
64
  'account' => {},
54
65
  },
55
66
  }
67
+
68
+ @context_walk_ignore = ['filter']
56
69
  end
57
70
 
58
71
  def register(accountable, short_name=nil)
@@ -83,7 +96,8 @@ module ESA
83
96
  end
84
97
 
85
98
  def context_providers_for_path(path=[])
86
- @context_providers.slice(*self.walk_context_tree(path).keys)
99
+ clean_path = path - context_walk_ignore
100
+ @context_providers.slice(*self.walk_context_tree(clean_path).keys)
87
101
  end
88
102
  end
89
103
  end
@@ -38,7 +38,8 @@ module ESA
38
38
 
39
39
  def self.contained_subcontexts(context, namespace, existing, options = {})
40
40
  contained_ids = contained_ids(context, options)
41
- existing_ids = existing.map{|sub| context_id(sub, options)}
41
+ existing_grouped = existing.group_by{|sub| context_id(sub, options)}
42
+ existing_ids = existing_grouped.keys
42
43
 
43
44
  new_ids = contained_ids - existing_ids
44
45
 
@@ -46,7 +47,11 @@ module ESA
46
47
  instantiate(context, namespace, id, options)
47
48
  end
48
49
 
49
- new_subcontexts + existing.select{|sub| context_id(sub).in? contained_ids}
50
+ keep_subcontexts = existing_grouped.map do |id,group|
51
+ (id.in? contained_ids) ? group.first : nil
52
+ end.compact
53
+
54
+ new_subcontexts + keep_subcontexts
50
55
  end
51
56
  end
52
57
  end
@@ -34,7 +34,12 @@ module ESA
34
34
  scope :with_accountable, lambda { |accountable,type|
35
35
  joins(:transaction).where(esa_transactions: {accountable_id: accountable, accountable_type: type})
36
36
  }
37
+ scope :excl_accountable, lambda { |accountable,type|
38
+ joins(:transaction).where(ESA::Transaction.arel_table[:accountable_id].not_eq(accountable), ESA::Transaction.arel_table[:accountable_type].not_eq(type))
39
+ }
40
+
37
41
  scope :with_accountable_def, lambda { |definitions| joins(:transaction).joins("INNER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `esa_transactions`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `esa_transactions`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`") }
42
+ scope :excl_accountable_def, lambda { |definitions| joins(:transaction).joins("LEFT OUTER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `esa_transactions`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `esa_transactions`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`").where("`accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` IS NULL") }
38
43
  end
39
44
  end
40
45
 
@@ -45,7 +50,12 @@ module ESA
45
50
  scope :with_accountable, lambda { |accountable,type|
46
51
  where(accountable_id: accountable, accountable_type: type)
47
52
  }
53
+ scope :excl_accountable, lambda { |accountable,type|
54
+ where(arel_table[:accountable_id].not_eq(accountable), arel_table[:accountable_type].not_eq(type))
55
+ }
56
+
48
57
  scope :with_accountable_def, lambda { |definitions| joins("INNER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `#{table_name}`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `#{table_name}`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`") }
58
+ scope :excl_accountable_def, lambda { |definitions| joins("LEFT OUTER JOIN (#{ESA::Filters::AccountableFilter.make_union_query(definitions)}) `accountables-#{(hash ^ definitions.hash).to_s(36)}` ON `#{table_name}`.`accountable_id` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` AND `#{table_name}`.`accountable_type` = `accountables-#{(hash ^ definitions.hash).to_s(36)}`.`type`").where("`accountables-#{(hash ^ definitions.hash).to_s(36)}`.`id` IS NULL") }
49
59
  end
50
60
  end
51
61
  end
@@ -6,6 +6,7 @@ module ESA
6
6
 
7
7
  included do
8
8
  scope :with_accountable_type, lambda { |type| joins(:transaction).where(esa_transactions: {accountable_type: type}) }
9
+ scope :excl_accountable_type, lambda { |type| joins(:transaction).where(ESA::Transaction.arel_table[:accountable_type].not_eq(type)) }
9
10
  end
10
11
  end
11
12
 
@@ -14,6 +15,7 @@ module ESA
14
15
 
15
16
  included do
16
17
  scope :with_accountable_type, lambda { |type| where(accountable_type: type) }
18
+ scope :excl_accountable_type, lambda { |type| where(arel_table[:accountable_type].not_eq(type)) }
17
19
  end
18
20
  end
19
21
  end
@@ -1,13 +1,25 @@
1
1
  module ESA
2
2
  class SubcontextChecker
3
- def self.check(context)
4
- ESA.configuration.context_providers_for_path(context.effective_path).each do |namespace,provider|
3
+ def self.providers(context, options = {})
4
+ if :namespace.in? options
5
+ if options[:namespace].respond_to? :each
6
+ ESA.configuration.context_providers.slice(*options[:namespace])
7
+ else
8
+ ESA.configuration.context_providers.slice("#{options[:namespace]}")
9
+ end
10
+ else
11
+ ESA.configuration.context_providers_for_path(context.effective_path)
12
+ end
13
+ end
14
+
15
+ def self.check(context, options = {})
16
+ providers(context, options).each do |namespace,provider|
5
17
  if provider.is_a? Class and provider.respond_to? :check_subcontexts
6
18
  provider.check_subcontexts(context, namespace)
7
- elsif provider.respond_to? :count and provider.count == 2 and
19
+ elsif provider.respond_to? :count and provider.count == 2 and
8
20
  provider[0].is_a? Class and provider[0].respond_to? :check_subcontexts and provider[1].is_a? Hash
9
- klass, options = provider
10
- klass.check_subcontexts(context, namespace, options)
21
+ provider_klass, provider_options = provider
22
+ provider_klass.check_subcontexts(context, namespace, provider_options)
11
23
  end
12
24
  end
13
25
  end
data/lib/esa/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module ESA
2
- VERSION = "0.1.6"
2
+ VERSION = "0.2.2"
3
3
  end