jets 1.9.32 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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