cathode 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +346 -125
  3. data/Rakefile +1 -0
  4. data/app/controllers/cathode/base_controller.rb +28 -45
  5. data/app/models/cathode/token.rb +21 -0
  6. data/config/routes.rb +16 -0
  7. data/db/migrate/20140425164100_create_cathode_tokens.rb +11 -0
  8. data/lib/cathode.rb +0 -13
  9. data/lib/cathode/_version.rb +2 -1
  10. data/lib/cathode/action.rb +197 -39
  11. data/lib/cathode/action_dsl.rb +60 -0
  12. data/lib/cathode/base.rb +81 -12
  13. data/lib/cathode/create_request.rb +21 -0
  14. data/lib/cathode/custom_request.rb +5 -0
  15. data/lib/cathode/debug.rb +25 -0
  16. data/lib/cathode/destroy_request.rb +13 -0
  17. data/lib/cathode/engine.rb +9 -0
  18. data/lib/cathode/exceptions.rb +20 -0
  19. data/lib/cathode/index_request.rb +40 -0
  20. data/lib/cathode/object_collection.rb +49 -0
  21. data/lib/cathode/query.rb +24 -0
  22. data/lib/cathode/railtie.rb +21 -0
  23. data/lib/cathode/request.rb +139 -7
  24. data/lib/cathode/resource.rb +50 -19
  25. data/lib/cathode/resource_dsl.rb +46 -0
  26. data/lib/cathode/show_request.rb +13 -0
  27. data/lib/cathode/update_request.rb +26 -0
  28. data/lib/cathode/version.rb +112 -23
  29. data/lib/tasks/cathode_tasks.rake +5 -4
  30. data/spec/dummy/app/api/api.rb +0 -0
  31. data/spec/dummy/app/models/payment.rb +3 -0
  32. data/spec/dummy/app/models/product.rb +1 -0
  33. data/spec/dummy/app/models/sale.rb +5 -0
  34. data/spec/dummy/app/models/salesperson.rb +3 -0
  35. data/spec/dummy/db/development.sqlite3 +0 -0
  36. data/spec/dummy/db/migrate/20140409183635_create_sales.rb +11 -0
  37. data/spec/dummy/db/migrate/20140423172419_create_salespeople.rb +11 -0
  38. data/spec/dummy/db/migrate/20140424181343_create_payments.rb +10 -0
  39. data/spec/dummy/db/schema.rb +31 -1
  40. data/spec/dummy/db/test.sqlite3 +0 -0
  41. data/spec/dummy/log/development.log +1167 -0
  42. data/spec/dummy/log/test.log +180602 -0
  43. data/spec/dummy/spec/factories/payments.rb +8 -0
  44. data/spec/dummy/spec/factories/products.rb +1 -1
  45. data/spec/dummy/spec/factories/sales.rb +9 -0
  46. data/spec/dummy/spec/factories/salespeople.rb +7 -0
  47. data/spec/dummy/spec/requests/requests_spec.rb +434 -0
  48. data/spec/lib/cathode/action_spec.rb +136 -0
  49. data/spec/lib/cathode/base_spec.rb +34 -0
  50. data/spec/lib/cathode/create_request_spec.rb +40 -0
  51. data/spec/lib/cathode/custom_request_spec.rb +31 -0
  52. data/spec/lib/cathode/debug_spec.rb +25 -0
  53. data/spec/lib/cathode/destroy_request_spec.rb +28 -0
  54. data/spec/lib/cathode/index_request_spec.rb +62 -0
  55. data/spec/lib/cathode/object_collection_spec.rb +66 -0
  56. data/spec/lib/cathode/query_spec.rb +28 -0
  57. data/spec/lib/cathode/request_spec.rb +58 -0
  58. data/spec/lib/cathode/resource_spec.rb +482 -0
  59. data/spec/lib/cathode/show_request_spec.rb +23 -0
  60. data/spec/lib/cathode/update_request_spec.rb +41 -0
  61. data/spec/lib/cathode/version_spec.rb +416 -0
  62. data/spec/models/cathode/token_spec.rb +62 -0
  63. data/spec/spec_helper.rb +8 -2
  64. data/spec/support/factories/payments.rb +3 -0
  65. data/spec/support/factories/sale.rb +3 -0
  66. data/spec/support/factories/salespeople.rb +3 -0
  67. data/spec/support/factories/token.rb +3 -0
  68. data/spec/support/helpers.rb +13 -2
  69. metadata +192 -47
  70. data/app/helpers/cathode/application_helper.rb +0 -4
  71. data/spec/dummy/app/api/dummy_api.rb +0 -4
  72. data/spec/integration/api_spec.rb +0 -88
  73. data/spec/lib/action_spec.rb +0 -140
  74. data/spec/lib/base_spec.rb +0 -28
  75. data/spec/lib/request_spec.rb +0 -5
  76. data/spec/lib/resources_spec.rb +0 -78
  77. data/spec/lib/versioning_spec.rb +0 -104
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4d84a893f57e8bb3db9121ed7c77b5129b097332
4
- data.tar.gz: 0ac0b4a4164c8eff2324e3dc394b220110f1d7dc
3
+ metadata.gz: a888fbaa2ee9784c6cf75d17b1958e631037a408
4
+ data.tar.gz: b2043c2f5fe8104f559c8aaf355bb65241d31aab
5
5
  SHA512:
