azeroth 2.1.0 → 2.2.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +6 -9
  3. data/.github/active_ext-usage.md +137 -0
  4. data/.github/azeroth-usage.md +277 -0
  5. data/.github/copilot-instructions.md +70 -0
  6. data/.github/core_ext-usage.md +324 -0
  7. data/.github/jace-usage.md +241 -0
  8. data/.rubocop.yml +14 -1
  9. data/.rubocop_todo.yml +1 -15
  10. data/Dockerfile +2 -2
  11. data/Gemfile +20 -16
  12. data/README.md +15 -7
  13. data/Rakefile +3 -0
  14. data/azeroth.gemspec +3 -3
  15. data/lib/azeroth/decorator/key_value_extractor.rb +1 -1
  16. data/lib/azeroth/resourceable/class_methods.rb +2 -2
  17. data/lib/azeroth/version.rb +1 -1
  18. data/spec/controllers/documents_controller_spec.rb +5 -5
  19. data/spec/controllers/documents_with_error_controller_spec.rb +3 -3
  20. data/spec/controllers/index_documents_controller_spec.rb +1 -1
  21. data/spec/controllers/paginated_documents_controller_spec.rb +1 -1
  22. data/spec/controllers/rendering_controller_spec.rb +1 -1
  23. data/spec/dummy/app/controllers/games_controller.rb +1 -0
  24. data/spec/dummy/app/controllers/publishers_controller.rb +1 -0
  25. data/spec/dummy/app/models/document.rb +1 -1
  26. data/spec/dummy/app/models/dummy_model.rb +1 -1
  27. data/spec/dummy/app/models/factory.rb +3 -3
  28. data/spec/dummy/app/models/game/decorator.rb +1 -1
  29. data/spec/dummy/app/models/game.rb +1 -1
  30. data/spec/dummy/app/models/movie.rb +1 -1
  31. data/spec/dummy/app/models/pokemon/decorator.rb +1 -3
  32. data/spec/dummy/app/models/pokemon.rb +8 -2
  33. data/spec/dummy/app/models/pokemon_master.rb +5 -3
  34. data/spec/dummy/app/models/product.rb +1 -1
  35. data/spec/dummy/app/models/publisher.rb +2 -2
  36. data/spec/dummy/app/models/user.rb +1 -1
  37. data/spec/dummy/app/models/website/decorator.rb +1 -1
  38. data/spec/dummy/app/models/website/with_location.rb +1 -1
  39. data/spec/dummy/app/models/website.rb +1 -1
  40. data/spec/dummy/bin/setup +0 -4
  41. data/spec/dummy/bin/update +0 -4
  42. data/spec/dummy/config/environments/development.rb +1 -1
  43. data/spec/lib/azeroth/model_spec.rb +1 -0
  44. data/spec/lib/azeroth/request_handler/index_spec.rb +5 -5
  45. data/spec/lib/azeroth/routes_builder_spec.rb +1 -1
  46. data/spec/spec_helper.rb +7 -0
  47. data/spec/support/app/controllers/controller.rb +1 -1
  48. data/spec/support/app/controllers/request_handler_controller.rb +1 -1
  49. metadata +13 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2bb67d32bc51b4aa26e666ec6d4f942dba16b454d07cf65cb5ebff1f052766ae
4
- data.tar.gz: b5d68c84445bde028dcc0038cfe075947de6d45ffb31afb5f7bec088de680a09
3
+ metadata.gz: 3abf116f0ed9063b02d06bba4bcedee4ec48036aaaf2a86238a5b4fcfe32b804
4
+ data.tar.gz: ba519353edc46b56834662db662a650b1bfddbbd3d10a3726b42aadb343fec73
5
5
  SHA512:
