azeroth 2.1.1 → 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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +6 -9
- data/.github/active_ext-usage.md +137 -0
- data/.github/azeroth-usage.md +277 -0
- data/.github/copilot-instructions.md +70 -0
- data/.github/core_ext-usage.md +324 -0
- data/.github/jace-usage.md +241 -0
- data/.rubocop.yml +14 -1
- data/.rubocop_todo.yml +1 -15
- data/Dockerfile +2 -2
- data/Gemfile +20 -16
- data/README.md +5 -6
- data/Rakefile +3 -0
- data/azeroth.gemspec +3 -3
- data/lib/azeroth/decorator/key_value_extractor.rb +1 -1
- data/lib/azeroth/version.rb +1 -1
- data/spec/controllers/documents_controller_spec.rb +5 -5
- data/spec/controllers/documents_with_error_controller_spec.rb +3 -3
- data/spec/controllers/index_documents_controller_spec.rb +1 -1
- data/spec/controllers/paginated_documents_controller_spec.rb +1 -1
- data/spec/controllers/rendering_controller_spec.rb +1 -1
- data/spec/dummy/app/controllers/games_controller.rb +1 -0
- data/spec/dummy/app/controllers/publishers_controller.rb +1 -0
- data/spec/dummy/app/models/document.rb +1 -1
- data/spec/dummy/app/models/dummy_model.rb +1 -1
- data/spec/dummy/app/models/factory.rb +3 -3
- data/spec/dummy/app/models/game/decorator.rb +1 -1
- data/spec/dummy/app/models/game.rb +1 -1
- data/spec/dummy/app/models/movie.rb +1 -1
- data/spec/dummy/app/models/pokemon/decorator.rb +1 -3
- data/spec/dummy/app/models/pokemon.rb +8 -2
- data/spec/dummy/app/models/pokemon_master.rb +5 -3
- data/spec/dummy/app/models/product.rb +1 -1
- data/spec/dummy/app/models/publisher.rb +2 -2
- data/spec/dummy/app/models/user.rb +1 -1
- data/spec/dummy/app/models/website/decorator.rb +1 -1
- data/spec/dummy/app/models/website/with_location.rb +1 -1
- data/spec/dummy/app/models/website.rb +1 -1
- data/spec/dummy/bin/setup +0 -4
- data/spec/dummy/bin/update +0 -4
- data/spec/dummy/config/environments/development.rb +1 -1
- data/spec/lib/azeroth/model_spec.rb +1 -0
- data/spec/lib/azeroth/request_handler/index_spec.rb +5 -5
- data/spec/lib/azeroth/routes_builder_spec.rb +1 -1
- data/spec/spec_helper.rb +7 -0
- data/spec/support/app/controllers/controller.rb +1 -1
- data/spec/support/app/controllers/request_handler_controller.rb +1 -1
- metadata +13 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3abf116f0ed9063b02d06bba4bcedee4ec48036aaaf2a86238a5b4fcfe32b804
|
|
4
|
+
data.tar.gz: ba519353edc46b56834662db662a650b1bfddbbd3d10a3726b42aadb343fec73
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
-
|
|
21
|
+
- main
|
|
22
22
|
jobs:
|
|
23
23
|
test:
|
|
24
24
|
docker:
|
|
25
|
-
- image: darthjee/circleci_rails_gems:2.
|
|
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:
|
|
41
|
-
command:
|
|
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.
|
|
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.
|
|
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.
|