jets 1.9.32 → 2.0.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/lib/jets/application/defaults.rb +6 -0
  4. data/lib/jets/cli.rb +9 -0
  5. data/lib/jets/commands/help/generate.md +25 -19
  6. data/lib/jets/commands/main.rb +3 -3
  7. data/lib/jets/commands/rake_tasks.rb +3 -0
  8. data/lib/jets/commands/templates/skeleton/app/views/layouts/application.html.erb.tt +1 -0
  9. data/lib/jets/commands/templates/skeleton/config/application.rb.tt +4 -0
  10. data/lib/jets/commands/templates/webpacker/app/javascript/src/jets/crud.js +3 -0
  11. data/lib/jets/commands/upgrade.rb +42 -109
  12. data/lib/jets/commands/upgrade/version1.rb +136 -0
  13. data/lib/jets/controller/base.rb +16 -0
  14. data/lib/jets/controller/error.rb +4 -0
  15. data/lib/jets/controller/error/invalid_authenticity_token.rb +6 -0
  16. data/lib/jets/controller/forgery_protection.rb +43 -0
  17. data/lib/jets/controller/middleware/local.rb +3 -3
  18. data/lib/jets/controller/middleware/local/route_matcher.rb +1 -1
  19. data/lib/jets/controller/middleware/main.rb +7 -1
  20. data/lib/jets/controller/rack/adapter.rb +1 -1
  21. data/lib/jets/controller/rack/env.rb +1 -1
  22. data/lib/jets/controller/rendering/rack_renderer.rb +44 -37
  23. data/lib/jets/controller/stage.rb +2 -1
  24. data/lib/jets/generator.rb +72 -8
  25. data/lib/jets/generator/templates/active_job/job/templates/application_job.rb.tt +6 -0
  26. data/lib/jets/generator/templates/active_job/job/templates/job.rb.tt +8 -0
  27. data/lib/jets/generator/templates/erb/scaffold/_form.html.erb +11 -16
  28. data/lib/jets/generator/templates/erb/scaffold/edit.html.erb +2 -2
  29. data/lib/jets/generator/templates/erb/scaffold/index.html.erb +5 -5
  30. data/lib/jets/generator/templates/erb/scaffold/new.html.erb +1 -1
  31. data/lib/jets/generator/templates/erb/scaffold/show.html.erb +3 -3
  32. data/lib/jets/generator/templates/rails/scaffold_controller/controller.rb +5 -5
  33. data/lib/jets/internal/app/controllers/jets/rack_controller.rb +1 -0
  34. data/lib/jets/overrides/rails.rb +2 -1
  35. data/lib/jets/overrides/rails/action_controller.rb +12 -0
  36. data/lib/jets/overrides/rails/url_helper.rb +66 -5
  37. data/lib/jets/resource/api_gateway/rest_api/routes/change/base.rb +1 -1
  38. data/lib/jets/resource/api_gateway/rest_api/routes/collision.rb +1 -1
  39. data/lib/jets/router.rb +32 -46
  40. data/lib/jets/router/dsl.rb +136 -0
  41. data/lib/jets/router/error.rb +4 -0
  42. data/lib/jets/router/helpers.rb +4 -0
  43. data/lib/jets/router/helpers/core_helper.rb +17 -0
  44. data/lib/jets/router/helpers/named_routes_helper.rb +8 -0
  45. data/lib/jets/router/method_creator.rb +54 -0
  46. data/lib/jets/router/method_creator/code.rb +98 -0
  47. data/lib/jets/router/method_creator/edit.rb +7 -0
  48. data/lib/jets/router/method_creator/generic.rb +11 -0
  49. data/lib/jets/router/method_creator/index.rb +42 -0
  50. data/lib/jets/router/method_creator/new.rb +7 -0
  51. data/lib/jets/router/method_creator/root.rb +15 -0
  52. data/lib/jets/router/method_creator/show.rb +7 -0
  53. data/lib/jets/router/resources/base.rb +7 -0
  54. data/lib/jets/router/resources/filter.rb +15 -0
  55. data/lib/jets/router/resources/options.rb +13 -0
  56. data/lib/jets/router/route.rb +226 -0
  57. data/lib/jets/router/scope.rb +65 -4
  58. data/lib/jets/router/util.rb +38 -0
  59. data/lib/jets/turbo/project/config/application.rb +1 -0
  60. data/lib/jets/version.rb +1 -1
  61. metadata +26 -2
  62. data/lib/jets/route.rb +0 -166
