para 0.12.8 → 0.12.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9cf58d90a0544acd64a6696d42e4fdd16a2f0aaa1af05c258fb913b6dcdba241
4
- data.tar.gz: 342113700bb23860d3db7be577c56e9f757b1293201c3175b936d11eb252b160
3
+ metadata.gz: ebd0ada08791d045fb7437af457ce37ea4d61ffe47d6e809786ebf6ef6c6dff6
4
+ data.tar.gz: ee5e91c50160e3ce4d838f0c530a3917dedff1339cc16d38b64adef87f108eb1
5
5
  SHA512:
6
- metadata.gz: 3d0b84d8a9e1c6bda68897498474b57d02a4f05a22137a790c302c4e4cc3a25678f9ca964d74f86bb3f729af9e723b18382b8dcecac63b58fa365d7b20654c07
7
- data.tar.gz: '08f583188635a61ef257e5cfd701d39c5a3a61e773d5697da2e827498c92d05b2501db8aff8706b56c7faa1537d1573584b595b27e68ab4fe4221201d052d9eb'
6
+ metadata.gz: 30e5d1f8554038eebcc28d3deba64e4ec7677106b760f2e79692b8e01752c5ecbbe9008283ba79335a3229a1415c84dbe4bcc81144272be8d6fc007090e721f8
7
+ data.tar.gz: 2d21b1015f554e2b4c534c871996b334b1bbecf97b074bf4ece24313b77df97533236b56bcc2204509753a7612b0870858a28d519a4df62cd73f59459eca7496
@@ -3,6 +3,25 @@ module Para
3
3
  class UndefinedComponentTypeError < StandardError; end
4
4
  class ComponentTooDeepError < StandardError; end
5
5
 
6
+ # Errors that signal the cached component state went stale, usually after a
7
+ # schema migration, a database reset or when a component class was unloaded
8
+ # by the code reloader. When one of these is raised while resolving a
9
+ # component, we reset the caches (and optionally reload the tree) and retry
10
+ # instead of letting it bubble up and break rails commands or the server.
11
+ #
12
+ RELOADABLE_ERRORS = [
13
+ ActiveRecord::RecordNotFound,
14
+ ActiveRecord::StatementInvalid,
15
+ ActiveRecord::SubclassNotFound,
16
+ ActiveModel::MissingAttributeError,
17
+ NameError
18
+ ].freeze
19
+
20
+ # Path to the app's `config/components.rb`, set by the engine. Used to
21
+ # rebuild the components tree when auto-reloading after a stale lookup.
22
+ #
23
+ attr_accessor :components_config_path
24
+
6
25
  def draw(&block)
7
26
  return unless components_installed?
8
27
 
@@ -10,6 +29,11 @@ module Para
10
29
  log_level = Rails.logger.level
11
30
  Rails.logger.level = :fatal
12
31
 
32
+ # Reset any previously drawn definitions so re-drawing (e.g. on reload)
33
+ # rebuilds a clean tree instead of appending duplicates.
34
+ @sections = []
35
+ reset!
36
+
13
37
  eager_load_components!
14
38
  instance_eval(&block)
15
39
  build
@@ -25,13 +49,45 @@ module Para
25
49
  end
26
50
 
27
51
  def method_missing(method, *args, &block)
28
- if (component = component_for(method))
52
+ if (component = resolve_component(method))
29
53
  component.tap(&ActiveDecorator::Decorator.instance.method(:decorate))
30
54
  else
31
55
  super
32
56
  end
33
57
  end
34
58
 
59
+ def respond_to_missing?(method, include_private = false)
60
+ # Answer respond_to? from the current state only: never trigger a reload
61
+ # just to probe whether a component exists.
62
+ !resolve_component(method, reload: false).nil? || super
63
+ end
64
+
65
+ # Clears the memoized identifier -> database id maps and the per-request
66
+ # resolved instance caches, so the next lookup rebuilds against fresh data.
67
+ # Keeps the in-memory section/component definitions (pure Ruby, schema
68
+ # independent) untouched.
69
+ #
70
+ def reset!
71
+ @sections_ids_hash = nil
72
+ @components_ids_hash = nil
73
+ RequestStore.store[:components_cache] = nil
74
+ RequestStore.store[:sections_cache] = nil
75
+ end
76
+
77
+ # Re-runs the app's `config/components.rb` to rebuild the whole components
78
+ # tree from scratch. Returns true on success, false if the file is missing
79
+ # or the reload itself fails (e.g. schema still mid-migration).
80
+ #
81
+ def reload!
82
+ path = components_config_path
83
+ return false unless path && File.exist?(path.to_s)
84
+
85
+ load(path.to_s)
86
+ true
87
+ rescue *RELOADABLE_ERRORS
88
+ false
89
+ end
90
+
35
91
  def section_for(identifier)
