card-mod-history 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/card/act.rb +137 -0
- data/lib/card/act/act_renderer.rb +217 -0
- data/lib/card/act/act_renderer/absolute_act_renderer.rb +34 -0
- data/lib/card/act/act_renderer/bridge_act_renderer.rb +53 -0
- data/lib/card/act/act_renderer/relative_act_renderer.rb +59 -0
- data/lib/card/action.rb +230 -0
- data/lib/card/action/action_renderer.rb +94 -0
- data/lib/card/action/admin.rb +36 -0
- data/lib/card/action/differ.rb +89 -0
- data/lib/card/change.rb +70 -0
- data/set/all/history.rb +109 -0
- data/set/all/history/act_listing.rb +124 -0
- data/set/all/history/actions.rb +124 -0
- data/set/all/history/acts.rb +8 -0
- data/set/all/history/events.rb +100 -0
- data/set/all/history/last.rb +98 -0
- data/set/all/history/revision.rb +65 -0
- data/set/all/history/selected.rb +64 -0
- data/set/all/history/views.rb +35 -0
- data/set/all/history_bridge.rb +65 -0
- metadata +80 -0
data/lib/card/change.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'activerecord-import'
|
3
|
+
|
4
|
+
class Card
|
5
|
+
# A _change_ is an alteration to a card's name, type, content, or trash state.
|
6
|
+
# Together, {Act acts}, {Action actions}, and {Change changes} comprise a
|
7
|
+
# comprehensive {Card card} history tracking system.
|
8
|
+
#
|
9
|
+
# For example, if a given web submission changes both the name and type of
|
10
|
+
# card, that would be recorded as one {Action action} with two
|
11
|
+
# {Change changes}.
|
12
|
+
#
|
13
|
+
# A {Change} records:
|
14
|
+
#
|
15
|
+
# * the _field_ changed
|
16
|
+
# * the new _value_ of that field
|
17
|
+
# * the {Action action} of which the change is part
|
18
|
+
#
|
19
|
+
class Change < ApplicationRecord
|
20
|
+
belongs_to :action, foreign_key: :card_action_id,
|
21
|
+
inverse_of: :card_changes
|
22
|
+
|
23
|
+
# lists the database fields for which changes are recorded
|
24
|
+
TRACKED_FIELDS = %w[name type_id db_content trash left_id right_id].freeze
|
25
|
+
|
26
|
+
class << self
|
27
|
+
# delete all {Change changes} not associated with an {Action action}
|
28
|
+
# (janitorial)
|
29
|
+
def delete_actionless
|
30
|
+
joins(
|
31
|
+
"LEFT JOIN card_actions "\
|
32
|
+
"ON card_changes.card_action_id = card_actions.id "
|
33
|
+
).where(
|
34
|
+
"card_actions.id is null"
|
35
|
+
).pluck_in_batches(:id) do |group_ids|
|
36
|
+
# used to be .delete_all here, but that was failing on large dbs
|
37
|
+
Rails.logger.info "deleting batch of changes"
|
38
|
+
where("id in (#{group_ids.join ','})").delete_all
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Change fields are recorded as integers. #field_index looks up the
|
43
|
+
# integer associated with a given field name.
|
44
|
+
# @param value [String, Symbol]
|
45
|
+
# @return [Integer]
|
46
|
+
def field_index value
|
47
|
+
value.is_a?(Integer) ? value : TRACKED_FIELDS.index(value.to_s)
|
48
|
+
end
|
49
|
+
|
50
|
+
# look up changes based on field name
|
51
|
+
# @param value [String, Symbol]
|
52
|
+
# @return [Change]
|
53
|
+
def find_by_field_name value
|
54
|
+
find_by_field field_index(value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# set field value (integer)
|
59
|
+
# @param value [String, Symbol]
|
60
|
+
def field= value
|
61
|
+
write_attribute(:field, TRACKED_FIELDS.index(value.to_s))
|
62
|
+
end
|
63
|
+
|
64
|
+
# retrieve field name
|
65
|
+
# @return [String]
|
66
|
+
def field
|
67
|
+
TRACKED_FIELDS[read_attribute(:field)]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/set/all/history.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
event :update_ancestor_timestamps, :integrate do
|
2
|
+
ids = history_ancestor_ids
|
3
|
+
return unless ids.present?
|
4
|
+
Card.where(id: ids).update_all(updater_id: Auth.current_id, updated_at: Time.now)
|
5
|
+
ids.map { |anc_id| Card.expire anc_id.cardname }
|
6
|
+
end
|
7
|
+
|
8
|
+
# track history (acts, actions, changes) on this card
|
9
|
+
def history?
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
# all cards whose acts are considered part of this card's history
|
14
|
+
def history_card_ids
|
15
|
+
nestee_ids << id
|
16
|
+
end
|
17
|
+
|
18
|
+
# all cards who are considered updated if this card's was updated
|
19
|
+
def history_parent_ids
|
20
|
+
nester_ids
|
21
|
+
end
|
22
|
+
|
23
|
+
def history_ancestor_ids recursion_level=0
|
24
|
+
return [] if recursion_level > 5
|
25
|
+
|
26
|
+
ids = history_parent_ids +
|
27
|
+
history_parent_ids.map { |id| Card[id].history_ancestor_ids(recursion_level + 1) }
|
28
|
+
ids.flatten
|
29
|
+
end
|
30
|
+
|
31
|
+
# ~~FIXME~~: optimize (no need to instantiate all actions and changes!)
|
32
|
+
# Nothing is instantiated here. ActiveRecord is much smarter than you think.
|
33
|
+
# Methods like #empty? and #size make sql queries if their receivers are not already
|
34
|
+
# loaded -pk
|
35
|
+
def first_change?
|
36
|
+
# = update or delete
|
37
|
+
@current_action.action_type != :create && action_count == 2 &&
|
38
|
+
create_action.card_changes.empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
def first_create?
|
42
|
+
@current_action.action_type == :create && action_count == 1
|
43
|
+
end
|
44
|
+
|
45
|
+
def action_count
|
46
|
+
Card::Action.where(card_id: @current_action.card_id).count
|
47
|
+
end
|
48
|
+
|
49
|
+
# card has account that is responsible for prior acts
|
50
|
+
def has_edits?
|
51
|
+
Card::Act.where(actor_id: id).where("card_id IS NOT NULL").present?
|
52
|
+
end
|
53
|
+
|
54
|
+
def changed_fields
|
55
|
+
Card::Change::TRACKED_FIELDS & (changed_attribute_names_to_save | saved_changes.keys)
|
56
|
+
end
|
57
|
+
|
58
|
+
def nestee_ids
|
59
|
+
requiring_id { @nestee_ids ||= nesting_ids(:referee_id, :referer_id) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def nester_ids
|
63
|
+
requiring_id { @nester_ids ||= nesting_ids(:referer_id, :referee_id) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def diff_args
|
67
|
+
{ diff_format: :text }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Delete all changes and old actions and make the last action the create action
|
71
|
+
# (that way the changes for that action will be created with the first update)
|
72
|
+
def make_last_action_the_initial_action
|
73
|
+
delete_all_changes
|
74
|
+
old_actions.delete_all
|
75
|
+
last_action.update! action_type: :create
|
76
|
+
end
|
77
|
+
|
78
|
+
def clear_history
|
79
|
+
delete_all_changes
|
80
|
+
delete_old_actions
|
81
|
+
end
|
82
|
+
|
83
|
+
def delete_old_actions
|
84
|
+
old_actions.delete_all
|
85
|
+
end
|
86
|
+
|
87
|
+
def delete_all_changes
|
88
|
+
Card::Change.where(card_action_id: all_action_ids).delete_all
|
89
|
+
end
|
90
|
+
|
91
|
+
def save_content_draft content
|
92
|
+
super
|
93
|
+
acts.create do |act|
|
94
|
+
act.ar_actions.build(draft: true, card_id: id, action_type: :update)
|
95
|
+
.card_changes.build(field: :db_content, value: content)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def nesting_ids return_field, where_field
|
102
|
+
Card::Reference.select(return_field).distinct.where(
|
103
|
+
ref_type: "I", where_field => id
|
104
|
+
).pluck(return_field).compact
|
105
|
+
end
|
106
|
+
|
107
|
+
def requiring_id
|
108
|
+
id ? yield : (return [])
|
109
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
ACTS_PER_PAGE = Card.config.acts_per_page
|
2
|
+
|
3
|
+
format :html do
|
4
|
+
def act_from_context
|
5
|
+
if (act_id = params["act_id"])
|
6
|
+
Act.find(act_id) || raise(Card::NotFound, "act not found")
|
7
|
+
else
|
8
|
+
card.last_action.act
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# used (by history and recent)for rendering act lists with legend and paging
|
13
|
+
#
|
14
|
+
# @param acts [ActiveRecord::Relation] relation that will return acts objects
|
15
|
+
# @param context [Symbol] :relative or :absolute
|
16
|
+
# @param draft_legend [Symbol] :show or :hide
|
17
|
+
def acts_layout acts, context, draft_legend=:hide
|
18
|
+
bs_layout container: false, fluid: false do
|
19
|
+
html _render_act_legend(draft_legend => :draft_legend)
|
20
|
+
row(12) { act_list acts, context }
|
21
|
+
row(12) { act_paging acts, context }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def act_list acts, context
|
26
|
+
act_accordion acts, context do |act, seq|
|
27
|
+
fmt = context == :relative ? self : act.card.format(:html)
|
28
|
+
fmt.act_listing act, seq, context
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def act_listing act, seq=nil, context=nil
|
33
|
+
opts = act_listing_opts_from_params(seq)
|
34
|
+
opts[:slot_class] = "revision-#{act.id} history-slot list-group-item"
|
35
|
+
context ||= (params[:act_context] || :absolute).to_sym
|
36
|
+
act_renderer(context).new(self, act, opts).render
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO: consider putting all these under one top-level param, eg:
|
40
|
+
# act: { seq: X, diff: [show/hide], action_view: Y }
|
41
|
+
def act_listing_opts_from_params seq
|
42
|
+
{ act_seq: (seq || params["act_seq"]),
|
43
|
+
action_view: (params["action_view"] || "summary").to_sym,
|
44
|
+
hide_diff: params["hide_diff"].to_s.strip == "true" }
|
45
|
+
end
|
46
|
+
|
47
|
+
def act_accordion acts, context, &block
|
48
|
+
accordion_group acts_for_accordion(acts, context, &block), nil, class: "clear-both"
|
49
|
+
end
|
50
|
+
|
51
|
+
def acts_for_accordion acts, context
|
52
|
+
clean_acts(current_page_acts(acts)).map do |act|
|
53
|
+
with_act_seq(context, acts) do |seq|
|
54
|
+
yield act, seq
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def with_act_seq context, acts
|
60
|
+
yield(context == :absolute ? nil : current_act_seq(acts))
|
61
|
+
end
|
62
|
+
|
63
|
+
def current_act_seq acts
|
64
|
+
@act_seq = @act_seq ? (@act_seq -= 1) : act_list_starting_seq(acts)
|
65
|
+
end
|
66
|
+
|
67
|
+
def clean_acts acts
|
68
|
+
# FIXME: if we get rid of bad act data, this will not be necessary
|
69
|
+
# The current
|
70
|
+
acts.select(&:card)
|
71
|
+
end
|
72
|
+
|
73
|
+
def current_page_acts acts
|
74
|
+
acts.page(acts_page_from_params).per acts_per_page
|
75
|
+
end
|
76
|
+
|
77
|
+
def act_list_starting_seq acts
|
78
|
+
acts.size - (acts_page_from_params - 1) * acts_per_page
|
79
|
+
end
|
80
|
+
|
81
|
+
def acts_per_page
|
82
|
+
@acts_per_page || ACTS_PER_PAGE
|
83
|
+
end
|
84
|
+
|
85
|
+
def acts_page_from_params
|
86
|
+
@acts_page_from_params ||= params["page"].present? ? params["page"].to_i : 1
|
87
|
+
end
|
88
|
+
|
89
|
+
def act_paging acts, context
|
90
|
+
wrap_with :div, class: "slotter btn-sm" do
|
91
|
+
acts = current_page_acts acts
|
92
|
+
opts = { remote: true, theme: "twitter-bootstrap-4" }
|
93
|
+
opts[:total_pages] = 10 if limited_paging? context
|
94
|
+
paginate acts, opts
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def limited_paging? context
|
99
|
+
context == :absolute && Act.count > 1000
|
100
|
+
end
|
101
|
+
|
102
|
+
def action_icon action_type, extra_class=nil
|
103
|
+
icon = case action_type
|
104
|
+
when :create then :add_circle
|
105
|
+
when :update then :pencil
|
106
|
+
when :delete then :remove_circle
|
107
|
+
when :draft then :wrench
|
108
|
+
end
|
109
|
+
icon_tag icon, extra_class
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def act_renderer context
|
115
|
+
case context
|
116
|
+
when :absolute
|
117
|
+
Act::ActRenderer::AbsoluteActRenderer
|
118
|
+
when :bridge
|
119
|
+
Act::ActRenderer::BridgeActRenderer
|
120
|
+
else # relative
|
121
|
+
Act::ActRenderer::RelativeActRenderer
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
def all_action_ids
|
4
|
+
Card::Action.where(card_id: id).pluck :id
|
5
|
+
end
|
6
|
+
|
7
|
+
def action_from_id action_id
|
8
|
+
return unless action_id.is_a?(Integer) || action_id =~ /^\d+$/
|
9
|
+
|
10
|
+
# if not an integer revision id is probably a mod (e.g. if you request
|
11
|
+
# files/:logo/standard.png)
|
12
|
+
action = Action.fetch action_id
|
13
|
+
return unless action.card_id == id
|
14
|
+
|
15
|
+
action
|
16
|
+
end
|
17
|
+
|
18
|
+
def old_actions
|
19
|
+
actions.where("id != ?", last_action_id)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_action
|
23
|
+
@create_action ||= actions.first
|
24
|
+
end
|
25
|
+
|
26
|
+
def nth_action index
|
27
|
+
index = index.to_i
|
28
|
+
return unless id && index.positive?
|
29
|
+
|
30
|
+
Action.where("draft is not true AND card_id = #{id}")
|
31
|
+
.order(:id).limit(1).offset(index - 1).first
|
32
|
+
end
|
33
|
+
|
34
|
+
def new_content_action_id
|
35
|
+
return unless @current_action && current_action_changes_content?
|
36
|
+
|
37
|
+
@current_action.id
|
38
|
+
end
|
39
|
+
|
40
|
+
def current_action_changes_content?
|
41
|
+
new_card? || @current_action.new_content? || db_content_is_changing?
|
42
|
+
end
|
43
|
+
|
44
|
+
format :html do
|
45
|
+
def action_from_context
|
46
|
+
if (action_id = voo.action_id || params[:action_id])
|
47
|
+
Action.fetch action_id
|
48
|
+
else
|
49
|
+
card.last_action
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def action_content action, view_type
|
54
|
+
return "" unless action.present?
|
55
|
+
|
56
|
+
wrap do
|
57
|
+
[action_content_toggle(action, view_type),
|
58
|
+
content_diff(action, view_type)]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def content_diff action, view_type
|
63
|
+
diff = action.new_content? && content_changes(action, view_type)
|
64
|
+
return "<i>empty</i>" unless diff.present?
|
65
|
+
|
66
|
+
diff
|
67
|
+
end
|
68
|
+
|
69
|
+
def action_content_toggle action, view_type
|
70
|
+
return unless show_action_content_toggle?(action, view_type)
|
71
|
+
|
72
|
+
toggle_action_content_link action, view_type
|
73
|
+
end
|
74
|
+
|
75
|
+
def show_action_content_toggle? action, view_type
|
76
|
+
view_type == :expanded || action.summary_diff_omits_content?
|
77
|
+
end
|
78
|
+
|
79
|
+
def toggle_action_content_link action, view_type
|
80
|
+
other_view_type = view_type == :expanded ? :summary : :expanded
|
81
|
+
css_class = "revision-#{action.card_act_id} float-right"
|
82
|
+
link_to_view "action_#{other_view_type}",
|
83
|
+
icon_tag(action_arrow_dir(view_type), class: "md-24"),
|
84
|
+
class: css_class,
|
85
|
+
path: { action_id: action.id, look_in_trash: true }
|
86
|
+
end
|
87
|
+
|
88
|
+
def action_arrow_dir view_type
|
89
|
+
view_type == :expanded ? :triangle_left : :triangle_right
|
90
|
+
end
|
91
|
+
|
92
|
+
def revert_actions_link link_text, path_args, html_args={}
|
93
|
+
return unless card.ok? :update
|
94
|
+
|
95
|
+
path_args.reverse_merge! action: :update, look_in_trash: true, assign: true,
|
96
|
+
card: { skip: :validate_renaming }
|
97
|
+
html_args.reverse_merge! remote: true, method: :post, rel: "nofollow", path: path_args
|
98
|
+
add_class html_args, "slotter"
|
99
|
+
link_to link_text, html_args
|
100
|
+
end
|
101
|
+
|
102
|
+
def action_legend
|
103
|
+
types = %i[create update delete]
|
104
|
+
legend = types.map do |action_type|
|
105
|
+
"#{action_icon(action_type)} #{action_type}d"
|
106
|
+
end
|
107
|
+
legend << _render_draft_legend if voo.show?(:draft_legend)
|
108
|
+
"<small>Actions: #{legend.join ' | '}</small>"
|
109
|
+
end
|
110
|
+
|
111
|
+
def content_legend
|
112
|
+
legend = [Card::Content::Diff.render_added_chunk("Additions"),
|
113
|
+
Card::Content::Diff.render_deleted_chunk("Subtractions")]
|
114
|
+
"<small>Content changes: #{legend.join ' | '}</small>"
|
115
|
+
end
|
116
|
+
|
117
|
+
def content_changes action, diff_type, hide_diff=false
|
118
|
+
if hide_diff
|
119
|
+
action.raw_view
|
120
|
+
else
|
121
|
+
action.content_diff diff_type
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# all acts with actions on self and on cards included in self (ie, acts shown in history)
|
2
|
+
def history_acts
|
3
|
+
@history_acts ||= Act.all_with_actions_on(history_card_ids, true).order id: :desc
|
4
|
+
end
|
5
|
+
|
6
|
+
def draft_acts
|
7
|
+
drafts.created_by(Card::Auth.current_id).map(&:act)
|
8
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# must be called on all actions and before :set_name, :process_subcards and
|
2
|
+
# :validate_delete_children
|
3
|
+
event :assign_action, :initialize, when: :actionable? do
|
4
|
+
act = director.need_act
|
5
|
+
@current_action = Card::Action.create(
|
6
|
+
card_act_id: act.id,
|
7
|
+
action_type: action,
|
8
|
+
draft: (Env.params["draft"] == "true")
|
9
|
+
)
|
10
|
+
if @supercard && @supercard != self
|
11
|
+
@current_action.super_action = @supercard.current_action
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# can we store an action? (can be overridden, eg in files)
|
16
|
+
def actionable?
|
17
|
+
history?
|
18
|
+
end
|
19
|
+
|
20
|
+
event :detect_conflict, :validate, on: :update, when: :edit_conflict? do
|
21
|
+
errors.add :conflict, tr(:error_not_latest_revision)
|
22
|
+
end
|
23
|
+
|
24
|
+
def edit_conflict?
|
25
|
+
last_action_id_before_edit &&
|
26
|
+
last_action_id_before_edit.to_i != last_action_id &&
|
27
|
+
(la = last_action) &&
|
28
|
+
la.act.actor_id != Auth.current_id
|
29
|
+
end
|
30
|
+
|
31
|
+
# stores changes in the changes table and assigns them to the current action
|
32
|
+
# removes the action if there are no changes
|
33
|
+
event :finalize_action, :finalize, when: :finalize_action? do
|
34
|
+
if changed_fields.present?
|
35
|
+
@current_action.update! card_id: id
|
36
|
+
|
37
|
+
# Note: #last_change_on uses the id to sort by date
|
38
|
+
# so the changes for the create changes have to be created before the first change
|
39
|
+
store_card_changes_for_create_action if first_change?
|
40
|
+
store_card_changes unless first_create?
|
41
|
+
# FIXME: a `@current_action.card` call here breaks specs in solid_cache_spec.rb
|
42
|
+
elsif @current_action.card_changes.reload.empty?
|
43
|
+
@current_action.delete
|
44
|
+
@current_action = nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# changes for the create action are stored after the first update
|
49
|
+
def store_card_changes_for_create_action
|
50
|
+
Card::Action.cache.delete "#{create_action.id}-changes"
|
51
|
+
store_each_history_field create_action.id do |field|
|
52
|
+
attribute_before_act field
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def store_card_changes
|
57
|
+
store_each_history_field @current_action.id, changed_fields do |field|
|
58
|
+
self[field]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def store_each_history_field action_id, fields=nil
|
63
|
+
fields ||= Card::Change::TRACKED_FIELDS
|
64
|
+
if false # Card::Change.supports_import?
|
65
|
+
# attach.feature fails with this
|
66
|
+
values = fields.map.with_index { |field, index| [index, yield(field), action_id] }
|
67
|
+
Card::Change.import [:field, :value, :card_action_id], values #, validate: false
|
68
|
+
else
|
69
|
+
fields.each do |field|
|
70
|
+
Card::Change.create field: field,
|
71
|
+
value: yield(field),
|
72
|
+
card_action_id: action_id
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def finalize_action?
|
78
|
+
actionable? && current_action
|
79
|
+
end
|
80
|
+
|
81
|
+
event :rollback_actions, :prepare_to_validate, on: :update, when: :rollback_request? do
|
82
|
+
update_args = process_revert_actions
|
83
|
+
Env.params["revert_actions"] = nil
|
84
|
+
update! update_args
|
85
|
+
clear_drafts
|
86
|
+
abort :success
|
87
|
+
end
|
88
|
+
|
89
|
+
event :finalize_act, after: :finalize_action, when: :act_card? do
|
90
|
+
Card::Director.act.update! card_id: id
|
91
|
+
end
|
92
|
+
|
93
|
+
event :remove_empty_act, :integrate_with_delay_final, when: :remove_empty_act? do
|
94
|
+
# Card::Director.act.delete
|
95
|
+
# Card::Director.act = nil
|
96
|
+
end
|
97
|
+
|
98
|
+
def remove_empty_act?
|
99
|
+
act_card? && Director.act&.ar_actions&.reload&.empty?
|
100
|
+
end
|