Module: CurrentScope::ParentChain::Declaration

Defined in:
lib/current_scope/parent_chain.rb

Overview

Installed on ActiveRecord::Base via ActiveSupport.on_load — the standard engine idiom for an acts_as_*-style declaration. Deliberately NOT hung off CurrentScope::Scopeable, whose contract is "BROWSE-ONLY — it does NOT gate access" (scopeable.rb:3) and whose included hook would also register the model in the scoped-role picker as a side effect of declaring a parent.

Instance Method Summary collapse

Instance Method Details

#current_scope_parent(association_name) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/current_scope/parent_chain.rb', line 51

def current_scope_parent(association_name)
  reflection = reflect_on_association(association_name)

  if reflection.nil?
    raise ConfigurationError,
          "#{name}.current_scope_parent(#{association_name.inspect}) names an " \
          "association that does not exist. Declare `belongs_to " \
          "#{association_name.inspect}` first, or name the association that " \
          "reaches the record scoped grants are held on."
  end

  unless reflection.belongs_to?
    raise ConfigurationError,
          "#{name}.current_scope_parent(#{association_name.inspect}) must name a " \
          "belongs_to association — #{association_name.inspect} is a " \
          "#{reflection.macro}. A scoped grant is held on ONE parent record, so " \
          "the chain walks upward one owner at a time. Declare it on the CHILD " \
          "instead — `current_scope_parent` in #{reflection.klass.name} — so a " \
          "grant held here reaches its #{association_name}."
  end

  if reflection.polymorphic?
    raise ConfigurationError,
          "#{name}.current_scope_parent(#{association_name.inspect}) names a " \
          "polymorphic belongs_to, which is not supported: the parent's class is " \
          "not knowable without loading every candidate row, so scope_for could " \
          "not build its query. Either name a concrete belongs_to if this model " \
          "has one, or keep this model flat and grant on it directly."
  end

  # The walk loads the parent THROUGH the association, so a scope on it is
  # applied; ancestor_scope_for rebuilds the join from the foreign key
  # alone, so the scope never reaches that SQL. The two then disagree in
  # the fail-OPEN direction — the gate denies while the collection-read
  # gate (which is scope_for(...).exists?) allows and the list renders the
  # rows. Refusing the shape is total; supporting it would mean building
  # the arm with joins(reflection.name) so one definition feeds both.
  if reflection.scope
    raise ConfigurationError,
          "#{name}.current_scope_parent(#{association_name.inspect}) names a SCOPED " \
          "belongs_to. The per-record walk would apply that scope and the collection " \
          "query would not, so the gate and the list would disagree about the same " \
          "record. Declare an unscoped belongs_to for the chain."
  end

  # Resolver#scope_for is handed the COLLECTION type a controller names,
  # which for STI is the base class, while the per-record gate reads the
  # declaration off the instance's own class. A chain declared on a
  # subclass would therefore be walked by the gate and invisible to the
  # list — the two disagreeing about the same record. Refusing the shape
  # removes that entire class of drift instead of documenting it.
  if self != base_class
    raise ConfigurationError,
          "#{name}.current_scope_parent(#{association_name.inspect}) is declared on " \
          "an STI subclass. Declare it on #{base_class.name} instead: scoped grants " \
          "store the base class, and the collection query is built from it, so a " \
          "subclass-only chain would open records the list could never show."
  end

  self.current_scope_parent_association = association_name.to_sym
  CurrentScope::ParentChain.register(self)
end

#current_scope_parent_declared?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'lib/current_scope/parent_chain.rb', line 114

def current_scope_parent_declared?
  !current_scope_parent_association.nil?
end