card 1.107.0 → 1.108.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/config/environments/development.rb +1 -9
  4. data/config/environments/test.rb +4 -4
  5. data/config/initializers/01_core_extensions/integer.rb +13 -0
  6. data/config/initializers/01_core_extensions/{persistent_identifiers.rb → persistent_identifier.rb} +0 -5
  7. data/config/initializers/01_core_extensions/symbol.rb +11 -0
  8. data/config/initializers/core_extensions.rb +2 -2
  9. data/db/schema.rb +9 -5
  10. data/lib/card/auth/token.rb +1 -1
  11. data/lib/card/cache/all.rb +30 -7
  12. data/lib/card/cache/card_class.rb +12 -6
  13. data/lib/card/cache/class_methods.rb +122 -0
  14. data/lib/card/cache/populate.rb +64 -0
  15. data/lib/card/cache/{persistent.rb → shared.rb} +22 -16
  16. data/lib/card/cache/{persistent_class.rb → shared_class.rb} +2 -2
  17. data/lib/card/cache/temporary.rb +40 -8
  18. data/lib/card/cache.rb +65 -130
  19. data/lib/card/codename.rb +66 -54
  20. data/lib/card/director/class_methods.rb +13 -13
  21. data/lib/card/director/stages.rb +1 -1
  22. data/lib/card/error.rb +8 -0
  23. data/lib/card/fetch/card_class.rb +5 -24
  24. data/lib/card/fetch/retrieve.rb +1 -1
  25. data/lib/card/fetch/store.rb +4 -18
  26. data/lib/card/format/nesting.rb +1 -1
  27. data/lib/card/format/registration.rb +15 -7
  28. data/lib/card/format/render.rb +16 -15
  29. data/lib/card/format.rb +11 -4
  30. data/lib/card/lexicon.rb +37 -23
  31. data/lib/card/mark.rb +2 -34
  32. data/lib/card/name/all.rb +3 -7
  33. data/lib/card/name/card_class.rb +2 -2
  34. data/lib/card/name/name_variants.rb +7 -1
  35. data/lib/card/name.rb +60 -12
  36. data/lib/card/query/card_class.rb +1 -1
  37. data/lib/card/query/card_query/match_attributes.rb +14 -4
  38. data/lib/card/query/card_query/relational_attributes.rb +2 -0
  39. data/lib/card/query/card_query/run.rb +9 -3
  40. data/lib/card/query/sql_statement.rb +1 -1
  41. data/lib/card/reference/all.rb +8 -3
  42. data/lib/card/reference.rb +4 -9
  43. data/lib/card/rule/cache.rb +2 -0
  44. data/lib/card/rule/read_rule_cache.rb +2 -0
  45. data/lib/card/set/event.rb +1 -4
  46. data/lib/card/set/format/abstract_format/view_opts.rb +1 -1
  47. data/lib/card/set/format/abstract_format.rb +20 -3
  48. data/lib/card/set/inheritance.rb +2 -3
  49. data/lib/card/set/pattern/all.rb +12 -0
  50. data/lib/card/subcards/add.rb +1 -1
  51. data/lib/card/subcards/remove.rb +1 -1
  52. data/lib/card/view/cache/cache_action.rb +24 -7
  53. data/lib/card/view/cache.rb +34 -6
  54. data/lib/card/view/options/voo_api.rb +1 -1
  55. data/lib/card/view.rb +1 -0
  56. data/lib/cardio/cli.rb +1 -1
  57. data/lib/cardio/command/rake_command.rb +10 -2
  58. data/lib/cardio/command/rspec_command/parser.rb +9 -11
  59. data/lib/cardio/command/rspec_command.rb +7 -3
  60. data/lib/cardio/generators/deck_helper.rb +7 -7
  61. data/lib/cardio/migration/port.rb +37 -0
  62. data/lib/cardio/migration.rb +5 -36
  63. data/lib/cardio/mod/class_methods.rb +1 -0
  64. data/lib/cardio/mod.rb +1 -1
  65. data/lib/cardio/railtie.rb +5 -4
  66. data/lib/cardio/utils.rb +1 -1
  67. data/lib/generators/deck/deck_generator.rb +5 -11
  68. data/lib/generators/deck/templates/Gemfile.erb +2 -2
  69. data/lib/generators/deck/templates/config/puma.rb +0 -6
  70. data/lib/generators/mod/mod_generator.rb +7 -0
  71. data/lib/generators/set/set_generator.rb +2 -1
  72. data/mod/core/config/locales/en.yml +1 -0
  73. data/mod/core/data/schema/20200805200729_add_unique_pair_indices.rb +1 -1
  74. data/mod/core/data/schema/20240628212556_add_trash_index.rb +11 -0
  75. data/mod/core/data/schema/20241017160402_unique_codename.rb +13 -0
  76. data/mod/core/data/transform/20140317035504_account_requests_to_signups.rb +4 -6
  77. data/mod/core/data/transform/20150724123438_update_file_and_image_cards.rb +1 -1
  78. data/mod/core/data/transform/20190320091257_upgrade_recaptcha_to_v3.rb +3 -3
  79. data/mod/core/data/transform/20190502130029_add_shark_and_help_desk_role.rb +1 -1
  80. data/mod/core/data/transform/20190822093633_move_help_text_to_code.rb +2 -2
  81. data/mod/core/data/transform/20190829205148_remove_add_help.rb +1 -1
  82. data/mod/core/lib/tasks/card/migrate.rake +6 -5
  83. data/mod/core/lib/tasks/card/mod.rake +3 -0
  84. data/mod/core/lib/tasks/card/seed.rake +1 -0
  85. data/mod/core/lib/tasks/card.rake +6 -2
  86. data/mod/core/set/all/autoname.rb +1 -1
  87. data/mod/core/set/all/codename.rb +6 -8
  88. data/mod/core/set/all/name_events.rb +7 -5
  89. data/mod/core/set/all/result.rb +1 -1
  90. data/mod/core/set/all/trash.rb +2 -4
  91. data/mod/core/set/all/type.rb +1 -1
  92. data/mod/core/set/self/mod.rb +1 -1
  93. data/mod/core/set/self/trash.rb +1 -1
  94. data/mod/core/spec/set/all/trash_spec.rb +3 -3
  95. metadata +29 -23
  96. data/lib/card/cache/prepopulate.rb +0 -48
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df537d34eb8d348b41930c7ce3e6de5ed600b4d7e8df87ba2d935b2e259ae8e6
4
- data.tar.gz: e108f9e1f0833c0aac0248c6ec59bf41c18aef70e09e42ea8bd364f94705fe7c
3
+ metadata.gz: ae50e986081ed9e3618c1b92ccfbedfc19a9076529cb17360700506fd8acb054
4
+ data.tar.gz: c383ab4419aee1792ff33d7e667858a40f3063d6132d03f5bd9530725b56ac78
5
5
  SHA512:
