bug_bunny 4.7.0 → 4.8.1
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/.agents/skills/documentation-writer/SKILL.md +45 -0
- data/.agents/skills/gem-release/SKILL.md +114 -0
- data/.agents/skills/quality-code/SKILL.md +51 -0
- data/.agents/skills/rabbitmq-expert/SKILL.md +1555 -0
- data/.agents/skills/sentry/SKILL.md +135 -0
- data/.agents/skills/sentry/references/api-endpoints.md +147 -0
- data/.agents/skills/sentry/scripts/sentry.rb +194 -0
- data/.agents/skills/skill-builder/SKILL.md +232 -0
- data/.agents/skills/skill-manager/SKILL.md +172 -0
- data/.agents/skills/skill-manager/scripts/sync.rb +310 -0
- data/.agents/skills/yard/SKILL.md +311 -0
- data/.agents/skills/yard/references/tipos.md +144 -0
- data/CHANGELOG.md +21 -0
- data/CLAUDE.md +29 -220
- data/lib/bug_bunny/client.rb +4 -3
- data/lib/bug_bunny/controller.rb +10 -14
- data/lib/bug_bunny/exception.rb +1 -1
- data/lib/bug_bunny/middleware/raise_error.rb +3 -3
- data/lib/bug_bunny/observability.rb +5 -5
- data/lib/bug_bunny/producer.rb +11 -13
- data/lib/bug_bunny/railtie.rb +8 -7
- data/lib/bug_bunny/request.rb +3 -11
- data/lib/bug_bunny/resource.rb +51 -21
- data/lib/bug_bunny/routing/route_set.rb +32 -21
- data/lib/bug_bunny/version.rb +1 -1
- data/lib/bug_bunny.rb +3 -2
- data/lib/generators/bug_bunny/install/install_generator.rb +45 -5
- data/lib/tasks/bug_bunny.rake +50 -0
- data/skill/SKILL.md +230 -0
- data/skill/references/client-middleware.md +144 -0
- data/skill/references/consumer.md +104 -0
- data/skill/references/controller.md +105 -0
- data/skill/references/errores.md +97 -0
- data/skill/references/resource.md +116 -0
- data/skill/references/routing.md +82 -0
- data/skill/references/testing.md +138 -0
- data/skills-lock.json +10 -0
- data/skills.lock +24 -0
- data/skills.yml +19 -0
- metadata +27 -17
- data/.claude/commands/pr.md +0 -42
- data/.claude/commands/release.md +0 -41
- data/.claude/commands/rubocop.md +0 -22
- data/.claude/commands/test.md +0 -28
- data/.claude/commands/yard.md +0 -46
- data/docs/concepts.md +0 -140
- data/docs/howto/controller.md +0 -194
- data/docs/howto/middleware_client.md +0 -119
- data/docs/howto/middleware_consumer.md +0 -127
- data/docs/howto/rails.md +0 -214
- data/docs/howto/resource.md +0 -200
- data/docs/howto/routing.md +0 -133
- data/docs/howto/testing.md +0 -259
- data/docs/howto/tracing.md +0 -119
- data/mejoras.md +0 -33
data/docs/howto/rails.md
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# Rails Setup
|
|
2
|
-
|
|
3
|
-
Complete integration guide for BugBunny in a Rails application using Puma and/or Sidekiq.
|
|
4
|
-
|
|
5
|
-
## Gemfile
|
|
6
|
-
|
|
7
|
-
```ruby
|
|
8
|
-
gem 'bug_bunny'
|
|
9
|
-
gem 'connection_pool'
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## Generator
|
|
13
|
-
|
|
14
|
-
```bash
|
|
15
|
-
rails generate bug_bunny:install
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
Creates `config/initializers/bug_bunny.rb` with a commented template.
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Initializer
|
|
23
|
-
|
|
24
|
-
```ruby
|
|
25
|
-
# config/initializers/bug_bunny.rb
|
|
26
|
-
|
|
27
|
-
BugBunny.configure do |config|
|
|
28
|
-
config.host = ENV.fetch('RABBITMQ_HOST', 'localhost')
|
|
29
|
-
config.port = ENV.fetch('RABBITMQ_PORT', '5672').to_i
|
|
30
|
-
config.username = ENV.fetch('RABBITMQ_USER', 'guest')
|
|
31
|
-
config.password = ENV.fetch('RABBITMQ_PASS', 'guest')
|
|
32
|
-
config.vhost = ENV.fetch('RABBITMQ_VHOST', '/')
|
|
33
|
-
|
|
34
|
-
config.rpc_timeout = 30
|
|
35
|
-
config.max_reconnect_attempts = 10
|
|
36
|
-
config.max_reconnect_interval = 60
|
|
37
|
-
config.network_recovery_interval = 5
|
|
38
|
-
|
|
39
|
-
config.exchange_options = { durable: true }
|
|
40
|
-
config.queue_options = { durable: true }
|
|
41
|
-
|
|
42
|
-
config.logger = Rails.logger
|
|
43
|
-
|
|
44
|
-
# Kubernetes / Docker Swarm liveness probe
|
|
45
|
-
config.health_check_file = Rails.root.join('tmp', 'bug_bunny_health').to_s
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Shared connection pool — size should match Puma/Sidekiq thread count
|
|
49
|
-
BUG_BUNNY_POOL = ConnectionPool.new(
|
|
50
|
-
size: ENV.fetch('RAILS_MAX_THREADS', 5).to_i,
|
|
51
|
-
timeout: 5
|
|
52
|
-
) do
|
|
53
|
-
BugBunny.create_connection
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Make the pool available to all Resource classes
|
|
57
|
-
BugBunny::Resource.connection_pool = BUG_BUNNY_POOL
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## Routes
|
|
63
|
-
|
|
64
|
-
```ruby
|
|
65
|
-
# config/initializers/bug_bunny_routes.rb
|
|
66
|
-
BugBunny.routes.draw do
|
|
67
|
-
resources :users
|
|
68
|
-
resources :orders do
|
|
69
|
-
member { post :cancel }
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Keep routes in a dedicated initializer so they load independently from `config/routes.rb`.
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## Directory Layout
|
|
79
|
-
|
|
80
|
-
The Rails generator configures Zeitwerk to autoload `app/rabbit`:
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
app/
|
|
84
|
-
rabbit/
|
|
85
|
-
controllers/ # BugBunny controllers (loaded under BugBunny::Controllers)
|
|
86
|
-
application_controller.rb
|
|
87
|
-
users_controller.rb
|
|
88
|
-
workers/ # Consumer processes (optional)
|
|
89
|
-
inventory_worker.rb
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
Or use the configurable controller namespace:
|
|
93
|
-
|
|
94
|
-
```ruby
|
|
95
|
-
config.controller_namespace = 'Rabbit::Controllers'
|
|
96
|
-
# → app/rabbit/controllers/ maps to Rabbit::Controllers
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Consumer Workers
|
|
102
|
-
|
|
103
|
-
The Consumer is a blocking subscribe loop. Run it in a dedicated process (not inside Puma).
|
|
104
|
-
|
|
105
|
-
### Rake task
|
|
106
|
-
|
|
107
|
-
```ruby
|
|
108
|
-
# lib/tasks/rabbit.rake
|
|
109
|
-
namespace :rabbit do
|
|
110
|
-
desc 'Start the RabbitMQ consumer'
|
|
111
|
-
task inventory: :environment do
|
|
112
|
-
consumer = BugBunny::Consumer.new
|
|
113
|
-
trap('TERM') { consumer.shutdown; exit }
|
|
114
|
-
trap('INT') { consumer.shutdown; exit }
|
|
115
|
-
|
|
116
|
-
consumer.subscribe(
|
|
117
|
-
queue_name: 'inventory_queue',
|
|
118
|
-
exchange_name: 'inventory_exchange',
|
|
119
|
-
routing_key: 'inventory',
|
|
120
|
-
block: true
|
|
121
|
-
)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
```bash
|
|
127
|
-
bundle exec rake rabbit:inventory
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Dockerfile entrypoint (separate service)
|
|
131
|
-
|
|
132
|
-
```dockerfile
|
|
133
|
-
# Dockerfile.worker
|
|
134
|
-
CMD ["bundle", "exec", "rake", "rabbit:inventory"]
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Multiple consumers
|
|
138
|
-
|
|
139
|
-
Run one process per queue. Each process has its own connection pool.
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## Puma Fork Safety
|
|
144
|
-
|
|
145
|
-
When Puma forks workers, open AMQP connections in the parent process become invalid in children. BugBunny's Railtie handles this automatically for Puma via `on_worker_boot`:
|
|
146
|
-
|
|
147
|
-
```ruby
|
|
148
|
-
# Railtie registers this automatically:
|
|
149
|
-
Puma::Server.on_worker_boot do
|
|
150
|
-
BugBunny.reconnect! if defined?(BUG_BUNNY_POOL)
|
|
151
|
-
end
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
If you use a custom pool variable name, add the hook manually:
|
|
155
|
-
|
|
156
|
-
```ruby
|
|
157
|
-
# config/puma.rb
|
|
158
|
-
on_worker_boot do
|
|
159
|
-
MY_CUSTOM_POOL.reload_connections { BugBunny.create_connection }
|
|
160
|
-
end
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## Sidekiq Integration
|
|
166
|
-
|
|
167
|
-
Sidekiq uses threads, not forks, so no special handling is needed. The shared `BUG_BUNNY_POOL` is thread-safe. Sidekiq jobs can call `BugBunny::Resource` methods directly.
|
|
168
|
-
|
|
169
|
-
Set the pool size to match Sidekiq's concurrency:
|
|
170
|
-
|
|
171
|
-
```ruby
|
|
172
|
-
BUG_BUNNY_POOL = ConnectionPool.new(
|
|
173
|
-
size: ENV.fetch('SIDEKIQ_CONCURRENCY', 10).to_i,
|
|
174
|
-
timeout: 5
|
|
175
|
-
) { BugBunny.create_connection }
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## Health Checks (Kubernetes / Docker Swarm)
|
|
181
|
-
|
|
182
|
-
The Consumer's internal heartbeat timer touches `config.health_check_file` every 30 seconds after verifying the RabbitMQ connection and queue are alive.
|
|
183
|
-
|
|
184
|
-
```yaml
|
|
185
|
-
# docker-compose.yml
|
|
186
|
-
healthcheck:
|
|
187
|
-
test: ["CMD", "test", "-f", "/app/tmp/bug_bunny_health"]
|
|
188
|
-
interval: 60s
|
|
189
|
-
timeout: 5s
|
|
190
|
-
retries: 3
|
|
191
|
-
start_period: 30s # allow time for Rails boot + first heartbeat
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
```yaml
|
|
195
|
-
# Kubernetes
|
|
196
|
-
livenessProbe:
|
|
197
|
-
exec:
|
|
198
|
-
command: ["test", "-f", "/app/tmp/bug_bunny_health"]
|
|
199
|
-
initialDelaySeconds: 30
|
|
200
|
-
periodSeconds: 60
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## Graceful Shutdown
|
|
206
|
-
|
|
207
|
-
The `Consumer#shutdown` method stops the heartbeat timer and closes the AMQP channel cleanly. It is also called automatically when `subscribe` exits for any reason.
|
|
208
|
-
|
|
209
|
-
```ruby
|
|
210
|
-
consumer = BugBunny::Consumer.new
|
|
211
|
-
trap('TERM') { consumer.shutdown; exit 0 }
|
|
212
|
-
trap('INT') { consumer.shutdown; exit 0 }
|
|
213
|
-
consumer.subscribe(...)
|
|
214
|
-
```
|
data/docs/howto/resource.md
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
# Resource ORM
|
|
2
|
-
|
|
3
|
-
`BugBunny::Resource` provides an ActiveRecord-like interface for remote services. Each Resource class represents a resource type in another microservice, reachable via RabbitMQ.
|
|
4
|
-
|
|
5
|
-
## Defining a Resource
|
|
6
|
-
|
|
7
|
-
```ruby
|
|
8
|
-
class RemoteNode < BugBunny::Resource
|
|
9
|
-
# AMQP infrastructure
|
|
10
|
-
self.exchange = 'inventory_exchange'
|
|
11
|
-
self.exchange_type = 'direct' # default
|
|
12
|
-
self.resource_name = 'nodes' # used as the path prefix and routing key
|
|
13
|
-
|
|
14
|
-
# Typed attributes (ActiveModel::Attributes)
|
|
15
|
-
attribute :name, :string
|
|
16
|
-
attribute :status, :string
|
|
17
|
-
attribute :cpu_cores, :integer
|
|
18
|
-
attribute :active, :boolean
|
|
19
|
-
|
|
20
|
-
# Validations (ActiveModel::Validations)
|
|
21
|
-
validates :name, presence: true
|
|
22
|
-
validates :status, inclusion: { in: %w[pending active draining decommissioned] }
|
|
23
|
-
end
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Connection Pool
|
|
27
|
-
|
|
28
|
-
All Resource classes in a service typically share one pool:
|
|
29
|
-
|
|
30
|
-
```ruby
|
|
31
|
-
# config/initializers/bug_bunny.rb
|
|
32
|
-
BUG_BUNNY_POOL = ConnectionPool.new(size: 5, timeout: 5) do
|
|
33
|
-
BugBunny.create_connection
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
BugBunny::Resource.connection_pool = BUG_BUNNY_POOL
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Individual classes can override:
|
|
40
|
-
|
|
41
|
-
```ruby
|
|
42
|
-
RemoteNode.connection_pool = OTHER_POOL
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## CRUD
|
|
48
|
-
|
|
49
|
-
```ruby
|
|
50
|
-
# Find by ID — returns nil on 404
|
|
51
|
-
node = RemoteNode.find('node-123')
|
|
52
|
-
|
|
53
|
-
# List all
|
|
54
|
-
nodes = RemoteNode.all
|
|
55
|
-
|
|
56
|
-
# Filter — query params forwarded to the consumer
|
|
57
|
-
nodes = RemoteNode.where(status: 'active')
|
|
58
|
-
nodes = RemoteNode.where(q: { cpu_cores: 4 }, page: 2)
|
|
59
|
-
|
|
60
|
-
# Create
|
|
61
|
-
node = RemoteNode.create(name: 'web-01', status: 'pending')
|
|
62
|
-
node.persisted? # => true if save succeeded
|
|
63
|
-
|
|
64
|
-
# Update
|
|
65
|
-
node = RemoteNode.find('node-123')
|
|
66
|
-
node.status = 'active'
|
|
67
|
-
node.save # PUT nodes/node-123
|
|
68
|
-
|
|
69
|
-
# Update (shorthand)
|
|
70
|
-
node.update(status: 'active', name: 'web-01')
|
|
71
|
-
|
|
72
|
-
# Destroy
|
|
73
|
-
node.destroy # DELETE nodes/node-123
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## Typed vs Dynamic Attributes
|
|
79
|
-
|
|
80
|
-
### Typed attributes
|
|
81
|
-
|
|
82
|
-
Declared with `attribute :name, :type`. Benefit from ActiveModel coercions, dirty tracking, and validations:
|
|
83
|
-
|
|
84
|
-
```ruby
|
|
85
|
-
attribute :cpu_cores, :integer
|
|
86
|
-
attribute :enabled, :boolean
|
|
87
|
-
attribute :score, :decimal
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Dynamic attributes
|
|
91
|
-
|
|
92
|
-
Any key received from the remote service that is not declared as a typed attribute is stored dynamically and accessible via `method_missing`:
|
|
93
|
-
|
|
94
|
-
```ruby
|
|
95
|
-
node = RemoteNode.find('node-123')
|
|
96
|
-
node.docker_id # => "abc123xyz" (not declared, but present in the response)
|
|
97
|
-
node.docker_id = 'new-id'
|
|
98
|
-
node.changed? # => true
|
|
99
|
-
node.changed # => ['docker_id']
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Dynamic attributes participate in `changed?`, `changed`, and `changes_to_send` — so they are serialized correctly on `save`.
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## Change Tracking
|
|
107
|
-
|
|
108
|
-
BugBunny::Resource merges ActiveModel::Dirty (for typed attributes) with its own tracking for dynamic attributes.
|
|
109
|
-
|
|
110
|
-
```ruby
|
|
111
|
-
node = RemoteNode.find('node-123') # changes cleared after find
|
|
112
|
-
node.changed? # => false
|
|
113
|
-
|
|
114
|
-
node.status = 'draining'
|
|
115
|
-
node.changed? # => true
|
|
116
|
-
node.changed # => ['status']
|
|
117
|
-
|
|
118
|
-
node.save # sends only changed attrs
|
|
119
|
-
node.changed? # => false (cleared after save)
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
`save` on a new record (not persisted) sends all attributes.
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## Validations
|
|
127
|
-
|
|
128
|
-
```ruby
|
|
129
|
-
node = RemoteNode.new(name: '', status: 'invalid')
|
|
130
|
-
node.valid? # => false
|
|
131
|
-
node.errors.full_messages
|
|
132
|
-
# => ["Name can't be blank", "Status is not included in the list"]
|
|
133
|
-
|
|
134
|
-
node.save # => false (does not send the request)
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Remote validation errors (422 responses) are loaded back into the object:
|
|
138
|
-
|
|
139
|
-
```ruby
|
|
140
|
-
node = RemoteNode.create(name: 'duplicate-name')
|
|
141
|
-
node.persisted? # => false
|
|
142
|
-
node.errors[:name] # => ["has already been taken"]
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## Callbacks
|
|
148
|
-
|
|
149
|
-
```ruby
|
|
150
|
-
class RemoteNode < BugBunny::Resource
|
|
151
|
-
before_save :normalize_name
|
|
152
|
-
after_create :notify_provisioner
|
|
153
|
-
around_save :with_timing
|
|
154
|
-
|
|
155
|
-
private
|
|
156
|
-
|
|
157
|
-
def normalize_name
|
|
158
|
-
self.name = name.to_s.downcase.strip
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Available callbacks: `before_save`, `after_save`, `before_create`, `after_create`, `before_update`, `after_update`, `before_destroy`, `after_destroy`.
|
|
164
|
-
|
|
165
|
-
---
|
|
166
|
-
|
|
167
|
-
## Dynamic Exchange Configuration with `.with`
|
|
168
|
-
|
|
169
|
-
Override AMQP settings for a single operation without changing the class defaults:
|
|
170
|
-
|
|
171
|
-
```ruby
|
|
172
|
-
# Different exchange for a single call
|
|
173
|
-
RemoteNode.with(exchange: 'us-east-inventory').where(status: 'active')
|
|
174
|
-
|
|
175
|
-
# Different routing key
|
|
176
|
-
RemoteNode.with(routing_key: 'nodes.priority').find('node-123')
|
|
177
|
-
|
|
178
|
-
# Chain is single-use — call .with again for the next operation
|
|
179
|
-
RemoteNode.with(exchange: 'staging').create(name: 'test-node')
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
`.with` sets thread-local values that are cleaned up after the single operation completes, even if an exception is raised.
|
|
183
|
-
|
|
184
|
-
---
|
|
185
|
-
|
|
186
|
-
## Payload Wrapping
|
|
187
|
-
|
|
188
|
-
By default, `save` wraps the payload under a root key derived from the model name:
|
|
189
|
-
|
|
190
|
-
```ruby
|
|
191
|
-
# RemoteNode → root key: 'node'
|
|
192
|
-
node.save
|
|
193
|
-
# Sends: { "node" => { "name" => "web-01", "status" => "active" } }
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
The consumer can then use `params.require(:node)` in the controller. Override the root key:
|
|
197
|
-
|
|
198
|
-
```ruby
|
|
199
|
-
self.param_key = 'server'
|
|
200
|
-
```
|
data/docs/howto/routing.md
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
# Routing
|
|
2
|
-
|
|
3
|
-
Routes declare how incoming AMQP messages map to controllers and actions. They are evaluated on every message received by the Consumer.
|
|
4
|
-
|
|
5
|
-
## Drawing Routes
|
|
6
|
-
|
|
7
|
-
Define routes in an initializer (or any file loaded at boot):
|
|
8
|
-
|
|
9
|
-
```ruby
|
|
10
|
-
BugBunny.routes.draw do
|
|
11
|
-
# routes here
|
|
12
|
-
end
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Call `draw` only once. Multiple calls replace the previous route set.
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## HTTP Verbs
|
|
20
|
-
|
|
21
|
-
```ruby
|
|
22
|
-
BugBunny.routes.draw do
|
|
23
|
-
get 'status', to: 'health#show'
|
|
24
|
-
post 'events', to: 'events#create'
|
|
25
|
-
put 'users/:id', to: 'users#update'
|
|
26
|
-
delete 'users/:id', to: 'users#destroy'
|
|
27
|
-
end
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
The verb is set by the producer via the `x-http-method` AMQP header. `BugBunny::Resource` and `BugBunny::Client` set it automatically.
|
|
31
|
-
|
|
32
|
-
Dynamic segments (`:id`) are extracted from the path and available in `params` inside the controller.
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Resources Macro
|
|
37
|
-
|
|
38
|
-
`resources` generates the standard seven CRUD routes in one line:
|
|
39
|
-
|
|
40
|
-
```ruby
|
|
41
|
-
resources :users
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
Generates:
|
|
45
|
-
|
|
46
|
-
| Verb | Path | Action |
|
|
47
|
-
|--------|---------------|-----------|
|
|
48
|
-
| GET | users | index |
|
|
49
|
-
| POST | users | create |
|
|
50
|
-
| GET | users/:id | show |
|
|
51
|
-
| PUT | users/:id | update |
|
|
52
|
-
| DELETE | users/:id | destroy |
|
|
53
|
-
|
|
54
|
-
### Filtering actions
|
|
55
|
-
|
|
56
|
-
```ruby
|
|
57
|
-
resources :orders, only: [:index, :show, :create]
|
|
58
|
-
resources :logs, except: [:update, :destroy]
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## Member and Collection Routes
|
|
64
|
-
|
|
65
|
-
```ruby
|
|
66
|
-
resources :nodes do
|
|
67
|
-
member do
|
|
68
|
-
put :drain # PUT nodes/:id/drain → NodesController#drain
|
|
69
|
-
post :reboot # POST nodes/:id/reboot → NodesController#reboot
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
collection do
|
|
73
|
-
post :rebalance # POST nodes/rebalance → NodesController#rebalance
|
|
74
|
-
get :summary # GET nodes/summary → NodesController#summary
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
Member routes receive `params[:id]` automatically. Collection routes do not.
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Namespace Blocks
|
|
84
|
-
|
|
85
|
-
Group routes under a controller namespace. Namespaces stack: nested `namespace` blocks accumulate with `::`.
|
|
86
|
-
|
|
87
|
-
```ruby
|
|
88
|
-
BugBunny.routes.draw do
|
|
89
|
-
namespace :api do
|
|
90
|
-
namespace :v1 do
|
|
91
|
-
resources :metrics # → Api::V1::MetricsController
|
|
92
|
-
resources :alerts # → Api::V1::AlertsController
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
resources :health # → Api::HealthController
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
resources :nodes # → BugBunny::Controllers::NodesController (global namespace)
|
|
99
|
-
end
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
The namespace in the route takes precedence over `config.controller_namespace`. Routes without a namespace block use the global controller namespace.
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## Nested Resources
|
|
107
|
-
|
|
108
|
-
```ruby
|
|
109
|
-
resources :clusters do
|
|
110
|
-
resources :nodes do # → nodes/:id nested under clusters/:cluster_id
|
|
111
|
-
member { put :drain }
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Nested resource routes inject all parent IDs into params. Example: `PUT clusters/c1/nodes/n2/drain` → `params[:cluster_id] = 'c1'`, `params[:id] = 'n2'`.
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Inspecting Routes
|
|
121
|
-
|
|
122
|
-
```ruby
|
|
123
|
-
BugBunny.routes.recognize('GET', 'nodes/123')
|
|
124
|
-
# => { controller: 'nodes', action: 'show', params: { 'id' => '123' }, namespace: nil }
|
|
125
|
-
|
|
126
|
-
BugBunny.routes.recognize('POST', 'api/v1/metrics')
|
|
127
|
-
# => { controller: 'metrics', action: 'create', params: {}, namespace: 'Api::V1' }
|
|
128
|
-
|
|
129
|
-
BugBunny.routes.recognize('GET', 'unknown/path')
|
|
130
|
-
# => nil
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Useful in tests to verify route definitions without sending real messages.
|