card 1.17.1 → 1.17.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/config/initializers/recaptcha.rb +27 -19
  4. data/db/migrate/20160122153608_new_indeces.rb +10 -0
  5. data/db/migrate_core_cards/20130411191151_renaming_for_menu.rb +2 -2
  6. data/db/migrate_core_cards/20140317035504_account_requests_to_signups.rb +2 -2
  7. data/db/migrate_core_cards/20150202143810_import_bootstrap_layout.rb +2 -2
  8. data/db/migrate_core_cards/20150807205221_create_references_for_search_cards.rb +4 -2
  9. data/db/schema.rb +5 -1
  10. data/db/version.txt +1 -1
  11. data/lib/card.rb +3 -3
  12. data/lib/card/reference.rb +68 -52
  13. data/mod/01_core/chunk/query_reference.rb +1 -1
  14. data/mod/01_core/set/all/collection.rb +1 -1
  15. data/mod/01_core/set/all/name.rb +27 -65
  16. data/mod/01_core/set/all/pattern.rb +6 -3
  17. data/mod/01_core/set/all/references.rb +135 -62
  18. data/mod/01_core/set/all/tracked_attributes.rb +1 -1
  19. data/mod/01_core/set/all/utils.rb +2 -2
  20. data/mod/01_core/spec/set/all/references_spec.rb +4 -3
  21. data/mod/01_core/spec/set/all/tracked_attributes_spec.rb +16 -22
  22. data/mod/01_history/lib/card/act.rb +32 -30
  23. data/mod/01_history/lib/card/change.rb +26 -18
  24. data/mod/01_history/set/all/history.rb +1 -1
  25. data/mod/04_settings/set/right/structure.rb +1 -1
  26. data/mod/05_email/set/all/notify.rb +1 -1
  27. data/mod/05_email/spec/set/right/followers_spec.rb +1 -1
  28. data/mod/05_standard/set/all/rich_html/editing.rb +42 -45
  29. data/mod/05_standard/set/type/list.rb +2 -2
  30. data/mod/05_standard/spec/set/self/all_spec.rb +3 -3
  31. data/mod/05_standard/spec/set/type/list_spec.rb +3 -3
  32. data/mod/05_standard/spec/set/type/listed_by_spec.rb +2 -2
  33. data/mod/05_standard/spec/set/type/search_type_spec.rb +1 -1
  34. data/spec/lib/card/reference_spec.rb +30 -28
  35. data/spec/models/card/trash_spec.rb +63 -63
  36. data/spec/models/card/type_transition_spec.rb +34 -35
  37. metadata +3 -2
@@ -1,6 +1,6 @@
1
1
 
2
2
  def patterns
3
- @patterns ||= set_patterns.map { |sub| sub.new(self) }.compact
3
+ @patterns ||= set_patterns.map { |sub| sub.new self }.compact
4
4
  end
5
5
 
6
6
  def patterns_with_new
@@ -9,7 +9,10 @@ end
9
9
  alias_method_chain :patterns, :new
10
10
 
11
11
  def reset_patterns
12
- @set_mods_loaded = @patterns = @set_modules = @junction_only = @set_names = @template = @rule_set_keys = @virtual = nil
12
+ @patterns = nil
13
+ @template = @virtual = nil
14
+ @set_mods_loaded = @set_modules = @set_names = @rule_set_keys = nil
15
+ @junction_only = nil # only applies to set cards
13
16
  true
14
17
  end
15
18
 
@@ -45,7 +48,7 @@ end
45
48
 
46
49
  def set_names
47
50
  if @set_names.nil?
48
- @set_names = patterns.map &:to_s
51
+ @set_names = patterns.map(&:to_s)
49
52
  Card.set_members @set_names, key
50
53
  end
51
54
  @set_names
@@ -1,99 +1,172 @@
1
+ # frozen_string_literal: true
1
2
 
2
- PARTIAL_REF_CODE = 'P'
3
+ # Cards can refer to other cards in their content, eg via links and nests.
4
+ # The card that refers is the "referer", the card that is referred to is
5
+ # the "referee". The reference itself has its own class (Card::Reference),
6
+ # which handles id-based reference tracking.
3
7
 