6
- metadata.gz: ec38c34b70331bf7a919963b7337682e1456286ec5007db1d6d5408f270bb326bb6c55cd2c8af455bb7ed2794d8bd7ae1012dc3eb741d89ba2ce575a63c804bd
7
- data.tar.gz: 901b1dc8198578fac3b59040919c02520b177ff8e240c4cebb0a1adb6c978054491b62aa4cc44a3a3514b114645b914a23515ffb36d71ab39b36054fe31c7cd7
6
+ metadata.gz: 1241479974329e34712f63ef0b3e00942a7a775a87c7e397bca3540114e1ef4a1757be95ee3dd7e8511963db768990e02b14dae74b8c31f1aea8b52ebffb948f
7
+ data.tar.gz: ea15daab38b40f7a41a272459f4c197724f22d9d7d37582ca30c70b01ad73b3140ade6fde2d990b6877fbc8beab9158e351622a2d8f7dd02029d5f8c32875a10
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.17.0
1
+ 0.18.1
@@ -4,7 +4,7 @@ Cardio.application.class.configure do
4
4
  # Settings specified here will take precedence over those in config/application.rb
5
5
 
6
6
  config.view_cache = false
7
-
7
+ # config.cache_log_level = :debug
8
8
  config.eager_load = false
9
9
 