@@ -2,5 +2,5 @@
2
2
 
3
3
  <%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>
4
4
 
5
- <%%= link_to 'Show', "/<%= plural_table_name %>/#{@<%= singular_table_name %>.id}" %> |
6
- <%%= link_to 'Back', "/<%= plural_table_name %>" %>
5
+ <%%= link_to 'Show', @<%= singular_table_name %> %> |
6
+ <%%= link_to 'Back', <%= index_helper %>_path %>
@@ -14,11 +14,11 @@
14
14
  <%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
15
15
  <tr class="jets-element-to-delete">
16
16
  <% attributes.reject(&:password_digest?).each do |attribute| -%>
17
- <td><%%= <%= singular_table_name %>.<%= attribute.name %> %></td>
17
+ <td><%%= <%= singular_table_name %>.<%= attribute.column_name %> %></td>
18
18
  <% end -%>
19
- <td><%%= link_to 'Show', "/<%= plural_table_name %>/#{<%= singular_table_name %>.id}" %></td>
20
- <td><%%= link_to 'Edit', "/<%= plural_table_name %>/#{<%= singular_table_name %>.id}/edit" %></td>
21
- <td><%%= link_to 'Destroy', "/<%= plural_table_name %>/#{<%= singular_table_name %>.id}", method: :delete, data: { confirm: 'Are you sure?' } %></td>
19
+ <td><%%= link_to 'Show', <%= model_resource_name %> %></td>
20
+ <td><%%= link_to 'Edit', edit_<%= singular_route_name %>_path(<%= singular_table_name %>) %></td>
21
+ <td><%%= link_to 'Destroy', <%= model_resource_name %>, method: :delete, data: { confirm: 'Are you sure?' } %></td>
22
22
  </tr>
23
23
  <%% end %>
24
24
  </tbody>
@@ -26,4 +26,4 @@
26
26
 
27
27
  <br>
28
28
 
29
- <%%= link_to 'New <%= singular_table_name.titleize %>', "/<%= plural_table_name %>/new" %>
29
+ <%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_route_name %>_path %>
@@ -2,4 +2,4 @@
2
2
 
3
3
  <%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>
4
4
 
5
- <%%= link_to 'Back', "/<%= plural_table_name %>" %>
5
+ <%%= link_to 'Back', <%= index_helper %>_path %>
@@ -1,9 +1,9 @@
1
1
  <% attributes.reject(&:password_digest?).each do |attribute| -%>
2
2
  <p>
3
3
  <strong><%= attribute.human_name %>:</strong>
4
- <%%= @<%= singular_table_name %>.<%= attribute.name %> %>
4
+ <%%= @<%= singular_table_name %>.<%= attribute.column_name %> %>
5
5
  </p>
6
6
 
7
7
  <% end -%>
8
- <%%= link_to 'Edit', "/<%= plural_table_name %>/#{@<%= singular_table_name %>.id}/edit" %> |
9
- <%%= link_to 'Back', "/<%= plural_table_name %>" %>
8
+ <%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>) %> |
9
+ <%%= link_to 'Back', <%= index_helper %>_path %>
@@ -30,9 +30,9 @@ class <%= controller_class_name %>Controller < ApplicationController
30
30
 
31
31
  if @<%= orm_instance.save %>
32
32
  if request.xhr?
33
- render json: {success: true, location: url_for("/<%= plural_table_name %>/#{@<%= singular_table_name %>.id}")}
33
+ render json: {success: true, location: url_for(@<%= singular_table_name %>)}
34
34
  else
35
- redirect_to "/<%= plural_table_name %>/#{@<%= singular_table_name %>.id}"
35
+ redirect_to <%= singular_table_name %>_path(@<%= singular_table_name %>)
36
36
  end
