card 1.107.0 → 1.108.1

Sign up to get free protection for your applications and to get access to all the features.
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