gearhead 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +220 -0
  4. data/Rakefile +32 -0
  5. data/app/controllers/gearhead/application_controller.rb +5 -0
  6. data/app/controllers/gearhead/gears_controller.rb +98 -0
  7. data/config/routes.rb +9 -0
  8. data/lib/gearhead.rb +44 -0
  9. data/lib/gearhead/actions/create.rb +30 -0
  10. data/lib/gearhead/actions/index.rb +88 -0
  11. data/lib/gearhead/actions/show.rb +22 -0
  12. data/lib/gearhead/actions/update.rb +25 -0
  13. data/lib/gearhead/configuration.rb +68 -0
  14. data/lib/gearhead/engine.rb +10 -0
  15. data/lib/gearhead/extensions/actions.rb +29 -0
  16. data/lib/gearhead/extensions/associations.rb +13 -0
  17. data/lib/gearhead/extensions/attributes.rb +20 -0
  18. data/lib/gearhead/extensions/custom_actions.rb +36 -0
  19. data/lib/gearhead/extensions/enabled_actions.rb +9 -0
  20. data/lib/gearhead/extensions/finder.rb +14 -0
  21. data/lib/gearhead/extensions/pagination.rb +26 -0
  22. data/lib/gearhead/extensions/permitted_params.rb +36 -0
  23. data/lib/gearhead/extensions/querying.rb +13 -0
  24. data/lib/gearhead/extensions/scoping.rb +18 -0
  25. data/lib/gearhead/extensions/serialization.rb +21 -0
  26. data/lib/gearhead/gear.rb +56 -0
  27. data/lib/gearhead/gear_lookup.rb +35 -0
  28. data/lib/gearhead/gearbox.rb +83 -0
  29. data/lib/gearhead/paginators/lookup.rb +27 -0
  30. data/lib/gearhead/paginators/paginator.rb +48 -0
  31. data/lib/gearhead/paginators/pagy_paginator.rb +17 -0
  32. data/lib/gearhead/paginators/will_paginate_paginator.rb +13 -0
  33. data/lib/gearhead/params_builder.rb +14 -0
  34. data/lib/gearhead/registry.rb +26 -0
  35. data/lib/gearhead/resource_finder.rb +20 -0
  36. data/lib/gearhead/router.rb +51 -0
  37. data/lib/gearhead/serializers/active_model_serializers/invalid_request_serializer.rb +7 -0
  38. data/lib/gearhead/serializers/active_model_serializers/invalid_resource_serializer.rb +7 -0
  39. data/lib/gearhead/serializers/active_model_serializers/resource_serializer.rb +16 -0
  40. data/lib/gearhead/serializers/fast_jsonapi/invalid_request_serializer.rb +7 -0
  41. data/lib/gearhead/serializers/fast_jsonapi/invalid_resource_serializer.rb +7 -0
  42. data/lib/gearhead/serializers/fast_jsonapi/resource_serializer.rb +16 -0
  43. data/lib/gearhead/serializers/invalid_request_serializer.rb +24 -0
  44. data/lib/gearhead/serializers/invalid_resource_serializer.rb +59 -0
  45. data/lib/gearhead/serializers/lookup.rb +27 -0
  46. data/lib/gearhead/version.rb +3 -0
  47. data/lib/generators/gearhead/gear/gear_generator.rb +14 -0
  48. data/lib/generators/gearhead/gear/templates/gear.rb.erb +3 -0
  49. data/lib/generators/gearhead/install/install_generator.rb +17 -0
  50. data/lib/generators/gearhead/install/templates/gearhead.rb.erb +61 -0
  51. metadata +266 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b2840550719a44e41f271d9dd420ac4c848f5a914214e1125ccae0db63f0e460
