gearhead 0.1.0

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 (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