dami 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 (56) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +306 -0
  3. data/LICENSE +21 -0
  4. data/README.md +106 -0
  5. data/bin/dami +10 -0
  6. data/docs/01.Getting_Started.md +220 -0
  7. data/docs/02.Models_and_Fields.md +244 -0
  8. data/docs/03.Querying.md +387 -0
  9. data/docs/04.Creating_Updating_Deleting.md +226 -0
  10. data/docs/05.Validation.md +259 -0
  11. data/docs/06.Protection.md +160 -0
  12. data/docs/07.Associations.md +239 -0
  13. data/docs/08.Scopes.md +99 -0
  14. data/docs/09.Migrations.md +165 -0
  15. data/docs/10.Flows_and_Commands.md +181 -0
  16. data/docs/11.Localization.md +435 -0
  17. data/docs/12.TheDamiWay.md +227 -0
  18. data/docs/Manifesto.md +305 -0
  19. data/docs/site.md +477 -0
  20. data/lib/dami/actions/command.rb +80 -0
  21. data/lib/dami/actions/context.rb +63 -0
  22. data/lib/dami/actions/draft.rb +36 -0
  23. data/lib/dami/actions/flow.rb +42 -0
  24. data/lib/dami/adapters/base.rb +40 -0
  25. data/lib/dami/adapters/sqlite/connection.rb +122 -0
  26. data/lib/dami/adapters/sqlite/core.rb +17 -0
  27. data/lib/dami/adapters/sqlite/query.rb +353 -0
  28. data/lib/dami/adapters/sqlite/schema.rb +135 -0
  29. data/lib/dami/cli.rb +117 -0
  30. data/lib/dami/configuration.rb +246 -0
  31. data/lib/dami/core.rb +98 -0
  32. data/lib/dami/dsl_guardrails.rb +45 -0
  33. data/lib/dami/errors.rb +89 -0
  34. data/lib/dami/inflector.rb +245 -0
  35. data/lib/dami/localization.rb +131 -0
  36. data/lib/dami/migration.rb +84 -0
  37. data/lib/dami/migrator.rb +105 -0
  38. data/lib/dami/plugins/associations.rb +284 -0
  39. data/lib/dami/plugins/nested_attributes.rb +180 -0
  40. data/lib/dami/plugins/protection.rb +30 -0
  41. data/lib/dami/plugins/validations.rb +90 -0
  42. data/lib/dami/query/builder.rb +132 -0
  43. data/lib/dami/query/enumerable.rb +62 -0
  44. data/lib/dami/query/persistence.rb +133 -0
  45. data/lib/dami/record_proxy.rb +32 -0
  46. data/lib/dami/result.rb +27 -0
  47. data/lib/dami/schema/diff.rb +84 -0
  48. data/lib/dami/schema/dumper.rb +60 -0
  49. data/lib/dami/schema/generator.rb +69 -0
  50. data/lib/dami/schema/introspector.rb +34 -0
  51. data/lib/dami/schema/loader.rb +58 -0
  52. data/lib/dami/schema.rb +13 -0
  53. data/lib/dami/validation_rules.rb +72 -0
  54. data/lib/dami/version.rb +3 -0
  55. data/lib/dami.rb +32 -0
  56. metadata +218 -0