4
+ data.tar.gz: 4efb4442da94ebdd53b4d55d865866cdf516315906ed7df74c090f46b8071db0
5
+ SHA512:
6
+ metadata.gz: 5c0c1f1fa96578021c7e52653f689442a35f840f5e883f69ac0fc0f1a07fcb8fdc7efbd9b9857a30ea457c59860cb38806678678d67c088a7b2d3c5dcac00e37
7
+ data.tar.gz: bdc1d6d0d3b5b93f8fbf7c727a80141720036960d3d162475e04b3ebbbdd29e79e5b0a2f74fe05550f06608fe0c797fc0744465e1a81ddcc4243a93c494c65f6
@@ -0,0 +1,20 @@
1
+ Copyright 2020 Josh
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.
@@ -0,0 +1,220 @@
1
+ # Gearhead test application
2
+
3
+ Gearhead turns your database into a RESTful API. It's like if ActiveAdmin and Grape had a baby.
4
+
5
+ ## Installation
6
+
7
+ Gearhead is a Rails engine, for now.
8
+
9
+ ```ruby
10
+ gem 'gearhead'
11
+ ```
12
+
13
+ ## Setting up Gearhead
14
+
15
+ After installing Gearhead, you need to run the generator.
16
+
17
+ ```
18
+ rails g gearhead:install
19
+ ```
20
+
21
+ which generates:
22
+
23
+ ```
24
+ config/initializers/gearhead.rb
25
+ app/gears/.keep
26
+ ```
27
+
28
+ and modifies your routes:
29
+
30
+ ```ruby
31
+ Gearbox.routes(self)
32
+ ```
33
+
34
+ ## Configuration
35
+
36
+ You can change most of these on the Gear-level, but we have opinions.
37
+
38
+ ```ruby
39
+ Gearhead.setup do |config|
40
+ # == Routing
41
+ #
42
+ # The endpoint in which to mount gearhead on.
43
+ # Default is "/gearhead" — must start with a slash, and have no trailing slash
44
+ # config.endpoint = "/api/v314"
45
+ #
46
+ # The default actions a Gear can respond to
47
+ # Default is [:index, :create, :show, :update, :destroy]
48
+ # config.actions = [:index, :show]
49
+ #
50
+
51
+ # == Querying
52
+ #
53
+ # The default scope of a Gear will be the default scope of the resource.
54
+ # Default is nil
55
+ # config.scope = :visible
56
+ #
57
+
58
+ # == Params
59
+ #
60
+ # Ignored params for creating/updating records
61
+ # Default is [:id, :created_at, :updated_at]
62
+ # config.ignored_params = [:id, :created_at, :updated_at]
63
+ #
64
+
65
+ # == Automount
66
+ #
67
+ # Change this to true if you want to automatically mount all your resources.
68
+ # config.automount.enabled = false
69
+ #
70
+ # Automatically mount most of your resources
71
+ # config.automount.resources = ['User', 'Post', 'Comment']
72
+ #
73
+ # Don't automatically mount these resources
74
+ # config.automount.excluded = ['User', 'Post']
75
+ #
76
+
77
+ # == Serialization
78
+ #
79
+ # Change the default serializer
80
+ # Currently supports FastJSONAPI (:fast_jsonapi) and ActiveModelSerializers (:active_model_serializers)
81
+ # Default is :fast_jsonapi
82
+ # config.serialization.adapter = :fast_jsonapi
83
+ #
84
+
85
+ # == Pagination
86
+ #
87
+ # Change the default paginator
88
+ # Currently supports :pagy and :will_paginate
89
+ # Default is :pagy
90
+ # config.pagination.adapter = :pagy
91
+ #
92
+
93
+ # == Controller
94
+ #
95
+ # Base class for the Gearhead controller
96
+ # Default is 'ApplicationController'
97
+ # config.base_controller = 'ApplicationController'
98
+ #
99
+ end
100
+ ```
101
+
102
+ ## Exposing your data
103
+
104
+ Gearhead does some metaprogramming to generate standard RESTful endpoints for your defined Rails models. They do this with Gears.
105
+
106
+ A Gear is responsible for configuring the controller, including resource retrieval, serialization, and response.
107
+
108
+ By default, Gearhead doesn't automatically mount all your resources. You can change this if you want in your initializer.
109
+
110
+ To create a Gear, drop it in `/app/gears` and register it with `Gearhead.register Model, options`. Prefer a constant over a string.
111
+
112
+ ```ruby
113
+ Gearhead.register Post; end
114
+ ```
115
+
116
+ will generate `/gearhead/posts` — or whatever `Model.model_name.route_key` is. Change the path:
117
+
118
+ ```ruby
119
+ Gearhead.register Post, path: "postz"; end
120
+ ```
121
+
122
+ **A note about automount**: If you have automount enabled and call `/posts`, and you have explicitly defined this Gear,
123
+ the defined Gear will take precedence. If you define a custom path such as `postz` and navigate to `/posts` with automount
124
+ enabled, the defined Gear will still take precedence.
125
+
126
+ ## Customizing Gears
127
+
128
+ ### Actions
129
+
130
+ Change what CRUD actions the Gear can respond to:
131
+
132
+ ```ruby
133
+ Gearhead.register Post do
134
+ actions :index, :show
135
+ # or
136
+ actions except: [:create, :update, :delete]
137
+ end
138
+ ```
139
+
140
+ Want to expose `/gearhead/posts/favorites`?
141
+
142
+ ```ruby
143
+ Gearhead.register Post do
144
+ collection_action :favorites, via: :get do
145
+ # do something
146
+ end
147
+ end
148
+ ```
149
+
150
+ Expose `GET /gearhead/posts/:id/report` and `POST /gearhead/posts/:id/report`:
151
+
152
+ ```ruby
153
+ Gearhead.register Post do
154
+ member_action :report, via: [:get, :post] do
155
+ if request.get?
156
+ resource.reports.all
157
+ elsif request.post?
158
+ resource.reports.create!
159
+ end
160
+ end
161
+ end
162
+ ```
163
+
164
+ ### Handling pagination
165
+
166
+ ```ruby
167
+ Gearhead.register Post do
168
+ per_page 5
169
+ # or disable it
170
+ pagination false
171
+ end
172
+ ```
173
+
174
+ ### Serializing your objects
175
+
176
+ Just define what attributes you want exposed:
177
+
178
+ ```ruby
179
+ Gearhead.register Post do
180
+ attributes :id, :name
181
+ end
182
+ ```
183
+
184
+ ### Finding resources
185
+
186
+ Change your finder:
187
+
188
+ ```ruby
189
+ Gearhead.register Post do
190
+ finder do
191
+ resource_class.find_by(token: params[:resource_id])
192
+ end
193
+ end
194
+ ```
195
+
196
+ ### Permitting params
197
+
198
+ Works just like the params you're used to:
199
+
200
+ ```ruby
201
+ Gearhead.register Post do
202
+ permit_params :user_id, :content
203
+ # or do it for a certain subset of actions
204
+ permit_params :user_id, :content, only: :create
205
+ permit_params :content, only: :update
206
+ end
207
+ ```
208
+
209
+ ## TODO
210
+
211
+ * Better param handling
212
+ * Handling params for member actions and collection actions
213
+
214
+ ## Contributing
215
+
216
+ Do things.
217
+
218
+ ## License
219
+
220
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,32 @@
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 = 'Gearhead'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1,5 @@
1
+ module Gearhead
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,98 @@
1
+ # todo:
2
+ if defined?(ActiveModelSerializers)
3
+ ActiveModelSerializers.config.serializer_lookup_enabled = false
4
+ end
5
+
6
+ module Gearhead
7
+ class GearsController < ::Gearhead.config.base_controller.constantize
8
+ before_action :find_gear!
9
+ before_action :ensure_action_enabled!, only: [:index, :create, :show, :update, :destroy]
10
+ before_action :find_resource!, except: [:index, :create, :collection_action]
11
+
12
+ def index
13
+ render json: Gearhead::Actions::Index.build(@gear, request), adapter: false
14
+ end
15
+
16
+ def create
17
+ @resource = Gearhead::Actions::Create.build(@gear, request)
18
+ if @resource.save
19
+ render json: @gear.serializer_class.new(@resource)
20
+ else
21
+ render json: { errors: @resource.errors }
22
+ end
23
+ end
24
+
25
+ def show
26
+ @resource = Gearhead::Actions::Show.build(@gear, request, resource: @resource)
27
+ render json: @gear.serializer_class.new(@resource)
28
+ end
29
+
30
+ def update
31
+ @resource = Gearhead::Actions::Update.build(@gear, request, resource: @resource)
32
+ if @resource.save
33
+ render json: @gear.serializer_class.new(@resource)
34
+ else
35
+ render json: { errors: @resource.errors }
36
+ end
37
+ end
38
+
39
+ def destroy
40
+ if @resource.destroy
41
+ render json: @gear.serializer_class.new(resource)
42
+ else
43
+ render json: { errors: resource.errors }
44
+ end
45
+ end
46
+
47
+ def member_action
48
+ action = @gear._gear_member_actions[params[:member_action].to_sym]
49
+ if action && action.verbs.include?(request.request_method.to_sym.downcase)
50
+ return render json: instance_exec(&action.block)
51
+ end
52
+ end
53
+
54
+ def collection_action
55
+ action = @gear._gear_collection_actions[params[:collection_action]]
56
+ if action && action.verbs.include?(request.request_method.to_sym.downcase)
57
+ return render json: instance_exec(&action.block)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def find_gear!
64
+ @gear = Gearhead.gear_for(request)
65
+ return @gear if @gear
66
+
67
+ if @gear.nil?
68
+ error!("Can't find or infer gear.", 404)
69
+ end
70
+ if @gear == false
71
+ error!("Gear already mounted somewhere else.", 500)
72
+ end
73
+ end
74
+
75
+ # remember that request method is from rack so GET/POST/etc.
76
+ def check_collection_actions!
77
+ action = @gear._gear_collection_actions[request.request_method][params[:resource_id].to_sym]
78
+ if action
79
+ return render json: instance_exec(&action)
80
+ end
81
+ end
82
+
83
+ def find_resource!
84
+ @resource = ::Gearhead::ResourceFinder.for(@gear, params)
85
+ error!("#{@gear.resource.name} not found for #{@gear._gear_param_key} #{params[:resource_id]}") if @resource.nil?
86
+ end
87
+
88
+ def ensure_action_enabled!
89
+ unless @gear.action_enabled?(action_name.to_sym)
90
+ error!("Action not allowed for #{@gear.resource.name}##{action_name}", 405)
91
+ end
92
+ end
93
+
94
+ def error!(msg, code = 400)
95
+ render json: Serializers::Lookup.for(:invalid_request).new(request, msg, code), serializer: nil
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,9 @@
1
+ Gearhead::Engine.routes.draw do
2
+ scope module: :gearhead do
3
+ # resources :gears, path: ':resource_class', param: :resource_id do
4
+ # member do
5
+ # match ':member_action' => 'gears#member_action', via: :all
6
+ # end
7
+ # end
8
+ end
9
+ end
@@ -0,0 +1,44 @@
1
+ require "zeitwerk"
2
+
3
+ loader = Zeitwerk::Loader.for_gem
4
+ loader.setup
5
+
6
+ module Gearhead
7
+ def self.gearbox
8
+ @gearbox ||= Gearbox.new
9
+ end
10
+
11
+ def self.routes(rails_router)
12
+ gearbox.routes(rails_router)
13
+ end
14
+
15
+ def self.config
16
+ @config ||= Configuration.new
17
+ end
18
+
19
+ def self.setup
20
+ gearbox.setup!
21
+ yield config
22
+ gearbox.prepare!
23
+ end
24
+
25
+ def self.register(resource_class, options = {}, &block)
26
+ gear = Gear.new(resource_class, options)
27
+ gear.instance_exec(&block)
28
+ registry.register(gear)
29
+ end
30
+
31
+ def self.registry
32
+ @registry ||= Registry.new
33
+ end
34
+
35
+ def self.gear_for(request)
36
+ GearLookup.for(request)
37
+ end
38
+ end
39
+
40
+ # things that don't support autoloading
41
+
42
+ require 'gearhead/engine'
43
+ require 'pagy'
44
+ require 'jsonapi/serializer'
@@ -0,0 +1,30 @@
1
+ module Gearhead
2
+ module Actions
3
+ class Create
4
+ delegate_missing_to :@request
5
+
6
+ def self.build(gear, request)
7
+ new(gear, request).build
8
+ end
9
+
10
+ attr_reader :resource, :gear, :request
11
+ def initialize(gear, request)
12
+ @gear = gear
13
+ @request = request
14
+ @resource = new_resource
15
+ end
16
+
17
+ def build
18
+ params = ParamsBuilder.new(self).for(:create)
19
+ @resource.assign_attributes(params)
20
+ @resource
21
+ end
22
+
23
+ private
24
+
25
+ def new_resource
26
+ @gear.resource.new
27
+ end
28
+ end
29
+ end
30
+ end