37
37
  else
38
38
  render :new
@@ -43,9 +43,9 @@ class <%= controller_class_name %>Controller < ApplicationController
43
43
  def update
44
44
  if @<%= orm_instance.update("#{singular_table_name}_params") %>
45
45
  if request.xhr?
46
- render json: {success: true, location: url_for("/<%= plural_table_name %>/#{@<%= singular_table_name %>.id}")}
46
+ render json: {success: true, location: url_for(@<%= singular_table_name %>)}
47
47
  else
48
- redirect_to "/<%= plural_table_name %>/#{@<%= singular_table_name %>.id}"
48
+ redirect_to <%= singular_table_name %>_path(@<%= singular_table_name %>)
49
49
  end
50
50
  else
51
51
  render :edit
@@ -58,7 +58,7 @@ class <%= controller_class_name %>Controller < ApplicationController
58
58
  if request.xhr?
59
59
  render json: {success: true}
60
60
  else
61
- redirect_to "/<%= plural_table_name %>"
61
+ redirect_to <%= table_name %>_path
62
62
  end
63
63
  end
64
64
 
@@ -1,6 +1,7 @@
1
1
  class Jets::RackController < Jets::Controller::Base
2
2
  layout false
3
3
  internal true
4
+ skip_forgery_protection
4
5
 
5
6
  # Megamode
6
7
  def process
@@ -1,4 +1,5 @@
1
1
  require "jets/overrides/rails/common_methods"
2
2
  require "jets/overrides/rails/url_helper"
3
3
  require "jets/overrides/rails/rendering_helper"
4
- require "jets/overrides/rails/asset_tag_helper"
4
+ require "jets/overrides/rails/asset_tag_helper"
5
+ require "jets/overrides/rails/action_controller"
@@ -0,0 +1,12 @@
1
+ ActiveSupport.on_load :action_controller do
2
+ class ActionController::Base
3
+ class << self
4
+ def _prefixes
5
+ @@_prefixes
6
+ end
7
+ def _prefixes=(v)
8
+ @@_prefixes = v
9
+ end
10
+ end
11
+ end
12
+ end
@@ -11,15 +11,76 @@ module Jets::UrlHelper
11
11
  options
12
12
  when :back
13
13
  _back_url
14
- # TODO: hook this up to Jets implmentation of config/routes.rb
15
- # when ActiveRecord::Base
16
- # record = options
17
- # record.id
14
+ when ActiveRecord::Base
15
+ _handle_model(options)
16
+ when Array
17
+ _handle_array(options)
18
18
  else
19
- raise ArgumentError, "Please provided a String to link_to as the the second argument. The Jets link_to helper takes as the second argument."
19
+ raise ArgumentError, "Please provided a String or ActiveRecord model to link_to as the the second argument. The Jets link_to helper takes as the second argument."
20
20
  end
21
21
 
22
22
  add_stage_name(url)
23
23
  end
24
+
25
+ def _handle_model(record)
26
+ model = record.to_model
27
+ if model.persisted?
28
+ meth = model.model_name.singular_route_key + "_path"
29
+ send(meth, record) # Example: post_path(record)
30
+ else
31
+ meth = model.model_name.route_key + "_path"
32
+ send(meth) # Example: posts_path
33
+ end
34
+ end
35
+
36
+ # Convention is that the model class name is the method name. Doesnt work if user is using as.
37
+ def _handle_array(array)
38
+ contains_nil = !array.select(&:nil?).empty?
39
+ if contains_nil
40
+ raise "ERROR: You passed a nil value in the Array. #{array.inspect}."
41
+ end
42
+
43
+ last_persisted = nil
44
+ items = array.map do |x|
45
+ if x.is_a?(ActiveRecord::Base)
46
+ last_persisted = x.persisted?
47
+ x.persisted? ? x.model_name.singular_route_key : x.model_name.route_key
48
+ else
49
+ x
50
+ end
51
+ end
52
+ meth = items.join('_') + "_path"
53
+
54
+ args = array.clone
55
+ args.shift if args.first.is_a?(Symbol) # drop the first element if its a symbol
56
+ args = last_persisted ? args : args[0..-2]
57
+
58
+ # post_comment_path(post_id) - keep all args - for update
59
+ # post_comments_path - drop last arg - for create
60
+ send(meth, *args)
61
+ end
62
+
63
+ # for forgery protection
64
+ def token_tag(token = nil, form_options: {})
65
+ return '' unless protect_against_forgery?
66
+
67
+ hidden_field_tag 'authenticity_token', masked_authenticity_token
68
+ end
69
+
70
+ def masked_authenticity_token
71
+ @masked_authenticity_token ||= SecureRandom.hex(32)
72
+ session[:authenticity_token] = @masked_authenticity_token
73
+ end
74
+
75
+ def protect_against_forgery?
76
+ @_jets[:controller].class.forgery_protection_enabled?
77
+ end
78
+
79
+ def csrf_meta_tags
80
+ if protect_against_forgery?
81
+ tag("meta", name: "csrf-token", content: masked_authenticity_token).html_safe
82
+ end
83
+ end
24
84
  end # UrlHelper