@@ -0,0 +1,435 @@
1
+ # Chapter 11: Internationalization (I18n) & Localization
2
+
3
+ Building a world-class application means speaking your users' language. Internationalization (i18n) is often a complex afterthought, involving scattered files and inconsistent APIs. Dami treats it as a first-class citizen with a simple, powerful philosophy: **all user-facing text that the framework is aware of should live in one clean, consistent, and localizable place.**
4
+
5
+ This is achieved through a new, core DSL: `Dami.localize`.
6
+
7
+
8
+
9
+ This unified system is not just for validation messages. It's a complete solution for translating every piece of "chrome" text connected to your data layer, providing a single source of truth for your application's vocabulary.
10
+
11
+ ---
12
+
13
+ ## The `Dami.localize` DSL
14
+
15
+ The `Dami.localize` block is the central hub for all framework-aware translations. You define a scope (like `:validations` or `:models`), specify a language, and provide your translations using a clean, block-based syntax.
16
+
17
+ ```ruby
18
+ # config/locales/dami.rb
19
+
20
+ Dami.localize :validations do
21
+ en do
22
+ set :required, "cannot be blank"
23
+ set :min_length, ->(min) { "is too short (minimum: #{min} characters)" }
24
+ end
25
+
26
+ es do
27
+ set :required, "no puede estar en blanco"
28
+ set :min_length, ->(min) { "es demasiado corto (mínimo: #{min} caracteres)" }
29
+ end
30
+ end
31
+ ````
32
+
33
+ This approach is powerful because it's pure Ruby. You can load these definitions from anywhere, and it's completely explicit—no "magic" `method_missing`.
34
+
35
+ -----
36
+
37
+ ## Why `Dami.localize` is More Than Just Validations
38
+
39
+ Your ORM is the single source of truth for your data's structure. As such, it's the perfect place to manage the human-readable text associated with that structure. `Dami.localize` is designed to be a holistic system, covering four key scopes.
40
+
41
+ ### 1\. `:validations` - Error Messages
42
+
43
+ This is the most common use case. It allows you to provide clean, translated error messages for all of Dami's built-in and custom validation rules.
44
+
45
+ * **The Problem:** Hardcoded English error messages scattered throughout your models or initializers.
46
+ * **The Solution:** A central `validations` scope that the `Dami.behavior` block will automatically use.
47
+
48
+ <!-- end list -->
49
+
50
+ ```ruby
51
+ Dami.localize :validations do
52
+ en { set :email, "must be a valid email address" }
53
+ es { set :email, "debe ser una dirección de correo electrónico válida" }
54
+ end
55
+ ```
56
+
57
+ ### 2\. `:models` - Attribute & Model Names
58
+
59
+ * **The Problem:** Your database uses `first_name`, but your HTML form label should say "First name". This couples your views to your schema's raw column names.
60
+ * **The Solution:** A `models` scope to provide canonical, human-readable names for your models and their attributes.
61
+
62
+ <!-- end list -->
63
+
64
+ ```ruby
65
+ Dami.localize :models do
66
+ en do
67
+ attributes_for :users do
68
+ set :model_name, "User"
69
+ set :first_name, "First Name"
70
+ end
71
+ end
72
+ es do
73
+ attributes_for :users do
74
+ set :model_name, "Usuario"
75
+ set :first_name, "Nombre"
76
+ end
77
+ end
78
+ end
79
+
80
+ # A future helper would make this easy to use in views:
81
+ # Dami.human_attribute_name(:users, :first_name, locale: :es) # => "Nombre"
82
+ ```
83
+
84
+ ### 3\. `:enums` - Stored Values
85
+
86
+ * **The Problem:** Your database stores a post's status as `"published"`, but you want to display "Published" or "Publicado" to the user.
87
+ * **The Solution:** An `enums` scope to map raw data values to clean, display-friendly text.
88
+
89
+ <!-- end list -->
90
+
91
+ ```ruby
92
+ Dami.localize :enums do
93
+ en do
94
+ enum_for :posts, :status do
95
+ set :published, "Published"
96
+ set :draft, "In Draft"
97
+ end
98
+ end
99
+ end
100
+ ```
101
+
102
+ ### 4\. `:errors` - System Exceptions
103
+
104
+ * **The Problem:** Dami might raise a `Dami::ProtectionError`. You want to rescue this and show a friendly, translated message to the user.
105
+ * **The Solution:** An `errors` scope to provide user-facing translations for framework-level exceptions.
106
+
107
+ <!-- end list -->
108
+
109
+ ```ruby
110
+ Dami.localize :errors do
111
+ en { set :protection_error, "You are not authorized to change one or more of the submitted fields." }
112
+ end
113
+ ```
114
+
115
+ -----
116
+
117
+ ## Locale Management
118
+
119
+ Dami is designed to integrate seamlessly with the existing Ruby ecosystem. It uses a smart, three-tiered approach to determine the current language.
120
+
121
+ 1. **Manual Override:** You can force a process-wide default with `Dami.locale = :es` (set it once at boot). Inside a web request use `Dami.thread_locale = :es` or `Dami.with_locale` instead — those are thread-local, so one request's locale never leaks into another thread's.
122
+ 2. **Automatic `I18n` Detection:** If the standard `i18n` gem is loaded, Dami will **automatically** use `I18n.locale`. This means it works out-of-the-box with Rails, Sinatra, and most other Ruby web frameworks.
123
+ 3. **Default:** If neither of the above is present, it defaults to `:en`.
124
+
125
+ For testing or handling specific requests, you can use the `Dami.with_locale` helper, which guarantees the original locale is restored. It is thread-local: only the calling thread sees the temporary locale.
126
+
127
+ ```ruby
128
+ Dami.with_locale(:es) do
129
+ # All Dami operations inside this block will use the Spanish locale.
130
+ puts Dami.current_locale # => :es
131
+ end
132
+ puts Dami.current_locale # => :en (back to the original)
133
+ ```
134
+ ---
135
+
136
+ ## Helper Methods for Views and Forms
137
+
138
+ Dami provides convenient helper methods to retrieve human-readable labels for your models and fields. These are essential for building forms, table headers, and any UI that displays field names to users.
139
+
140
+ ### `Dami.localize_field` (alias: `lf`)
141
+
142
+ Retrieves the translated label for a specific field on a model.
143
+ ```ruby
144
+ # Define your translations
145
+ Dami.localize :models do
146
+ en do
147
+ attributes_for :users do
148
+ set :first_name, "First Name"
149
+ set :email, "Email Address"
150
+ set :phone_number, "Phone"
151
+ end
152
+ end
153
+ es do
154
+ attributes_for :users do
155
+ set :first_name, "Nombre"
156
+ set :email, "Correo Electrónico"
157
+ set :phone_number, "Teléfono"
158
+ end
159
+ end
160
+ end
161
+
162
+ # In your views or controllers
163
+ Dami.localize_field(:users, :first_name) # => "First Name" (or "Nombre" if locale is :es)
164
+ Dami.lf(:users, :email) # => "Email Address" (shorthand)
165
+ ```
166
+
167
+ **Real-world example in a form:**
168
+ ```ruby
169
+ # In a Sinatra/Hanami view
170
+ <form>
171
+ <label><%= Dami.lf(:users, :first_name) %>:</label>
172
+ <input type="text" name="user[first_name]">
173
+
174
+ <label><%= Dami.lf(:users, :email) %>:</label>
175
+ <input type="email" name="user[email]">
176
+ </form>
177
+ ```
178
+
179
+ When a Spanish-speaking user visits, the labels automatically display as "Nombre" and "Correo Electrónico".
180
+
181
+
182
+ ### `Dami.localize_model` (alias: `lm`)
183
+
184
+ Retrieves the translated name for a model itself. Useful for page titles, breadcrumbs, and headers.
185
+ ```ruby
186
+ Dami.localize :models do
187
+ en do
188
+ attributes_for :users do
189
+ set :model_name, "User"
190
+ end
191
+ attributes_for :products do
192
+ set :model_name, "Product"
193
+ end
194
+ end
195
+ es do
196
+ attributes_for :users do
197
+ set :model_name, "Usuario"
198
+ end
199
+ attributes_for :products do
200
+ set :model_name, "Producto"
201
+ end
202
+ end
203
+ end
204
+
205
+ # In your views
206
+ Dami.localize_model(:users) # => "User" (or "Usuario" if locale is :es)
207
+ Dami.lm(:products) # => "Product" (shorthand)
208
+ ```
209
+
210
+ **Real-world example in a page header:**
211
+ ```ruby
212
+ # In a controller
213
+ class UsersController
214
+ def index
215
+ @model_name = Dami.lm(:users)
216
+ # Renders "Users" or "Usuarios" depending on locale
217
+ end
218
+ end
219
+
220
+ # In the view
221
+ <h1><%= @model_name %> Directory</h1>
222
+ ```
223
+
224
+
225
+ ### Explicit Locale Override
226
+
227
+ Both helpers accept an optional `locale:` parameter to override the current locale:
228
+ ```ruby
229
+ Dami.locale = :en
230
+
231
+ # Force Spanish translation
232
+ Dami.lf(:users, :first_name, locale: :es) # => "Nombre"
233
+ Dami.lm(:users, locale: :es) # => "Usuario"
234
+
235
+ # Current locale still English
236
+ Dami.lf(:users, :first_name) # => "First Name"
237
+ ```
238
+
239
+ This is useful for:
240
+ - Admin interfaces showing translations for review
241
+ - Email templates that need to use the recipient's preferred language
242
+ - Testing different locale outputs
243
+
244
+ ### Automatic Locale Detection
245
+
246
+ The helpers automatically use the current locale through the three-tier detection system:
247
+
248
+ 1. **Manual override**: `Dami.locale = :es`
249
+ 2. **I18n gem**: `I18n.locale` (automatic in Rails/Sinatra)
250
+ 3. **Default**: `:en`
251
+
252
+ **In a Rails controller:**
253
+ ```ruby
254
+ class ProductsController < ApplicationController
255
+ def show
256
+ # Rails sets I18n.locale per-request based on user preference
257
+ # Dami automatically picks it up
258
+
259
+ @product_name_label = Dami.lf(:products, :name)
260
+ # => "Product Name" for English users
261
+ # => "Nombre del Producto" for Spanish users
262
+ # Zero configuration needed!
263
+ end
264
+ end
265
+ ```
266
+
267
+ ### Building Dynamic Forms
268
+
269
+ Here's a complete example of using these helpers to build a multi-lingual form:
270
+ ```ruby
271
+ # Define all your translations
272
+ Dami.localize :models do
273
+ en do
274
+ attributes_for :products do
275
+ set :model_name, "Product"
276
+ set :name, "Product Name"
277
+ set :description, "Description"
278
+ set :price, "Price"
279
+ set :category, "Category"
280
+ end
281
+ end
282
+ es do
283
+ attributes_for :products do
284
+ set :model_name, "Producto"
285
+ set :name, "Nombre del Producto"
286
+ set :description, "Descripción"
287
+ set :price, "Precio"
288
+ set :category, "Categoría"
289
+ end
290
+ end
291
+ end
292
+
293
+ # In your view template
294
+ <h1>New <%= Dami.lm(:products) %></h1>
295
+
296
+ <form action="/products" method="post">
297
+ <div class="field">
298
+ <label><%= Dami.lf(:products, :name) %></label>
299
+ <input type="text" name="product[name]">
300
+ </div>
301
+
302
+ <div class="field">
303
+ <label><%= Dami.lf(:products, :description) %></label>
304
+ <textarea name="product[description]"></textarea>
305
+ </div>
306
+
307
+ <div class="field">
308
+ <label><%= Dami.lf(:products, :price) %></label>
309
+ <input type="number" name="product[price]">
310
+ </div>
311
+
312
+ <button>Create <%= Dami.lm(:products) %></button>
313
+ </form>
314
+ ```
315
+
316
+ This same template renders perfectly in any language you've defined, with zero code changes.
317
+
318
+ ### Best Practices
319
+
320
+ 1. **Define labels even for English**: Don't assume field names are self-explanatory. `email` should be "Email Address", not just "email".
321
+
322
+ 2. **Use shorthand in views**: `lf` and `lm` are shorter and cleaner in templates where you'll use them frequently.
323
+
324
+ 3. **Consistent naming**: Use the same field names across all locales to make translation maintenance easier.
325
+
326
+ 4. **Fallback gracefully**: If a translation is missing, the helper returns `nil`. Handle this in your views:
327
+ ```ruby
328
+ label = Dami.lf(:users, :field_name) || "Field Name"
329
+ ```
330
+
331
+ -----
332
+
333
+ ## Putting It All Together: An End-to-End Example
334
+
335
+ This example shows how the entire system works together, from definition to execution.
336
+
337
+ ```ruby
338
+ # 1. Define your translations
339
+ Dami.localize :validations do
340
+ en { set :required, "cannot be blank" }
341
+ es { set :required, "no puede estar en blanco" }
342
+ end
343
+
344
+ # 2. Define your model and behavior
345
+ Dami.model(:articles) { fields { field :title, :string } }
346
+ Dami.behavior(:articles) { validate { rule :title, :required } }
347
+
348
+ # 3. Trigger the validation in English (default)
349
+ error_en = assert_raises(Dami::ValidationError) do
350
+ Dami.db(:articles).create(title: '')
351
+ end
352
+ assert_includes error_en.errors[:title], "cannot be blank"
353
+
354
+ # 4. Trigger the validation in Spanish
355
+ Dami.with_locale(:es) do
356
+ error_es = assert_raises(Dami::ValidationError) do
357
+ Dami.db(:articles).create(title: '')
358
+ end
359
+ assert_includes error_es.errors[:title], "no puede estar en blanco"
360
+ end
361
+ ```
362
+
363
+ -----
364
+
365
+ ## Integrating with `I18n` and Other Gems
366
+
367
+ Dami's localization system is designed to be a great citizen in the Ruby ecosystem. It integrates seamlessly with standard tools like the `i18n` gem, so you don't have to choose between Dami's power and your existing workflow.
368
+
369
+ ### The "It Just Works" Principle: Automatic `I18n` Detection
370
+
371
+ For over 90% of use cases, **you don't need to do anything.**
372
+
373
+ If Dami detects that the `i18n` gem is loaded in your application (as it is in all Rails, Sinatra, and Hanami apps), it will automatically use `I18n.locale` to determine the current language. When a user's locale is set to `:es` in a web request, Dami's validation messages will automatically be in Spanish.
374
+
375
+ ```ruby
376
+ # In a Rails or Sinatra controller
377
+ I18n.with_locale(:es) do
378
+ begin
379
+ # This will now raise a ValidationError with the Spanish message
380
+ # because Dami automatically respects I18n.locale.
381
+ Dami.db(:articles).create!(title: '')
382
+ rescue Dami::ValidationError => e
383
+ # e.errors[:title] will be ["no puede estar en blanco"]
384
+ end
385
+ end
386
+ ```
387
+
388
+ ### Bridging the Gap: Loading YAML Files into Dami
389
+
390
+ You don't need to rewrite all your existing translations. You can easily load standard `i18n` YAML files directly into Dami's localization system in an initializer. This gives you a single source of truth for all your application's text.
391
+
392
+ Imagine you have a standard Rails `es.yml` file:
393
+
394
+ ```yaml
395
+ # config/locales/es.yml
396
+ es:
397
+ dami:
398
+ validations:
399
+ required: "no puede estar en blanco (desde YAML)"
400
+ models:
401
+ user:
402
+ first_name: "Nombre (desde YAML)"
403
+ ```
404
+
405
+ You can load this into Dami in a single, clean step:
406
+
407
+ ```ruby
408
+ # config/initializers/dami_i18n.rb
409
+ require 'yaml'
410
+
411
+ # Load the YAML file
412
+ translations = YAML.load_file('config/locales/es.yml')
413
+ dami_translations = translations['es']['dami']
414
+
415
+ # Feed the translations into Dami's DSL
416
+ Dami.localize :validations do
417
+ es do
418
+ dami_translations['validations'].each do |key, value|
419
+ set key.to_sym, value
420
+ end
421
+ end
422
+ end
423
+
424
+ Dami.localize :models do
425
+ es do
426
+ attributes_for :user do
427
+ dami_translations['models']['user'].each do |key, value|
428
+ set key.to_sym, value
429
+ end
430
+ end
431
+ end
432
+ end
433
+ ```
434
+
435
+ This pattern allows you to maintain your translations in the standard `i18n` format while still benefiting from Dami's fast, type-safe localization engine at runtime. It's the best of both worlds.
@@ -0,0 +1,227 @@
1
+ Absolutely. This is the perfect way to end the documentation—a confident, opinionated "manifesto" that clearly articulates Dami's superior design philosophy. Your draft is a great start, and we can definitely crank up the energy to get developers hyped.
2
+
3
+ You're right, the draft is missing the knockout punches about validations and other key differentiators. Let's upgrade this from a simple comparison to a powerful final statement.
4
+
5
+ Here is the revised, high-energy final chapter.
6
+
7
+ -----
8
+
9
+ ````markdown
10
+ # Chapter 12: The Dami Way - A Manifesto
11
+
12
+ You've learned the "what" of Dami's features. This final chapter is about the "why." It's a declaration of the principles that make Dami a fundamentally better way to build modern, maintainable applications.
13
+
14
+ Dami was not created to be another clone in a sea of ORMs. It was forged from experience, designed to eliminate the common pain points and architectural decay that plague traditional frameworks. This philosophy is **"The Dami Way,"** and it's built on four pillars: **Clarity, Architectural Purity, Performance, and Ergonomic Joy.**
15
+
16
+ This chapter is a "shootout" that compares Dami's design choices to the conventional approach (represented by Rails/ActiveRecord). By the end, it will be blatantly obvious why Dami is a superior choice.
17
+
18
+
19
+
20
+ ---
21
+
22
+ ## 1. Architectural Purity: The Decisive End of the "Fat Model"
23
+
24
+ The single biggest source of technical debt in most web applications is the "fat model"—a single file that becomes a sprawling, thousand-line dumping ground for schema, business logic, query logic, and presentation helpers. It's impossible to read, a nightmare to test, and actively resists change.
25
+
26
+ #### **The Old Way: The Monolithic Model**
27
+
28
+ In a typical Rails application, every concern is crammed into a single `ApplicationRecord` class. This convenience comes at a devastating long-term cost.
29
+
30
+ ```ruby
31
+ # The "Fat Model" Anti-Pattern in Rails
32
+ class User < ApplicationRecord
33
+ # --- Structure, Behavior, Querying, and Presentation are all mixed ---
34
+ has_many :posts
35
+
36
+ validates :email, presence: true, uniqueness: true
37
+ before_save :downcase_email # A callback... but where is it defined?
38
+
39
+ scope :active, -> { where(status: 'active') }
40
+
41
+ def full_name
42
+ "#{first_name} #{last_name}"
43
+ end
44
+ # ... 500 more lines of tangled logic ...
45
+ end
46
+ ````
47
+
48
+ #### **The Dami Way: The Four Pillars of Clarity**
49
+
50
+ Dami makes this architectural mess impossible **by design**. It provides four distinct, purpose-built DSLs. This isn't a suggestion; it's a core feature enforced by the framework itself.
51
+
52
+ ```ruby
53
+ # The Dami Way: Clean, Enforced Separation of Concerns
54
+
55
+ # --- 1. Structure (The Skeleton) ---
56
+ Dami.model(:users) { fields { ... }; relationships { ... } }
57
+
58
+ # --- 2. Behavior (The Internal Rules) ---
59
+ Dami.behavior(:users) { validate { ... }; before(:save) { ... } }
60
+
61
+ # --- 3. Presentation (The Public Interface) ---
62
+ Dami.present(:users) { def full_name; ...; end }
63
+
64
+ # --- 4. Querying (The Collection Logic) ---
65
+ Dami.scopes(:users) { scope :active, -> { ... } }
66
+ ```
67
+
68
+ ### **The Shootout: Why Dami Wins Decisively**
69
+
70
+ * **Clarity & Discoverability:** When you open a Dami model file, the architecture is immediately obvious. You know exactly where to find schema, write-side rules, read-side helpers, and query logic. No more hunting for a `scope` buried between a dozen private methods.
71
+ * **Superior Testability:** Each pillar can be tested in isolation. You can unit test your presenter methods with plain Ruby hashes, without ever touching the database. You can test your behavior rules without mocking query scopes. This leads to faster, more reliable, and more focused tests.
72
+ * **Architectural Guardrails:** Dami actively prevents architectural decay. You *can't* put a validation rule in your `model` block. The framework will stop you. These guardrails ensure the codebase stays clean and understandable as it grows and new developers join the team.
73
+
74
+ -----
75
+
76
+ ## 2\. Validations: A Central Registry vs. Ad-Hoc Chaos
77
+
78
+ Validations are critical, but in most frameworks, they are a source of constant repetition and inconsistency.
79
+
80
+ #### **The Old Way: Repetitive, Scattered Definitions**
81
+
82
+ In Rails, if you need a custom `phone_number` validation, you have to define it ad-hoc in every single model that needs it, often with slightly different regexes and error messages.
83
+
84
+ ```ruby
85
+ # The Repetitive Anti-Pattern in Rails
86
+ class User < ApplicationRecord
87
+ validates :phone, format: { with: /\A\d{10}\z/, message: "must be 10 digits" }
88
+ end
89
+
90
+ class Company < ApplicationRecord
91
+ validates :main_phone, format: { with: /\A\d{3}-\d{3}-\d{4}\z/, message: "use XXX-XXX-XXXX format" }
92
+ end # ❌ Inconsistent logic and messages!
93
+ ```
94
+
95
+ #### **The Dami Way: The Central Validation Registry**
96
+
97
+ Dami treats validations as a first-class, reusable component of your application. You define a rule **once** in a central registry and reuse it everywhere.
98
+
99
+ ```ruby
100
+ # The Dami Way: Define Once, Reuse Everywhere
101
+
102
+ # 1. Register your custom rule
103
+ Dami.rules :default, {
104
+ phone: {
105
+ check: ->(v) { v.to_s.gsub(/\D/, '').length == 10 },
106
+ message: "must be a valid 10-digit phone number"
107
+ }
108
+ }
109
+
110
+ # 2. Use it by its simple, declarative name
111
+ Dami.behavior(:users) { validate { rule :phone, :phone } }
112
+ Dami.behavior(:companies) { validate { rule :main_phone, :phone } } # ✅ Consistent logic and message!
113
+ ```
114
+
115
+ ### **The Shootout: Why Dami Wins Decisively**
116
+
117
+ * **DRY (Don't Repeat Yourself):** Dami's registry eliminates the possibility of having dozens of slightly different, copy-pasted validations littered across your codebase.
118
+ * **Consistency:** It guarantees that the logic and error message for a given rule are identical everywhere, providing a consistent user experience.
119
+ * **Maintainability:** If your business rule for a "phone number" changes, you update it in **one single place**, and the entire application is instantly updated. This is a massive win for long-term maintenance.
120
+
121
+ -----
122
+
123
+ ## 3\. Business Logic: Explicit Orchestration vs. "Callback Hell"
124
+
125
+ Complex business processes are the heart of any application. Dami provides a dedicated, top-tier tool for this, while other frameworks rely on a pattern that is notoriously brittle and opaque.
126
+
127
+ #### **The Old Way: Implicit Callback Chains ("Callback Hell")**
128
+
129
+ A user signup process in Rails often becomes an invisible, untestable chain of `after_create` callbacks. When it breaks, it's nearly impossible to debug.
130
+
131
+ ```ruby
132
+ # The "Callback Hell" Anti-Pattern
133
+ class User < ApplicationRecord
134
+ after_create :send_welcome_email, :sync_to_crm, :assign_to_default_team
135
+ end
136
+
137
+ # In the controller:
138
+ def create
139
+ @user = User.new(user_params)
140
+ if @user.save # ❌ What happens here? An invisible, multi-step process kicks off.
141
+ # What if sync_to_crm fails? The user is already created,
142
+ # the email is sent, but the system is now in an inconsistent state.
143
+ end
144
+ end
145
+ ```
146
+
147
+ #### **The Dami Way: An Explicit, Transactional Flow**
148
+
149
+ Dami's **Flows** make the entire business process visible, atomic, and safe. It reads like a clear, imperative script for your most critical workflows.
150
+
151
+ ```ruby
152
+ # The Dami Way: An Explicit, Readable, and Resilient Script
153
+ Dami.run(:user_signup_flow) do
154
+ # 1. Validate and prepare all incoming data
155
+ clean_data = prepare CreateUserCommand, with: params
156
+
157
+ # 2. Run all database operations inside a single, atomic transaction
158
+ user = db(:users).create(clean_data[:user])
159
+ db(:teams).default.add_member(user)
160
+
161
+ # 3. Perform external actions with built-in resilience
162
+ perform "Sync to CRM", retry_options: { on: [Crm::ApiError], times: 2 } do
163
+ CrmService.sync_user(user)
164
+ end
165
+
166
+ # 4. Safely enqueue side effects ONLY AFTER the transaction commits
167
+ succeed with: user, and_then: [
168
+ WelcomeEmailJob.with(user_id: user[:id])
169
+ ]
170
+ end
171
+ ```
172
+
173
+ ### **The Shootout: Why Dami Wins Decisively**
174
+
175
+ * **Visibility & Clarity:** The Dami Flow is a self-documenting script. Any developer can read it top-to-bottom and understand the entire business process. There are no hidden actions.
176
+ * **Transactional Integrity:** If the `CrmService` call fails, the entire database transaction is **automatically rolled back**. The user and team membership are never saved. The system remains in a consistent state, eliminating an entire class of bugs.
177
+ * **Safety for Side Effects:** The `and_then:` block is a transaction-aware gatekeeper. It guarantees that irreversible actions like sending an email or charging a credit card happen **only after** all database operations have been successfully and permanently committed. This solves the infamous "welcome email sent to a ghost user" bug.
178
+
179
+ -----
180
+
181
+ ### A Caveat: When *Not* to Use Dami (The Honest Guide)
182
+
183
+ Every great tool is defined as much by what it *doesn't* do as by what it does. Dami is an opinionated framework designed for a specific philosophy. We believe it's a better way to build, but like any sharp tool, it's not the right one for every single job.
184
+
185
+ Being open about these trade-offs is critical. Dami might **not** be the best fit for your project if:
186
+
187
+ #### 1. ...You Need a Massive, Established Ecosystem Out of the Box.
188
+
189
+ The Rails and ActiveRecord ecosystem is a vast, mature jungle of gems and plugins for everything from authentication (`Devise`) to admin panels (`ActiveAdmin`). This is an incredible strength born of over a decade of community effort.
190
+
191
+ **Dami does not have this.**
192
+
193
+ * **The Dami Trade-off:** Dami prioritizes architectural purity and performance over a vast, plug-and-play ecosystem. It provides a cleaner, more robust foundation for you to build your *own* solutions, free from the constraints and magic of a monolithic framework.
194
+ * **The Bottom Line:** If your project's success depends on quickly integrating a dozen mature, third-party gems for common features, the ActiveRecord ecosystem is currently the more pragmatic choice.
195
+
196
+ #### 2. ...You Need Broad Database Support *Today*.
197
+
198
+ Dami v1.0 is laser-focused on providing a world-class, battle-tested experience for a single database: **SQLite**. This focus allows us to guarantee performance and stability.
199
+
200
+ * **The Dami Trade-off:** We chose to do one thing exceptionally well before expanding. Support for **PostgreSQL** and **MySQL** are high-priority items on the roadmap for the v1.x series, but they are not here today.
201
+ * **The Bottom Line:** If your production environment requires PostgreSQL, MySQL, or another database *right now*, you should wait for a future release of Dami.
202
+
203
+ #### 3. ...Your Team is 100% "Rails Fluent" and on a Tight Deadline.
204
+
205
+ Dami's "Four Pillars" architecture is its greatest strength, but it represents a different paradigm. It requires un-learning the "fat model" habit and embracing an explicit separation of concerns.
206
+
207
+ * **The Dami Trade-off:** Dami offers a long-term solution to the pain points of architectural decay, but this requires an initial investment in learning a new, more disciplined way of organizing code. This shift, while powerful, requires team buy-in.
208
+ * **The Bottom Line:** If your team is under a tight deadline and is composed entirely of senior Rails developers who can move at lightning speed within that ecosystem, the cost of switching paradigms might outweigh the immediate benefits.
209
+
210
+ #### 4. ...You Want an All-in-One, "Convention over Configuration" Framework.
211
+
212
+ Dami is a world-class **ORM and business logic layer**. It is not a full-stack web framework. It has no opinions about routing, controllers, views, or asset pipelines.
213
+
214
+ * **The Dami Position:** This is a feature, not a limitation. Dami is designed to be the powerful data and logic core of *any* Ruby application—a Sinatra or Hanami web app, a command-line tool, a data processing script, or a standalone API. It doesn't lock you into a single way of building.
215
+ * **The Bottom Line:** If you want a single command to generate a full CRUD application from front to back, you are looking for a full-stack framework like Rails, not a specialized, best-in-class component like Dami.
216
+
217
+ ---
218
+
219
+ ## Your Call to Action
220
+
221
+ The Dami Way is a commitment to building better software. It's a choice to favor clarity over magic, purity over convenience, and long-term maintainability over short-term shortcuts.
222
+
223
+ You've seen the difference. You've seen the architecture. Now it's time to decide if you're ready to build on a foundation designed for the long haul.
224
+
225
+ Dami is not a Swiss Army knife. **It's a scalpel. And for the right job, it's the best tool there is.**
226
+
227
+ **Get hyped. Get building.** Welcome to The Dami Way.