Class: RuboCop::Cop::Seams::NoCrossEngineModelAccess

Inherits:
Base
  • Object
show all
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

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