card-mod-history 0.11.0
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.
- 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
|