4
- def name_referencers link_name=nil
5
- link_name = link_name.nil? ? key : link_name.to_name.key
6
- Card.joins(:references_to).where card_references: { referee_key: link_name }
8
+ PARTIAL_REF_CODE = 'P'.freeze
9
+
10
+ # cards that refer to self
11
+ def referers
12
+ references_in.map(&:referer_id).map(&Card.method(:fetch)).compact
13
+ end
14
+
15
+ # cards that include self
16
+ def includers
17
+ refs = references_in.where(ref_type: 'I')
18
+ refs.map(&:referer_id).map(&Card.method(:fetch)).compact
19
+ end
20
+
21
+ # cards that self refers to
22
+ def referees
23
+ references_out.map { |ref| Card.fetch ref.referee_key, new: {} }
7
24
  end
8
25
 
9
- def extended_referencers
10
- # FIXME: .. we really just need a number here.
11
- (descendants + [self]).map(&:referencers).flatten.uniq
26
+ # cards that self includes
27
+ def includees
28
+ refs = references_out.where(ref_type: 'I')
29
+ refs.map { |ref| Card.fetch ref.referee_key, new: {} }
30
+ end
31
+
32
+ # cards that refer to self by name
33
+ # (finds cards not yet linked by id)
34
+ def name_referers
35
+ Card.joins(:references_out).where card_references: { referee_key: key }
36
+ end
37
+
38
+ # cards that refer to self or any descendant
39
+ def family_referers
40
+ @family_referers ||= ([self] + descendants).map(&:referers).flatten.uniq
41
+ # FIXME: could be much more efficient!
12
42
  end
13
43
 
14
44
  # replace references in card content
15
- def replace_references old_name, new_name
45
+ def replace_reference_syntax old_name, new_name
16
46
  obj_content = Card::Content.new raw_content, self
17
47
  obj_content.find_chunks(Card::Chunk::Reference).select do |chunk|
18
48
  next unless (old_ref_name = chunk.referee_name)
19
49
  next unless (new_ref_name = old_ref_name.replace_part old_name, new_name)
20
50
  chunk.referee_name = chunk.replace_reference old_name, new_name
21
- old_references = Card::Reference.where(referee_key: old_ref_name.key)
22
- old_references.update_all referee_key: new_ref_name.key
51
+ refs = Card::Reference.where referee_key: old_ref_name.key
52
+ refs.update_all referee_key: new_ref_name.key
23
53
  end
24
54
 
25
55
  obj_content.to_s
26
56
  end
27
57
 
28
- # update entries in reference table
29
- def update_references rendered_content=nil
30
- raise 'update references should not be called on new cards' if id.nil?
31
-
32
- Card::Reference.delete_all_from self unless self.new_card?
33
-
34
- rendered_content ||= Card::Content.new raw_content, self
35
- rendered_content.find_chunks(Card::Chunk::Reference).each do |chunk|
36
- create_reference_to chunk
37
- end
58
+ # delete old references from this card's content, create new ones
59
+ def update_references_out
60
+ delete_references_out
61
+ create_references_out
38
62
  end
39
63
 
40
- def create_reference_to chunk
41
- referee_name = chunk.referee_name
42
- return false unless referee_name # eg no commented nest
43
-
44
- referee_name.piece_names.each do |name|
45
- next if name.key == key # don't create self reference
46
-
47
- # reference types:
48
- # L = link
49
- # I = inclusion
50
- # P = partial (i.e. the name is part of a compound name that is
51
- # referenced by a link or inclusion)
52
-
53
- # The partial type is needed to keep track of references of virtual cards.
54
- # For example a link [[A+*self]] won't make it to the reference table
55
- # because A+*self is virtual and doesn't have an id but when A's name is
56
- # changed we have to find and update that link.
57
- ref_type = name != referee_name ? PARTIAL_REF_CODE : chunk.reference_code
58
- Card::Reference.create!(
59
- referer_id: id,
60
- referee_id: Card.fetch_id(name),
61
- referee_key: name.key,
62
- ref_type: ref_type,
63
- present: 1
64
- )
64
+ # interpret references from this card's content and
65
+ # insert entries in reference table
66
+ def create_references_out
67
+ ref_hash = {}
68
+ content_obj = Card::Content.new raw_content, self
69
+ content_obj.find_chunks(Card::Chunk::Reference).each do |chunk|
70
+ interpret_reference ref_hash, chunk.referee_name, chunk.reference_code
65
71
  end