85
+
25
86
  ActionView::Helpers.send(:include, Jets::UrlHelper)
@@ -33,7 +33,7 @@ class Jets::Resource::ApiGateway::RestApi::Routes::Change
33
33
  path = recreate_path(resource.path)
34
34
  method = http_verb.downcase.to_sym
35
35
  to = to(resource.id, http_verb)
36
- route = Jets::Route.new(path: path, method: method, to: to)
36
+ route = Jets::Router::Route.new(path: path, method: method, to: to)
37
37
  routes << route
38
38
  end
39
39
  end
@@ -62,7 +62,7 @@ class Jets::Resource::ApiGateway::RestApi::Routes
62
62
  end
63
63
  paths.map do |path|
64
64
  path.sub("#{parent}/",'').gsub(%r{/.*},'')
65
- end.uniq.sort
65
+ end.select { |x| x =~ /^:/ }.uniq.sort
66
66
  end
67
67
 
68
68
  def parent?(parent, path)
@@ -2,9 +2,12 @@ require 'text-table'
2
2
 
3
3
  module Jets
4
4
  class Router
5
+ include Dsl
6
+
5
7
  attr_reader :routes
6
8
  def initialize
7
9
  @routes = []
10
+ @scope = Scope.new
8
11
  end
9
12
 
10
13
  def draw(&block)
@@ -20,51 +23,40 @@ module Jets
20
23
  raise collision.exception if collide
21
24
  end
22
25
 
23
- # Methods supported by API Gateway
24
- %w[any delete get head options patch post put].each do |method_name|
25
- define_method method_name do |path, options|
26
- create_route(options.merge(path: path, method: __method__))
27
- end
28
- end
29
-
30
26
  def create_route(options)
31
- # Currently only using scope to add namespace
32
27
  # TODO: Can use it to add additional things like authorization_type
33
28
  # Would be good to add authorization_type at the controller level also
34
- options[:path] = add_namespace(options[:path])
35
- @routes << Route.new(options)
29
+ infer_to_option!(options)
30
+ handle_on!(options)
31
+ MethodCreator.new(options, @scope).define_url_helper!
32
+ @routes << Route.new(options, @scope)
36
33
  end
37
34
 
38
- def add_namespace(path)
39
- return path unless @scope
40
- ns = @scope.full_namespace
41
- return path unless ns
42
- "#{ns}/#{path}"
43
- end
35
+ # Can possibly infer to option from the path. Example:
36
+ #
37
+ # get 'posts/index'
38
+ # get 'posts', to: 'posts#index'
39
+ #
40
+ # get 'posts/show'
41
+ # get 'posts', to: 'posts#show'
42
+ #
43
+ def infer_to_option!(options)
44
+ return if options[:to]
44
45
 
45
- def namespace(ns, &block)
46
- scope(namespace: ns, &block)
47
- end
46
+ path = options[:path].to_s
47
+ return unless path.include?('/')
48
48
 
