Class: ArchSpec::DSL::ComponentProxy
- Inherits:
-
Object
- Object
- ArchSpec::DSL::ComponentProxy
- 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
-
#definition ⇒ Object
readonly
Returns the value of attribute definition.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
Instance Method Summary collapse
-
#can_only_be_used_by(*consumers, because: nil) ⇒ Object
Allowlists the components that may reference this one, the inverse of #can_only_use.
-
#can_only_use(*targets, because: nil) ⇒ Object
Allowlists the components this one may depend on: only the listed components are permitted, and a reference to any other declared component fails.
-
#cannot_call(*methods, receiver: :any, because: nil) ⇒ Object
Forbids calling the named methods.
-
#cannot_define(*methods, because: nil) ⇒ Object
Forbids defining the named methods in this component.
-
#cannot_instantiate_and_invoke(because: nil) ⇒ Object
Forbids the one-shot Thing.new(...).call pattern, where a class is instantiated and immediately invoked.
-
#cannot_reference_constants(*constants, because: nil) ⇒ Object
Forbids referencing the named constants or anything under them.
-
#cannot_reference_includers(because: nil) ⇒ Object
Forbids a concern from referencing the constants that include it.
-
#cannot_use(*targets, because: nil) ⇒ Object
Forbids depending on the named components.
-
#initialize(definition, name) ⇒ ComponentProxy
constructor
A new instance of ComponentProxy.
-
#method_names(scope: :instance) ⇒ Object
Starts a naming-convention rule over the component's defined, public methods.
-
#must_be_empty(because: nil) ⇒ Object
Requires the component to hold no files.
-
#must_implement(*methods, scope: :instance, arity: nil, keywords: nil, because: nil) ⇒ Object
Requires every class in the component to implement all the named methods.
-
#must_implement_one_of(*methods, scope: :instance, because: nil) ⇒ Object
Requires every class in the component to implement at least one of the named methods.
-
#public_api(*patterns, constants: nil, namespace: nil, because: nil) ⇒ Object
Marks part of the component as its public API.
Constructor Details
#initialize(definition, name) ⇒ ComponentProxy
Returns a new instance of ComponentProxy.
172 173 174 175 |
# File 'lib/archspec/dsl.rb', line 172 def initialize(definition, name) @definition = definition @name = name.to_sym end |
Instance Attribute Details
#definition ⇒ Object (readonly)
Returns the value of attribute definition.
170 171 172 |
# File 'lib/archspec/dsl.rb', line 170 def definition @definition end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
170 171 172 |
# File 'lib/archspec/dsl.rb', line 170 def name @name end |
Instance Method Details
#can_only_be_used_by(*consumers, because: nil) ⇒ 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.
209 210 211 212 213 |
# File 'lib/archspec/dsl.rb', line 209 def can_only_be_used_by(*consumers, because: nil) DSL.assert_known_components!(definition, consumers, for_rule: "#{name}.can_only_be_used_by") add_rule(Rules::AllowedConsumersRule.new(name, consumers), because: because) self end |
#can_only_use(*targets, because: nil) ⇒ 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.
184 185 186 187 188 |
# File 'lib/archspec/dsl.rb', line 184 def can_only_use(*targets, because: nil) DSL.assert_known_components!(definition, targets, for_rule: "#{name}.can_only_use") add_rule(Rules::AllowDependenciesRule.new(name, targets), because: because) self end |
#cannot_call(*methods, receiver: :any, because: nil) ⇒ 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, or a
constant name to match that semantic class receiver and its descendants.
queries.cannot_call :save, :update, :destroy
services.cannot_call :render, :params, receiver: :none
models.cannot_call :find_by_sql, receiver: "ActiveRecord::Base"
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. Resolved method aliases are matched to their target.
Rule id: methods.forbid.
228 229 230 231 |
# File 'lib/archspec/dsl.rb', line 228 def cannot_call(*methods, receiver: :any, because: nil) add_rule(Rules::CannotCallRule.new(name, methods, receiver: receiver), because: because) self end |
#cannot_define(*methods, because: nil) ⇒ 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.
240 241 242 243 |
# File 'lib/archspec/dsl.rb', line 240 def cannot_define(*methods, because: nil) add_rule(Rules::CannotDefineMethodRule.new(name, methods), because: because) self end |
#cannot_instantiate_and_invoke(because: nil) ⇒ Object
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.
250 251 252 253 |
# File 'lib/archspec/dsl.rb', line 250 def cannot_instantiate_and_invoke(because: nil) add_rule(Rules::CannotInstantiateAndInvokeRule.new(name), because: because) self end |
#cannot_reference_constants(*constants, because: nil) ⇒ 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.
261 262 263 264 |
# File 'lib/archspec/dsl.rb', line 261 def cannot_reference_constants(*constants, because: nil) add_rule(Rules::CannotReferenceConstantsRule.new(name, constants), because: because) self end |
#cannot_reference_includers(because: nil) ⇒ Object
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.
292 293 294 295 |
# File 'lib/archspec/dsl.rb', line 292 def cannot_reference_includers(because: nil) add_rule(Rules::ConcernIndependenceRule.new(name), because: because) self end |
#cannot_use(*targets, because: nil) ⇒ 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.
196 197 198 199 200 |
# File 'lib/archspec/dsl.rb', line 196 def cannot_use(*targets, because: nil) DSL.assert_known_components!(definition, targets, for_rule: "#{name}.cannot_use") add_rule(Rules::ForbidDependenciesRule.new(name, targets), because: because) 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.
362 363 364 |
# File 'lib/archspec/dsl.rb', line 362 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.
305 306 307 308 |
# File 'lib/archspec/dsl.rb', line 305 def must_be_empty(because: nil) add_rule(Rules::MustBeEmptyRule.new(name), because: because) self end |
#must_implement(*methods, scope: :instance, arity: nil, keywords: nil, because: nil) ⇒ Object
Requires every class in the component to implement all the named
methods. Instance methods are checked by default; pass
scope: :class for the class side. Methods inherited from
resolvable superclasses or mixins count. Optional arity: and
keywords: constraints check whether each method accepts that call.
commands.must_implement :perform
commands.must_implement :call, arity: 1, keywords: :actor
jobs.must_implement :perform_later, scope: :class
Rule id: protocol.must_implement.
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/archspec/dsl.rb', line 321 def must_implement(*methods, scope: :instance, arity: nil, keywords: nil, because: nil) 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, scope: scope, arity: arity, keywords: keywords ), because: because ) end self end |
#must_implement_one_of(*methods, scope: :instance, because: nil) ⇒ Object
Requires every class in the component to implement at least one of the named methods. Useful when a protocol allows either name. Pass scope: :class to check the class side.
commands.must_implement_one_of :perform, :call
Rule id: protocol.must_implement_one_of.
346 347 348 349 |
# File 'lib/archspec/dsl.rb', line 346 def must_implement_one_of(*methods, scope: :instance, because: nil) add_rule(Rules::MustImplementOneOfRule.new(name, methods, scope: scope), because: because) self end |
#public_api(*patterns, constants: nil, namespace: nil, because: 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.
276 277 278 279 280 281 282 |
# File 'lib/archspec/dsl.rb', line 276 def public_api(*patterns, constants: nil, namespace: nil, because: nil) add_rule( Rules::PublicApiRule.new(name, files: patterns, constants: constants, namespaces: namespace), because: because ) self end |