Class: ArchSpec::DSL::ComponentProxy

Inherits:
Object
  • Object
show all
Defined in:
lib/archspec/dsl.rb

Overview

A handle to one component, returned by ArchSpec::DSL::Context#component and by calling a declared component's name. Rule methods return self, so they chain.

services.cannot_use(:controllers).cannot_call(:render, receiver: :none)

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(definition, name) ⇒ ComponentProxy

Returns a new instance of ComponentProxy.



163
164
165
166
# File 'lib/archspec/dsl.rb', line 163

def initialize(definition, name)
  @definition = definition
  @name = name.to_sym
end

Instance Attribute Details

#definitionObject (readonly)

Returns the value of attribute definition.



161
162
163
# File 'lib/archspec/dsl.rb', line 161

def definition
  @definition
end

#nameObject (readonly)

Returns the value of attribute name.



161
162
163
# File 'lib/archspec/dsl.rb', line 161

def name
  @name
end

Instance Method Details

#can_only_be_used_by(*consumers) ⇒ Object

Allowlists the components that may reference this one, the inverse of #can_only_use. A reference from any other component fails. Use it to protect a shared kernel or a component with a deliberately narrow audience.

shared_kernel.can_only_be_used_by :billing, :catalog

Rule id: dependencies.consumers.



200
201
202
203
204
# File 'lib/archspec/dsl.rb', line 200

def can_only_be_used_by(*consumers)
  DSL.assert_known_components!(definition, consumers, for_rule: "#{name}.can_only_be_used_by")
  add_rule(Rules::AllowedConsumersRule.new(name, consumers))
  self
end

#can_only_use(*targets) ⇒ Object

Allowlists the components this one may depend on: only the listed components are permitted, and a reference to any other declared component fails. The mirror image of #can_only_be_used_by.

controllers.can_only_use :models, :services

Rule id: dependencies.allow.



175
176
177
178
179
# File 'lib/archspec/dsl.rb', line 175

def can_only_use(*targets)
  DSL.assert_known_components!(definition, targets, for_rule: "#{name}.can_only_use")
  add_rule(Rules::AllowDependenciesRule.new(name, targets))
  self
end

#cannot_call(*methods, receiver: :any) ⇒ Object

Forbids calling the named methods. By default any receiver matches, so this catches record.update and cache.update alike. Pass receiver: :none to match only bare, implicit-+self+ calls, which is how the Rails presets keep the controller API out of models.

queries.cannot_call :save, :update, :destroy
services.cannot_call :render, :params, receiver: :none

A bare call to a method the component defines, inherits, or generates with attr_*, Rails attribute, or delegate is treated as its own API and not flagged. Rule id: methods.forbid.



218
219
220
221
# File 'lib/archspec/dsl.rb', line 218

def cannot_call(*methods, receiver: :any)
  add_rule(Rules::CannotCallRule.new(name, methods, receiver: receiver))
  self
end

#cannot_define(*methods) ⇒ Object

Forbids defining the named methods in this component. Use it when the method name itself is a design smell there, such as call on a component that should not hold command objects.

models.cannot_define :call

Rule id: methods.define_forbid.



230
231
232
233
# File 'lib/archspec/dsl.rb', line 230

def cannot_define(*methods)
  add_rule(Rules::CannotDefineMethodRule.new(name, methods))
  self
end

#cannot_instantiate_and_invokeObject

Forbids the one-shot Thing.new(...).call pattern, where a class is instantiated and immediately invoked. Use it to steer a component toward plain methods over anonymous command objects.

Rule id: objects.instantiate_and_invoke_forbid.



240
241
242
243
# File 'lib/archspec/dsl.rb', line 240

def cannot_instantiate_and_invoke
  add_rule(Rules::CannotInstantiateAndInvokeRule.new(name))
  self
end

#cannot_reference_constants(*constants) ⇒ Object

Forbids referencing the named constants or anything under them. Use this when the boundary is a framework constant rather than a component.

models.cannot_reference_constants "ActionController", "ActionView"

Rule id: constants.forbid.



251
252
253
254
# File 'lib/archspec/dsl.rb', line 251