10
10
  config.asset_refresh = :eager
@@ -38,14 +38,6 @@ Cardio.application.class.configure do
38
38
  # in the nest where the error occurred
39
39
  config.raise_all_rendering_errors = true
40
40
 
41
- # config.performance_logger = {
42
- # methods: [:event, :search, :fetch, :view], # choose methods to log
43
- # min_time: 100, # show only method calls that are slower than 100ms
44
- # max_depth: 3, # show nested method calls only up to depth 3
45
- # details: true # show method arguments and sql
46
- # log_level: :info
47
- # }
48
-
49
41
  # Only use best-standards-support built into browsers
50
42
  config.action_dispatch.best_standards_support = :builtin
51
43
 
@@ -16,8 +16,8 @@ Cardio.application.class.configure do
16
16
 
17
17
  config.assets.enabled = true if Object.const_defined?(:JasmineRails)
18
18
 
19
- config.persistent_cache = false
20
- config.prepopulate_cache = true
19
+ config.shared_cache = false
20
+ config.seed_cache_from_stash = true
21
21
 
22
22
  # Configure static asset server for tests with Cache-Control for performance
23
23
  config.serve_static_files = true
@@ -46,13 +46,13 @@ Cardio.application.class.configure do
46
46
  # config.action_mailer.delivery_method = :smtp
47
47
  # config.action_mailer.smtp_settings = { address: "localhost", port: 1025 }
48
48
 
49
- # Use SQL instead of Active Record's schema dumper when creating the test database.
49
+ # Use SQL instead of ActiveRecord's schema dumper when creating the test database.
50
50
  # This is necessary if your schema can't be completely dumped by the schema dumper,
51
51
  # like if you have constraints or database-specific column types
52
52
  # config.active_record.schema_format = :sql
53
53
 
54
54
  # FIXME: - add back the next one when we go back to 3.2
55
- # Raise exception on mass assignment protection for Active Record models
55
+ # Raise exception on mass assignment protection for ActiveRecord models
56
56
  # config.active_record.mass_assignment_sanitizer = :strict
57
57
 
58
58
  # Print deprecation notices to the stderr
@@ -0,0 +1,13 @@
1
+ require_relative "persistent_identifier"
2
+
3
+ module CoreExtensions
4
+ # extensions to Integer class
5
+ module Integer
6
+ include PersistentIdentifier
7
+
8
+ # interpret integer as id
9
+ def cardname
10
+ Card::Lexicon.name self
11
+ end
12
+ end
13
+ end
@@ -7,11 +7,6 @@ module CoreExtensions
7
7
  Card[self]
8
8
  end
9
9
 
10
- # interpret symbol/integer as codename/id
11
- def cardname
12
- Card.quick_fetch(self)&.name
13
- end
14
-
15
10
  # don't interpret symbol/integer as codename/id
16
11
  def to_name
17
12
  Card::Name.new to_s
@@ -0,0 +1,11 @@
1
+ module CoreExtensions
2
+ # extensions to Symbol class
3
+ module Symbol
4
+ include PersistentIdentifier
5
+
6
+ # interpret symbol as codename
7
+ def cardname
8
+ Card::Codename.name self
9
+ end
10
+ end
11
+ end
@@ -9,8 +9,8 @@ module CoreExtensions
9
9
  ::String.include String
10
10
  ::Array.include Array
11
11
  ::Hash.include Hash::Merging
