cathode 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +236 -0
  4. data/Rakefile +26 -0
  5. data/app/assets/javascripts/cathode/application.js +13 -0
  6. data/app/assets/stylesheets/cathode/application.css +13 -0
  7. data/app/controllers/cathode/base_controller.rb +47 -0
  8. data/app/helpers/cathode/application_helper.rb +4 -0
  9. data/app/views/layouts/cathode/application.html.erb +14 -0
  10. data/config/routes.rb +2 -0
  11. data/lib/cathode.rb +14 -0
  12. data/lib/cathode/_version.rb +3 -0
  13. data/lib/cathode/action.rb +82 -0
  14. data/lib/cathode/base.rb +28 -0
  15. data/lib/cathode/engine.rb +5 -0
  16. data/lib/cathode/exceptions.rb +3 -0
  17. data/lib/cathode/request.rb +15 -0
  18. data/lib/cathode/resource.rb +40 -0
  19. data/lib/cathode/version.rb +49 -0
  20. data/lib/tasks/cathode_tasks.rake +4 -0
  21. data/spec/dummy/README.rdoc +28 -0
  22. data/spec/dummy/Rakefile +6 -0
  23. data/spec/dummy/app/api/dummy_api.rb +4 -0
  24. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  25. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  26. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  27. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  28. data/spec/dummy/app/models/product.rb +2 -0
  29. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  30. data/spec/dummy/bin/bundle +3 -0
  31. data/spec/dummy/bin/rails +4 -0
  32. data/spec/dummy/bin/rake +4 -0
  33. data/spec/dummy/config.ru +4 -0
  34. data/spec/dummy/config/application.rb +28 -0
  35. data/spec/dummy/config/boot.rb +5 -0
  36. data/spec/dummy/config/database.yml +25 -0
  37. data/spec/dummy/config/environment.rb +5 -0
  38. data/spec/dummy/config/environments/development.rb +29 -0
  39. data/spec/dummy/config/environments/production.rb +80 -0
  40. data/spec/dummy/config/environments/test.rb +36 -0
  41. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  42. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  43. data/spec/dummy/config/initializers/inflections.rb +16 -0
  44. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  45. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  46. data/spec/dummy/config/initializers/session_store.rb +3 -0
  47. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  48. data/spec/dummy/config/locales/en.yml +23 -0
  49. data/spec/dummy/config/routes.rb +3 -0
  50. data/spec/dummy/db/development.sqlite3 +0 -0
  51. data/spec/dummy/db/migrate/20140404222551_create_products.rb +10 -0
  52. data/spec/dummy/db/schema.rb +23 -0
  53. data/spec/dummy/db/test.sqlite3 +0 -0
  54. data/spec/dummy/log/development.log +312 -0
  55. data/spec/dummy/log/test.log +12708 -0
  56. data/spec/dummy/public/404.html +58 -0
  57. data/spec/dummy/public/422.html +58 -0
  58. data/spec/dummy/public/500.html +57 -0
  59. data/spec/dummy/public/favicon.ico +0 -0
  60. data/spec/dummy/spec/factories/products.rb +8 -0
  61. data/spec/integration/api_spec.rb +88 -0
  62. data/spec/lib/action_spec.rb +140 -0
  63. data/spec/lib/base_spec.rb +28 -0
  64. data/spec/lib/request_spec.rb +5 -0
  65. data/spec/lib/resources_spec.rb +78 -0
  66. data/spec/lib/versioning_spec.rb +104 -0
  67. data/spec/spec_helper.rb +30 -0
  68. data/spec/support/factories/products.rb +3 -0
  69. data/spec/support/helpers.rb +9 -0
  70. metadata +259 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4d84a893f57e8bb3db9121ed7c77b5129b097332