6
- metadata.gz: c2604e2557de1bf029e1fbb0f32d16957e150a302c80b4db0c95e70dc2c7809f4883365b0e7f5756dfb8348aafd304f7262087ca80f95fa3f24e626a4dafe2b0
7
- data.tar.gz: a54c254012d8de5f00c799a5cc9fa3e65467d8c624e3ad1d8eff8adf892f3bd7ec6b80134abd61766e0cadf37719a56d607b9d241af7c038fb56591b93255e94
6
+ metadata.gz: 181f6a5e4f0caf6a4d7e1e383956aad1b7cd34fccb3b30586e5d39e920de143ba5a1fd4dde681f3db368a39badbf9d3eb53ca3fc77e3e719927c8fd00c5043cf
7
+ data.tar.gz: 41baee7b1b1f00fe82a32da29ccd4c38470e504855a6036c56e28db51d04c3335a40a76c68b84cf2b266045d88c6d685f941fbd66370dd1763e8b671e51ed13d
data/.circleci/config.yml CHANGED
@@ -18,18 +18,15 @@ workflows:
18
18
  only: /\d+\.\d+\.\d+/
19
19
  branches:
20
20
  only:
21
- - master
21
+ - main
22
22
  jobs:
23
23
  test:
24
24
  docker:
25
- - image: darthjee/circleci_rails_gems:2.0.0
25
+ - image: darthjee/circleci_rails_gems:2.1.1
26
26
  environment:
27
27
  PROJECT: azeroth
28
28
  steps:
29
29
  - checkout
30
- - run:
31
- name: Prepare Coverage Test Report
32
- command: cc-test-reporter before-build
33
30
  - run:
34
31
  name: Bundle Install
35
32
  command: bundle install
@@ -37,11 +34,11 @@ jobs:
37
34
  name: RSpec
38
35
  command: bundle exec rspec
39
36
  - run:
