Module: ArchSpec::DSL::Context
- Defined in:
- lib/archspec/dsl.rb
Overview
The top-level DSL. Declare the project, its components, an architecture preset, and global rules.
root "."
source "app/**/*.rb", "lib/**/*.rb"
ignore "app/legacy/**/*.rb"
todo "archspec_todo.yml"
component :models, in: "app/models/**/*.rb"
component :controllers, in: "app/controllers/**/*.rb"
models.cannot_use :controllers
Declaring a component defines a reader for it, so models and
controllers above return an ArchSpec::DSL::ComponentProxy you attach
rules to.
Instance Method Summary collapse
-
#architecture(name, **options) ⇒ Object
Applies a bundled architecture preset, defining its components and rules together.
-
#component(name, in: nil, namespace: nil, constants: nil) ⇒ Object
(also: #layer, #role)
Declares a component: a named set of files, matched by glob, namespace, or explicit constant.
-
#each_directory(glob) ⇒ Object
Yields each subdirectory matching a glob, so you can declare one component per engine or pack without hardcoding their names.
-
#ignore(*patterns) ⇒ Object
Adds glob patterns for files to skip.
-
#inflect(map) ⇒ Object
Declares acronym inflections for Zeitwerk name checks, mirroring your
config/initializers/inflections.rb. - #method_missing(name) ⇒ Object
-
#no_cycles!(among: nil) ⇒ Object
Forbids dependency cycles between components.
- #respond_to_missing?(name, include_private = false) ⇒ Boolean
-
#root(path = nil) ⇒ Object
Sets or reads the project root that file patterns resolve against.
-
#rule(rule) ⇒ Object
Adds a custom rule object.
-
#source(*patterns) ⇒ Object
Adds glob patterns for the files ArchSpec parses.
-
#todo(path = 'archspec_todo.yml') ⇒ Object
Points at a todo file of accepted violations.
-
#verify_zeitwerk_names!(*only) ⇒ Object
Checks that files define the constant their path implies under Zeitwerk.
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name) ⇒ Object
155 156 157 158 159 |
# File 'lib/archspec/dsl.rb', line 155 def method_missing(name, ...) return ComponentProxy.new(self, name) if component?(name) super end |
Instance Method Details
#architecture(name, **options) ⇒ Object
Applies a bundled architecture preset, defining its components and rules together.
architecture :rails
architecture :hexagonal
architecture :modular_monolith, components: { ... }, allow: { ... }
See ArchSpec::Architectures for every preset and its options.
121 122 123 |
# File 'lib/archspec/dsl.rb', line 121 def architecture(name, **) Architectures.apply(name, self, **) end |
#component(name, in: nil, namespace: nil, constants: nil) ⇒ Object Also known as: layer, role
Declares a component: a named set of files, matched by glob, namespace, or explicit constant.
component :services, in: "app/services/**/*.rb"
component :billing, namespace: "Billing"
component :legacy, constants: %w[OldReport OldExport]
Returns an ArchSpec::DSL::ComponentProxy for attaching rules. The component is also available by name later in the file.
layer and role are aliases. Use whichever word fits the
architecture you are describing.
103 104 105 106 107 108 |
# File 'lib/archspec/dsl.rb', line 103 def component(name, in: nil, namespace: nil, constants: nil) add_component( ComponentSpec.new(name, files: binding.local_variable_get(:in), namespace: namespace, constants: constants) ) ComponentProxy.new(self, name) end |
#each_directory(glob) ⇒ Object
Yields each subdirectory matching a glob, so you can declare one
component per engine or pack without hardcoding their names. Paths
resolve against the Archspec.rb directory, not the working directory,
so it does not matter where archspec is run from.
each_directory "engines/*" do |name, path|
component name, in: "#{path}/**/*.rb"
end
Yields the directory basename and its root-relative path. Returns the [name, path] pairs when called without a block.
80 81 82 83 84 85 86 87 88 89 |
# File 'lib/archspec/dsl.rb', line 80 def each_directory(glob) base = absolute_root pairs = Dir.glob(File.join(base, glob)).select { |path| File.directory?(path) }.sort.map do |absolute| [File.basename(absolute), Pathname(absolute).relative_path_from(Pathname(base)).to_s] end return pairs unless block_given? pairs.each { |name, path| yield(name, path) } end |
#ignore(*patterns) ⇒ Object
Adds glob patterns for files to skip. Combines with the built-in
ignores for .git, tmp, vendor, and node_modules.
44 45 46 |
# File 'lib/archspec/dsl.rb', line 44 def ignore(*patterns) add_ignore_patterns(patterns) end |
#inflect(map) ⇒ Object
Declares acronym inflections for Zeitwerk name checks, mirroring your
config/initializers/inflections.rb.
inflect "api" => "API", "graphql" => "GraphQL"
app/models/api_client.rb then expects APIClient.
65 66 67 |
# File 'lib/archspec/dsl.rb', line 65 def inflect(map) add_inflections(map) end |
#no_cycles!(among: nil) ⇒ Object
Forbids dependency cycles between components. Pass among: to limit the
check to a subset; omit it to check every declared component.
no_cycles!
no_cycles! among: %i[billing catalog shared]
Rule id: dependencies.no_cycles.
132 133 134 |
# File 'lib/archspec/dsl.rb', line 132 def no_cycles!(among: nil) add_rule(Rules::NoCyclesRule.new(among: among)) end |
#respond_to_missing?(name, include_private = false) ⇒ Boolean
161 162 163 |
# File 'lib/archspec/dsl.rb', line 161 def respond_to_missing?(name, include_private = false) component?(name) || super end |
#root(path = nil) ⇒ Object
Sets or reads the project root that file patterns resolve against.
Defaults to the directory of the Archspec.rb file.
29 30 31 32 33 |
# File 'lib/archspec/dsl.rb', line 29 def root(path = nil) return root_path unless path self.root_path = path.to_s end |
#rule(rule) ⇒ Object
Adds a custom rule object. A rule responds to id and
evaluate(graph), returning ArchSpec::Diagnostic objects. Use
this to extend ArchSpec with project-specific checks.
151 152 153 |
# File 'lib/archspec/dsl.rb', line 151 def rule(rule) add_rule(rule) end |
#source(*patterns) ⇒ Object
Adds glob patterns for the files ArchSpec parses. Defaults cover
app, lib, packs, and engines. Component patterns are always
analyzed, so most projects never need this.
38 39 40 |
# File 'lib/archspec/dsl.rb', line 38 def source(*patterns) add_source_patterns(patterns) end |
#todo(path = 'archspec_todo.yml') ⇒ Object
Points at a todo file of accepted violations. Diagnostics recorded there are subtracted from future runs, so you can adopt ArchSpec in an existing app without fixing everything first, then burn the list down.
todo "archspec_todo.yml"
Write or refresh it with archspec check --update-todo.
55 56 57 |
# File 'lib/archspec/dsl.rb', line 55 def todo(path = 'archspec_todo.yml') self.todo_path = path.to_s end |
#verify_zeitwerk_names!(*only) ⇒ Object
Checks that files define the constant their path implies under Zeitwerk.
Pass globs to restrict the check to the autoloaded tree, since Rails
does not autoload lib by default.
verify_zeitwerk_names!
verify_zeitwerk_names! "app/**/*.rb"
Rule id: zeitwerk.naming.
144 145 146 |
# File 'lib/archspec/dsl.rb', line 144 def verify_zeitwerk_names!(*only) add_rule(Rules::ZeitwerkNamingRule.new(only: only)) end |