12
- ::Symbol.include PersistentIdentifier
13
- ::Integer.include PersistentIdentifier
12
+ ::Symbol.include Symbol
13
+ ::Integer.include Integer
14
14
  ::Hash.extend Hash::ClassMethods::Nesting
15
15
  ::MatchData.include MatchData
16
16
  end
data/db/schema.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # This file is auto-generated from the current state of the database. Instead
2
- # of editing this file, please use the migrations feature of Active Record to
2
+ # of editing this file, please use the migrations feature of ActiveRecord to
3
3
  # incrementally modify your database, and then regenerate this schema definition.
4
4
  #
5
5
  # This file is the source Rails uses to define your schema when running `bin/rails
@@ -9,6 +9,10 @@
9
9
  # migrations use external dependencies or application code.
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
+ #
13
+ # Remark: We used to use size: medium for text columns. This option is supported by mysql but not by postgres.
14
+ # limit: 16.megabytes - 1 translates to the same size in mysql databases.
15
+
12
16
 
13
17
  ActiveRecord::Schema.define(version: 2022_10_31_182227) do
14
18
 
@@ -36,7 +40,7 @@ ActiveRecord::Schema.define(version: 2022_10_31_182227) do
36
40
  create_table "card_changes", id: :integer, charset: "utf8mb3", force: :cascade do |t|
37
41
  t.integer "card_action_id"
38
42
  t.integer "field"
39
- t.text "value", size: :medium
43
+ t.text "value", limit: 16.megabytes - 1
40
44
  t.index ["card_action_id"], name: "card_changes_card_action_id_index"
41
45
  end
42
46
 
@@ -65,7 +69,7 @@ ActiveRecord::Schema.define(version: 2022_10_31_182227) do
65
69
  t.integer "left_id"
66
70
  t.integer "right_id"
67
71
  t.string "left_key"
68
- t.text "content", size: :medium
72
+ t.text "content", limit: 16.megabytes - 1
69
73
  t.datetime "updated_at"
70
74
  t.index ["left_id"], name: "right_id_index"
71
75
  t.index ["right_id"], name: "left_id_index"
@@ -87,7 +91,7 @@ ActiveRecord::Schema.define(version: 2022_10_31_182227) do
87
91
  t.integer "references_expired"
88
92
  t.boolean "trash", null: false
89
93
  t.integer "type_id", null: false
90
- t.text "db_content", size: :medium
94
+ t.text "db_content", limit: 16.megabytes - 1
91
95
  t.index ["codename"], name: "cards_codename_index"
92
96
  t.index ["created_at"], name: "cards_created_at_index"
93
97
  t.index ["key"], name: "cards_key_index", unique: true
@@ -102,7 +106,7 @@ ActiveRecord::Schema.define(version: 2022_10_31_182227) do
102
106
  create_table "delayed_jobs", charset: "utf8mb3", force: :cascade do |t|
103
107
  t.integer "priority", default: 0, null: false
104
108
  t.integer "attempts", default: 0, null: false
105
- t.text "handler", size: :medium, null: false
109
+ t.text "handler", limit: 16.megabytes - 1, null: false
106
110
  t.text "last_error"
107
111
  t.datetime "run_at"
108
112
  t.datetime "locked_at"
@@ -4,7 +4,7 @@ class Card
4
4
  module Auth
5
5
  # methods for setting current account
6
6
  module Token
7
- SECRET_KEY = Rails.application.secrets.secret_key_base.to_s
7
+ SECRET_KEY = Rails.application.credentials.secret_key_base.to_s
8
8
 
9
9
  class << self
10
10
  def encode user_id, extra_payload={}
@@ -2,12 +2,25 @@ class Card
2
2
  class Cache
3
3
  # cache-related instance methods available to all Cards
4
4
  module All
5
+ def write_lexicon
6
+ Lexicon.write_to_temp_cache id, name, lex
7
+ end
8
+
9
+ def lex
10
+ if simple?
11
+ name
12
+ elsif left_id && right_id
13
+ [left_id, right_id]
14
+ end
15
+ end
16
+
5
17
  def expire cache_type=nil