72
+ return if ref_hash.empty?
73
+ Card::Reference.mass_insert reference_values_array(ref_hash)
66
74
  end
67
75
 
68
- def referencers
69
- references_from.map(&:referer_id).map(&Card.method(:fetch)).compact
76
+ # delete references from this card
77
+ def delete_references_out
78
+ fail 'id required to delete references' if id.nil?
79
+ Card::Reference.delete_all referer_id: id
70
80
  end
71
81
 
72
- def includers
73
- references_from.where(ref_type: 'I')
74
- .map(&:referer_id).map(&Card.method(:fetch)).compact
82
+ # interpretation phase helps to prevent duplicate references
83
+ # results in hash like:
84
+ # { referee1_key: [referee1_id, referee1_type1, referee1_type2],
85
+ # referee2_key...
86
+ # }
87
+ def interpret_reference ref_hash, referee_name, ref_type
88
+ return unless referee_name # eg commented nest has no referee_name
89
+ referee_key = (referee_name = referee_name.to_name).key
90
+ return if referee_key == key # don't create self reference
91
+
92
+ referee_id = Card.fetch_id(referee_name)
93
+ ref_hash[referee_key] ||= [referee_id]
94
+ ref_hash[referee_key] << ref_type
95
+
96
+ interpret_partial_references ref_hash, referee_name unless referee_id
75
97
  end
76
98
 
77
- def referees
78
- references_to.map { |ref| Card.fetch ref.referee_key, new: {} }
99
+ # Partial references are needed to track references to virtual cards.
100
+ # For example a link to virual card [[A+*self]] won't have a referee_id,
101
+ # but when A's name is changed we have to find and update that link.
102
+ def interpret_partial_references ref_hash, referee_name
103
+ [referee_name.left, referee_name.right].each do |sidename|
104
+ interpret_reference ref_hash, sidename, PARTIAL_REF_CODE
105
+ end
79
106
  end
80
107
 
81
- def includees
82
- references_to.where(ref_type: 'I')
83
- .map { |ref| Card.fetch ref.referee_key, new: {} }
108
+ # translate interpreted reference hash into values array,
109
+ # removing duplicate and unnecessary ref_types
110
+ def reference_values_array ref_hash
111
+ values = []
112
+ ref_hash.each do |referee_key, hash_val|
113
+ referee_id = hash_val.shift || 'null'
114
+ ref_types = hash_val.uniq
115
+ ref_types.delete PARTIAL_REF_CODE if ref_types.size > 1
116
+ # partial references are not necessary if there are explicit references
117
+ ref_types.each do |ref_type|
118
+ values << [id, referee_id, "'#{referee_key}'", "'#{ref_type}'"]
119
+ end
120
+ end
121
+ values
84
122
  end
85
123
 
86
124
  protected
87
125
 
88
- event :refresh_references, after: :store, on: :save, changed: :content do
89
- update_references
126
+ # test for updating referer content & preload referer list
127
+ event :prepare_referer_update, before: :approve, on: :update, changed: :name do
128
+ self.update_referers = ![nil, false, 'false'].member?(update_referers)
129
+ family_referers
90
130
  end
91
131
 
92
- event :refresh_references_on_create, before: :refresh_references, on: :create do
93
- Card::Reference.update_existing_key self
94
- # FIXME: bogus blank default content is set on structured cards...
132
+ # when name changes, update references to card
133
+ event :refresh_references_in, after: :store, on: :save, changed: :name do
134
+ Card::Reference.unmap_referees id if @action == :update && !update_referers
135
+ Card::Reference.map_referees key, id
136
+ end
137
+
138
+ # when content changes, update references to other cards
139
+ event :refresh_references_out, after: :store, on: :save, changed: :content do
140
+ update_references_out
141
+ end
142
+
143
+ # clean up reference table when card is deleted
144
+ event :clear_references, after: :store, on: :delete do
145
+ delete_references_out
146
+ Card::Reference.unmap_referees id
147
+ end
148
+
149
+ # on rename, update names in cards that refer to self by name (as directed)
150
+ event :update_referer_content,
151
+ after: :store, on: :update, changed: :name,
152
+ when: proc { |c| c.update_referers } do
153
+ # FIXME: break into correct stages
154
+ Auth.as_bot do
155
+ family_referers.each do |card|
156
+ next if card == self || card.structure
157
+ card = card.refresh
158
+ card.db_content = card.replace_reference_syntax name_was, name
159
+ card.save!
160
+ end
161
+ end
95
162
  end