4
+ data.tar.gz: 0ac0b4a4164c8eff2324e3dc394b220110f1d7dc
5
+ SHA512:
6
+ metadata.gz: 8595c76223683aa134f22aeb9498569706ac4059f2b738e1420cb0603533aaae5327c93ee1ccfc7a2ee27b66dfde1323e38509da7de3c8a75d8976bea16e1455
7
+ data.tar.gz: 1bd851d2603db7b9c0ab5e0d07e2f64620c10cd52b48212e714fa9b99082d1ab9900e29666d2692f6cd855bed2880b95e8aa0a246529306865e21b276bcfea60
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 Moby, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,236 @@
1
+ **This gem is under heavy development and is not ready to be used in product
2
+ systems.**
3
+
4
+ # Cathode
5
+ Cathode is a gem for Rails projects that generates API boilerplate for REST
6
+ applications.
7
+
8
+ ## Features
9
+ * Automatically generate endpoints (routes + controllers) for common RESTful
10
+ actions:
11
+ * Listing resources, optionally filtered and paginated
12
+ * Finding a single resource
13
+ * Creating a new resource
14
+ * Updating a resource
15
+ * 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
20
+
21
+ ## Getting Started
22
+ To generate the `api` directory and mount the engine:
23
+ ```ruby
24
+ rails generate cool
25
+ ```
26
+
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.*
32
+
33
+ In the simplest case, you can use only default actions:
34
+ ```ruby
35
+ resource :products, actions: [:index, :show, :search]
36
+ ```
37
+
38
+ Contrary to Rails’s `routes.rb` file–in which the default actions are included
39
+ 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`.
41
+
42
+ In this case, the following routes are created: `get api/products/`, `get
43
+ api/products/{id}`, and `get api/products/search`. By default, all products will
44
+ be returned in the `index` call, and no permissions will be enforced on the
45
+ `show` call. By default, the `search` call will take a `query` parameter and
46
+ search for it using the `Product.title` field.
47
+
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.
53
+
54
+ ## 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.
60
+
61
+ If you define your resources without any versions, Cathode assumes it’s version
62
+ `1.0.0` of your API. When you’re ready to introduce changes, you can
63
+ easily provision a new version:
64
+
65
+ ```ruby
66
+ resource :products, actions: [:index, :show, :search]
67
+
68
+ version 1.1 do
69
+ resource :sales, actions: [:all]
70
+ # the products resource is inherited from version 1
71
+ end
72
+
73
+ version 2 do
74
+ # the products resource is inherited from version 1.1, except we explicitly
75
+ # remove the `search` action
76
+ resource :products
77
+ remove_action :search
78
+ end
79
+ end
80
+ ```
81
+
82
+ Versions inherit from their ancestors, so anything not explicitly changed in
83
+ version `2` will be carried over from version `1.1`, which in turn inherits from
84
+ version `1`.
85
+
86
+ In version `1.1`, we’ve added a new `sales` endpoint. This doesn’t introduce a
87
+ breaking change, as users of version `1.0` can still use it without knowing
88
+ about the `sales` endpoint.
89
+
90
+ However, in version `2` we *do* introduce a breaking change–namely, that
91
+ products can no longer be searched. Users of versions `1.x` of our API will
92
+ still be able to access the endpoint, but users of version `2` will not.
93
+
94
+ Usually, changes like these would require the addition of new route namespaces
95
+ and groups of controllers. With Cathode, these are all taken care of for you.
96
+
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:
102
+
103
+ ```ruby
104
+ GET /api/products?query=flashlight&sort=desc&page=5
105
+ ```
106
+
107
+ To add support for this functionality in Cathode, just flip on querying, sorting,
108
+ and paging in the `index` action:
109
+
110
+ ```ruby
111
+ resource :products do
112
+ action :index do
113
+ allows :querying, :sorting, :paging
114
+ end
115
+ end
116
+ ```
117
+
118
+ ## Params
119
+ All actions have access to the request `params` hash.
120
+
121
+ ## Custom action behavior
122
+ Of course, you won’t want to use Cathode’s default actions in every scenario.
123
+
124
+ ```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
132
+ end
133
+ end
134
+ end
135
+ ```
136
+
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.
142
+
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:
145
+
146
+ ```ruby
147
+ resource :sales, actions: [:all] do
148
+ # show a random sale instead
149
+ override_action :show do
150
+ render json: Sale.sample
151
+ end
152
+ end
153
+ ```
154
+
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`:
162
+
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
+ }
170
+ ```
171
+
172
+ ## 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:
177
+
178
+ ```ruby
179
+ # app/api/products_api.rb
180
+
181
+ class ProductsAPI < Cathode::API
182
+ actions: [:index, :show, :search] # version 1.0.0 is implied
183
+
184
+ version 2 do
185
+ remove_action :search
186
+ end
187
+ end
188
+ ```
189
+
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.
193
+
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`:
201
+
202
+ ```markdown
203
+ # Changelog
204
+ ## Version 2.1.0
205
+ Checks user permission in `sales#show`.
206
+
207
+ ## Version 2.0.0
208
+ Removes the `search` endpoint from `products` resource.
209
+
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}`
218
+
219
+ ## Version 1.0.0
220
+ Initial release
221
+
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}`
229
+ ```
230
+
231
+ ## Related Reading & Projects
232
+ * [Versionist](https://github.com/bploetz/versionist)
233
+ * [Existing Rails API Solutions Suck](http://joshsymonds.com/blog/2013/02/22/existing-rails-api-solutions-suck/])
234
+ * [Grape](https://github.com/intridea/grape)
235
+ * [Roar](https://github.com/apotonick/roar)
236
+ * [Rails API](https://github.com/rails-api/rails-api)
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Cathode'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ Bundler::GemHelper.install_tasks
21
+
22
+ require 'rspec/core'
23
+ require 'rspec/core/rake_task'
24
+ desc "Run all specs in spec directory (excluding plugin specs)"
25
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
26
+ task :default => :spec
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,47 @@
1
+ class Cathode::BaseController < ActionController::Base
2
+ before_action :process_access_filter
3
+
4
+ def index
5
+ render json: resources.load
6
+ end
7
+
8
+ def create
9
+ render json: model.create(resource_params)
10
+ end
11
+
12
+ def destroy
13
+ resource.destroy
14
+ head :ok
15
+ end
16
+
17
+ def show
18
+ make_request(request)
19
+ end
20
+
21
+ private
22
+
23
+ def make_request(http_request)
24
+ request = Cathode::Request.new(http_request, params)
25
+ render json: request.body, status: request.status
26
+ end
27
+
28
+ def resources
29
+ model.all
30
+ end
31
+
32
+ def resource
33
+ model.find params[:id]
34
+ end
35
+
36
+ def resource_params
37
+ params[controller_name.singularize]
38
+ end
39
+
40
+ def model
41
+ controller_name.classify.constantize
42
+ end
43
+
44
+ def process_access_filter
45
+
46
+ end
47
+ end
@@ -0,0 +1,4 @@
1
+ module Cathode
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Cathode</title>
5
+ <%= stylesheet_link_tag "cathode/application", media: "all" %>
6
+ <%= javascript_include_tag "cathode/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Cathode::Engine.routes.draw do
2
+ end
data/lib/cathode.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'cathode/engine'
2
+ require 'cathode/base'
3
+ require 'cathode/exceptions'
4
+
5
+ module Cathode
6
+ class Engine < ::Rails::Engine
7
+ config.generators do |g|
8
+ g.test_framework :rspec, :fixture => false
9
+ g.fixture_replacement :factory_girl, :dir => 'spec/factories'
10
+ g.assets false
11
+ g.helper false
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Cathode
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,82 @@
1
+ module Cathode
2
+ class Action
3
+ attr_reader :action_access_filter,
4
+ :name,
5
+ :resource
6
+
7
+
8
+ def self.create(action, resource, &block)
9
+ klass = case action
10
+ when :index
11
+ IndexAction
12
+ when :show
13
+ ShowAction
14
+ when :create
15
+ CreateAction
16
+ when :update
17
+ UpdateAction
18
+ when :destroy
19
+ DestroyAction
20
+ end
21
+ klass.new(action, resource, &block)
22
+ end
23
+
24
+ def initialize(action, resource, &block)
25
+ @name, @resource = action, resource
26
+
27
+ self.instance_eval &block if block_given?
28
+ end
29
+
30
+ def perform(params)
31
+ if action_access_filter && !action_access_filter.call
32
+ return { status: :unauthorized }
33
+ end
34
+
35
+ body = perform_action params
36
+
37
+ return { body: body, status: :ok }
38
+ end
39
+
40
+ private
41
+
42
+ def model
43
+ resource.to_s.camelize.singularize.constantize
44
+ end
45
+
46
+ def access_filter(&filter)
47
+ @action_access_filter = filter
48
+ end
49
+ end
50
+
51
+ class IndexAction < Action
52
+ def perform_action(params)
53
+ model.all
54
+ end
55
+ end
56
+
57
+ class ShowAction < Action
58
+ def perform_action(params)
59
+ model.find params[:id]
60
+ end
61
+ end
62
+
63
+ class CreateAction < Action
64
+ def perform_action(params)
65
+ model.create params
66
+ end
67
+ end
68
+
69
+ class UpdateAction < Action
70
+ def perform_action(params)
71
+ record = model.find(params[:id])
72
+ record.update params
73
+ record.reload
74
+ end
75
+ end
76
+
77
+ class DestroyAction < Action
78
+ def perform_action(params)
79
+ model.find(params[:id]).destroy
80
+ end
81
+ end
82
+ end