6
18
  return unless (cache_class = cache_class_from_type cache_type)
7
19
 
8
20
  expire_views
9
21
  expire_names cache_class
10
22
  expire_id cache_class
23
+ expire_left cache_type
11
24
  end
12
25
 
13
26
  def view_cache_clean?
@@ -18,18 +31,28 @@ class Card
18
31
  return if view_cache_keys.include? cache_key
19
32
 
20
33
  view_cache_keys << cache_key
21
- hard_write_view_cache_keys
34
+ shared_write_view_cache_keys
22
35
  end
23
36
 
24
37
  private
25
38
 
26
- def hard_read_view_cache_keys key_root=key
27
- Card.cache.hard&.read_attribute key_root, :view_cache_keys
39
+ def expire_left cache_type
40
+ return unless name.compound? && expire_left?
41
+
42
+ Card.cache.read(name.left_name.key)&.expire cache_type
43
+ end
44
+
45
+ def expire_left?
46
+ true
47
+ end
48
+
49
+ def shared_read_view_cache_keys key_root=key
50
+ Card.cache.shared&.read_attribute key_root, :view_cache_keys
28
51
  end
29
52
 
30
- def hard_write_view_cache_keys
53
+ def shared_write_view_cache_keys
31
54
  # puts "WRITE VIEW CACHE KEYS (#{name}): #{view_cache_keys}"
32
- Card.cache.hard&.write_attribute key, :view_cache_keys, view_cache_keys
55
+ Card.cache.shared&.write_attribute key, :view_cache_keys, view_cache_keys
33
56
  end
34
57
 
35
58
  def cache_class_from_type cache_type
@@ -37,7 +60,7 @@ class Card
37
60
  end
38
61
 
39
62
  def view_cache_keys
40
- @view_cache_keys ||= hard_read_view_cache_keys(key) || []
63
+ @view_cache_keys ||= shared_read_view_cache_keys(key) || []
41
64
  end
42
65
 
43
66
  def expire_names cache
@@ -58,7 +81,7 @@ class Card
58
81
  def expire_views
59
82
  each_key_version do |key|
60
83
  # puts "EXPIRE VIEW CACHE (#{name}): #{view_cache_keys}"
61
- view_keys = hard_read_view_cache_keys key
84
+ view_keys = shared_read_view_cache_keys key
62
85
  next unless view_keys.present?
63
86
 
64
87
  expire_view_cache_keys view_keys
@@ -2,10 +2,16 @@ class Card
2
2
  class Cache
3
3
  # cache-related class methods
4
4
  module CardClass
5
- def retrieve_from_cache cache_key, local_only=false
6
- return unless cache
5
+ def cache
6
+ Card::Cache[Card]
7
+ end
7
8
 
8
- local_only ? cache.soft.read(cache_key) : cache.read(cache_key)
9
+ def after_write_to_temp_cache card
10
+ card.write_lexicon if card.is_a? Card
11
+ end
12
+
13
+ def retrieve_from_cache cache_key, local_only=false
14
+ local_only ? cache.temp.read(cache_key) : cache.read(cache_key)
9
15
  end
10
16
 
11
17
  def retrieve_from_cache_by_id id, local_only=false
@@ -21,16 +27,16 @@ class Card
21
27
 
22
28
  def write_to_cache card, local_only=false
23
29
  if local_only
24
- write_to_soft_cache card
30
+ write_to_temp_cache card
25
31
  elsif cache
26
32
  cache.write card.key, card
27
33
  end
28
34
  end
29
35
 
30
- def write_to_soft_cache card
36
+ def write_to_temp_cache card
31
37
  return unless cache
32
38
 
33
- cache.soft.write card.key, card
39
+ cache.temp.write card.key, card, callback: false
34
40
  end