96
163
 
97
- event :refresh_references_on_delete, after: :store, on: :delete do
98
- Card::Reference.update_on_delete self
164
+ # on rename, when NOT updating referer content, update references to ensure
165
+ # that partial references are correctly tracked
166
+ # eg. A links to X+Y. if X+Y is renamed and we're not updating the link in A,
167
+ # then we need to be sure that A has a partial reference
168
+ event :update_referer_references_out,
169
+ after: :store, on: :update, changed: :name,
170
+ when: proc { |c| !c.update_referers } do
171
+ family_referers.map(&:update_references_out)
99
172
  end
@@ -141,5 +141,5 @@ end
141
141
  event :expire_related_names, before: :expire_related, changed: :name do
142
142
  # FIXME: look for opportunities to avoid instantiating the following
143
143
  descendants.each { |c| c.expire(true) }
144
- name_referencers.each { |c| c.expire(true) }
144
+ name_referers.each { |c| c.expire(true) }
145
145
  end
@@ -4,8 +4,8 @@ module ClassMethods
4
4
  Card.delete_trashed_files
5
5
  Card.where(trash: true).delete_all
6
6
  Card::Action.delete_cardless
7
- Card::Reference.repair_missing_referees
8
- Card::Reference.delete_missing_referers
7
+ Card::Reference.unmap_if_referee_missing
8
+ Card::Reference.delete_if_referer_missing
9
9
  Card.delete_tmp_files_of_cached_uploads
10
10
  end
11
11
 
@@ -1,8 +1,9 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
3
  describe Card::Set::All::References do
4
- it "should replace references should work on inclusions inside links" do
5
- card = Card.create!(name: "ref test", content: "[[test_card|test{{test}}]]" )
6
- assert_equal "[[test_card|test{{best}}]]", card.replace_references("test", "best" )
4
+ it 'should replace references should work on inclusions inside links' do
5
+ card = Card.create! name: 'ref test', content: '[[test_card|test{{test}}]]'
6
+ assert_equal '[[test_card|test{{best}}]]',
7
+ card.replace_reference_syntax('test', 'best')
7
8
  end
8
9
  end
@@ -6,7 +6,7 @@ module RenameMethods
6
6
  content: card.content,
7
7
  # updater_id: card.updater_id,
8
8
  # revisions: card.actions.count,
9
- referencers: card.referencers.map(&:name).sort,
9
+ referers: card.referers.map(&:name).sort,
10
10
  referees: card.referees.map(&:name).sort,
11
11
  descendants: card.descendants.map(&:id).sort
12
12
  }
@@ -16,7 +16,7 @@ module RenameMethods
16
16
  attrs_before = name_invariant_attributes(card)
17
17
  actions_count_before = card.actions.count
18
18
  card.name = new_name
19
- card.update_referencers = true
19
+ card.update_referers = true
20
20
  card.save!
21
21
  expect(card.actions.count).to eq(actions_count_before + 1)
22
22
  assert_equal attrs_before, name_invariant_attributes(card)
@@ -119,13 +119,13 @@ describe Card::Set::All::TrackedAttributes do
119
119
  c = Card['Menu']
120
120
  c.name = 'manure'
121
121
  c.save!
122
- expect(Card['manure'].references_from.size).to eq(0)
122
+ expect(Card['manure'].references_in.size).to eq(0)
123
123
  end
124
124
 
125
125
  it 'picks up new references' do
126
126
  Card.create name: 'kinds of poop', content: '[[manure]]'
127
127
  assert_rename Card['Menu'], 'manure'
128
- expect(Card['manure'].references_from.size).to eq(2)
128
+ expect(Card['manure'].references_in.size).to eq(2)
129
129
  end
130
130
 
131
131
  it 'handles name variants' do
@@ -155,15 +155,9 @@ describe Card::Set::All::TrackedAttributes do
155
155
  end