40
- name: Coverage Test Report
41
- command: cc-test-reporter after-build --exit-code $?
37
+ name: Upload coverage to Codacy
38
+ command: bash <(curl -Ls https://coverage.codacy.com/get.sh) report -r coverage/lcov/project.lcov
42
39
  checks:
43
40
  docker:
44
- - image: darthjee/circleci_rails_gems:2.0.0
41
+ - image: darthjee/circleci_rails_gems:2.1.1
45
42
  environment:
46
43
  PROJECT: azeroth
47
44
  steps:
@@ -66,7 +63,7 @@ jobs:
66
63
  command: check_specs
67
64
  build-and-release:
68
65
  docker:
69
- - image: darthjee/circleci_rails_gems:2.0.0
66
+ - image: darthjee/circleci_rails_gems:2.1.1
70
67
  environment:
71
68
  PROJECT: azeroth
72
69
  steps:
@@ -0,0 +1,137 @@
1
+ # Using `darthjee-active_ext` in This Project
2
+
3
+ This project uses the [`darthjee-active_ext`](https://github.com/darthjee/active_ext) gem,
4
+ which adds utility methods to `ActiveRecord::Relation` and `ActiveRecord::Base`.
5
+
6
+ ---
7
+
8
+ ## Installation
9
+
10
+ Add to your `Gemfile`:
11
+
12
+ ```ruby
13
+ gem 'darthjee-active_ext'
14
+ ```
15
+
16
+ Then run:
17
+
18
+ ```console
19
+ bundle install
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Methods Added
25
+
26
+ The gem adds the following methods to **`ActiveRecord::Relation`** and, by delegation,
27
+ to every **`ActiveRecord::Base`** subclass (i.e., every model):
28
+
29
+ | Method | Returns | Purpose |
30
+ |---|---|---|
31
+ | `#percentage(*filters)` | `Float` (0.0 – 1.0) | Fraction of records matching a condition within the current scope |
32
+ | `#pluck_as_json(*keys)` | `Array<Hash>` | Like `pluck`, but returns an array of hashes instead of an array of arrays |
33
+
34
+ ---
35
+
36
+ ## `#percentage`
37
+
38
+ Returns the fraction of records within the current relation that match the
39
+ given condition. The result is a `Float` between `0.0` and `1.0`.
40
+ Returns `0` (integer) when the relation is empty, to avoid division by zero.
41
+
42
+ ### Accepted filter forms
43
+
44
+ | Form | Example argument |
45
+ |---|---|
46
+ | Named scope (Symbol) | `:with_error` |
47
+ | Multiple chained scopes (Symbols) | `:active, :with_error` |
48
+ | Hash condition | `status: :error` |
49
+ | Raw SQL string | `"status = 'error'"` |
50
+
51
+ ### Examples
52
+
53
+ ```ruby
54
+ # Given a model and some data:
55
+ class Document < ActiveRecord::Base
56
+ scope :with_error, -> { where(status: :error) }
57
+ scope :with_success, -> { where(status: :success) }
58
+ scope :active, -> { where(active: true) }
59
+ end
60
+
61
+ # 3 error documents, 1 success document (4 total)
62
+ Document.percentage(:with_error) #=> 0.75
63
+ Document.percentage(status: :error) #=> 0.75
64
+ Document.percentage("status = 'error'") #=> 0.75
65
+
66
+ # Nested scope: among active documents only
67
+ Document.active.percentage(:with_error) #=> 0.5
68
+
69
+ # Multiple scope filters chained together
70
+ Document.percentage(:active, :with_error) #=> 0.25
71
+
72
+ # Empty relation → returns 0, not a float, to avoid division by zero
73
+ Document.where(id: nil).percentage(:with_error) #=> 0
74
+ ```
75
+
76
+ ### When to use `percentage`
77
+
78
+ - Displaying statistics or progress indicators (e.g., "75% of tasks completed").
79
+ - Feature-flag rollout checks across a filtered user base.
80
+ - Any situation where you need a ratio of one subset to its parent scope.
81
+
82
+ ---
83
+
84
+ ## `#pluck_as_json`
85
+
86
+ Works like `ActiveRecord`'s built-in `pluck`, but returns an **array of hashes**
87
+ instead of an array of arrays. Each hash maps column name (as a Symbol) to its
88
+ value.
89
+
90
+ When called **with no arguments**, it returns the full `as_json` representation
91
+ of every record in the relation (equivalent to `map(&:as_json)`).
92
+
93
+ ### Examples
94
+
95
+ ```ruby
96
+ # Standard pluck returns nested arrays — column order matters
97
+ Document.pluck(:id, :status)
98
+ #=> [[1, "error"], [2, "success"], [3, "success"]]
99
+
100
+ # pluck_as_json returns hashes — keys make meaning explicit
101
+ Document.pluck_as_json(:id, :status)
102
+ #=> [
103
+ # { id: 1, status: "error" },
104
+ # { id: 2, status: "success" },
105
+ # { id: 3, status: "success" }
106
+ # ]
107
+
108
+ # Works with any ActiveRecord scope chain
109
+ Document.active.pluck_as_json(:id, :status)
110
+ #=> [{ id: 3, status: "success" }]
111
+
112
+ # No arguments — returns all columns for every record as JSON hashes
113
+ Document.pluck_as_json
114
+ #=> [
115
+ # { id: 1, status: "error", active: false, created_at: ..., updated_at: ... },
116
+ # { id: 2, status: "success", active: false, created_at: ..., updated_at: ... },
117
+ # { id: 3, status: "success", active: true, created_at: ..., updated_at: ... }
118
+ # ]
119
+ ```
120
+
121
+ ### When to use `pluck_as_json`
122
+
123
+ - Building JSON API responses without loading full ActiveRecord objects.
124
+ - Feeding data into serializers or view helpers that expect hashes.
125
+ - Any scenario where column position in a plain array would be fragile or unclear.
126
+
127
+ ---
128
+
129
+ ## Notes
130
+
131
+ - Both methods are available directly on model classes (`Document.percentage(...)`)
132
+ **and** on any `ActiveRecord::Relation` (`Document.active.percentage(...)`),
133
+ because the class-level methods are delegated to `Model.all`.
134
+ - `percentage` with Symbol arguments chains named scopes; with a Hash or String it
135
+ uses `where`. Do **not** mix Symbols with Hashes/Strings in a single call.
136
+ - The gem requires `darthjee-core_ext` (pulled in automatically as a dependency),
137
+ which provides the `Array#as_hash` helper used internally by `pluck_as_json`.
@@ -0,0 +1,277 @@
1
+ # Azeroth Gem Usage Instructions
2
+
3
+ This file provides guidance for GitHub Copilot when working in projects that use the [Azeroth](https://github.com/darthjee/azeroth) gem.
4
+
5
+ ## About Azeroth
6
+
7
+ Azeroth is a Ruby gem that simplifies the creation of Rails controller endpoints. Its main feature is the `resource_for` class method, which automatically generates controller action methods (`create`, `show`, `index`, `update`, `delete`, `edit`) and handles both HTML and JSON request formats transparently.
8
+
9
+ - HTML requests render templates without performing database operations.
10
+ - JSON requests perform database operations and return serialized JSON.
11
+
12
+ ## Installation
13
+
14
+ Add Azeroth to your `Gemfile`:
15
+
16
+ ```ruby
17
+ gem 'azeroth'
18
+ ```
19
+
20
+ Then run:
21
+
22
+ ```bash
23
+ bundle install
24
+ ```
25
+
26
+ ## Basic Usage
27
+
28
+ ### Setting Up `resource_for`
29
+
30
+ Include `Azeroth::Resourceable` in your controller and call `resource_for` with the resource name:
31
+
32
+ ```ruby
33
+ class PublishersController < ApplicationController
34
+ include Azeroth::Resourceable
35
+ skip_before_action :verify_authenticity_token
36
+
37
+ resource_for :publisher, only: %i[create index]
38
+ end
39
+ ```
40
+
41
+ This generates the following behavior:
42
+ - `GET /publishers` → calls `index`, returns all publishers
43
+ - `POST /publishers` → calls `create`, creates a new publisher
44
+
45
+ ### Available Actions
46
+
47
+ By default, `resource_for` builds all six standard actions: `create`, `show`, `index`, `update`, `delete`, and `edit`. Use `only:` or `except:` to restrict which actions are generated:
48
+
49
+ ```ruby
50
+ # Only generate index and show
51
+ resource_for :article, only: %i[index show]
52
+
53
+ # Generate everything except delete
54
+ resource_for :article, except: :delete
55
+ ```
56
+
57
+ ### Nested Resources
58
+
59
+ Override the collection method to scope resources to a parent:
60
+
61
+ ```ruby
62
+ class GamesController < ApplicationController
63
+ include Azeroth::Resourceable
64
+ skip_before_action :verify_authenticity_token
65
+
66
+ resource_for :game, except: :delete
67
+
68
+ private
69
+
70
+ def games
71
+ publisher.games
72
+ end
73
+
74
+ def publisher
75
+ @publisher ||= Publisher.find(publisher_id)
76
+ end
77
+
78
+ def publisher_id
79
+ params.require(:publisher_id)
80
+ end
81
+ end
82
+ ```
83
+
84
+ ## `resource_for` Options
85
+
86
+ | Option | Type | Description |
87
+ |---|---|---|
88
+ | `only` | Symbol / Array | List of actions to generate |
89
+ | `except` | Symbol / Array | List of actions to skip |
90
+ | `decorator` | Class / Boolean | Decorator class or flag to enable/disable decoration |
91
+ | `before_save` | Symbol / Proc | Method or proc called before `create` or `update` saves |
92
+ | `after_save` | Symbol / Proc | Method or proc called after `create` or `update` saves |
93
+ | `build_with` | Symbol / Proc | Method or block used to build the resource on `create` |
94
+ | `update_with` | Symbol / Proc | Method or block used to update the resource on `update` |
95
+ | `paginated` | Boolean | Enable pagination on the `index` action |
96
+ | `per_page` | Integer | Number of items per page when pagination is active (default: 20) |
97
+ | `id_key` | Symbol | Parameter key used to look up a single resource (default: `:id`) |
98
+ | `param_key` | Symbol | Parameter key used to find the model |
99
+
100
+ ### Callbacks: `before_save` and `after_save`
101
+
102
+ ```ruby
103
+ class PokemonsController < ApplicationController
104
+ include Azeroth::Resourceable
105
+
106
+ resource_for :pokemon,
107
+ only: %i[create update],
108
+ before_save: :set_favorite
109
+
110
+ private
111
+
112
+ def set_favorite
113
+ pokemon.favorite = true
114
+ end
115
+ end
116
+ ```
117
+
118
+ ### Pagination
119
+
120
+ ```ruby
121
+ class DocumentsController < ApplicationController
122
+ include Azeroth::Resourceable
123
+
124
+ resource_for :document, only: :index, paginated: true, per_page: 10
125
+ end
126
+ ```
127
+
128
+ A paginated `index` request returns the items for the requested page and sets the following response headers:
129
+
130
+ | Header | Description |
131
+ |---|---|
132
+ | `pages` | Total number of pages |
133
+ | `per_page` | Number of items per page |
134
+ | `page` | Current page number |
135
+
136
+ ```bash
137
+ GET /documents.json # page 1 – first 10 documents
138
+ GET /documents.json?page=2 # page 2 – next 10 documents
139
+ ```
140
+
141
+ ## JSON Serialization with `Azeroth::Decorator`
142
+
143
+ Decorators control which attributes are included in JSON responses.
144
+
145
+ ### Defining a Decorator
146
+
147
+ Create a decorator class that inherits from `Azeroth::Decorator` and call `expose` for each attribute to include:
148
+
149
+ ```ruby
150
+ # app/decorators/pokemon/decorator.rb
151
+ class Pokemon::Decorator < Azeroth::Decorator
152
+ expose :name
153
+ expose :previous_form_name, as: :evolution_of, if: :evolution?
154
+
155
+ def evolution?
156
+ previous_form
157
+ end
158
+
159
+ def previous_form_name
160
+ previous_form.name
161
+ end
162
+ end
163
+ ```
164
+
165
+ ### `expose` Options
166
+
167
+ | Option | Type | Description |
168
+ |---|---|---|
169
+ | `as` | Symbol | Custom JSON key for the attribute |
170
+ | `if` | Symbol / Proc | Method or block; attribute is only included when it returns truthy |
171
+ | `decorator` | Class / Boolean | Nested decorator class or flag to enable/disable decoration |
172
+ | `reader` | Boolean | Whether to create a reader method for the attribute |
173
+ | `override` | Boolean | Whether to override an existing method with the same name |
174
+
175
+ ### Decorator Inheritance
176
+
177
+ Extend decorators by subclassing them:
178
+
179
+ ```ruby
180
+ # app/decorators/pokemon/favorite_decorator.rb
181
+ class Pokemon::FavoriteDecorator < Pokemon::Decorator
182
+ expose :nickname
183
+ end
184
+ ```
185
+
186
+ ### Using a Custom Decorator in a Controller
187
+
188
+ Pass the decorator class to `resource_for`:
189
+
190
+ ```ruby
191
+ class PokemonsController < ApplicationController
192
+ include Azeroth::Resourceable
193
+
194
+ resource_for :pokemon, decorator: Pokemon::FavoriteDecorator
195
+ end
196
+ ```
197
+
198
+ ## `model_for`
199
+
200
+ `model_for` adds singular and plural resource accessor methods to a controller (e.g., `document` and `documents`) **without** generating any HTTP route actions. This is useful when a controller needs to access a resource as context (for example a parent record) but should not expose CRUD endpoints for it.
201
+
202
+ ### Options
203
+
204
+ | Option | Type | Description |
205
+ |---|---|---|
206
+ | `id_key` | Symbol | Parameter key used to find the record (default: `:id`) |
207
+ | `param_key` | Symbol | Parameter key used to find the model |
208
+
209
+ ### Example
210
+
211
+ ```ruby
212
+ class PostsController < ApplicationController
213
+ include Azeroth::Resourceable
214
+
215
+ # Generates full CRUD endpoints for Post,
216
+ # scoped to the author resolved by model_for below.
217
+ resource_for :post
218
+
219
+ # Only adds `author` and `authors` helper methods —
220
+ # no HTTP actions are created for Author.
221
+ model_for :author, id_key: :author_id
222
+
223
+ private
224
+
225
+ def posts
226
+ author.posts
227
+ end
228
+ end
229
+ ```
230
+
231
+ In this example:
232
+ - `GET /authors/:author_id/posts.json` returns all posts belonging to the author.
233
+ - `author` is resolved automatically via `Author.find(params[:author_id])`.
234
+ - No `/authors` CRUD endpoints are generated.
235
+
236
+ ## Best Practices
237
+
238
+ - **Use `only:` / `except:`** to expose only the actions your controller actually needs.
239
+ - **Override collection methods** (e.g., `def games`) to scope resources to a parent instead of returning the full table.
240
+ - **Use decorators** to keep serialization logic out of controllers and models.
241
+ - **Use `before_save` / `after_save`** callbacks for business logic that must run around persistence, rather than overriding generated action methods.
242
+ - **Prefer `resource_for`** over hand-written CRUD actions for standard resources to ensure consistent behavior across your application.
243
+
244
+ ## Testing Controllers That Use Azeroth
245
+
246
+ Use standard Rails request specs or controller specs with RSpec. Since Azeroth generates conventional Rails actions, they can be exercised like any other controller action:
247
+
248
+ ```ruby
249
+ RSpec.describe PokemonsController, type: :request do
250
+ let(:master) { create(:pokemon_master) }
251
+
252
+ describe 'POST /pokemons' do
253
+ it 'creates a pokemon' do
254
+ post "/pokemon_masters/#{master.id}/pokemons.json",
255
+ params: { pokemon: { name: 'Bulbasaur' } }
256
+
257
+ expect(response).to have_http_status(:created)
258
+ expect(response.parsed_body['name']).to eq('Bulbasaur')
259
+ end
260
+ end
261
+
262
+ describe 'GET /pokemons' do
263
+ it 'returns all pokemons' do
264
+ get "/pokemon_masters/#{master.id}/pokemons.json"
265
+
266
+ expect(response).to have_http_status(:ok)
267
+ expect(response.parsed_body).to be_an(Array)
268
+ end
269
+ end
270
+ end
271
+ ```
272
+
273
+ ## Further Reading
274
+
275
+ - [Azeroth on RubyGems](https://rubygems.org/gems/azeroth)
276
+ - [YARD Documentation](https://www.rubydoc.info/gems/azeroth)
277
+ - [GitHub Repository](https://github.com/darthjee/azeroth)
@@ -0,0 +1,70 @@
1
+ # GitHub Copilot Instructions for Azeroth
2
+
3
+ ## Project Overview
4
+
5
+ Azeroth is a Ruby gem that simplifies the creation of Rails controller endpoints. The main feature is the `resource_for` method, which automatically generates controller action methods (`create`, `show`, `index`, `update`, `delete`, `edit`) and handles both HTML and JSON request formats transparently.
6
+
7
+ ## Language Requirements
8
+
9
+ - All pull requests, comments, documentation, and code must be written in **English**.
10
+
11
+ ## Testing Requirements
12
+
13
+ - **Tests are mandatory** for all code changes.
14
+ - Every new class, module, or method must have corresponding specs under `spec/`.
15
+ - Files without test coverage must be listed in `config/check_specs.yml` under the `ignore:` key.
16
+ - Use RSpec for all tests, following the conventions already established in the `spec/` directory.
17
+ - Ensure comprehensive test coverage for new features and changes, including edge cases.
18
+
19
+ ## Documentation Requirements
20
+
21
+ - Use **YARD** format for all documentation.
22
+ - Document all public methods, classes, and modules with:
23
+ - A summary line (single-line, ending with a period).
24
+ - `@param` tags for each parameter.
25
+ - `@return` tag describing the return value.
26
+ - `@example` tags showing representative usage where appropriate.
27
+ - `@api public` or `@api private` tags to mark visibility.
28
+ - Documentation coverage is enforced via `config/yardstick.yml` (threshold: 96.5%).
29
+
30
+ ## Code Style and Design Principles
31
+
32
+ - Follow the principles from **Sandi Metz's "99 Bottles of OOP"**:
33
+ - Prefer small, well-named methods with a single responsibility.
34
+ - Avoid large, complex methods—break them into smaller, well-defined ones.
35
+ - Aim for high cohesion and low coupling between classes.
36
+ - Respect the **Law of Demeter**: avoid chaining method calls across object boundaries.
37
+ - Keep classes focused; a class should do one thing and do it well.
38
+ - Prefer composition over inheritance where possible.
39
+ - Follow the **RuboCop** rules configured in `.rubocop.yml` (Ruby 3.3 target).
40
+
41
+ ## Project-Specific Guidelines
42
+
43
+ - The gem is a **Rails** extension—follow Rails conventions and best practices.
44
+ - The core abstraction is `Azeroth::Resourceable` (mixed into controllers via `include Azeroth::Resourceable`), which exposes the `resource_for` class method.
45
+ - `Azeroth::Decorator` is used to control JSON serialization; use `expose` to declare which attributes are rendered.
46
+ - **Maintain backward compatibility** when modifying existing public APIs.
47
+ - Pagination is opt-in via `paginated: true` on `resource_for`; respect the `per_page` option and the pagination response headers (`pages`, `per_page`, `page`).
48
+ - New options added to `resource_for` or `model_for` must be documented and covered by integration specs (see `spec/integration/`).
49
+
50
+ ## Using dependencies
51
+
52
+ ### Using Jace for Event-Driven Hooks
53
+
54
+ Azeroth uses the [jace](https://github.com/darthjee/jace) gem to provide event-driven lifecycle hooks around controller actions. Refer to [`.github/jace-usage.md`](.github/jace-usage.md) for the full Jace API reference and usage patterns.
55
+
56
+ Key points when working with Jace inside Azeroth:
57
+
58
+ - Use `Jace::Registry` to register and trigger events.
59
+ - Register handlers with `registry.register(event, instant = :after, &block)`, where `instant` is `:before` or `:after`.
60
+ - Trigger events with `registry.trigger(event, context, &block)`; `:before` and `:after` handlers are `instance_eval`'d in `context`, while the main block is called in the surrounding scope.
61
+ - Triggering an event with no registered handlers is safe—the main block still runs.
62
+
63
+
64
+ ### Using darthjee-core_ext for extending core classes
65
+
66
+ Azeroth uses the [darthjee-core_ext](https://github.com/darthjee/core_ext) gem to extend core Ruby classes with additional methods. Refer to [`.github/core_ext-usage.md`](.github/core_ext-usage.md) for the full list of available extensions and usage examples.
67
+
68
+ ### Using darthjee-active_ext for extending active record and active support classes
69
+
70
+ Azeroth uses the [darthjee-active_ext](https://github.com/darthjee/active_ext) gem to extend Active Record and Active Support classes with additional methods. Refer to [`.github/active_ext-usage.md`](.github/active_ext-usage.md) for the full list of available extensions and usage examples.