35
41
 
36
42
  def expire name
@@ -0,0 +1,122 @@
1
+
2
+ class Card
3
+ class Cache
4
+ # class methods for Card::Cache
5
+ module ClassMethods
6
+ include Populate
7
+
8
+ attr_accessor :no_renewal
9
+ attr_accessor :counter
10
+
11
+ # create a new cache for the ruby class provided
12
+ # @param klass [Class]
13
+ # @return [{Card::Cache}]
14
+ def [] klass
15
+ raise "nil klass" if klass.nil?
16
+
17
+ cache_by_class[klass] ||= new class: klass, store: (shared_cache || nil)
18
+ end
19
+
20
+ # clear the temporary caches and ensure we're using the latest stamp
21
+ # on the shared caches.
22
+ def renew
23
+ # Cardio.config.cache_log_level = :debug
24
+ # Cardio.config.view_cache = true
25
+ # Cardio.config.asset_refresh = :cautious
26
+ # Cache.reset_all
27
+
28
+ Card::Cache.counter = nil
29
+ return if no_renewal
30
+
31
+ renew_shared
32
+ cache_by_class.each_value do |cache|
33
+ cache.temp.reset
34
+ cache.shared&.renew
35
+ end
36
+
37
+ populate_temp_cache
38
+ end
39
+
40
+ def renew_shared
41
+ Card::Cache::Shared.renew if shared_cache
42
+ end
43
+
44
+ # reset standard cached for all classes
45
+ def reset
46
+ reset_shared
47
+ reset_temp
48
+ end
49
+
50
+ # reset all caches for all classes
51
+ def reset_all
52
+ reset_shared
53
+ reset_temp
54
+ reset_other
55
+ end
56
+
57
+ # completely wipe out all caches, often including the Shared cache of
58
+ # other decks using the same mechanism.
59
+ # Generally prefer {.reset_all}
60
+ # @see .reset_all
61
+ def reset_global
62
+ cache_by_class.each_value do |cache|
63
+ cache.temp.reset
64
+ cache.shared&.annihilate
65
+ end
66
+ reset_other
67
+ end
68
+
69
+ # reset the Shared cache for all classes
70
+ def reset_shared
71
+ Card::Cache::Shared.reset if shared_cache
72
+ cache_by_class.each_value do |cache|
73
+ cache.shared&.reset
74
+ end
75
+ end
76
+
77
+ # reset the Temporary cache for all classes
78
+ def reset_temp
79
+ cache_by_class.each_value { |cache| cache.temp.reset }
80
+ end
81
+
82
+ # reset Codename cache and delete tmp files
83
+ # (the non-standard caches)
84
+ def reset_other
85
+ Card::Codename.reset_cache
86
+ Cardio::Utils.delete_tmp_files!
87
+ end
88
+
89
+ def restore
90
+ reset_temp
91
+ seed_from_stash
92
+ end
93
+
94
+ def shared_on!
95
+ return if @shared_cache
96
+
97
+ @cache_by_class = {}
98
+ @shared_cache = Cardio.config.shared_cache && Cardio.cache
99
+ end
100
+
101
+ def cache_by_class
102
+ @cache_by_class ||= {}
103
+ end
104
+
105
+ def shared_cache
106
+ return @shared_cache unless @shared_cache.nil?
107
+
108
+ @shared_cache = (ENV["NO_RAILS_CACHE"] != "true") && shared_on!
109
+ end
110
+
111
+ def tallies
112
+ "#{tally_total} Cache calls (" + counter.map { |k, v| "#{k}=#{v} " }.join + ")"
113
+ end
114
+
115
+ private
116
+
117
+ def tally_total
118
+ counter.values.map(&:values).flatten.sum
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,64 @@
1
+ class Card
2
+ class Cache
3
+ # population-related class methods for Card::Cache
4
+ module Populate
5
+ def populate_ids ids
6
+ # use ids to look up names
7
+ results = Lexicon.cache.read_multi(ids.map(&:to_s)).values
8
+ names = []
9
+ pairs = []
10
+ results.each do |result|
11
+ result.is_a?(String) ? (names << result) : (pairs << result)
12
+ end
13
+
14
+ if pairs.any?
15
+ populate_ids pairs.flatten
16
+ names += pairs.map(&:cardname)
17
+ end
18
+
19
+ # use keys to look up
20
+ populate_names names
21
+ end
22
+
23
+ def populate_names names
24
+ keys = names.map { |n| n.to_name.key }
25
+ Card.cache.read_multi keys
26
+ end
27
+
28
+ def populate_fields list, *fields
29
+ name_arrays = list.each_with_object([]) do |item, arrays|
30
+ fields.flatten.each do |field|
31
+ arrays << [item, field]
32
+ end
33
+ end
34
+ populate_names name_arrays
35
+ end
36
+
37
+ private
38
+
39
+ def populate_temp_cache
40
+ return unless shared_cache
41
+
42
+ populate_ids Codename.ids
43
+ # Codename.process_codenames if result.blank?
44
+ Card.cache.read_multi Set.basket[:cache_seed_strings]
45
+ populate_names Set.basket[:cache_seed_names]
46
+ end
47
+
48
+ # for testing, stash rules in variable and use that to re-seed cache
49
+ def seed_from_stash
50
+ return unless Cardio.config.seed_cache_from_stash
51
+
52
+ stash_to_cache("RULES") { Card::Rule.rule_cache }
53
+ stash_to_cache("READRULES") { Card::Rule.read_rule_cache }
54
+ stash_to_cache("PREFERENCES") { Card::Rule.preference_cache }
55
+ end
56
+
57
+ def stash_to_cache variable
58
+ @stash ||= {}
59
+ value = @stash[variable] ||= yield
60
+ Card.cache.temp.write variable, value.clone
61
+ end
62
+ end
63
+ end
64
+ end
@@ -2,23 +2,20 @@
2
2
 