156
156
 
157
157
  it 'test_update_descendants' do
158
- card_list = [
159
- Card['One+Two'],
160
- Card['One+Two+Three'],
161
- Card['Four+One'],
162
- Card['Four+One+Five']
163
- ]
164
-
165
- old_names = %w{ One+Two One+Two+Three Four+One Four+One+Five }
166
- new_names = %w{ Uno+Two Uno+Two+Three Four+Uno Four+Uno+Five }
158
+ old_names = %w( One+Two One+Two+Three Four+One Four+One+Five )
159
+ new_names = %w( Uno+Two Uno+Two+Three Four+Uno Four+Uno+Five )
160
+ card_list = old_names.map { |name| Card[name] }
167
161
 
168
162
  assert_equal old_names, card_list.map(&:name)
169
163
  Card['One'].update_attributes! name: 'Uno'
@@ -193,24 +187,24 @@ describe Card::Set::All::TrackedAttributes do
193
187
  expect(Card['Banana Card']).not_to be_nil
194
188
  end
195
189
 
196
- it 'test_rename_should_not_fail_when_updating_inaccessible_referencer' do
190
+ it 'test_rename_should_not_fail_when_updating_inaccessible_referer' do
197
191
  Card.create! name: 'Joe Card', content: 'Whattup'
198
192
  Card::Auth.as 'joe_admin' do
199
193
  Card.create! name: 'Admin Card', content: '[[Joe Card]]'
200
194
  end
201
195
  c = Card['Joe Card']
202
- c.update_attributes! name: 'Card of Joe', update_referencers: true
196
+ c.update_attributes! name: 'Card of Joe', update_referers: true
203
197
  assert_equal '[[Card of Joe]]', Card['Admin Card'].content
204
198
  end
205
199
 
206
- it 'test_rename_should_update_structured_referencer' do
200
+ it 'test_rename_should_update_structured_referer' do
207
201
  Card::Auth.as_bot do
208
202
  c = Card.create! name: 'Pit'
209
203
  Card.create! name: 'Orange', type: 'Fruit', content: '[[Pit]]'
210
204
  Card.create! name: 'Fruit+*type+*structure', content: 'this [[Pit]]'
211
205
 
212
206
  assert_equal 'this [[Pit]]', Card['Orange'].raw_content
213
- c.update_attributes! name: 'Seed', update_referencers: true
207
+ c.update_attributes! name: 'Seed', update_referers: true
214
208
  assert_equal 'this [[Seed]]', Card['Orange'].raw_content
215
209
  end
216
210
  end
@@ -260,14 +254,14 @@ describe Card::Set::All::TrackedAttributes do
260
254
  it 'test_renaming_card_with_self_link_should_not_hang' do
261
255
  c = Card['Dairy']
262
256
  c.name = 'Buttah'
263
- c.update_referencers = true
257
+ c.update_referers = true
264
258
  c.save!
265
259
  assert_equal '[[/new/{{_self|name}}|new]]', Card['Buttah'].content
266
260
  end
267
261
 
268
262
  it 'should rename card without updating references' do
269
263
  c = Card['Dairy']
270
- c.update_attributes name: 'Newt', update_referencers: false
264
+ c.update_attributes name: 'Newt', update_referers: false
271
265
  assert_equal '[[/new/{{_self|name}}|new]]', Card['Newt'].content
272
266
  end
273
267
  end
@@ -291,7 +285,7 @@ describe Card::Set::All::TrackedAttributes do
291
285
  c1 = Card['Blue']
292
286
  c2 = Card['blue includer 1']
293
287
  c3 = Card['blue includer 2']
294
- c1.update_attributes name: 'Red', update_referencers: true
288
+ c1.update_attributes name: 'Red', update_referers: true
295
289
  assert_equal '{{Red}}', Card.find(c2.id).content
296
290
  # NOTE these attrs pass through a hash stage that may not preserve order
297
291
  assert_equal '{{Red|closed;other:stuff}}', Card.find(c3.id).content
@@ -301,7 +295,7 @@ describe Card::Set::All::TrackedAttributes do
301
295
  c1 = Card['Blue']
302
296
  c2 = Card['blue includer 1']
303
297
  c1.update_attributes name: 'blue includer 1+color',
