Skip to content

Commit a0dfc97

Browse files
authored
Merge pull request #1179 from owen2345/fix/phase-4-session-shortcode-comment-helpers
Phase 4: Refactor session/shortcode/comment helpers to remove helper ivars
2 parents 16c95dd + cb6174a commit a0dfc97

14 files changed

Lines changed: 431 additions & 35 deletions

.rubocop_todo.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,6 @@ Naming/VariableNumber:
151151
Rails/HelperInstanceVariable:
152152
Exclude:
153153
- 'app/helpers/camaleon_cms/frontend/nav_menu_helper.rb'
154-
- 'app/helpers/camaleon_cms/session_helper.rb'
155-
- 'app/helpers/camaleon_cms/short_code_helper.rb'
156154

157155
# Offense count: 2
158156
Security/Eval:

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
## Unreleased
44

5+
- **Refactor:** Replace Phase 4 session/shortcode/comment helper instance-variable state, [#1179](https://github.com/owen2345/camaleon-cms/pull/1179)
6+
- Refactors `session_helper`, `short_code_helper`, and `comment_helper` to avoid helper ivar state via request-scoped `CurrentRequest` and explicit context passing
7+
- Preserves controller/view compatibility points used by existing admin/session/shortcode flows
8+
- Removes Phase 4 helper exclusions from `Rails/HelperInstanceVariable` in `.rubocop_todo.yml`
9+
- Adds helper coverage for session and comment helpers and keeps shortcode helper coverage in place
10+
511
- **Refactor:** Replace Phase 3 admin/menu/taxonomy helper instance-variable state with CurrentRequest-backed state, [#1178](https://github.com/owen2345/camaleon-cms/pull/1178)
612
- Refactors admin menus, post type, and custom fields helpers to use request-scoped CurrentRequest state
713
- Eliminates traversal stack and registry instance variables from admin/menus, taxonomy hierarchy, and custom field helpers

app/helpers/camaleon_cms/comment_helper.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ def cama_comments_get_common_data
1717

1818
# render as html content all comments recursively
1919
# comments: collection of comments
20-
def cama_comments_render_html(comments)
20+
def cama_comments_render_html(comments, post_id = nil)
21+
if post_id.nil? && respond_to?(:controller) && controller.respond_to?(:instance_variable_get)
22+
post_id = controller.instance_variable_get(:@post)&.id
23+
end
2124
comments.decorate.map do |comment|
2225
author = comment.the_author
2326
content_tag(:div, class: 'media') do
@@ -45,7 +48,7 @@ def cama_comments_render_html(comments)
4548
content_tag(:div, class: 'pull-left') do
4649
[
4750
link_to(
48-
cama_admin_post_comment_answer_path(@post.id, comment.id),
51+
cama_admin_post_comment_answer_path(post_id, comment.id),
4952
data: { comment_id: comment.id },
5053
title: t('camaleon_cms.admin.comments.tooltip.reply_comment'),
5154
class: 'btn btn-info reply btn-xs ajax_modal'
@@ -88,7 +91,7 @@ def cama_comments_render_html(comments)
8891
end,
8992
content_tag(:hr),
9093
content_tag(:div, '', class: 'clearfix'),
91-
cama_comments_render_html(comment.children)
94+
cama_comments_render_html(comment.children, post_id)
9295
].join.html_safe
9396
end
9497
].join.html_safe

app/helpers/camaleon_cms/session_helper.rb

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,11 @@ def login_user(user, remember_me = false, redirect_url = nil)
3636
# login a user using username and password
3737
# return boolean: true => authenticated, false => authentication failed
3838
def login_user_with_password(username, password)
39-
@user = current_site.users.find_by(username: username)
40-
r = { user: @user, params: params, password: password, captcha_validate: true }
39+
user = current_site.users.find_by(username: username)
40+
assign_controller_user(user)
41+
r = { user: user, params: params, password: password, captcha_validate: true }
4142
hooks_run('user_before_login', r)
42-
@user&.authenticate(password)
43+
user&.authenticate(password)
4344
end
4445

4546
# User registration.
@@ -51,20 +52,21 @@ def login_user_with_password(username, password)
5152
# - password
5253
# - password_confirmation
5354
def cama_register_user(user_data, meta)
54-
@user = current_site.users.new(user_data)
55-
r = { user: @user, params: params }
55+
user = current_site.users.new(user_data)
56+
assign_controller_user(user)
57+
r = { user: user, params: params }
5658
hook_run('user_before_register', r)
5759

5860
if current_site.security_user_register_captcha_enabled? && !cama_captcha_verified?
5961
{ result: false, type: :captcha_error, message: t('camaleon_cms.admin.users.message.error_captcha') }
60-
elsif @user.save
61-
@user.set_metas(meta)
62+
elsif user.save
63+
user.set_metas(meta)
6264
message = if current_site.need_validate_email?
6365
t('camaleon_cms.admin.users.message.created_pending_validate_email')
6466
else
6567
t('camaleon_cms.admin.users.message.created')
6668
end
67-
r = { user: @user, message: message, redirect_url: cama_admin_login_path }
69+
r = { user: user, message: message, redirect_url: cama_admin_login_path }
6870
hooks_run('user_after_register', r)
6971
{ result: true, message: r[:message], redirect_url: r[:redirect_url] }
7072
else
@@ -103,6 +105,7 @@ def cama_logout_user
103105
c_data = { value: nil, expires: 24.hours.ago }
104106
c_data[:domain] = :all if PluginRoutes.system_info['users_share_sites'].present? && CamaleonCms::Site.count > 1
105107
cookies[:auth_token] = c_data
108+
CurrentRequest.user = nil
106109
redirect_to safe_redirect_url(params[:return_to]) || cama_admin_login_path,
107110
notice: t('camaleon_cms.admin.logout.message.closed')
108111
end
@@ -122,16 +125,16 @@ def cama_current_role
122125

123126
# return the current user logged in
124127
def cama_current_user
125-
return @cama_current_user if defined?(@cama_current_user)
128+
return CurrentRequest.user if CurrentRequest.user
126129

127130
# api current user...
128-
@cama_current_user = cama_calc_api_current_user
129-
return @cama_current_user if @cama_current_user
131+
current_user = cama_calc_api_current_user
132+
return CurrentRequest.user = current_user if current_user
130133

131134
return nil unless cookie_auth_token_complete?
132135

133-
@cama_current_user = current_site.users_include_admins
134-
.find_by(auth_token: user_auth_token_from_cookie).try(:decorate)
136+
users = current_site.users_include_admins
137+
CurrentRequest.user = users.find_by(auth_token: user_auth_token_from_cookie).try(:decorate)
135138
end
136139

137140
def cookie_auth_token_complete?
@@ -171,6 +174,13 @@ def cama_get_session_id
171174

172175
private
173176

177+
def assign_controller_user(user)
178+
target = respond_to?(:controller) ? controller : self
179+
return unless target.respond_to?(:instance_variable_set)
180+
181+
target.instance_variable_set(:@user, user)
182+
end
183+
174184
# validate redirect url to prevent open redirect attacks
175185
def safe_redirect_url(url)
176186
return if url.blank?

app/helpers/camaleon_cms/short_code_helper.rb

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ module CamaleonCms
22
module ShortCodeHelper
33
# Internal method
44
def shortcodes_init
5-
@_shortcodes = []
6-
@_shortcodes_template = {}
7-
@_shortcodes_descr = {}
5+
CurrentRequest.shortcodes = []
6+
CurrentRequest.shortcodes_template = {}
7+
CurrentRequest.shortcodes_descr = {}
88

99
shortcode_add('widget', nil, "Renderize the widget content in this place.
1010
Don't forget to create and copy the shortcode of your widgets in appearance -> widgets
@@ -80,22 +80,22 @@ def shortcodes_init
8080
# Also can be a function to execute that instead a render, sample: lambda{|attrs, args| return "my custom content" }
8181
# descr: description for shortcode
8282
def shortcode_add(key, template = nil, descr = '')
83-
@_shortcodes << key
84-
@_shortcodes_template = @_shortcodes_template.merge({ key.to_s => template }) if template.present?
85-
@_shortcodes_descr[key] = descr if descr.present?
83+
shortcode_keys << key
84+
CurrentRequest.shortcodes_template = shortcode_templates.merge({ key.to_s => template }) if template.present?
85+
shortcode_descriptions[key] = descr if descr.present?
8686
end
8787

8888
# add or update shortcode template
8989
# key: chortcode key to add or update
9090
# template: template to render, if nil will render "shortcode_templates/<key>"
9191
def shortcode_change_template(key, template = nil)
92-
@_shortcodes_template[key] = template
92+
shortcode_templates[key] = template
9393
end
9494

9595
# Delete the shortcode
9696
# key: chortcode key to delete
9797
def shortcode_delete(key)
98-
@_shortcodes.delete(key)
98+
shortcode_keys.delete(key)
9999
end
100100

101101
# run all shortcodes in the content
@@ -138,6 +138,14 @@ def render_shortcode(text, key, template = nil)
138138
text
139139
end
140140

141+
def shortcodes_list
142+
shortcode_keys
143+
end
144+
145+
def shortcodes_descriptions
146+
shortcode_descriptions
147+
end
148+
141149
private
142150

143151
# helper to replace shortcodes adding support for closed shortcodes, sample: [title]my title[/title]
@@ -163,20 +171,33 @@ def _cama_replace_shortcode(content, item, args = {}, template = nil)
163171
def cama_reg_shortcode(codes = nil)
164172
# doesn't support for similar names, like: [media] and [media_gallery]
165173
# "(\\[(#{codes || (@_shortcodes || []).join("|")})(\s|\\]){0}(.*?)\\])"
166-
"(\\[(#{codes || (@_shortcodes || []).join('|')})((\s)((?!\\]).)*|)\\])"
174+
"(\\[(#{codes || shortcode_keys.join('|')})((\s)((?!\\]).)*|)\\])"
167175
end
168176

169177
# determine the content to replace instead the shortcode
170178
# return string
171179
def _eval_shortcode(code, attrs, args = {}, template = nil)
172-
template ||= @_shortcodes_template[code].presence || "camaleon_cms/shortcode_templates/#{code}"
173-
if @_shortcodes_template[code].instance_of?(::Proc)
174-
@_shortcodes_template[code].call(_shortcode_parse_attr(attrs), args)
180+
templates = shortcode_templates
181+
template ||= templates[code].presence || "camaleon_cms/shortcode_templates/#{code}"
182+
if templates[code].instance_of?(::Proc)
183+
templates[code].call(_shortcode_parse_attr(attrs), args)
175184
else
176185
render template: template, locals: { attributes: _shortcode_parse_attr(attrs), args: args }, formats: [:html]
177186
end
178187
end
179188

189+
def shortcode_keys
190+
CurrentRequest.shortcodes ||= []
191+
end
192+
193+
def shortcode_templates
194+
CurrentRequest.shortcodes_template ||= {}
195+
end
196+
197+
def shortcode_descriptions
198+
CurrentRequest.shortcodes_descr ||= {}
199+
end
200+
180201
# parse the attributes of a shortcode
181202
def _shortcode_parse_attr(text)
182203
res = {}

app/models/current_request.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ class CurrentRequest < ActiveSupport::CurrentAttributes
1717
:frontend_visited_home, :frontend_visited_post, :frontend_visited_ajax, :frontend_visited_search,
1818
:frontend_visited_post_type, :frontend_visited_tag, :frontend_visited_category,
1919
:frontend_visited_profile, :frontend_user,
20-
:admin_menu_items, :custom_field_elements, :extra_models_for_fields
20+
:admin_menu_items, :custom_field_elements, :extra_models_for_fields,
21+
:shortcodes, :shortcodes_template, :shortcodes_descr
2122
end

app/views/camaleon_cms/admin/settings/shortcodes.html.erb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@
1313
</tr>
1414
</thead>
1515
<tbody>
16-
<% @_shortcodes.each do |code| %>
16+
<% shortcodes_list.each do |code| %>
1717
<tr>
1818
<td>
1919
<code>[<%= code %>]</code>
2020
</td>
21-
<td class="col_descr"><%= @_shortcodes_descr[code] %></td>
21+
<td class="col_descr"><%= shortcodes_descriptions[code] %></td>
2222
</tr>
2323
<% end %>
2424
</tbody>
2525
</table>
26-
<%= content_tag("div", t('camaleon_cms.admin.message.data_found_list'), class: "alert alert-warning") if @_shortcodes.empty? %>
26+
<%= content_tag("div", t('camaleon_cms.admin.message.data_found_list'), class: "alert alert-warning") if shortcodes_list.empty? %>
2727
</div>
2828
</div>
2929
</div>
@@ -34,4 +34,4 @@
3434
$(this).html($(this).html().replace(/\n/g, "<br>"));
3535
});
3636
})
37-
</script>
37+
</script>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Refactor Plan: Replace Helper Instance Variables with Explicit State APIs
2+
3+
Release context: `docs/ai/plans/releases/2.9.3.md`
4+
5+
## Problem
6+
`Rails/HelperInstanceVariable` currently flags 160 offenses across 16 helper modules. Most of these helpers are stateful DSL/builders (menus, SEO, shortcode, content buffers, current object/site/session memoization), so removing ivars safely requires incremental API-preserving refactors with focused specs.
7+
8+
## Proposed approach
9+
Refactor in small, behavior-safe phases (max 5 files/phase), introducing explicit state containers/memoized helper methods instead of ad-hoc instance variables. Keep public helper method signatures stable, add/expand helper specs per phase, and remove files from `Rails/HelperInstanceVariable` exclusions as each phase becomes clean.
10+
11+
## Todos
12+
1. **prep-baseline-and-guardrails**
13+
- Confirm current branch and helper offense scope.
14+
- Document affected helper families and target phase order.
15+
- Define the per-phase verification command set and rollback rule.
16+
17+
2. **phase-1-content-and-asset-helpers**
18+
- Refactor: `content_helper.rb`, `html_helper.rb`, `theme_helper.rb`, `hooks_helper.rb`.
19+
- Replace ivar mutations/reads with explicit per-request state accessors.
20+
- Add/update helper specs for content buffers and asset registries.
21+
- Remove these files from `.rubocop_todo.yml` exclusion.
22+
23+
3. **phase-2-frontend-context-helpers**
24+
- Refactor: `frontend/content_select_helper.rb`, `frontend/seo_helper.rb`, `frontend/site_helper.rb`, `site_helper.rb`.
25+
- Preserve nested block semantics (`process_in_block`) and current-site behavior.
26+
- Add/update specs for object-context switching and SEO settings isolation.
27+
- Remove these files from `.rubocop_todo.yml` exclusion.
28+
29+
4. **phase-3-admin-menu-taxonomy-helpers**
30+
- Refactor: `admin/menus_helper.rb`, `admin/post_type_helper.rb`, `admin/custom_fields_helper.rb`, `camaleon_helper.rb`.
31+
- Replace temporary ivar traversal stacks with explicit local/context structures.
32+
- Add/update specs for menu active-state resolution and taxonomy rendering.
33+
- Remove these files from `.rubocop_todo.yml` exclusion.
34+
35+
5. **phase-4-session-shortcode-and-remaining-helpers**
36+
- Refactor: `session_helper.rb`, `short_code_helper.rb`, `comment_helper.rb`.
37+
- Preserve login/session flows and shortcode registration/render behavior.
38+
- Add/update specs for auth lookups and shortcode registry lifecycle.
39+
- Remove these files from `.rubocop_todo.yml` exclusion.
40+
41+
6. **phase-5-final-cleanup-and-ci-parity**
42+
- Refactor remaining helper: `frontend/nav_menu_helper.rb`.
43+
- Remove final `Rails/HelperInstanceVariable` helper exclusion(s) once compliant.
44+
- Run full RuboCop and RSpec suite.
45+
- Run `(cd spec/dummy && bin/rails zeitwerk:check)`.
46+
- Delete `Rails/HelperInstanceVariable` exclusion block when empty.
47+
48+
## Notes and considerations
49+
- Keep backwards compatibility for helper APIs used by themes/plugins.
50+
- Avoid large all-at-once rewrites; each phase should be independently mergeable.
51+
- Treat memoization separately from mutable builder state to avoid request leakage.
52+
- If a helper cannot be safely refactored without wider architectural change, stop and document a focused follow-up design decision.

0 commit comments

Comments
 (0)