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,21 @@
1
+ module Gearhead
2
+ module Extensions
3
+ module Serialization
4
+ def self.included(klass)
5
+ end
6
+
7
+ def serializer(klass)
8
+ @_gear_serializer = klass
9
+ end
10
+
11
+ def serializer_class
12
+ real_serializer = Serializers::Lookup.for(:resource)
13
+ if real_serializer.respond_to?(:for)
14
+ real_serializer.for(self)
15
+ else
16
+ real_serializer
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,56 @@
1
+ module Gearhead
2
+ module Settings
3
+ def self.included(klass)
4
+ klass.extend ClassMethods
5
+ klass.class_attribute :settings, default: {}
6
+ end
7
+
8
+ def define_gear_setting(name, default = nil)
9
+ instance_variable_set("@_gear_#{name}", default)
10
+ ivar = "@_gear_#{name}"
11
+ self.class.define_method :"_gear_#{name}" do
12
+ return instance_variable_get(ivar) if instance_variable_defined?(ivar)
13
+ instance_variable_get("@_gear_#{name}")
14
+ end
15
+ self.class.define_method :"_gear_#{name}=" do |value|
16
+ instance_variable_set("@_gear_#{name}", value)
17
+ end
18
+ end
19
+
20
+ module ClassMethods
21
+ def define_gear_setting(name, default = nil)
22
+ settings[name] = default
23
+ end
24
+ end
25
+ end
26
+ class Gear
27
+ include Settings
28
+
29
+ include Extensions::Actions
30
+ include Extensions::Attributes
31
+ include Extensions::Associations
32
+ include Extensions::CustomActions
33
+ include Extensions::Finder
34
+ include Extensions::Pagination
35
+ include Extensions::PermittedParams
36
+ include Extensions::Querying
37
+ include Extensions::Scoping
38
+ include Extensions::Serialization
39
+
40
+ attr_reader :resource
41
+
42
+ def initialize(resource_class, options = {})
43
+ @resource_class = resource_class
44
+ @resource = @resource_class.to_s.constantize
45
+ @options = options
46
+
47
+ self.class.settings.each do |k,v|
48
+ define_gear_setting k, v
49
+ end
50
+ end
51
+
52
+ def path
53
+ @options[:path] || @resource.model_name.route_key
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,35 @@
1
+ module Gearhead
2
+ class GearLookup
3
+ def self.for(request)
4
+ new(request).gear
5
+ end
6
+
7
+ attr_reader :request
8
+ def initialize(request)
9
+ @request = request
10
+ end
11
+
12
+ # if the resource is already mounted, we don't want to expose it twice. return false
13
+ # return nil if we don't know the class
14
+ def gear
15
+ registered_gear = Gearhead.registry.find(request.params[:resource_class])
16
+ return registered_gear if registered_gear
17
+ return nil if inferred_resource_class.nil?
18
+ return false if Gearhead.registry.for_resource(inferred_resource_class.name)
19
+
20
+ default_gear
21
+ end
22
+
23
+ private
24
+
25
+ def default_gear
26
+ return nil unless Gearhead.config.automount.enabled?
27
+
28
+ Gear.new(inferred_resource_class)
29
+ end
30
+
31
+ def inferred_resource_class
32
+ request.params[:resource_class].classify.safe_constantize
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,83 @@
1
+ module Gearhead
2
+ class Gearbox
3
+ def initialize
4
+ end
5
+
6
+ def setup!
7
+ # nothing
8
+ end
9
+
10
+ def prepare!
11
+ ActiveSupport::Dependencies.autoload_paths -= load_paths
12
+ Rails.application.config.eager_load_paths -= load_paths
13
+ attach_reloader
14
+ end
15
+
16
+ # Event that gets triggered on load of Active Admin
17
+ BeforeLoadEvent = "gearbox.application.before_load".freeze
18
+ AfterLoadEvent = "gearbox.application.after_load".freeze
19
+
20
+ def load!
21
+ unless loaded?
22
+ ActiveSupport::Notifications.publish BeforeLoadEvent, self # before_load hook
23
+ files.each { |file| load file } # load files
24
+ ActiveSupport::Notifications.publish AfterLoadEvent, self # after_load hook
25
+ @@loaded = true
26
+ end
27
+ end
28
+
29
+ def files
30
+ load_paths.flatten.compact.uniq.flat_map { |path| Dir["#{path}/**/*.rb"] }.sort
31
+ end
32
+
33
+ def load_paths
34
+ [File.expand_path("app/gears", Rails.root)]
35
+ end
36
+
37
+ def loaded?
38
+ @@loaded ||= false
39
+ end
40
+
41
+ def unload!
42
+ @@loaded = false
43
+ end
44
+
45
+ def routes(rails_router)
46
+ load!
47
+ Router.new(router: rails_router).apply
48
+ end
49
+
50
+ private
51
+
52
+ def attach_reloader
53
+ Rails.application.config.after_initialize do |app|
54
+ unload_gearhead = -> { Gearhead.gearbox.unload! }
55
+
56
+ if app.config.reload_classes_only_on_change
57
+ ActiveSupport::Reloader.to_prepare(prepend: true, &unload_gearhead)
58
+ else
59
+ ActiveSupport::Reloader.to_complete(&unload_gearhead)
60
+ end
61
+
62
+ admin_dirs = {}
63
+
64
+ load_paths.each do |path|
65
+ admin_dirs[path] = [:rb]
66
+ end
67
+
68
+ routes_reloader = app.config.file_watcher.new([], admin_dirs) do
69
+ app.reload_routes!
70
+ end
71
+
72
+ app.reloaders << routes_reloader
73
+
74
+ ActiveSupport::Reloader.to_prepare do
75
+ unless Gearhead.gearbox.loaded?
76
+ routes_reloader.execute_if_updated
77
+ Gearhead.application.load!
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,27 @@
1
+ module Gearhead
2
+ module Paginators
3
+ class Lookup
4
+ def self.for(action)
5
+ new(action).paginator
6
+ end
7
+
8
+ def initialize(action)
9
+ @action = action.to_s
10
+ end
11
+
12
+ def paginator
13
+ "::Gearhead::Paginators::#{adapter}Paginator".constantize
14
+ end
15
+
16
+ private
17
+
18
+ def adapter_name
19
+ ::Gearhead.config.pagination.adapter
20
+ end
21
+
22
+ def adapter
23
+ adapter_name.to_s.classify
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,48 @@
1
+ module Gearhead
2
+ module Paginators
3
+ class Paginator
4
+ def self.paginate(records, action)
5
+ ::Gearhead::Paginators::Lookup.for(action).new(records, action)
6
+ end
7
+
8
+ attr_reader :collection
9
+ def initialize(records, action)
10
+ @records = records
11
+ @collection = nil
12
+ @action = action
13
+ end
14
+
15
+ # should set @collection
16
+ def paginate
17
+ raise NotImplementedError, "define paginate and set @collection"
18
+ end
19
+
20
+ def call
21
+ paginate
22
+ self
23
+ end
24
+
25
+ def serialization_options
26
+ {}
27
+ end
28
+
29
+ private
30
+
31
+ def per_page
32
+ gear._gear_per_page
33
+ end
34
+
35
+ def page
36
+ request.params[:page] || 1
37
+ end
38
+
39
+ def gear
40
+ @action.gear
41
+ end
42
+
43
+ def request
44
+ @action.request
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ require 'pagy/extras/metadata'
2
+
3
+ module Gearhead
4
+ module Paginators
5
+ class PagyPaginator < Paginator
6
+ include Pagy::Backend
7
+
8
+ def paginate
9
+ @pagy, @collection = pagy(@records, items: per_page, page: page)
10
+ end
11
+
12
+ def serialization_options
13
+ pagy_metadata(@pagy)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Gearhead
2
+ module Paginators
3
+ class WillPaginatePaginator < Paginator
4
+ def paginate
5
+ @collection = @records.paginate(page: page, per_page: per_page)
6
+ end
7
+
8
+ def serialization_options
9
+ abort
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module Gearhead
2
+ class ParamsBuilder
3
+ def initialize(page)
4
+ @page = page
5
+ end
6
+
7
+ def for(action)
8
+ params = @page.gear.permitted_params(action).map.with_object({}) do |key, obj|
9
+ obj[key] = @page.request.params[key]
10
+ end
11
+ params
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ module Gearhead
2
+ class Registry
3
+ def initialize
4
+ @resource_map = {}
5
+ @router = {}
6
+ end
7
+
8
+ def register(gear)
9
+ @resource_map[gear.resource.name] = gear
10
+ @router[gear.path] = gear
11
+ true
12
+ end
13
+
14
+ def for_resource(resource)
15
+ @resource_map[resource]
16
+ end
17
+
18
+ def find(path)
19
+ @router[path]
20
+ end
21
+
22
+ def all
23
+ @resource_map.values
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,20 @@
1
+ module Gearhead
2
+ class ResourceFinder
3
+ def self.for(gear, params)
4
+ new(gear, params).resource
5
+ end
6
+
7
+ def initialize(gear, params)
8
+ @gear = gear
9
+ @params = params
10
+ end
11
+
12
+ def resource
13
+ if @gear._gear_finder.present?
14
+ @gear._gear_finder.call(@params)
15
+ else
16
+ @gear.resource.find_by(@gear._gear_param_key => @params[:resource_id])
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ module Gearhead
2
+ class Router
3
+ attr_reader :router
4
+
5
+ def initialize(router:)
6
+ @router = router
7
+ end
8
+
9
+ def apply
10
+ define_gear_routes
11
+ end
12
+
13
+ def define_gear_routes
14
+ ::Gearhead.registry.all.each do |thing|
15
+ @router.resources thing.resource.model_name.route_key, path: [::Gearhead.config.endpoint, thing.path].join("/"), controller: "gearhead/gears", param: :resource_id, defaults: { resource_class: thing.resource.model_name.route_key } do
16
+ define_actions(thing)
17
+ end
18
+ end
19
+ end
20
+
21
+ def define_actions(gear)
22
+ @router.member do
23
+ gear._gear_member_actions.values.each do |custom_action|
24
+ custom_action.verbs.each do |verb|
25
+ args = [custom_action.name, to: 'gearhead/gears#member_action', defaults: { member_action: custom_action.name }]
26
+ router.send(verb, *args)
27
+ end
28
+ end
29
+ end
30
+
31
+ @router.collection do
32
+ gear._gear_collection_actions.values.each do |custom_action|
33
+
34
+ custom_action.verbs.each do |verb|
35
+
36
+ args = [custom_action.name, to: 'gearhead/gears#collection_action', defaults: { collection_action: custom_action.name }]
37
+ router.send(verb, *args)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ def build_action(action)
44
+ build_route(action.verbs, action.name)
45
+ end
46
+
47
+ def build_route(verbs, *args)
48
+ Array.wrap(verbs).each { |verb| router.send(verb, *args) }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,7 @@
1
+ module Gearhead
2
+ module Serializers
3
+ module ActiveModelSerializers
4
+ class InvalidRequestSerializer < ::Gearhead::Serializers::InvalidRequestSerializer; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Gearhead
2
+ module Serializers
3
+ module ActiveModelSerializers
4
+ class InvalidResourceSerializer < ::Gearhead::Serializers::InvalidResourceSerializer; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ module Gearhead
2
+ module Serializers
3
+ module ActiveModelSerializers
4
+ class ResourceSerializer
5
+ def self.for(page)
6
+ if defined?(ActiveModelSerializers::Serializer)
7
+ klass = Class.new(ActiveModelSerializers::Serializer)
8
+ klass.attributes *(page._gear_attributes.presence || page.default_attributes)
9
+
10
+ klass
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end