3
3
  class Card
4
4
  class Cache
5
- # _Persistent_ (or _Hard_) caches closely mirror the database and are
5
+ # _Shared_ caches closely mirror the database and are
6
6
  # intended to be altered only upon database alterations.
7
7
  #
8
- # Unlike the database, the persistent cache stores records of records that
8
+ # Unlike the database, the shared cache stores records of records that
9
9
  # have been requested but are missing or, in the case of some {Card cards},
10
10
  # "virtual", meaning that they follow known patterns but do not exist in the
11
11
  # database.
12
12
  #
13
- # Most persistent cache implementations cannot store objects with singleton
13
+ # Most shared cache implementations cannot store objects with singleton
14
14
  # classes, therefore {Card cards} generally must have set_modules
15
- # re-included after retrieval from the persistent cache.
15
+ # re-included after retrieval from the shared cache.
16
16
  #
17
- class Persistent
18
- extend PersistentClass
19
-
20
- attr_accessor :prefix
21
-
17
+ class Shared
18
+ extend SharedClass
22
19
  # @param opts [Hash]
23
20
  # @option opts [Rails::Cache] :store
24
21
  # @option opts [ruby Class] :class, typically ActiveRecord descendant
@@ -82,27 +79,36 @@ class Card
82
79
  @store.read full_key(key)
83
80
  end
84
81
 
82
+ def read_multi keys
83
+ map = keys.each_with_object({}) { |k, h| h[full_key k] = k }
84
+ raw = @store.read_multi(*map.keys)
85
+ raw.each_with_object({}) { |(k, v), h| h[map[k]] = v }
86
+ end
87
+
85
88
  # update an attribute of an object already in the cache
86
89
  # @param key [String]
87
90
  # @param attribute [String, Symbol]
88
91
  def write_attribute key, attribute, value
89
92
  return value unless @store
90
93
 
