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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0d27e7d5a72541d63d026ed24e418d0acc195bf33d724f6086afcfece355998b
4
+ data.tar.gz: eadb86a3f0a4aae782971785f3c180a532d30561b82fe23f43d8fe236d0724af
5
+ SHA512:
6
+ metadata.gz: 4a636d87228fb6e1ad6d8b83d7c101bec962423647cf096650b5026e9242776005c64287ad19817ea9a0474b64227a5282d290ff15aa802ea555c5784ffdafd4
7
+ data.tar.gz: a2cbe7c1417947fdc97876875de4aafba02a6cf326ff3a429f11feb7ba48db3908a33208a12c763160fe7d2c61334e22a63aaaf67bdfd85d1b1afe52682b245b
data/CHANGELOG.md ADDED
@@ -0,0 +1,306 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0] - 2026-09-12 (pre-publish review pass)
4
+
5
+ Fixes applied after a full review of the 1.0.0 working tree, before the first
6
+ rubygems release. The suite is 276 tests / 840 assertions, green.
7
+
8
+ ### ๐Ÿ› Correctness
9
+
10
+ * **`Dami.run` now rolls back on a `Failure` halt.** Previously a flow that wrote rows and then halted with a `Failure` (e.g. `prepare` rejecting its input) committed those rows. After-commit hooks now run after the transaction has actually committed, not inside it.
11
+ * **Nested flows use savepoints.** `run(:other_flow)` inside a flow rolls back only the inner flow's writes on failure; the outer flow keeps going with the returned `Failure`. Any nested `transaction` call behaves the same way.
12
+ * **Foreign keys are enforced.** Every pooled connection now runs `PRAGMA foreign_keys = ON`, so `references:` columns and `Dami::ForeignKeyViolation` mean something. (If your test helper drops tables in arbitrary order, drop them inside one transaction with `PRAGMA defer_foreign_keys = ON` โ€” see `test/database_helper.rb`.)
13
+ * **Two `NoMethodError`s fixed:** lazy `has_many :through` (`post.tags` without `preload`) and nested attributes without an explicit `foreign_key:` called `String#singularize`, which does not exist without ActiveSupport. Both use `Dami::Inflector` now. 17 previously-red tests pass.
14
+ * **`:memory:` databases use one connection.** An in-memory SQLite database exists per connection, so a pool of five was five unrelated databases. `pool_size` is forced to 1 for `path: ':memory:'`.
15
+ * **Inserts read `last_insert_row_id` on the connection that inserted.** `insert_record` and `insert_many` run inside a transaction so both statements share one pooled connection.
16
+ * **One parameter conversion path.** `execute`, `get_first_row` and prepared statements all convert `Time`/`DateTime`/`Date`/booleans/JSON the same way; `create_many` with a `Time` or `true` no longer raises inside the driver.
17
+ * **`min:` / `max:` validations compare numbers, not `to_i`.** `rule :amount, min: 0.01` now accepts `0.5` and `"9.99"`, and rejects `"abc"`.
18
+ * **Locale is thread-safe.** `Dami.locale=` remains the process-wide default; `Dami.with_locale` and the new `Dami.thread_locale=` are thread-local, so a web request can no longer leak its locale into other threads.
19
+ * **`NOT NULL` violations raise `Dami::NotNullViolation`** (with `.column`) instead of a raw driver error.
20
+
21
+ ### โœจ Schema
22
+
23
+ * `create_table` / `add_column` keep their constraints: `null: false`, `unique: true`, `default:` (incl. `:current_timestamp`), `references:` (+ `on_delete:`). `t.references :user` is shorthand for `user_id INTEGER REFERENCES users(id)`.
24
+ * `add_index(..., unique: true)` creates a `UNIQUE` index; multi-column indexes accepted.
25
+ * One type map for `create_table` and `add_column`: `:float`/`:decimal` โ†’ `REAL`, `:datetime`/`:date`/`:time` โ†’ `DATETIME` (previously `create_table` used `TEXT` for all of these while `add_column` did not).
26
+ * `:json` fields are serialized on write and parsed on read, as the docs already claimed.
27
+ * `created_at` / `updated_at` are filled automatically when the model declares them.
28
+ * `dami db rollback [STEPS]` added to the CLI; rollback refreshes `db/schema.rb` like migrate does. CLI commands are `dami db migrate`, `dami db rollback`, `dami db schema_dump` (docs previously showed `dami db:migrate`, which Thor does not accept).
29
+
30
+ ### ๐Ÿ”’ Hardening
31
+
32
+ * Column and table names passed to `where`, `join`, `insert` are validated as plain identifiers (`Dami::InvalidIdentifier` otherwise). Values were always bound; names were interpolated.
33
+
34
+ ### ๐Ÿงน Packaging
35
+
36
+ * `sqlite3` dependency corrected to `>= 2.0` (was `~> 1.6`, which could not be bundled with any 2.x app). `required_ruby_version >= 3.0`, rubygems metadata (MFA required, source/changelog URIs).
37
+ * `spec.files` is an explicit `Dir[]` list instead of `git ls-files`.
38
+ * Added `README.md`, `LICENSE` (MIT), `.gitignore`; `rake test` points at the real runner.
39
+ * Removed dead files: `lib/dami/untitled.rb`, `lib/dami/draft.rb`, `lib/dami/flow.rb` (commented out), `lib/dami/locale_manager.rb`, `lib/dami/plugins/{scopes,timestamps,presenter}.rb`, `lib/dami/adapters/sqlite/associations.rb`; duplicate `docs/1.Getting_Started.md`.
40
+ * Runtime dependencies are `sqlite3`, `connection_pool` and `thor` (the previous "zero dependencies" note was wrong).
41
+
42
+ ## [1.0.0] - 2025-10-18
43
+
44
+ ๐ŸŽ‰ **Dami v1.0 - Production Ready**
45
+
46
+ This is the first production-ready release of Dami. The 1.0 milestone represents a mature, battle-tested ORM with a clean architecture, comprehensive test coverage (228 tests, 737 assertions), and production-grade error handling.
47
+
48
+ ### ๐Ÿ—๏ธ **Major Architecture Changes**
49
+
50
+ * **Four Pillars DSL:** Complete separation of concerns across four distinct DSL blocks:
51
+ * `Dami.model` - Structure (fields, relationships, virtual fields)
52
+ * `Dami.behavior` - Write-side logic (validations, protection rules)
53
+ * `Dami.scopes` - Collection-level queries
54
+ * Each pillar has a focused, single responsibility, making code clearer and more maintainable
55
+
56
+ * **Architectural Guardrails:** The framework now enforces clean separation at the DSL level
57
+ * Attempting to define validations in `Dami.model` raises `Dami::InvalidDSLError` with helpful guidance
58
+ * Attempting to define fields in `Dami.behavior` raises an error pointing to the correct location
59
+ * Makes architectural decay impossible and provides immediate, clear feedback to developers
60
+ * Zero magic - explicit method definitions with no `method_missing` tricks
61
+
62
+ ### ๐Ÿ› **Critical Bug Fixes**
63
+
64
+ * **`create_many` Security Fix:** Fixed a critical bug where `create_many` bypassed protection rules
65
+ * Now enforces field protection on every record in batch operations
66
+ * Validates all records before any database writes (atomic validation)
67
+ * Raises `Dami::ProtectionError` when protected fields are present
68
+ * **Impact:** High - prevents bulk operations from bypassing security rules
69
+
70
+ ### โœจ **Enhanced Error Handling**
71
+
72
+ * **Database-Specific Exceptions:** Replaced generic SQLite errors with semantic Dami exceptions
73
+ * `Dami::UniqueConstraintViolation` - raised on unique constraint failures, includes column name
74
+ * `Dami::ForeignKeyViolation` - raised on foreign key constraint failures
75
+ * Improves error handling in application code with meaningful, catchable exceptions
76
+
77
+ ### ๐Ÿ”ง **Configuration Enhancements**
78
+
79
+ * **Configurable Connection Pool:** Added `pool_size` option to connection configuration
80
+ * Default: 5 connections per pool
81
+ * Usage: `Dami.connect(:default, adapter: :sqlite, path: 'db.sqlite', pool_size: 10)`
82
+ * Allows tuning for high-concurrency scenarios
83
+
84
+ ### ๐Ÿ“Š **Test Coverage**
85
+
86
+ * **228 Comprehensive Tests** covering:
87
+ * Core CRUD operations
88
+ * All relationship types (belongs_to, has_many, has_one, polymorphic, has_many_through)
89
+ * Nested attributes (3+ levels deep)
90
+ * Validations (all rule types, contexts, conditionals)
91
+ * Protection & permissions
92
+ * Scopes & query building
93
+ * Transactions & concurrency
94
+ * Edge cases (connection pooling, unicode, NULL handling, large datasets)
95
+ * DSL guardrails enforcement
96
+
97
+ ### ๐ŸŽฏ **Developer Experience**
98
+
99
+ * **Clear Error Messages:** All DSL violations now provide helpful guidance
100
+ * Example: `'validate' is not allowed here. Please define it in a Dami.behavior block.`
101
+ * **Minimal dependencies:** `sqlite3`, `connection_pool`, `thor`
102
+ * **Production Tested:** Stress-tested with concurrent operations, large batches, and edge cases
103
+
104
+ ### ๐Ÿ“ **Breaking Changes**
105
+
106
+ * **DSL Reorganization:** Models must now separate concerns across pillar blocks
107
+ * **Before (0.9.x):**
108
+ ```ruby
109
+ Dami.model :users do
110
+ fields { field :name, :string }
111
+ validate { rule :name, :required }
112
+ protection { protect :role }
113
+ end
114
+ ```
115
+ * **After (1.0.0):**
116
+ ```ruby
117
+ Dami.model :users do
118
+ fields { field :name, :string }
119
+ end
120
+
121
+ Dami.behavior :users do
122
+ validate { rule :name, :required }
123
+ protection { protect :role }
124
+ end
125
+ ```
126
+
127
+ ### ๐Ÿš€ **Upgrade Guide**
128
+
129
+ 1. Separate `validate` and `protection` blocks from `Dami.model` into `Dami.behavior`
130
+ 2. Move `scope` definitions to `Dami.scopes` blocks
131
+ 3. Update exception handling to catch `Dami::UniqueConstraintViolation` instead of `SQLite3::ConstraintException`
132
+ 4. Optionally configure connection pool size if needed for your workload
133
+
134
+ ---
135
+
136
+ Dami 1.0 represents a stable, production-ready foundation for Ruby applications. The architecture is clean, the test coverage is comprehensive, and the framework is ready for real-world use.
137
+
138
+ ## [0.9.0] - 2025-10-17
139
+
140
+ This is the official first major release of the Dami ORM. It includes a complete feature set for building robust applications, significant performance optimizations, and a hardened codebase.
141
+
142
+ ### โœจ New Features
143
+
144
+ * **Nested Attributes:** Implemented a powerful and secure way to manage associated records through a parent record.
145
+ * New `nests` DSL in models to explicitly allow nested operations (e.g., `nests :comments, allow_destroy: true`).
146
+ * Supports deep, recursive nesting for grandchildren and beyond.
147
+ * Handles creating, updating, and destroying nested records via `_attributes` keys (`comments_attributes`).
148
+ * All operations are wrapped in a single, atomic database transaction.
149
+ * Fully integrated with Dami's validation and protection layers, ensuring child records respect their own rules.
150
+
151
+ * **Resilient Flows:** The `perform` step in the Flow DSL is now robust for handling unreliable external actions.
152
+ * Added a `:retry_options` parameter to `perform` (e.g., `retry_options: { on: [ApiError], times: 2 }`).
153
+ * Added a `:timeout` parameter to automatically fail a step that takes too long.
154
+
155
+ * **Custom Inflector:** Introduced a dependency-free internal inflector (`Dami::Inflector`) to handle pluralization and singularization, removing the need for external gems like Active Support.
156
+
157
+ ### ๐Ÿš€ Performance
158
+
159
+ A major focus of this release was optimizing the performance of association loading to be competitive with leading ORMs.
160
+
161
+ * **Optimized `.count` Method:** The `.count` method now executes a highly efficient `SELECT COUNT(*)` database query instead of loading all records into memory, resulting in a **~1.13x speed improvement** for counting operations.
162
+
163
+ * **Preload Optimization (~85% Improvement):** The performance of eager loading (`.preload`) has been massively improved through two key architectural changes:
164
+ 1. **Flyweight Pattern:** Replaced per-instance method definition on `RecordProxy` objects with cached, shared modules. This eliminated the primary object-creation bottleneck.
165
+ 2. **Inflector Memoization:** Implemented caching for inflection results, dramatically reducing redundant string and regex operations in hot loops.
166
+ * **Result:** `Dami (preload)` is now **~1.6x faster than ActiveRecord (`includes`)** in competitive benchmarks for common scenarios.
167
+
168
+ ### ๐Ÿ”ง Fixes & Hardening
169
+
170
+ * **CLI Hardening:** The `dami` command-line interface is now more robust and provides user-friendly error messages for common misconfigurations, such as:
171
+ * Missing `config/initializers/dami.rb` file.
172
+ * Database not connected when running `db:*` tasks.
173
+ * Missing `db/schema.rb` file when generating migrations.
174
+
175
+ * **Test Suite Reliability:** Fixed flaky tests by implementing a central reset mechanism (`Dami.clear_all!`). This ensures that all global state (model definitions, plugin caches) is cleared before every test, guaranteeing 100% test isolation and reliability.
176
+
177
+ * **Bug Fixes:**
178
+ * Corrected the `perform` helper in the Flow DSL (was mistakenly documented as `call`).
179
+ * Fixed a bug in the associations plugin where polymorphic and `has_many :through` definitions were not being parsed correctly, leading to `NoMethodError`s in certain load orders.
180
+
181
+ ## [0.8.0] - 2025-10-07
182
+
183
+ ### โœจ Added
184
+
185
+ * **Flows for Orchestration**: Introduced `Dami.run` and `Dami.flow`, the "Tier 3" system for orchestrating complex, multi-step business processes. This provides a clean, readable, and imperative DSL for defining application workflows.
186
+ * **Automatic Transactional Guarantee**: `Dami.run` automatically wraps the entire flow in a database transaction, ensuring all database operations are atomic. If any step raises an exception, the entire transaction is rolled back.
187
+ * **Safe Side Effects (`succeed with:`)**: Flows now have a `succeed with: ..., and_then: [...]` method. This provides a transaction-aware "holding area" for irreversible actions (like enqueuing jobs), guaranteeing they only execute *after* the database transaction has successfully committed.
188
+ * **Flow DSL**: The block inside a `Dami.flow` now has a rich set of helpers for building robust workflows:
189
+ * `prepare`: Runs a `Dami::Command` to validate and transform data, halting automatically on failure.
190
+ * `perform`: Executes an arbitrary block of code (e.g., an external API call) with resilience options.
191
+ * `db`: Provides access to the query builder.
192
+ * `run`: Calls other Dami flows as sub-routines.
193
+ * **Error Handling (`rescue_from`)**: Flows can now define custom handlers for specific exceptions, allowing for graceful error recovery.
194
+
195
+ ## [0.7.0] - 2025-10-01
196
+
197
+ ### โœจ Added
198
+
199
+ * **Commands (`Dami::Command`)**: Introduced `Dami::Command`, a powerful class for encapsulating complex, reusable business logic. Commands provide a formal structure for contextual validation, data transformation, and dependency injection.
200
+ * **Declarative Command DSL**: `Dami::Command` includes a rich DSL for building business rules:
201
+ * `requires_context`: For declaring dependencies.
202
+ * `validate`: For defining validation rules with `if:` and `unless:` conditionals.
203
+ * `transform`: For defining data transformation steps.
204
+ * `apply` & `compose`: For building complex commands from smaller, reusable components.
205
+
206
+ ## [0.6.0] - 2025-10-01
207
+
208
+ ### โœจ Added
209
+
210
+ * **Inline Drafts**: The `.create` and `.update` methods now accept an optional block that yields a `draft` object. This provides a powerful, inline DSL for adding contextual validation and data transformation for simple-to-medium complexity operations. The `draft` DSL (`verify`, `transform`, `prevent_changes`) mirrors the `Command` API for a consistent developer experience.
211
+
212
+ ## [0.5.8] - 2025-10-01
213
+
214
+ ### โœจ Added
215
+
216
+ * **Commands**: Introduced `Dami::Command`, the "Tier 3" tool for encapsulating complex, reusable business logic. Commands provide a formal structure for contextual validation (`validate`), data transformation (`transform`), and dependency injection (`requires_context`).
217
+ * **Command Composition**: Commands can now be built from smaller, reusable components using `compose` and `apply`, promoting a DRY and modular architecture for business rules.
218
+ * **Conditional Logic**: The `validate` DSL now supports powerful conditional execution with `if:` and `unless:` options.
219
+
220
+ ## [0.5.3] - 2025-10-01
221
+
222
+ ### โœจ Added
223
+
224
+ * **Inline Drafts**: The `.create` and `.update` methods now accept an optional block that yields a `draft` object. This provides a powerful, inline DSL for adding contextual validation and data transformation for simple-to-medium complexity operations. It serves as the "Tier 2" of the business logic spectrum and uses an API (`verify`, `transform`, `prevent_changes`) designed to mirror the full `Dami::Command` for a consistent developer experience.
225
+
226
+ ## [0.5.0] - 2025-09-27
227
+
228
+ This is a major release introducing a powerful new migration workflow and a professional command-line interface, significantly improving developer productivity.
229
+
230
+ ### โœจ Added
231
+
232
+ * **Automatic Migration Generator**: Introduced a new `dami generate migration NAME` command. Dami now introspects your model definitions, compares them to the canonical schema (`db/schema.rb`), and automatically generates migration files for adding/removing columns and indexes. This nearly eliminates the need to write migrations by hand.
233
+ * **Full Command-Line Interface (CLI)**: Replaced all Rake tasks with a professional, self-documenting CLI built with Thor. The new `bin/dami` executable provides a standard interface with subcommands like `db:migrate` and `generate migration`.
234
+ * **Centralized Configuration**: Added a `Dami.configure` block. Users can now easily configure essential paths like `models_path`, `migrations_path`, and `schema_path` to integrate Dami into any project structure.
235
+ * **Schema Dumper (`db/schema.rb`)**: The migrator now automatically creates and updates a `db/schema.rb` file after successful migrations. This file serves as the reliable source of truth for the migration generator.
236
+
237
+ ### โ™ป๏ธ Changed
238
+
239
+ * **Rails-Style Migration DSL**: The migration DSL has been updated to use the more conventional `up` and `down` methods, replacing the previous `run` and `reverse`.
240
+ * **Index Support in Migrations**: The migration DSL now includes `add_index` and `remove_index` methods for managing database indexes.
241
+
242
+
243
+ ## [0.4.0] - 2025-09-21
244
+
245
+ ### โœจ Added
246
+
247
+ * **OR Conditions**: The query builder now supports chainable `.or` conditions for building complex queries (e.g., `.where(name: 'A').or(status: 'active')`).
248
+ * **LEFT Joins**: Added a `.left_join` method to the query builder for performing `LEFT OUTER JOIN`s, allowing you to find records that may not have an associated record.
249
+ * **Advanced String Matching**: `where` clauses now support `starts_with`, `contains`, and `ends_with` for generating `LIKE` queries (e.g., `where(name: { starts_with: 'A' })`).
250
+ * **NOT IN Queries**: Added a `{ not_in: [...] }` operator to `where` clauses for `NOT IN` conditions.
251
+ * **Existence Checks**: Added performant `.any?` and `.exists?` methods to the query builder, which use `LIMIT 1` for efficient database checks.
252
+ * **First/Last Methods**: Added efficient `.first` and `.last` methods to the query builder. `.last` intelligently reverses the query's sort order.
253
+
254
+ ### โ™ป๏ธ Changed
255
+
256
+ * **Major Internal Refactor**: The entire test suite has been refactored to be database-agnostic. It can now run against SQLite, PostgreSQL, and MySQL via the `DB` environment variable.
257
+ * **Transactional Tests**: The test suite now uses a transactional cleanup strategy for significantly faster and more reliable test isolation, eliminating file I/O issues.
258
+
259
+ ---
260
+
261
+ ## [0.3.0] - 2025-09-14
262
+
263
+ ### โœจ Added
264
+
265
+ * **Polymorphic Associations**: `belongs_to` associations now support a `{ polymorphic: true }` option, allowing a model to belong to more than one other model on a single association. The corresponding `has_many` and `has_one` now support the `{ as: :... }` syntax.
266
+ * **`has_many :through` Associations**: You can now define `has_many :through` relationships to create many-to-many connections through a join model.
267
+ * **Performance Optimizations**: Implemented `find_each` and `find_in_batches` for efficiently processing large numbers of records without loading them all into memory at once.
268
+ * **Bulk Inserts**: Added a `.create_many` method for inserting a large number of records in a single, highly performant SQL statement.
269
+
270
+ ### ๐Ÿ› Fixed
271
+
272
+ * Corrected an issue where preloading multiple associations could fail under certain edge cases.
273
+ * Ensured that the query builder methods (`where`, `limit`, etc.) are fully immutable and always return a new builder instance.
274
+
275
+ ---
276
+
277
+ ## [0.2.0] - 2025-09-08
278
+
279
+ ### โœจ Added
280
+
281
+ * **Comprehensive Validation System**: Introduced a powerful `validate` block for model definitions.
282
+ * Supports contextual validations with `on :create` and `on :update`.
283
+ * Supports conditional validations using `if:`, `unless:`, `when:`.
284
+ * Added bulk-application helpers like `all except:` and `all only:`.
285
+ * **Virtual Fields**: Models now support a `virtual` block to define attributes that are validated but not persisted to the database (e.g., `password_confirmation`).
286
+ * **Attribute Protection**: Implemented a `protection` block with `.protect` and `.permit` to prevent mass-assignment vulnerabilities. Operations can bypass this using the `permit: [...]` or `protect: false` options.
287
+ * **Scope DSL**: Added a `scopes` block to define reusable query scopes on models (e.g., `scope :active, -> { where(status: 'active') }`).
288
+
289
+ ### ๐Ÿ› Fixed
290
+
291
+ * Ensured that unknown fields in `create` or `update` calls raise an `UnknownFieldsError` *before* validations are run, providing clearer error messages.
292
+
293
+ ---
294
+
295
+ ## [0.1.0] - 2025-09-01
296
+
297
+ ### โœจ Added
298
+
299
+ * **Database Migrations**: Introduced `Dami::Migration` and `Dami::Migrator` for managing database schema changes over time. Supports `migrate` and `rollback` commands.
300
+ * **Basic Associations**: Implemented `has_many`, `has_one`, and `belongs_to` relationships, including support for both lazy-loading and eager-loading via `.preload`.
301
+ * **Connection Pooling**: The SQLite adapter now uses a connection pool for improved concurrency and performance under threaded environments.
302
+
303
+ ### โ™ป๏ธ Changed
304
+
305
+ * **Hardened Query Builder**: The `.order` method is now hardened against SQL injection attacks.
306
+ * The core `create` method now returns a `RecordProxy` instance instead of a plain hash.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Steven Garcia
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # Dami
2
+
3
+ A lightweight, policy-oriented ORM for Ruby on SQLite. One gem, no framework required.
4
+
5
+ - **Four-pillar DSL** โ€” structure (`Dami.model`), write rules (`Dami.behavior`), queries (`Dami.scopes`), presentation (`Dami.present`). Put something in the wrong block and Dami tells you where it goes.
6
+ - **Hardened writes by default** โ€” unknown fields raise, protected fields need an explicit `permit:`, every value is bound (never interpolated).
7
+ - **Real constraints** โ€” `null:`, `unique:`, `default:`, `references:` in migrations; foreign keys are enforced; violations come back as `Dami::NotNullViolation`, `Dami::UniqueConstraintViolation`, `Dami::ForeignKeyViolation`.
8
+ - **Flows** โ€” multi-step business operations that run in one transaction, roll back on failure, and fire side effects only after the commit.
9
+ - **Small and fast** โ€” a connection pool, WAL, prepared-statement cache, and nothing you didn't ask for.
10
+
11
+ ## Install
12
+
13
+ ```ruby
14
+ # Gemfile
15
+ gem "dami"
16
+ ```
17
+
18
+ ## Five-minute tour
19
+
20
+ ```ruby
21
+ require "dami"
22
+
23
+ Dami.connect(:default, adapter: :sqlite, path: "app.db")
24
+
25
+ # Schema (or write a migration โ€” see docs/09.Migrations.md)
26
+ Dami::Migration.new(Dami.database).create_table(:users) do |t|
27
+ t.field :id, :primary_key
28
+ t.field :email, :string, null: false, unique: true
29
+ t.field :name, :string, null: false
30
+ t.field :role, :string, default: "member"
31
+ t.timestamps
32
+ end
33
+
34
+ # Structure
35
+ Dami.model :users do
36
+ fields do
37
+ field :email, :string
38
+ field :name, :string
39
+ field :role, :string
40
+ field :created_at, :datetime
41
+ field :updated_at, :datetime
42
+ end
43
+ end
44
+
45
+ # Write-side rules
46
+ Dami.behavior :users do
47
+ validate do
48
+ rule :email, :required, :email
49
+ rule :name, :required
50
+ end
51
+ protection { protect :role }
52
+ end
53
+
54
+ # Collection queries
55
+ Dami.scopes :users do
56
+ scope :admins, -> { where(role: "admin") }
57
+ end
58
+
59
+ user = Dami.db(:users).create(email: "ana@example.com", name: "Ana")
60
+ user[:id] # => 1
61
+ user.name # => "Ana"
62
+ user[:created_at] # => "2026-09-12 18:40:00" (UTC)
63
+
64
+ Dami.db(:users).where(id: user[:id]).update(name: "Ana G.")
65
+ Dami.db(:users).admins.count # => 0
66
+ Dami.db(:users).where(role: "admin").to_a # => []
67
+
68
+ Dami.db(:users).create(email: "not-an-email", name: "")
69
+ # => Dami::ValidationError, errors: { email: ["must be valid email"], name: ["is required"] }
70
+
71
+ Dami.db(:users).where(id: 1).update(role: "admin")
72
+ # => Dami::ProtectionError (protected field) โ€” pass permit: [:role] where you mean it
73
+ ```
74
+
75
+ ## Documentation
76
+
77
+ Everything is in [`docs/`](docs/), in reading order:
78
+
79
+ 1. [Getting Started](docs/01.Getting_Started.md)
80
+ 2. [Models and Fields](docs/02.Models_and_Fields.md)
81
+ 3. [Querying](docs/03.Querying.md)
82
+ 4. [Creating, Updating, Deleting](docs/04.Creating_Updating_Deleting.md)
83
+ 5. [Validation](docs/05.Validation.md)
84
+ 6. [Protection](docs/06.Protection.md)
85
+ 7. [Associations](docs/07.Associations.md)
86
+ 8. [Scopes](docs/08.Scopes.md)
87
+ 9. [Migrations](docs/09.Migrations.md)
88
+ 10. [Flows and Commands](docs/10.Flows_and_Commands.md)
89
+ 11. [Localization](docs/11.Localization.md)
90
+ 12. [The Dami Way](docs/12.TheDamiWay.md)
91
+
92
+ ## Scope
93
+
94
+ SQLite only, on purpose. If you need PostgreSQL or MySQL today, Dami is not the right tool yet.
95
+
96
+ ## Development
97
+
98
+ ```
99
+ bundle install
100
+ rake test # ruby test/run_all.rb
101
+ rake build # gem build dami.gemspec
102
+ ```
103
+
104
+ ## License
105
+
106
+ MIT โ€” see [LICENSE](LICENSE).
data/bin/dami ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This makes sure the local `lib` directory is on the load path, which is
4
+ # essential for development before the gem is installed.
5
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
6
+
7
+ require 'dami/cli'
8
+
9
+ # Start the Thor command-line interface, passing it the user's arguments.
10
+ Dami::CLI.start(ARGV)
@@ -0,0 +1,220 @@
1
+ # Chapter 1: Getting Started
2
+
3
+ ## Installation
4
+
5
+ Add Dami to your project:
6
+
7
+ ```ruby
8
+ # Gemfile
9
+ gem 'dami'
10
+ ```
11
+
12
+ Or install directly:
13
+
14
+ ```bash
15
+ gem install dami
16
+ ```
17
+
18
+ ## Your First Database
19
+
20
+ Dami connects to SQLite by default. Create a database connection:
21
+
22
+ ```ruby
23
+ require 'dami'
24
+ # Connect to a SQLite database
25
+ Dami.connect(:default, adapter: :sqlite, path: 'app.db', pool_size: 5)
26
+ ```
27
+
28
+ **Configuration options:**
29
+
30
+ `:pool_size` - Number of connections in the pool (default: 5). Increase for high-concurrency scenarios. Ignored for `path: ':memory:'` โ€” an in-memory database exists per connection, so Dami always uses a single connection there.
31
+ `:default` - optional but recommended if you're using multiple databases later.
32
+
33
+ ## Define Your First Model
34
+
35
+ Models define your data structure and behavior:
36
+
37
+ ### The Four Pillars Architecture
38
+
39
+ Dami organizes your code into four distinct concerns:
40
+
41
+ ```ruby
42
+ # 1. STRUCTURE (Dami.model) - What your data looks like
43
+ Dami.model :users do
44
+ fields do
45
+ field :name, :string
46
+ field :email, :string
47
+ end
48
+ relationships do
49
+ has_many :posts
50
+ end
51
+ end
52
+
53
+ # 2. BEHAVIOR (Dami.behavior) - Write-side rules
54
+ Dami.behavior :users do
55
+ validate do
56
+ rule :name, :required
57
+ rule :email, :email
58
+ end
59
+ protection do
60
+ protect :admin # Can't be mass-assigned
61
+ end
62
+ end
63
+
64
+ # 3. SCOPES (Dami.scopes) - Collection-level queries
65
+ Dami.scopes :users do
66
+ scope :active, -> { where(status: 'active') }
67
+ scope :admins, -> { where(role: 'admin') }
68
+ end
69
+ ```
70
+
71
+ This separation makes code clearer and prevents mixing concerns. Dami enforces this at the DSL level - attempting to define validations inside `Dami.model` raises a helpful error pointing you to the right place.
72
+
73
+ ## Create Your Table
74
+
75
+ Dami can generate migrations directly from your model definition:
76
+
77
+ ```ruby
78
+ # 1. Define your model
79
+ Dami.model :users do
80
+ fields do
81
+ field :name, :string
82
+ field :email, :string
83
+ field :age, :integer
84
+ end
85
+ end
86
+
87
+ # 2. Generate and run migration
88
+ migrator = Dami::Migrator.new(Dami.database(:default))
89
+ migrator.generate_from_schema(:users)
90
+ migrator.migrate
91
+ ```
92
+
93
+ Dami automatically creates the migration file and runs it. The generated migration is reversible - you can rollback if needed.
94
+
95
+ **Advanced: Manual migrations**
96
+
97
+ For complex schema changes, write migrations by hand:
98
+
99
+ ```ruby
100
+ # db/migrations/20250101000000_create_users.rb
101
+ def up
102
+ create_table(:users) do |t|
103
+ t.field(:name, :string)
104
+ t.field(:email, :string)
105
+ t.field(:age, :integer)
106
+ end
107
+ end
108
+
109
+ def down
110
+ drop_table(:users)
111
+ end
112
+ ```
113
+
114
+ ## Your First Record
115
+
116
+ Create, read, update, delete:
117
+
118
+ ```ruby
119
+ # Create
120
+ user = Dami.db(:users).create(
121
+ name: 'Alice',
122
+ email: 'alice@example.com',
123
+ age: 30
124
+ )
125
+
126
+ puts user[:id] # => 1
127
+ puts user[:name] # => "Alice"
128
+
129
+ # Read
130
+ user = Dami.db(:users).find(1)
131
+ all_users = Dami.db(:users).where(age: 30).to_a
132
+
133
+ # Update
134
+ Dami.db(:users).where(id: 1).update(age: 31)
135
+
136
+ # Delete
137
+ Dami.db(:users).where(id: 1).delete
138
+ ```
139
+
140
+ ## Complete Example
141
+
142
+ Here's a full working example with automatic migrations:
143
+
144
+ ```ruby
145
+ require 'dami'
146
+ require 'fileutils'
147
+
148
+ # 1. Connect
149
+ FileUtils.mkdir_p('db/migrations')
150
+ Dami.connect(:default, adapter: :sqlite, path: 'blog.db')
151
+
152
+ # 2. Define model
153
+ Dami.model :posts do
154
+ fields do
155
+ field :title, :string
156
+ field :body, :text
157
+ field :published, :boolean
158
+ end
159
+ end
160
+
161
+ # 3. Generate and run migrations
162
+ migrator = Dami::Migrator.new(Dami.database(:default))
163
+ migrator.generate_from_schema(:posts)
164
+ migrator.migrate
165
+
166
+ # 4. Use it
167
+ post = Dami.db(:posts).create(
168
+ title: 'Hello World',
169
+ body: 'This is my first post!',
170
+ published: true
171
+ )
172
+
173
+ puts "Created post ##{post[:id]}: #{post[:title]}"
174
+
175
+ # Find published posts
176
+ published = Dami.db(:posts).where(published: true).to_a
177
+ puts "Found #{published.length} published posts"
178
+
179
+ # Need to change the schema? Just update the model and regenerate
180
+ Dami.model :posts do
181
+ fields do
182
+ field :title, :string
183
+ field :body, :text
184
+ field :published, :boolean
185
+ field :view_count, :integer # New field!
186
+ end
187
+ end
188
+
189
+ migrator.generate_from_schema(:posts)
190
+ migrator.migrate
191
+ ```
192
+
193
+ ## What's Next?
194
+
195
+ You now have a working Dami application. Here's what to explore next:
196
+
197
+ - **Chapter 2: Models & Fields** - Virtual fields, field types, defaults
198
+ - **Chapter 3: Querying** - Advanced queries, joins, ordering
199
+ - **Chapter 5: Validation** - Keep your data clean and consistent
200
+
201
+ ## Quick Reference
202
+
203
+ ```ruby
204
+ # Connect
205
+ Dami.connect(:default, adapter: :sqlite, path: 'db.sqlite')
206
+
207
+ # Define model
208
+ Dami.model :table_name do
209
+ fields do
210
+ field :column_name, :type
211
+ end
212
+ end
213
+
214
+ # CRUD
215
+ Dami.db(:table_name).create(column: value)
216
+ Dami.db(:table_name).find(id)
217
+ Dami.db(:table_name).where(column: value).to_a
218
+ Dami.db(:table_name).where(id: id).update(column: new_value)
219
+ Dami.db(:table_name).where(id: id).delete
220
+ ```