6
- metadata.gz: 8595c76223683aa134f22aeb9498569706ac4059f2b738e1420cb0603533aaae5327c93ee1ccfc7a2ee27b66dfde1323e38509da7de3c8a75d8976bea16e1455
7
- data.tar.gz: 1bd851d2603db7b9c0ab5e0d07e2f64620c10cd52b48212e714fa9b99082d1ab9900e29666d2692f6cd855bed2880b95e8aa0a246529306865e21b276bcfea60
6
+ metadata.gz: 0fd0da2467fc558b0d8515cf077d41751544bb60aa880552f132b01eacc00b36b14063f835475b10c60fb9fe4ec6a8ba92e074076efdef551074643fc5660670
7
+ data.tar.gz: 02fb36f863034d3829f538d115bb05b6fe6d2c7f71b8a5fd679513caf735c5f7145f12bf1845cf76167df88d4ef21dd3df22a5b7eaac103cb5536788cbf9b5f7
data/README.md CHANGED
@@ -1,81 +1,123 @@
1
- **This gem is under heavy development and is not ready to be used in product
2
- systems.**
1
+ # Warning
2
+ This gem is undergoing heavy development and is using [Semantic
3
+ Versioning](http://semver.org/), which means that **nothing in version 0.1.x is
4
+ stable**. Things are changing rapidly; the API will be stabilized in version
5
+ 1.0.0. In the meantime, any feedback and testing is welcome.
3
6
 
4
7
  # Cathode
5
- Cathode is a gem for Rails projects that generates API boilerplate for REST
6
- applications.
8
+ [![Gem Version](https://badge.fury.io/rb/cathode.svg)](http://badge.fury.io/rb/cathode)
9
+ [![Build Status](https://travis-ci.org/mobyinc/Cathode.svg)](https://travis-ci.org/mobyinc/Cathode)
10
+ [![Documentation](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/mobyinc/Cathode/master/frames)
11
+ [![Coverage Status](https://coveralls.io/repos/mobyinc/Cathode/badge.png)](https://coveralls.io/r/mobyinc/Cathode)
12
+ [![Code Climate](https://codeclimate.com/github/mobyinc/Cathode.png)](https://codeclimate.com/github/mobyinc/Cathode)
13
+ [![Dependency Status](https://gemnasium.com/mobyinc/Cathode.svg)](https://gemnasium.com/mobyinc/Cathode)
14
+
15
+ Cathode is a gem for Rails projects that provides a DSL for generating dynamic
16
+ API boilerplate (created at runtime, no generated files) for resourceful
17
+ applications. It comes with default behavior for CRUD operations and support for
18
+ ActiveModel associations, allowing you to greatly reduce the amount of
19
+ boilerplate you write. Cathode also supports custom actions and token
20
+ authorization. It has first-class support for versioning, so that nothing in
21
+ your API exists outside the context of a version.
7
22
 
8
23
  ## Features
9
- * Automatically generate endpoints (routes + controllers) for common RESTful
10
- actions:
11
- * Listing resources, optionally filtered and paginated
24
+ * Versioning of resources and actions
25
+ * Default behavior for CRUD operations
26
+ * Listing resources, optionally paginated
12
27
  * Finding a single resource
13
28
  * Creating a new resource
14
29
  * Updating a resource
15
30
  * Deleting a resource
16
- * Endpoints respond to JSON and output JSON by default (possibly add XML et al later on?)
17
- * Versioning of endpoints
18
- * Deprecation
19
- * Auto-documentation
31
+ * Endpoints respond to JSON and output JSON
32
+ * Custom (non-resourceful) actions on versions and resources
33
+ * Nested resources
34
+ * Singular resources
35
+ * Strong parameters
36
+ * API tokens
37
+
38
+ ## Roadmap
39
+ See [roadmap.md](https://github.com/mobyinc/Cathode/blob/master/roadmap.md).
40
+
41
+ ## Documentation
42
+ The full documentation is [here](http://rubydoc.info/github/mobyinc/Cathode/master/frames).
20
43
 
21
44
  ## Getting Started
22
- To generate the `api` directory and mount the engine:
45
+ ### Requirements
46
+ Cathode currently requires Rails 4.0 or higher. (We hope to support 3.2 in the
47
+ future.) Since ActiveSupport requires Ruby 1.9.3 or higher, so does Cathode. So
48
+ you need:
49
+
50
+ * Ruby >= 1.9.3
51
+ * Rails >= 4.0
52
+ * ActiveRecord (more ORMs will be supported in the future)
53
+
54
+ ### Installation
55
+ Install the gem:
56
+
57
+ ```bash
58
+ gem install cathode
59
+ ```
60
+
61
+ Mount the engine in your `config/routes.rb` file:
23
62
  ```ruby
24
- rails generate cool
63
+ mount Cathode::Engine => '/api' # use a namespace of your choice
25
64
  ```
26
65
 
27
- *Note: Your API can be defined using the `resource` method in your `api/api.rb` file,
28
- using separate files and the `Cathode::API` class, or a combination of both. For
29
- brevity, this document uses `resource` with the assumption that everything is in
30
- `api.rb`. See the section below, “Files & Naming Conventions,” for details on
31
- using the other method.*
66
+ ### Defining Your API
67
+ Cathode’s DSL provides an easy way to define your API’s versions and the
68
+ resources inside of them. Place your API files in `app/api` for Cathode to load
69
+ them automatically; alternatively, place them anywhere you wish and `require`
70
+ them manually. See [Files & Naming Conventions](#files_naming) for some different ideas for
71
+ organizing your files.
32
72
 
33
- In the simplest case, you can use only default actions:
34
73
  ```ruby
35
- resource :products, actions: [:index, :show, :search]
74
+ Cathode::Base.define do
75
+ # version 1.0.0 is implied if no version block is given
76
+ resources :products, actions: [:index, :show, :search]
77
+
78
+ version '1.0.1' do
79
+ resources :sales, actions: [:index, :show]
80
+ end
81
+ end
36
82
  ```
37
83
 
38
84
  Contrary to Rails’s `routes.rb` file–in which the default actions are included
39
85
  unless explicitly excluded–only actions that you specify are defined. Out of
40
- the box, the actions available are: `:index, :show, :create, :update, :delete, :search`.
86
+ the box, the actions available are: `:index, :show, :create, :update, :delete`.
41
87
 
42
- In this case, the following routes are created: `get api/products/`, `get
88
+ In version 1, the following endpoints are created: `get api/products/`, `get
43
89
  api/products/{id}`, and `get api/products/search`. By default, all products will
44
90
  be returned in the `index` call, and no permissions will be enforced on the
45
91
  `show` call. By default, the `search` call will take a `query` parameter and
46
92
  search for it using the `Product.title` field.
47
93
 
48
- ## Serialization
49
- Cathode doesn’t do any explicit serialization of resources when responding to
50
- requests. However, it does use `render: json`, which will invoke [ActiveModel
51
- serializers](https://github.com/rails-api/active_model_serializers) if you’ve
52
- defined them.
94
+ In version 1.0.1, endpoints are added for the `sales` endpoint: `get api/sales/`
95
+ and `get api/sales/{id}`. Those endpoints are not accessible in version 1.
96
+ However, because versions cascade, the actions on the `products` resource are all
97
+ accessible in version 1.0.1.
53
98
 
54
99
  ## Versioning
55
- Versioning is encouraged to prevent you from introducing breaking API changes.
56
- Your API’s versions should use [Semantic Versioning](http://semver.org/), which
57
- enables Cathode to deduce patches, non-breaking changes, and breaking changes. Cathode
58
- also makes it easy to deprecate all or part of any version of your API at any
59
- time.
100
+ Cathode has first-class support for API versioning and aims to make extending
101
+ versions and deprecating old functionality an easy process. You’re encouraged to
102
+ use [Semantic Versioning](http://semver.org/), and all Cathode version numbers
103
+ must be SemVer-compliant.
60
104
 
61
- If you define your resources without any versions, Cathode assumes it’s version
105
+ If you define resources without a version, Cathode assumes it’s version
62
106
  `1.0.0` of your API. When you’re ready to introduce changes, you can
63
107
  easily provision a new version:
64
108
 
65
109
  ```ruby
66
- resource :products, actions: [:index, :show, :search]
110
+ resources :products, actions: [:index, :show, :search]
67
111
 
68
112
  version 1.1 do
69
- resource :sales, actions: [:all]
113
+ resources :sales, actions: [:index]
70
114
  # the products resource is inherited from version 1
71
115
  end
72
116
 
73
117
  version 2 do
74
118
  # the products resource is inherited from version 1.1, except we explicitly
75
119
  # remove the `search` action
76
- resource :products
77
- remove_action :search
78
- end
120
+ remove_action :products, :search
79
121
  end
80
122
  ```
81
123
 
@@ -94,140 +136,319 @@ still be able to access the endpoint, but users of version `2` will not.
94
136
  Usually, changes like these would require the addition of new route namespaces
95
137
  and groups of controllers. With Cathode, these are all taken care of for you.
96
138
 
97
- ## Goodies on the `index` action
98
- By default `index` actions return all records in your resource’s default scope.
99
- However, common operations–like filtering, sorting, pagination, cursoring,
100
- etc–are also supported. For example, an application might make the following API
101
- call:
139
+ Note that, while these examples are using minor- and patch-level versions, you
140
+ are not required to do so. You can use only major versions (x.0.0), major and
141
+ minor versions (x.y.0), or all three (x.y.z).
142
+
143
+ ## Attributes Block (Strong Parameters)
144
+ Using Cathode’s `attribute` method, you can pass a block to any action that will
145
+ be evaluated by Rails’s [Strong Parameters](https://github.com/rails/strong_parameters).
146
+ The request params are available in the block’s context, so you can use the
147
+ strong params API just as you would in a controller.
148
+
149
+ The `create` and `update` actions require an attributes block be present if
150
+ you’re using the default action behavior. If you call `attributes` at the
151
+ resource level, Cathode uses it for both the `create` and `update` actions. Call
152
+ it at the action level to limit the strong params to just that action.
102
153
 
103
154
  ```ruby
104
- GET /api/products?query=flashlight&sort=desc&page=5
155
+ # use the same attribute whitelist for `create` and `update`
156
+ resources :products, actions: [:create, :update] do
157
+ attributes do
158
+ params.require(:product).permit(:title, :description, :cost)
159
+ end
160
+ end
161
+
162
+ # use different attribute whitelists for `create` and `update`
163
+ resources :products do
164
+ action :create do
165
+ attributes do
166
+ params.require(:product).permit(:title, :description, :cost)
167
+ end
168
+ end
169
+
170
+ action :update do
171
+ attributes do
172
+ params.require(:product).permit(:description, :cost)
173
+ end
174
+ end
175
+ end
176
+
177
+ # use strong params on a custom action
178
+ resources :products do
179
+ get :custom do
180
+ attributes do
181
+ params.require(:secret)
182
+ end
183
+ end
184
+ end
105
185
  ```
106
186
 
107
- To add support for this functionality in Cathode, just flip on querying, sorting,
108
- and paging in the `index` action:
187
+ Requests that raise a params exception (e.g., `ActionController::ParameterMissing`)
188
+ respond with `400 Bad Request`.
189
+
190
+ ## Serialization
191
+ Cathode doesn’t do any explicit serialization of resources when responding to
192
+ requests. However, it does use `render: json`, which will invoke [ActiveModel
193
+ serializers](https://github.com/rails-api/active_model_serializers) if you’ve
194
+ defined them.
195
+
196
+ ## API Tokens
197
+ Cathode comes with a token manager for controlling access to your API. By
198
+ default, tokens aren’t required to access any APIs you define. To require tokens
199
+ to access an API, use the `require_tokens` method:
109
200
 
110
201
  ```ruby
111
- resource :products do
112
- action :index do
113
- allows :querying, :sorting, :paging
114
- end
202
+ Cathode::Base.define do
203
+ require_tokens
204
+
205
+ resources :products, actions: [:index]
115
206
  end
116
207
  ```
117
208
 
118
- ## Params
119
- All actions have access to the request `params` hash.
209
+ Cathode doesn’t currently have a way to associate tokens with a particular user,
210
+ nor does it generate tokens automatically. Use Cathode’s `Token` model to generate
211
+ a new token:
120
212
 
121
- ## Custom action behavior
122
- Of course, you won’t want to use Cathode’s default actions in every scenario.
213
+ ```ruby
214
+ Cathode::Token.new
215
+ ```
216
+
217
+ Rails’s [`authenticate_or_request_with_http_token`](http://apidock.com/rails/ActionController/HttpAuthentication/Token/ControllerMethods/authenticate_or_request_with_http_token) scheme is used to validate tokens, so API consumers
218
+ must pass a valid `Authorization` header in the following form:
219
+
220
+ ```
221
+ Authorization: Token token={consumer’s token}
222
+ ```
223
+
224
+ ## CORS
225
+ Because Cathode is meant to be paired with a client-side Anode library,
226
+ cross-origin resource sharing (CORS) is enabled by adding
227
+ [Rack::Cors](https://github.com/cyu/rack-cors) to the application’s middleware
228
+ stack.
229
+
230
+ ## Singular Resources
231
+ Singular resources can be defined as well. If the resource is independent (i.e.,
232
+ not nested inside another resource), there is no defined default behavior for
233
+ the default actions; you must provide the behavior yourself. Nested singular
234
+ resources do have default behavior, however, which is described in the “Nested
235
+ Resources” section.
123
236
 
124
237
  ```ruby
125
- version 2.1 do
126
- resource :sales, actions: [:all] do
127
- action :show do
128
- change 'Checks user permission'
129
- access_filter do |current_user|
130
- resource.user == current_user
131
- end
238
+ Cathode::Base.define do
239
+ resource :product do
240
+ override_action :create do
241
+ # custom creation logic…
132
242
  end
133
243
  end
134
244
  end
135
245
  ```
136
246
 
137
- In this case, we need to prevent users from seeing sales that aren’t theirs.
138
- Happily, Cathode provides some neat shorthands for common scenarios like this.
139
- `access_filter` can be applied to any action, and should be a method that
140
- returns `true` if the user can access the resource and `false` if not. If the
141
- user cannot access the resource, a `401 Unauthorized` header will be sent.
247
+ ## Nested Resources
248
+ Resources can be nested arbitrarily deep inside other resources. When resources
249
+ are nested, Cathode uses your models’ associations to determine the default action
250
+ behavior. For example, with the following setup, all of a product’s sales would
251
+ be returned by the `api/products/{product_id}/sales` endpoint.
252
+
253
+ ```ruby
254
+ # app/api/api.rb
255
+ resources :products do
256
+ resources :sales, actions: [:index]
257
+ end
258
+
259
+ # app/models/product.rb
260
+ class Product < ActiveRecord::Base
261
+ has_many :sales
262
+ end
263
+
264
+ # app/models/sale.rb
265
+ class Sale < ActiveRecord::Base
266
+ belongs_to :product
267
+ end
268
+ ```
269
+
270
+ Here’s how ActiveModel associations map to resourceful endpoints on your API:
271
+
272
+ Product | Sale | Endpoints
273
+ ----------- | ------------ | --------------------
274
+ `has_many` | `belongs_to` | `products/{id}/sales`, `sales/{id}/product`
275
+ `has_one` | `belongs_to` | `products/{id}/sale`, `sales/{id}/product`
276
+ `habtm` | `habtm` | `products/{id}/sales`, `sales/{id}/products`
277
+
278
+ When there is exactly one model attached to the first resource (i.e., a
279
+ `has_one` association), you can use all the default actions except `:index`, so
280
+ `:show` will return the sale associated with the product, `:create` will create
281
+ a new sale and associate it with the product, `:update` will modify the sale,
282
+ and `:destroy` will delete it.
283
+
284
+ When there are many models attached to the resource (i.e., a `has_many` or
285
+ `has_and_belongs_to_many` association), you can use the `:index` and `:create`
286
+ actions. With a `has_many` on `Product` and a `belongs_to` on `Sale`, `:index`
287
+ returns all the sales associated with a product, and `:create` adds a new sale
288
+ associated with the product.
142
289
 
143
- In those cases where you want to do all of the logic yourself, and just want the
144
- endpoints that Cathode generates, you can override an action entirely:
290
+ For `belongs_to` associations, the `:show` action can be used to return the
291
+ associated parent model.
292
+
293
+ ## Replacing & Overriding Default Actions
294
+ Of course, you won’t want to use Cathode’s default actions in every scenario.
295
+ There are two ways to supply your own behavior:
296
+
297
+ * *Replacing* the action allows you to override what the action does but still
298
+ go through the normal Cathode request pipeline, so you’ll still be able to
299
+ use request helpers like `body` and `status`. Use the `replace` method at
300
+ the action level or the `replace_action` method at the resource level.
301
+ * *Overriding* the action allows you to remove the action from Cathode’s
302
+ request pipeline and requires you to do all the processing and rendering
303
+ logic yourself. Overriding an action causes it to be run in the context of
304
+ the *Rails request*, as if you were in a normal controller. You won’t have
305
+ access to any Cathode helpers like `body` and `status`, but you will have
306
+ access to anything a controller would, like `request` and `render`. Use the
307
+ `override` method at the action level or the `override_action` method at the
308
+ resource level. Prefer `replace` over `override`.
145
309
 
146
310
  ```ruby
147
- resource :sales, actions: [:all] do
148
- # show a random sale instead
311
+ resources :sales, actions: :all do
149
312
  override_action :show do
150
313
  render json: Sale.sample
151
314
  end
152
315
  end
153
316
  ```
154
317
 
155
- ## Deprecation
156
- With Cathode’s slick versioning, you’ll be implicitly deprecating junk in previous
157
- versions each time you introduce a new breaking change. When that happens, users
158
- of previous versions of your API should be told that a feature they’re using is
159
- deprecated. By default, Cathode will respond with a deprecation warning for those
160
- users. So users of version `1.1` of your API would receive the following
161
- response when making a call to `/api/products/search`:
318
+ ```ruby
319
+ resources :sales, actions: :all do
320
+ replace_action :show do
321
+ body Sale.sample
322
+ end
323
+ end
324
+ ```
325
+
326
+ ## Custom Actions & Non-Resourceful Endpoints
327
+ Your API is bound to need actions that don’t map to standard CRUD operations,
328
+ and Cathode makes this easy to do. You can define your own actions at both the
329
+ version level and the resource level by using the `get`, `post`, `put`, and
330
+ `destroy` methods.
331
+
332
+ Use the `body` and `status` methods to set the response body and HTTP status.
333
+ Both of these methods take either a single argument, or a block that will be
334
+ evaluated to determine the value. Choose whichever works best in your situation.
335
+
336
+ ```ruby
337
+ Cathode::Base.version 1 do
338
+ # sending a different body but the same status
339
+ get :status do
340
+ body { API.alive? ? 'Everything running smoothly' : 'Something has gone wrong' }
341
+ status :ok # not really necessary, since `:ok` is the default
342
+ end
162
343
 
163
- ```json
164
- {
165
- "products": [ array of the products found… ]
166
- "messages": [
167
- "The search endpoint is deprecated and is removed in version 2.0.0 of the
168
- API"]
169
- }
344
+ # sending different body/status depending on the result of a condition
345
+ resources :users do
346
+ get :password_reset_code do
347
+ user = User.find(params[:id])
348
+ if user.present?
349
+ body user.password_reset_code
350
+ status :ok
351
+ else
352
+ body "No user exists with id #{params[:id]}"
353
+ status :bad_request
354
+ end
355
+ end
356
+ end
357
+ end
170
358
  ```
171
359
 
360
+ Note that custom actions use the `replace` functionality described above by
361
+ default. You can use the override functionality instead by using the `override`
362
+ and `override_action` methods.
363
+
364
+ <a name="files_naming"></a>
172
365
  ## Files & Naming Conventions
173
- While this example has been putting all actions in a single file, in reality
174
- you’ll probably want to specify individual files for each resource. You can use
175
- the same versioning scheme in those files; as long as your resource APIs inherit
176
- from `Cathode::API`, Cathode will match up everything accordingly:
366
+ Cathode is agnostic to the way you organize your API files. Since you can either
367
+ define an API all at once using `Cathode::Base.define`, or each version
368
+ separately using `Cathode::Base.version`, you have many organization options.
369
+ Following are some examples.
370
+
371
+ ### Single file for API
372
+ If your API is tiny and you only have a handful of versions, you could place
373
+ them all in a single file:
177
374
 
178
375
  ```ruby
179
- # app/api/products_api.rb
376
+ # api/api.rb
180
377
 
181
- class ProductsAPI < Cathode::API
182
- actions: [:index, :show, :search] # version 1.0.0 is implied
378
+ Cathode::Base.define do
379
+ version 1 do
380
+ resources :products, actions: :all
381
+ end
183
382
 
184
383
  version 2 do
185
- remove_action :search
384
+ resources :products do
385
+ remove_action :delete
386
+ end
387
+
388
+ resources :sales, actions: [:index]
186
389
  end
187
390
  end
188
391
  ```
189
392
 
190
- Since nothing about products changed in version 1.2 (which only added sales,
191
- above), it will use the same actions as it did in version 1. In version 2,
192
- everything is carried over except for the `search` endpoint.
393
+ ### Files for each version
394
+ To break things apart a bit more, you can use a single file for each version.
395
+
396
+ ```
397
+ api/
398
+ v1.rb
399
+ v2.rb
400
+ ```
401
+ ```ruby
402
+ # api/v1.rb
403
+ Cathode::Base.version 1 do
404
+ resources :products, actions: [:index, :show, :delete]
405
+ end
193
406
 
194
- ## Documentation & Changelogs
195
- By sticking to Cathode’s versioning scheme, you tell it a lot about your API. So
196
- much, in fact, that we can use it to automatically generate documentation for
197
- all versions of your API and generate a changelog. Running `cool docs` will
198
- generate the documentation at `docs/api/1.0.0`, `docs/api/1.1.0`,
199
- `docs/api/2.0.0`, and `docs/api/2.1.0`. It will also automatically add a
200
- `Changelog`:
407
+ # api/v2.rb
408
+ Cathode::Base.version 2 do
409
+ resources :products do
410
+ remove_action :delete
411
+ end
201
412
 
202
- ```markdown
203
- # Changelog
204
- ## Version 2.1.0
205
- Checks user permission in `sales#show`.
413
+ resources :sales, actions: [:index]
414
+ end
415
+ ```
206
416
 
207
- ## Version 2.0.0
208
- Removes the `search` endpoint from `products` resource.
417
+ ### Folders for each version, files for each resource
418
+ Another method would be to have a folder for each version of your API, and files
419
+ for each resource:
209
420
 
210
- ## Version 1.1.0
211
- Adds `sales` resource with the following endpoints:
212
- - `GET /api/sales`
213
- - `GET /api/sales/{id}`
214
- - `GET /api/sales/search`
215
- - `POST /api/sales`
216
- - `PUT /api/sales/{id}`
217
- - `DELETE /api/sales/{id}`
421
+ ```
422
+ api/
423
+ v1/
424
+ products.rb
425
+ v2/
426
+ products.rb
427
+ sales.rb
428
+ ```
429
+ ```ruby
430
+ # api/v1/products.rb
431
+ Cathode::Base.version 1 do
432
+ resources :products, actions: [:index, :show, :delete]
433
+ end
218
434
 
219
- ## Version 1.0.0
220
- Initial release
435
+ # api/v2/products.rb
436
+ Cathode::Base.version 2 do
437
+ resources :products do
438
+ remove_action :delete
439
+ end
440
+ end
221
441
 
222
- Adds `products` resource with the following endpoints:
223
- - `GET /api/products`
224
- - `GET /api/products/{id}`
225
- - `GET /api/products/search`
226
- - `POST /api/products`
227
- - `PUT /api/products/{id}`
228
- - `DELETE /api/products/{id}`
442
+ # api/v2/sales.rb
443
+ Cathode::Base.version 2 do
444
+ resources :sales, actions: [:index]
445
+ end
229
446
  ```
230
447
 
448
+ ## License
449
+ Cathode is licensed under the MIT license. See
450
+ [LICENSE](https://github.com/mobyinc/Cathode/blob/master/LICENSE).
451
+
231
452
  ## Related Reading & Projects
232
453
  * [Versionist](https://github.com/bploetz/versionist)
233
454
  * [Existing Rails API Solutions Suck](http://joshsymonds.com/blog/2013/02/22/existing-rails-api-solutions-suck/])