49
- def scope(options={})
50
- root_level = @scope.nil?
51
- @scope = root_level ? Scope.new(options) : @scope.new(options)
52
- yield
53
- ensure
54
- @scope = @scope.parent if @scope
49
+ items = path.split('/')
50
+ if items.size == 2
51
+ options[:to] = items.join('#')
52
+ end
55
53
  end
56
54
 
57
- # resources macro expands to all the routes
58
- def resources(name)
59
- get "#{name}", to: "#{name}#index"
60
- get "#{name}/new", to: "#{name}#new" unless api_mode?
61
- get "#{name}/:id", to: "#{name}#show"
62
- post "#{name}", to: "#{name}#create"
63
- get "#{name}/:id/edit", to: "#{name}#edit" unless api_mode?
64
- put "#{name}/:id", to: "#{name}#update"
65
- post "#{name}/:id", to: "#{name}#update" # for binary uploads
66
- patch "#{name}/:id", to: "#{name}#update"
67
- delete "#{name}/:id", to: "#{name}#delete"
55
+ def handle_on!(options)
56
+ if options[:on] && !%w[resources resource].include?(@scope.from.to_s)
57
+ raise Error.new("ERROR: The `on:` option can only be used within a resource or resources block")
58
+ end
59
+ options[:on] ||= @on_option if @on_option
68
60
  end
69
61
 
70
62
  def api_mode?
@@ -81,13 +73,6 @@ module Jets
81
73
  api_mode
82
74
  end
83
75
 
84
- # root "posts#index"
85
- def root(to, options={})
86
- default = {path: '', to: to, method: :get, root: true}
87
- options = default.merge(options)
88
- @routes << Route.new(options)
89
- end
90
-
91
76
  # Useful for creating API Gateway Resources
92
77
  def all_paths
93
78
  results = []
@@ -141,6 +126,7 @@ module Jets
141
126
 
142
127
  def self.clear!
143
128
  @@drawn_router = nil
129
+ Jets::Router::Helpers::NamedRoutesHelper.clear!
144
130
  end
145
131
 
146
132
  def self.routes
@@ -155,13 +141,13 @@ module Jets
155
141
  drawn_router.all_paths
156
142
  end
157
143
 
158
- def self.routes_help
144
+ def self.help(routes)
159
145
  return "Your routes table is empty." if routes.empty?
160
146
 
161
147
  table = Text::Table.new
