Class: RuboCop::Cop::Seams::NoCrossEngineModelAccess
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::Seams::NoCrossEngineModelAccess
- Defined in:
- lib/seams/cops/no_cross_engine_model_access.rb
Overview
Flags references to another engine's data classes from inside an engine. Engines should communicate via events or via explicitly-exposed concerns — never by reaching into another engine's data layer.
Configured per-engine via the OwnEngine, OtherEngines, and
ExposedConcerns options inside the engine's own .rubocop.yml.
The cop deliberately ignores Rails framework constants that
every engine exposes (Engine, VERSION, ApplicationController,
ApplicationRecord, ApplicationJob, ApplicationMailer) and
any class whose name ends in one of the configured suffixes
(Controller, Job, Mailer, Helper, Component, Engine).
Concerns (Billing::Billable, Billing::Concerns::Billable)
are exempt when listed in ExposedConcerns.
<Engine>::Current is also exempt by design: every engine ships
its own ActiveSupport::CurrentAttributes namespace
(Auth::Current, Accounts::Current, Teams::Current, etc.)
and these per-request state holders are intentionally readable
from anywhere in the host. Treating them as boundary-violations
would force every cross-engine read of per-request identity /
account / team to go through a host-defined shim, which defeats
the purpose of CurrentAttributes as a shared per-request bus.
The exception is documented in doc/reference/CURRENT_ATTRIBUTES.md.
Constant Summary collapse
- MSG =
"Engine `%<own>s` must not access `%<const>s` directly. " \ "Use an event or a %<other>s-exposed concern instead."
- DEFAULT_IGNORED_LEAF_NAMES =
%w[ Engine VERSION ApplicationController ApplicationRecord ApplicationJob ApplicationMailer ApplicationHelper ApplicationCable Routes Current ].freeze
- DEFAULT_IGNORED_LEAF_SUFFIXES =
%w[ Controller Job Mailer Helper Component Channel Engine ].freeze
- ASSOCIATION_MACROS =
%i[ belongs_to has_one has_many has_and_belongs_to_many ].freeze
Instance Method Summary collapse
- #on_const(node) ⇒ Object
-
#on_send(node) ⇒ Object
Association macros name the target class via a
class_name:STRING (belongs_to :account, class_name: "Accounts::Account"), whichon_constcannot see — string literals are not constant nodes, so this exact pattern used to sail through the cop.
Instance Method Details
#on_const(node) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/seams/cops/no_cross_engine_model_access.rb', line 67 def on_const(node) return unless flaggable?(node) full_name = node.const_name.to_s top_level = full_name.split("::").first assert_own_engine_configured! add_offense( node, message: format(MSG, own: own_engine, const: full_name, other: top_level) ) end |
#on_send(node) ⇒ Object
Association macros name the target class via a class_name:
STRING (belongs_to :account, class_name: "Accounts::Account"),
which on_const cannot see — string literals are not constant
nodes, so this exact pattern used to sail through the cop.
Flag the option when the named class lives in another engine.
class_name: given as a constant is already caught by
on_const, so only string values are flagged here (no double
offense). polymorphic: true associations are exempt: the
target class is decided at runtime by the owning record.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/seams/cops/no_cross_engine_model_access.rb', line 91 def on_send(node) pair = string_class_name_pair(node) return unless pair full_name = pair.value.value.to_s.delete_prefix("::") return unless cross_engine_class_name?(full_name) assert_own_engine_configured! add_offense( pair, message: format(MSG, own: own_engine, const: full_name, other: full_name.split("::").first) ) end |