304
- update_referencers: true
298
+ update_referers: true
305
299
  assert_equal '{{blue includer 1+color}}', Card.find(c2.id).content
306
300
  end
307
301
 
@@ -310,7 +304,7 @@ describe Card::Set::All::TrackedAttributes do
310
304
  c2 = Card['blue linker 1']
311
305
  c3 = Card['blue linker 2']
312
306
  c1.reload.name = 'Red'
313
- c1.update_referencers = true
307
+ c1.update_referers = true
314
308
  c1.save!
315
309
  assert_equal '[[Red]]', Card.find(c2.id).content
316
310
  assert_equal '[[Red]]', Card.find(c3.id).content
@@ -2,39 +2,39 @@
2
2
  class Card
3
3
  class Act < ActiveRecord::Base
4
4
  before_save :set_actor
5
- has_many :actions, -> { order :id },
6
- { foreign_key: :card_act_id, inverse_of: :act, class_name: "Card::Action" }
5
+ has_many :actions,
6
+ -> { order :id },
7
+ foreign_key: :card_act_id,
8
+ inverse_of: :act,
9
+ class_name: 'Card::Action'
7
10
 
8
- belongs_to :actor, class_name: "Card"
11
+ belongs_to :actor, class_name: 'Card'
9
12
  belongs_to :card
10
- def set_actor
11
- self.actor_id ||= Auth.current_id
12
- end
13
13
 
14
- def self.delete_actionless
15
- Card::Act.where(
16
- "id NOT IN (?)",
17
- Card::Action.pluck("card_act_id"),
18
- ).delete_all
19
- end
14
+ class << self
15
+ def delete_actionless
16
+ joins(
17
+ 'LEFT JOIN card_actions '\
18
+ 'ON card_acts.id = card_actions.card_act_id'
19
+ ).where(
20
+ 'card_actions.id is null'
21
+ ).delete_all
22
+ end
20
23
 
21
- def self.find_all_with_actions_on card_ids, args={}
22
- sql = 'card_actions.card_id IN (:card_ids) AND ( (draft is not true) '
23
- sql << ( args[:with_drafts] ? 'OR actor_id = :current_user_id)' : ')' )
24
- vars = {card_ids: card_ids, current_user_id: Card::Auth.current_id }
25
- Card::Act.joins(:actions).where( sql, vars ).uniq.order(:id).reverse_order
24
+ def find_all_with_actions_on card_ids, args={}
25
+ sql = 'card_actions.card_id IN (:card_ids) AND ( (draft is not true) '
26
+ sql << (args[:with_drafts] ? 'OR actor_id = :current_user_id)' : ')')
27
+ vars = { card_ids: card_ids, current_user_id: Card::Auth.current_id }
28
+ joins(:actions).where(sql, vars).uniq.order(:id).reverse_order
29
+ end
26
30
  end
27
31
 
28
- # def actor
29
- # Card[ actor_id ]
30
- # end
31
-
32
- # def card
33
- # Card[ card_id ]
34
- # end
32
+ def set_actor
33
+ self.actor_id ||= Auth.current_id
34
+ end
35
35
 
36
36
  def action_on card_id
37
- actions.where( "card_id = #{card_id} and draft is not true" ).first
37
+ actions.where("card_id = #{card_id} and draft is not true").first
38
38
  end
39
39
 
40
40
  def main_action
@@ -47,20 +47,22 @@ class Card
47
47
 
48
48
  def relevant_drafts_for card
49
49
  drafts.select do |action|
50
- card.included_card_ids.include?(action.card_id) || (card.id == action.card_id)
50
+ card.included_card_ids.include?(action.card_id) ||
51
+ (card.id == action.card_id)
51
52
  end
52
53
  end
53
54
 
54
- def relevant_actions_for card, with_drafts=false
55
+ def relevant_actions_for card
55
56
  actions.select do |action|
56
- card.included_card_ids.include?(action.card_id) || (card.id == action.card_id)
57
+ card.included_card_ids.include?(action.card_id) ||
58
+ (card.id == action.card_id)
57
59
  end
58
60
  end
59
61
 
60
- private
62
+ private
63
+
61
64
  def timestamp_attributes_for_create
62
65
  super << :acted_at
63
66
  end
64
-
65
67
  end
66
68
  end