cathode 0.0.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.
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