railsmith 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.tool-versions +1 -0
  3. data/CHANGELOG.md +64 -0
  4. data/LICENSE.txt +21 -0
  5. data/MIGRATION.md +156 -0
  6. data/README.md +249 -0
  7. data/Rakefile +14 -0
  8. data/docs/cookbook.md +605 -0
  9. data/docs/legacy-adoption.md +283 -0
  10. data/docs/quickstart.md +110 -0
  11. data/lib/generators/railsmith/domain/domain_generator.rb +57 -0
  12. data/lib/generators/railsmith/domain/templates/domain.rb.tt +14 -0
  13. data/lib/generators/railsmith/install/install_generator.rb +21 -0
  14. data/lib/generators/railsmith/install/templates/railsmith.rb +10 -0
  15. data/lib/generators/railsmith/model_service/model_service_generator.rb +121 -0
  16. data/lib/generators/railsmith/model_service/templates/model_service.rb.tt +28 -0
  17. data/lib/generators/railsmith/operation/operation_generator.rb +88 -0
  18. data/lib/generators/railsmith/operation/templates/operation.rb.tt +27 -0
  19. data/lib/railsmith/arch_checks/cli.rb +79 -0
  20. data/lib/railsmith/arch_checks/direct_model_access_checker.rb +94 -0
  21. data/lib/railsmith/arch_checks/missing_service_usage_checker.rb +206 -0
  22. data/lib/railsmith/arch_checks/violation.rb +14 -0
  23. data/lib/railsmith/arch_checks.rb +7 -0
  24. data/lib/railsmith/arch_report.rb +96 -0
  25. data/lib/railsmith/base_service/bulk_actions.rb +77 -0
  26. data/lib/railsmith/base_service/bulk_contract.rb +56 -0
  27. data/lib/railsmith/base_service/bulk_execution.rb +68 -0
  28. data/lib/railsmith/base_service/bulk_params.rb +56 -0
  29. data/lib/railsmith/base_service/crud_actions.rb +63 -0
  30. data/lib/railsmith/base_service/crud_error_mapping.rb +78 -0
  31. data/lib/railsmith/base_service/crud_model_resolution.rb +36 -0
  32. data/lib/railsmith/base_service/crud_record_helpers.rb +60 -0
  33. data/lib/railsmith/base_service/crud_transactions.rb +31 -0
  34. data/lib/railsmith/base_service/domain_context_propagation.rb +29 -0
  35. data/lib/railsmith/base_service/dup_helpers.rb +15 -0
  36. data/lib/railsmith/base_service/validation.rb +67 -0
  37. data/lib/railsmith/base_service.rb +96 -0
  38. data/lib/railsmith/configuration.rb +18 -0
  39. data/lib/railsmith/cross_domain_guard.rb +90 -0
  40. data/lib/railsmith/cross_domain_warning_formatter.rb +66 -0
  41. data/lib/railsmith/deep_dup.rb +20 -0
  42. data/lib/railsmith/domain_context.rb +44 -0
  43. data/lib/railsmith/errors.rb +50 -0
  44. data/lib/railsmith/instrumentation.rb +64 -0
  45. data/lib/railsmith/railtie.rb +10 -0
  46. data/lib/railsmith/result.rb +60 -0
  47. data/lib/railsmith/version.rb +5 -0
  48. data/lib/railsmith.rb +31 -0
  49. data/lib/tasks/railsmith.rake +24 -0
  50. data/sig/railsmith.rbs +4 -0
  51. metadata +116 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b7d121f9233f121988b7811ac803943a3d2d784ccf018e31ecb1dd681747c632