36
92
  if (section = sections_cache[identifier])
37
93
  section
@@ -44,6 +100,34 @@ module Para
44
100
  end
45
101
  end
46
102
 
103
+ # Resolves a component by identifier while healing stale caches. It covers
104
+ # both failure modes seen after a schema migration or a code reload:
105
+ #
106
+ # * `component_for` raises a RELOADABLE_ERROR (stale id, missing column,
107
+ # unloaded STI subclass) ;
108
+ # * `component_for` returns nil because the ids hash is empty/stale, which
109
+ # would otherwise surface as `undefined method 'xxx'` from method_missing.
110
+ #
111
+ # On the first failure it resets the caches and -- in auto-reload mode --
112
+ # rebuilds the tree from config/components.rb, then retries once. If the
113
+ # component still can't be found it returns nil, letting method_missing
114
+ # raise the usual NoMethodError for genuinely unknown components.
115
+ #
116
+ def resolve_component(identifier, reload: true, retried: false)
117
+ component = component_for(identifier)
118
+ return component if component
119
+ return nil if retried || !reload
120
+
121
+ heal_components_cache!
122
+ resolve_component(identifier, reload: reload, retried: true)
123
+ rescue *RELOADABLE_ERRORS => error
124
+ return nil if retried || !reload
125
+
126
+ log_components_failure(error)
127
+ heal_components_cache!
128
+ resolve_component(identifier, reload: reload, retried: true)
129
+ end
130
+
47
131
  def component_for(identifier)
48
132
  if (component = components_cache[identifier])
49
133
  component
@@ -83,6 +167,21 @@ module Para
83
167
 
84
168
  private
85
169
 
170
+ # Drops the stale caches and, when auto-reload is enabled, rebuilds the
171
+ # whole components tree before the lookup is retried.
172
+ #
173
+ def heal_components_cache!
174
+ reset!
175
+ reload! if Para.config.components_auto_reload
176
+ end
177
+
178
+ def log_components_failure(error)
179
+ Rails.logger.warn(
180
+ "Para components lookup failed (#{error.class}: #{error.message}).\n" \
181
+ 'Resetting the components cache and retrying.'
182
+ )
183
+ end
184
+
86
185
  def build
87
186
  sections.each_with_index do |section, index|
88
187
  section.refresh(position: index)
data/lib/para/config.rb CHANGED
@@ -25,6 +25,15 @@ module Para
25
25
  mattr_accessor :components_directory
26
26
  @@para_components_directory = 'components'
27
27
 
28
+ # When a `Para.components.xxx` lookup hits a stale cache (after a schema
29
+ # migration, a database reset or a code reload), Para resets its caches and
30
+ # -- with auto-reload enabled -- re-runs `config/components.rb` to rebuild
31
+ # the whole tree before retrying, so the lookup recovers transparently
32
+ # instead of raising. Enabled by default; set to false to opt out.
33
+ #
34
+ mattr_accessor :components_auto_reload
35
+ @@components_auto_reload = true
36
+
28
37
  mattr_reader :plugins
29
38
  @@plugins = Para::Plugins::Set.new
30
39
 
data/lib/para/engine.rb CHANGED
@@ -63,6 +63,10 @@ module Para
63
63
  initializer 'Build components tree' do |app|
64
64
  components_config_path = Rails.root.join('config', 'components.rb')
65
65
 
66
+ # Remember the path so the components configuration can rebuild the tree
67
+ # by itself when auto-reloading after a stale lookup.
68
+ Para::Component.config.components_config_path = components_config_path
69
+
66
70
  app.config.to_prepare do
67
71
  require components_config_path if File.exist?(components_config_path)
68
72
  end
data/lib/para/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Para
4
- VERSION = '0.12.8'
4
+ VERSION = '0.12.9'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: para
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.8
4
+ version: 0.12.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Valentin Ballestrino
@@ -790,7 +790,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
790
790
  - !ruby/object:Gem::Version
791
791
  version: '0'
792
792
  requirements: []
793
- rubygems_version: 3.6.9
793
+ rubygems_version: 4.0.12
794
794
  specification_version: 4
795
795
  summary: Rails admin engine
796
796
  test_files: []