91
- if (object = deep_read key)
94
+ # if (object = deep_read key)
95
+ if (object = read key)
92
96
  object.instance_variable_set "@#{attribute}", value
93
97
  write key, object
94
98
  end
95
99
  value
96
100
  end
97
101
 
98
- def deep_read key
99
- local_cache = @store.send :local_cache
100
- local_cache&.clear
101
- read key
102
- end
102
+ # def deep_read key
103
+ # binding.pry
104
+ # # local_cache = @store.send :local_cache
105
+ # # local_cache&.clear
106
+ # read key
107
+ # end
103
108
 
104
109
  def read_attribute key, attribute
105
- object = deep_read key
110
+ # object = deep_read key
111
+ object = read key
106
112
  object.instance_variable_get "@#{attribute}"
107
113
  end
108
114
 
@@ -1,7 +1,7 @@
1
1
  class Card
2
2
  class Cache
3
- # class methods for Card::Cache::Persistent
4
- module PersistentClass
3
+ # class methods for Card::Cache::Shared
4
+ module SharedClass
5
5
  def stamp
6
6
  @stamp ||= Cardio.cache.fetch(stamp_key) { new_stamp }
7
7
  end
@@ -8,30 +8,47 @@ class Card
8
8
  # In practice, it's a set of Cache-like methods for using a
9
9
  # simple Hash.
10
10
  #
11
- # Unlike the Persistent cache, the Temporary cache can handle objects with
11
+ # Unlike the Shared cache, the Temporary cache can handle objects with
12
12
  # singleton classes.
13
13
  class Temporary
14
+ MAX_KEYS = 10_000
14
15
  attr_reader :store
15
16
 
16
- def initialize
17
+ def initialize klass
18
+ @klass = klass
17
19
  @store = {}
20
+ @reps = 0
18
21
  end
19
22
 
20
23
  # @param key [String]
21
24
  def read key
22
- return unless @store.key? key
23
-
24
25
  @store[key]
25
26
  end
26
27
 
27
28
  # @param key [String]
28
- def write key, value
29
- @store[key] = value
29
+ def write key, value, callback: true
30
+ within_key_counts do
31
+ @store[key] = value.tap do
32
+ @reps += 1
33
+ @klass.try :after_write_to_temp_cache, value if callback
34
+ end
35
+ end
30
36
  end
31
37
 
32
38
  # @param key [String]
33
- def fetch key, &_block
34
- read(key) || write(key, yield)
39
+ def fetch key
40
+ # read(key) || write(key, yield)
41
+ exist?(key) ? read(key) : write(key, yield)
42
+ end
43
+
44
+ def fetch_multi keys
45
+ @store.slice(*keys).tap do |found|
46
+ missing = keys - found.keys
47
+ if (newfound = missing.present? && yield(missing))
48
+ found.merge! newfound
49
+ newfound.each { |key, value| write key, value }
50
+ end
51
+ end
35
52
  end
36
53
 
37
54
  # @param key [String]
@@ -46,6 +63,7 @@ class Card
46
63
  end
47
64
 
48
65
  def reset
66
+ @reps = 0
49
67
  @store = {}
50
68
  end
51
69
 
@@ -53,6 +71,20 @@ class Card
53
71
  def exist? key
54
72
  @store.key? key
55
73
  end
74
+
75
+ private
76
+
77
+ # enforces MAX_KEYS. The @reps count increments with each write but may
78
+ # overestimate the store size, because of multiple writes to the same key.
79
+ # (but this approach avoids recounting each time)
80
+ def within_key_counts
81
+ if @reps >= MAX_KEYS && (@reps = @store.size) > MAX_KEYS
82
+ Rails.logger.info "RESETTING temporary #{@klass} cache. " \
83
+ "MAX_KEYS (#{MAX_KEYS}) exceeded"
84
+ reset
85
+ end
86
+ yield
87
+ end
56
88
  end
57
89
  end
58
90
  end