162
- table.head = %w[Verb Path Controller#action]
148
+ table.head = %w[As Verb Path Controller#action]
163
149
  routes.each do |route|
164
- table.rows << [route.method, route.path, route.to]
150
+ table.rows << [route.as, route.method, route.path, route.to]
165
151
  end
166
152
  table
167
153
  end
@@ -0,0 +1,136 @@
1
+ class Jets::Router
2
+ module Dsl
3
+ # Methods supported by API Gateway
4
+ %w[any delete get head options patch post put].each do |method_name|
5
+ define_method method_name do |path, options={}|
6
+ create_route(options.merge(path: path, method: __method__))
7
+ end
8
+ end
9
+
10
+ def namespace(ns, &block)
11
+ scope(module: ns, prefix: ns, as: ns, from: :namespace, &block)
12
+ end
13
+
14
+ def prefix(prefix, &block)
15
+ scope(prefix: prefix, &block)
16
+ end
17
+
18
+ # scope supports three options: module, prefix and as.
19
+ # Jets vs Rails:
20
+ # module - module
21
+ # prefix - path
22
+ # as - as
23
+ def scope(args)
24
+ # normalizes `scope(:admin)` as `scope(prefix: :admin)`
25
+ options = case args
26
+ when Hash
27
+ args
28
+ when String, Symbol
29
+ { prefix: args }
30
+ end
31
+
32
+ root_level = @scope.nil?
33
+ @scope = root_level ? Scope.new(options) : @scope.new(options)
34
+ yield
35
+ ensure
36
+ @scope = @scope.parent if @scope
37
+ end
38
+
39
+ # resources macro expands to all the routes
40
+ def resources(*items, **options)
41
+ items.each do |item|
42
+ scope_options = scope_options!(item, options)
43
+ scope_options[:from] = :resources # flag for MethodCreator logic: to handle method_name_leaf and more
44
+ scope(scope_options) do
45
+ each_resources(item, options, block_given?)
46
+ yield if block_given?
47
+ end
48
+ end
49
+ end
50
+
51
+ def scope_options!(item, options)
52
+ prefix = if options[:prefix]
53
+ # prefix given from the resources macro get automatically prepended to the item name
54
+ p = options.delete(:prefix)
55
+ "#{p}/#{item}"
56
+ else
57
+ item
58
+ end
59
+
60
+ {
61
+ as: options.delete(:as) || item, # delete as or it messes with create_route
62
+ prefix: prefix,
63
+ # module: options.delete(:module) || item, # NOTE: resources does not automatically set module, but namespace does
64
+ }
65
+ end
66
+
67
+ def each_resources(name, options={}, has_block=nil)
68
+ o = Resources::Options.new(name, options)
69
+ f = Resources::Filter.new(name, options)
70
+ param = default_param(has_block, name, options)
71
+
72
+ get name, o.build(:index) if f.yes?(:index)
73
+ get "#{name}/new", o.build(:new) if f.yes?(:new) && !api_mode?
74
+ get "#{name}/:#{param}", o.build(:show) if f.yes?(:show)
75
+ post name, o.build(:create) if f.yes?(:create)
76
+ get "#{name}/:#{param}/edit", o.build(:edit) if f.yes?(:edit) && !api_mode?
77
+ put "#{name}/:#{param}", o.build(:update) if f.yes?(:update)
78
+ post "#{name}/:#{param}", o.build(:update) if f.yes?(:update) # for binary uploads
79
+ patch "#{name}/:#{param}", o.build(:update) if f.yes?(:update)
80
+ delete "#{name}/:#{param}", o.build(:delete) if f.yes?(:delete)
81
+ end
82
+
83
+ def resource(*items, **options)
84
+ items.each do |item|
85
+ scope_options = scope_options!(item, options)
86
+ scope_options[:from] = :resource # flag for MethodCreator logic: to handle method_name_leaf and more
87
+ scope(scope_options) do
88
+ each_resource(item, options, block_given?)
89
+ yield if block_given?
90
+ end
91
+ end
92
+ end
93
+
94
+ def each_resource(name, options={}, has_block=nil)
95
+ o = Resources::Options.new(name, options.merge(singular_resource: true))
96
+ f = Resources::Filter.new(name, options)
97
+
98
+ get "#{name}/new", o.build(:new) if f.yes?(:new) && !api_mode?
99
+ get name, o.build(:show) if f.yes?(:show)
100
+ post name, o.build(:create) if f.yes?(:create)
101
+ get "#{name}/edit", o.build(:edit) if f.yes?(:edit) && !api_mode?
102
+ put name, o.build(:update) if f.yes?(:update)
103
+ post name, o.build(:update) if f.yes?(:update) # for binary uploads
104
+ patch name, o.build(:update) if f.yes?(:update)
105
+ delete name, o.build(:delete) if f.yes?(:delete)
106
+ end
107
+
108
+ def member
109
+ @on_option = :member
110
+ yield
111
+ @on_option = nil
112
+ end
113
+
114
+ def collection
115
+ @on_option = :collection
116
+ yield
117
+ @on_option = nil
118
+ end
119
+
120
+ # If a block has pass then we assume the resources will be nested and then prefix
121
+ # the param name with the resource. IE: post_id instead of id
122
+ # This avoids an API Gateway parent sibling variable collision.
123
+ def default_param(has_block, name, options)
124
+ default_param = has_block ? "#{name.to_s.singularize}_id".to_sym : :id
125
+ options[:param] || default_param
126
+ end
127
+
128
+ # root "posts#index"
129
+ def root(to, options={})
130
+ default = {path: '', to: to, method: :get, root: true}
131
+ options = default.merge(options)
132
+ MethodCreator.new(options, @scope).create_root_helper
133
+ @routes << Route.new(options, @scope)
134
+ end
135
+ end
136
+ end