def cannot_reference_constants(*constants)
  add_rule(Rules::CannotReferenceConstantsRule.new(name, constants))
  self
end

#cannot_reference_includersObject

Forbids a concern from referencing the constants that include it. A concern that names its includer knows too much about who uses it, which couples the two and defeats the point of extracting the concern.

component :model_concerns, in: "app/models/concerns/**/*.rb"
model_concerns.cannot_reference_includers

Rule id: concerns.independence.



279
280
281
282
# File 'lib/archspec/dsl.rb', line 279

def cannot_reference_includers
  add_rule(Rules::ConcernIndependenceRule.new(name))
  self
end

#cannot_use(*targets) ⇒ Object

Forbids depending on the named components. Narrower than #can_only_use: only the listed components fail, other dependencies are left alone.

models.cannot_use :controllers, :helpers

Rule id: dependencies.forbid.



187
188
189
190
191
# File 'lib/archspec/dsl.rb', line 187

def cannot_use(*targets)
  DSL.assert_known_components!(definition, targets, for_rule: "#{name}.cannot_use")
  add_rule(Rules::ForbidDependenciesRule.new(name, targets))
  self
end

#method_names(scope: :instance) ⇒ Object

Starts a naming-convention rule over the component's defined, public methods. Select the methods with matching, then assert something about them. Every check is name-based and exact.

models.method_names.matching(/\A(get|set)_/).forbidden
chat.method_names.matching(/\Awith_(?<base>.+)/).requires("without_%{base}")
chat.method_names.matching(/\Awith_(?<b>.+)/).requires("%{b}", on: agent, scope: :class)

Pass scope: :class to select class methods instead of instance methods. See ArchSpec::Rules::Naming::Selected for the constraints (+forbidden+, requires). Rule ids: naming.forbidden, naming.requires.



335
336
337
# File 'lib/archspec/dsl.rb', line 335

def method_names(scope: :instance)
  Rules::Naming::Builder.new(self, scope: scope)
end

#must_be_empty(because: nil) ⇒ Object

Requires the component to hold no files. Use it to keep a directory empty, such as app/services in a vanilla Rails app, with a reason shown in the diagnostic.

component(:services, in: "app/services/**/*.rb")
.must_be_empty(because: "behavior belongs on models")

Rule id: components.empty.



292
293
294
295
# File 'lib/archspec/dsl.rb', line 292

def must_be_empty(because: nil)
  add_rule(Rules::MustBeEmptyRule.new(name, because: because))
  self
end

#must_implement(*methods) ⇒ Object

Requires every class in the component to implement all the named instance methods. Methods inherited from resolvable superclasses or mixins count.

commands.must_implement :perform

Rule id: protocol.must_implement.

Raises:



304
305
306
307
308
309
310
311
# File 'lib/archspec/dsl.rb', line 304

def must_implement(*methods)
  raise Error, 'must_implement requires at least one method' if methods.flatten.compact.empty?

  methods.each do |method_name|
    add_rule(Rules::MustImplementRule.new(name, method_name))
  end
  self
end

#must_implement_one_of(*methods) ⇒ Object

Requires every class in the component to implement at least one of the named instance methods. Useful when a protocol allows either name.

commands.must_implement_one_of :perform, :call

Rule id: protocol.must_implement_one_of.



319
320
321
322
# File 'lib/archspec/dsl.rb', line 319

def must_implement_one_of(*methods)
  add_rule(Rules::MustImplementOneOfRule.new(name, methods))
  self
end

#public_api(*patterns, constants: nil, namespace: nil) ⇒ Object

Marks part of the component as its public API. References from outside must resolve to a public constant; everything else becomes private.

billing.public_api "packs/billing/app/public/**/*.rb"
billing.public_api constants: "Billing::Api"
billing.public_api namespace: "Billing::Public"

constants matches exact names, namespace matches a name and its children. Code inside the component may still reach its own internals. Rule id: dependencies.privacy.



266
267
268
269
# File 'lib/archspec/dsl.rb', line 266

def public_api(*patterns, constants: nil, namespace: nil)
  add_rule(Rules::PublicApiRule.new(name, files: patterns, constants: constants, namespaces: namespace))
  self
end