4
+ data.tar.gz: 0d8b3debcd71cd67bec213d7614528d61584b45f1a380b35536d5e738d848871
5
+ SHA512:
6
+ metadata.gz: 6c82716d751374815ebe15f4fdc0763c3ff79dbf58b1985d9d8a2bbaa70e19fe010174563401fc23c811533009ad054c8d0de7130502ce80a10e2346f309d6f7
7
+ data.tar.gz: 97bf97006557c7fa4e6f5c19d83d47b81c48dd751bee5d66a6d812ee5072edeeaf5a3e95b8b09ec42bc3ad778e1fae3b886c93c63cbd676240cdfef96b413786
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.3.8
data/CHANGELOG.md ADDED
@@ -0,0 +1,64 @@
1
+ # Changelog
2
+
3
+ All notable changes to Railsmith are documented here.
4
+
5
+ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
+ Versioning follows [Semantic Versioning](https://semver.org/).
7
+
8
+ ---
9
+
10
+ ## [1.0.0] — 2026-03-29
11
+
12
+ First stable release. Public DSL and result contract are now frozen.
13
+
14
+ ### Added
15
+
16
+ #### Core
17
+ - `Railsmith::Result` — immutable value object with `success?`, `failure?`, `value`, `error`, `code`, `meta`, and `to_h`.
18
+ - `Railsmith::Errors` — normalized error builders: `validation_error`, `not_found`, `conflict`, `unauthorized`, `unexpected`.
19
+ - `Railsmith::BaseService` — lifecycle entrypoint `call(action:, params:, context:)` with deterministic hook ordering and subclass override points.
20
+
21
+ #### CRUD
22
+ - Default `create`, `update`, and `destroy` actions on any service that declares `model(ModelClass)`.
23
+ - Automatic exception mapping: `ActiveRecord::RecordNotFound` → `not_found`, `ActiveRecord::RecordInvalid` → `validation_error`, `ActiveRecord::RecordNotUnique` → `conflict`.
24
+ - Safe record lookup helper with consistent not-found failure shape.
25
+
26
+ #### Bulk Operations
27
+ - `bulk_create`, `bulk_update`, `bulk_destroy` on model-backed services.
28
+ - Per-item result aggregation with batch `summary` (`total`, `success_count`, `failure_count`, `all_succeeded`).
29
+ - Transaction modes: `:all_or_nothing` (rollback on any failure) and `:best_effort` (commit successful items).
30
+ - Configurable batch size limit.
31
+
32
+ #### Domain Context
33
+ - `Railsmith::DomainContext` — carries `current_domain` and arbitrary `meta` through a call chain.
34
+ - `service_domain :name` declaration on `BaseService` subclasses.
35
+ - Context propagation guard: emits `cross_domain.warning.railsmith` ActiveSupport instrumentation event when context domain differs from service domain.
36
+ - Allowlist configuration for approved cross-domain crossings.
37
+ - `on_cross_domain_violation` callback hook for custom handling.
38
+
39
+ #### Architecture Checks
40
+ - `Railsmith::ArchChecks::DirectModelAccessChecker` — static analysis for controllers that access models directly.
41
+ - `Railsmith::ArchChecks::MissingServiceUsageChecker` — flags controller actions that touch models without calling a service-style entrypoint.
42
+ - Text and JSON report formatters (`Railsmith::ArchReport`).
43
+ - `Railsmith::ArchChecks::Cli` — Ruby API for the same scan as `railsmith:arch_check`, with optional `env:`, `output:`, and `warn_proc:` for tests and embedding.
44
+ - `rake railsmith:arch_check` task with `RAILSMITH_PATHS`, `RAILSMITH_FORMAT`, and `RAILSMITH_FAIL_ON_ARCH_VIOLATIONS` environment variable support; the task delegates to `Railsmith::ArchChecks::Cli.run` (same report shape and exit semantics for callers).
45
+
46
+ #### Generators
47
+ - `railsmith:install` — creates `config/initializers/railsmith.rb` and `app/services/` directory tree.
48
+ - `railsmith:domain NAME` — scaffolds a domain module skeleton with conventional subdirectories.
49
+ - `railsmith:model_service MODEL` — scaffolds a `BaseService` subclass, namespace-aware with `--domain` flag.
50
+ - `railsmith:operation NAME` — scaffolds a plain-Ruby operation with `call` entrypoint returning `Railsmith::Result`.
51
+
52
+ #### Configuration
53
+ - `Railsmith.configure` block with: `warn_on_cross_domain_calls`, `strict_mode`, `on_cross_domain_violation`, `cross_domain_allowlist`, `fail_on_arch_violations`.
54
+
55
+ #### Documentation
56
+ - [Quickstart](docs/quickstart.md)
57
+ - [Cookbook](docs/cookbook.md) — CRUD, bulk, domain context, error mapping, observability
58
+ - [Legacy Adoption Guide](docs/legacy-adoption.md) — incremental migration strategy
59
+
60
+ ---
61
+
62
+ ## [0.1.0] — pre-release
63
+
64
+ Internal bootstrap release. Gem skeleton, CI baseline, and initial service scaffolding. Not intended for production use.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 samaswin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/MIGRATION.md ADDED
@@ -0,0 +1,156 @@
1
+ # Migration Guide
2
+
3
+ ## Upgrading from 0.x (pre-release) to 1.0.0
4
+
5
+ Railsmith 1.0.0 is the first stable release. If you were using the 0.x development version, the changes below are required before upgrading.
6
+
7
+ ---
8
+
9
+ ### Requirements
10
+
11
+ | | 0.x | 1.0.0 |
12
+ |---|---|---|
13
+ | Ruby | >= 3.2.0 | >= 3.2.0 |
14
+ | Rails | 7.0–8.x | 7.0–8.x |
15
+
16
+ No changes to minimum runtime requirements.
17
+
18
+ ---
19
+
20
+ ### Result contract — now frozen
21
+
22
+ The `Railsmith::Result` interface is stable and will not change in any 1.x release.
23
+
24
+ **No action required** if you are already using the documented API (`success?`, `failure?`, `value`, `error`, `code`, `meta`, `to_h`).
25
+
26
+ If you were accessing any internal instance variables directly (e.g., `result.instance_variable_get(:@data)`), switch to the public API before upgrading.
27
+
28
+ ---
29
+
30
+ ### Error builders — keyword arguments required
31
+
32
+ All `Railsmith::Errors` factory methods now require keyword arguments.
33
+
34
+ ```ruby
35
+ # Before (0.x, positional — no longer accepted)
36
+ Railsmith::Errors.not_found("User not found", { model: "User" })
37
+
38
+ # After (1.0.0)
39
+ Railsmith::Errors.not_found(message: "User not found", details: { model: "User" })
40
+ ```
41
+
42
+ Both `message:` and `details:` are optional but must be passed as keywords when provided.
43
+
44
+ ---
45
+
46
+ ### `BaseService.call` — `context:` is now required
47
+
48
+ In 0.x, `context:` was optional and defaulted to `{}` silently. In 1.0.0, omitting `context:` raises `ArgumentError`.
49
+
50
+ ```ruby
51
+ # Before (0.x — context omitted)
52
+ MyService.call(action: :create, params: { ... })
53
+
54
+ # After (1.0.0 — context required)
55
+ MyService.call(action: :create, params: { ... }, context: {})
56
+ ```
57
+
58
+ Pass `context: {}` at minimum. Pass a `Railsmith::DomainContext` hash when using domain boundaries.
59
+
60
+ ---
61
+
62
+ ### Cross-domain warnings — ActiveSupport instrumentation only
63
+
64
+ In 0.x, cross-domain violations could be configured to write directly to `Rails.logger`. In 1.0.0, all violation events are emitted exclusively via ActiveSupport Instrumentation (`cross_domain.warning.railsmith`). Wire up your own subscriber if you need log output:
65
+
66
+ ```ruby
67
+ # config/initializers/railsmith.rb
68
+ ActiveSupport::Notifications.subscribe("cross_domain.warning.railsmith") do |_name, _start, _finish, _id, payload|
69
+ Rails.logger.warn("[Railsmith] cross-domain: #{payload.inspect}")
70
+ end
71
+ ```
72
+
73
+ The `on_cross_domain_violation` config callback still fires and is the recommended place for custom handling.
74
+
75
+ ---
76
+
77
+ ### Generator output paths — finalized
78
+
79
+ Domain-scoped services are now always generated under `app/domains/<domain>/services/`. If you used the generator during 0.x development and accepted a different default path, move the files and update `require` paths accordingly.
80
+
81
+ | Generator | Output path (1.0.0) |
82
+ |-----------|---------------------|
83
+ | `railsmith:model_service User` | `app/services/operations/user_service.rb` |
84
+ | `railsmith:model_service Billing::Invoice --domain=Billing` | `app/domains/billing/services/invoice_service.rb` |
85
+ | `railsmith:operation Billing::Invoices::Create` | `app/domains/billing/operations/invoices/create.rb` |
86
+
87
+ ---
88
+
89
+ ### Initializer — new configuration keys
90
+
91
+ Add any missing keys to `config/initializers/railsmith.rb`. All keys have safe defaults so omitting them will not raise, but explicit configuration is recommended.
92
+
93
+ ```ruby
94
+ Railsmith.configure do |config|
95
+ config.warn_on_cross_domain_calls = true # default: true
96
+ config.strict_mode = false # default: false (reserved for v1.2)
97
+ config.fail_on_arch_violations = false # default: false
98
+ config.cross_domain_allowlist = [] # default: []
99
+ config.on_cross_domain_violation = nil # default: nil (no-op)
100
+ end
101
+ ```
102
+
103
+ ---
104
+
105
+ ### Bulk operations — transaction mode default
106
+
107
+ The default `transaction_mode` for bulk operations changed from `:best_effort` in 0.x to `:all_or_nothing` in 1.0.0.
108
+
109
+ If you rely on partial-success behavior, explicitly pass `transaction_mode: :best_effort`:
110
+
111
+ ```ruby
112
+ MyService.call(
113
+ action: :bulk_create,
114
+ params: { items: [...], transaction_mode: :best_effort },
115
+ context: {}
116
+ )
117
+ ```
118
+
119
+ ---
120
+
121
+ ### Upgrade steps
122
+
123
+ 1. Update `Gemfile`: `gem "railsmith", "~> 1.0"`
124
+ 2. Run `bundle install`.
125
+ 3. Run `bundle exec rspec` — fix any `ArgumentError` on `call` (add `context: {}`).
126
+ 4. Search for positional `Railsmith::Errors.*` calls and convert to keywords.
127
+ 5. Review initializer against the full key list above.
128
+ 6. Run `rake railsmith:arch_check` as a smoke test.
129
+ 7. Deploy.
130
+
131
+ ---
132
+
133
+ ## Embedding architecture checks from Ruby
134
+
135
+ You do not need this section for a normal upgrade. The `railsmith:arch_check` Rake task behaves the same from the shell (`RAILSMITH_PATHS`, `RAILSMITH_FORMAT`, `RAILSMITH_FAIL_ON_ARCH_VIOLATIONS`, and `Railsmith.configure { |c| c.fail_on_arch_violations }`).
136
+
137
+ If you maintain custom Rake tasks, CI scripts in Ruby, or tests that should run the same scan without shelling out, call the library entrypoint:
138
+
139
+ ```ruby
140
+ require "railsmith/arch_checks"
141
+
142
+ status = Railsmith::ArchChecks::Cli.run
143
+ # 0 — success or warn-only; 1 — fail-on enabled and violations present
144
+
145
+ # Optional: isolated env, capture output, or custom warnings
146
+ # require "stringio"
147
+ # out = StringIO.new
148
+ # warnings = []
149
+ # status = Railsmith::ArchChecks::Cli.run(
150
+ # env: { "RAILSMITH_PATHS" => "app/controllers", "RAILSMITH_FORMAT" => "text" },
151
+ # output: out,
152
+ # warn_proc: ->(message) { warnings << message }
153
+ # )
154
+ ```
155
+
156
+ Replace `rake railsmith:arch_check` with `Cli.run` only when you explicitly need an in-process API; the task remains the supported default for apps.
data/README.md ADDED
@@ -0,0 +1,249 @@
1
+ # Railsmith
2
+
3
+ Railsmith is a service-layer gem for Rails. It standardizes domain-oriented service boundaries with sensible defaults for CRUD operations, bulk operations, result handling, and cross-domain enforcement.
4
+
5
+ **Requirements**: Ruby >= 3.2.0, Rails 7.0–8.x
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```ruby
12
+ # Gemfile
13
+ gem "railsmith"
14
+ ```
15
+
16
+ ```bash
17
+ bundle install
18
+ rails generate railsmith:install
19
+ ```
20
+
21
+ The install generator creates `config/initializers/railsmith.rb` and the `app/services/` directory tree.
22
+
23
+ ---
24
+
25
+ ## Quick Start
26
+
27
+ Generate a service for a model:
28
+
29
+ ```bash
30
+ rails generate railsmith:model_service User
31
+ ```
32
+
33
+ Call it:
34
+
35
+ ```ruby
36
+ result = Operations::UserService.call(
37
+ action: :create,
38
+ params: { attributes: { name: "Alice", email: "alice@example.com" } },
39
+ context: {}
40
+ )
41
+
42
+ if result.success?
43
+ puts result.value.id
44
+ else
45
+ puts result.error.message # => "Validation failed"
46
+ puts result.error.details # => { errors: { email: ["is invalid"] } }
47
+ end
48
+ ```
49
+
50
+ See [docs/quickstart.md](docs/quickstart.md) for a full walkthrough.
51
+
52
+ ---
53
+
54
+ ## Result Contract
55
+
56
+ Every service call returns a `Railsmith::Result`. You never rescue exceptions from service calls.
57
+
58
+ ```ruby
59
+ # Success
60
+ result = Railsmith::Result.success(value: { id: 123 }, meta: { request_id: "abc" })
61
+ result.success? # => true
62
+ result.value # => { id: 123 }
63
+ result.meta # => { request_id: "abc" }
64
+ result.to_h # => { success: true, value: { id: 123 }, meta: { request_id: "abc" } }
65
+
66
+ # Failure
67
+ error = Railsmith::Errors.not_found(message: "User not found", details: { model: "User", id: 1 })
68
+ result = Railsmith::Result.failure(error:)
69
+ result.failure? # => true
70
+ result.code # => "not_found"
71
+ result.error.to_h # => { code: "not_found", message: "User not found", details: { ... } }
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Generators
77
+
78
+ | Command | Output |
79
+ |---------|--------|
80
+ | `rails g railsmith:install` | Initializer + service directories |
81
+ | `rails g railsmith:domain Billing` | `app/domains/billing.rb` + subdirectories |
82
+ | `rails g railsmith:model_service User` | `app/services/operations/user_service.rb` |
83
+ | `rails g railsmith:model_service Billing::Invoice --domain=Billing` | `app/domains/billing/services/invoice_service.rb` |
84
+ | `rails g railsmith:operation Billing::Invoices::Create` | `app/domains/billing/operations/invoices/create.rb` |
85
+
86
+ ---
87
+
88
+ ## CRUD Actions
89
+
90
+ Services that declare a `model` inherit `create`, `update`, and `destroy` with automatic exception mapping:
91
+
92
+ ```ruby
93
+ module Operations
94
+ class UserService < Railsmith::BaseService
95
+ model(User)
96
+ end
97
+ end
98
+
99
+ # create
100
+ Operations::UserService.call(action: :create, params: { attributes: { email: "a@b.com" } }, context: {})
101
+
102
+ # update
103
+ Operations::UserService.call(action: :update, params: { id: 1, attributes: { email: "new@b.com" } }, context: {})
104
+
105
+ # destroy
106
+ Operations::UserService.call(action: :destroy, params: { id: 1 }, context: {})
107
+ ```
108
+
109
+ Common ActiveRecord exceptions (`RecordNotFound`, `RecordInvalid`, `RecordNotUnique`) are caught and converted to structured failure results automatically.
110
+
111
+ ---
112
+
113
+ ## Bulk Operations
114
+
115
+ ```ruby
116
+ # bulk_create
117
+ Operations::UserService.call(
118
+ action: :bulk_create,
119
+ params: {
120
+ items: [{ name: "Alice", email: "a@b.com" }, { name: "Bob", email: "b@b.com" }],
121
+ transaction_mode: :best_effort # or :all_or_nothing
122
+ },
123
+ context: {}
124
+ )
125
+
126
+ # bulk_update
127
+ Operations::UserService.call(
128
+ action: :bulk_update,
129
+ params: { items: [{ id: 1, attributes: { name: "Alice Smith" } }] },
130
+ context: {}
131
+ )
132
+
133
+ # bulk_destroy
134
+ Operations::UserService.call(
135
+ action: :bulk_destroy,
136
+ params: { items: [1, 2, 3] },
137
+ context: {}
138
+ )
139
+ ```
140
+
141
+ All bulk results include a `summary` (`total`, `success_count`, `failure_count`, `all_succeeded`) and per-item detail. See [docs/cookbook.md](docs/cookbook.md) for the full result shape.
142
+
143
+ ---
144
+
145
+ ## Domain Boundaries
146
+
147
+ Tag services with a bounded context and track it through all calls:
148
+
149
+ ```bash
150
+ rails generate railsmith:domain Billing
151
+ rails generate railsmith:model_service Billing::Invoice --domain=Billing
152
+ ```
153
+
154
+ ```ruby
155
+ module Billing
156
+ module Services
157
+ class InvoiceService < Railsmith::BaseService
158
+ model(Billing::Invoice)
159
+ service_domain :billing
160
+ end
161
+ end
162
+ end
163
+ ```
164
+
165
+ Pass context on every call:
166
+
167
+ ```ruby
168
+ ctx = Railsmith::DomainContext.new(
169
+ current_domain: :billing,
170
+ meta: { request_id: "req-abc" }
171
+ ).to_h
172
+
173
+ Billing::Services::InvoiceService.call(action: :create, params: { ... }, context: ctx)
174
+ ```
175
+
176
+ When `current_domain` in the context differs from a service's declared `service_domain`, Railsmith emits a `cross_domain.warning.railsmith` instrumentation event.
177
+
178
+ Configure enforcement in `config/initializers/railsmith.rb`:
179
+
180
+ ```ruby
181
+ Railsmith.configure do |config|
182
+ config.warn_on_cross_domain_calls = true # default
183
+ config.strict_mode = false
184
+ config.on_cross_domain_violation = ->(payload) { ... }
185
+ config.cross_domain_allowlist = [{ from: :catalog, to: :billing }]
186
+ end
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Error Types
192
+
193
+ | Code | Factory |
194
+ |------|---------|
195
+ | `validation_error` | `Railsmith::Errors.validation_error(message:, details:)` |
196
+ | `not_found` | `Railsmith::Errors.not_found(message:, details:)` |
197
+ | `conflict` | `Railsmith::Errors.conflict(message:, details:)` |
198
+ | `unauthorized` | `Railsmith::Errors.unauthorized(message:, details:)` |
199
+ | `unexpected` | `Railsmith::Errors.unexpected(message:, details:)` |
200
+
201
+ ---
202
+
203
+ ## Architecture Checks
204
+
205
+ Detect controllers that access models directly (and related service-layer rules). From the shell:
206
+
207
+ ```bash
208
+ rake railsmith:arch_check
209
+ RAILSMITH_FORMAT=json rake railsmith:arch_check
210
+ RAILSMITH_FAIL_ON_ARCH_VIOLATIONS=true rake railsmith:arch_check
211
+ ```
212
+
213
+ From Ruby (same environment variables and exit codes as the task), after `require "railsmith/arch_checks"`:
214
+
215
+ ```ruby
216
+ Railsmith::ArchChecks::Cli.run # => 0 or 1
217
+ ```
218
+
219
+ See [Migration](MIGRATION.md#embedding-architecture-checks-from-ruby) for optional `env:`, `output:`, and `warn_proc:` arguments.
220
+
221
+ ---
222
+
223
+ ## Documentation
224
+
225
+ - [Quickstart](docs/quickstart.md) — install, generate, first call
226
+ - [Cookbook](docs/cookbook.md) — CRUD, bulk, domain context, error mapping, observability
227
+ - [Legacy Adoption Guide](docs/legacy-adoption.md) — incremental migration strategy
228
+
229
+ ---
230
+
231
+ ## Development
232
+
233
+ ```bash
234
+ bin/setup # install dependencies
235
+ bundle exec rake spec # run tests
236
+ bin/console # interactive prompt
237
+ ```
238
+
239
+ To install locally: `bundle exec rake install`.
240
+
241
+ ---
242
+
243
+ ## Contributing
244
+
245
+ Bug reports and pull requests are welcome at [github.com/samaswin/railsmith](https://github.com/samaswin/railsmith).
246
+
247
+ ## License
248
+
249
+ [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ load File.expand_path("lib/tasks/railsmith.rake", __dir__)
6
+ require "rspec/core/rake_task"
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ require "rubocop/rake_task"
11
+
12
+ RuboCop::RakeTask.new
13
+
14
+ task default: %i[spec rubocop]