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
@@ -10,6 +10,8 @@ class Jets::Controller
10
10
  include Params
11
11
  include Rendering
12
12
  include ActiveSupport::Rescuable
13
+ include Jets::Router::Helpers
14
+ include ForgeryProtection
13
15
 
14
16
  delegate :headers, to: :request
15
17
  delegate :set_header, to: :response
@@ -78,6 +80,20 @@ class Jets::Controller
78
80
  JSON.dump(data)
79
81
  end
80
82
 
83
+ def controller_paths
84
+ paths = []
85
+ klass = self.class
86
+ while klass != Jets::Controller::Base
87
+ paths << klass.controller_path
88
+ klass = klass.superclass
89
+ end
90
+ paths
91
+ end
92
+
93
+ def self.controller_path
94
+ name.sub(/Controller$/, "".freeze).underscore
95
+ end
96
+
81
97
  def self.process(event, context={}, meth)
82
98
  controller = new(event, context, meth)
83
99
  # Using send because process! is private method in Jets::RackController so
@@ -0,0 +1,4 @@
1
+ class Jets::Controller
2
+ class Error < RuntimeError #:nodoc:
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ class Jets::Controller
2
+ class Error
3
+ class InvalidAuthenticityToken < Error #:nodoc:
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,43 @@
1
+ class Jets::Controller
2
+ module ForgeryProtection
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ config = Jets.config
7
+ default_protect_from_forgery = config.dig(:controllers, :default_protect_from_forgery)
8
+ if default_protect_from_forgery
9
+ protect_from_forgery
10
+ end
11
+ end
12
+
13
+ class_methods do
14
+ def protect_from_forgery(options = {})
15
+ before_action :verify_authenticity_token, options
16
+ end
17
+
18
+ def skip_forgery_protection
19
+ skip_before_action :verify_authenticity_token
20
+ end
21
+
22
+ def forgery_protection_enabled?
23
+ # Example:
24
+ #
25
+ # before_actions [[:verify_authenticity_token, {}], [:set_post, {:only=>[:show, :edit, :update, :delete]}
26
+ #
27
+ before_actions.map { |a| a[0] }.include?(:verify_authenticity_token)
28
+ end
29
+ end
30
+
31
+ # Instance methods
32
+ def verify_authenticity_token
33
+ return true if ENV['TEST'] || request.get? || request.head?
34
+
35
+ token = session[:authenticity_token]
36
+ verified = !token.nil? && (token == params[:authenticity_token] || token == request.headers["x-csrf-token"])
37
+
38
+ unless verified
39
+ raise Error::InvalidAuthenticityToken
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,6 +1,6 @@
1
1
  require 'kramdown'
2
2
 
3
- # Handles mimicking of API Gateway to Lambda function call locally
3
+ # Handles mimicing of API Gateway to Lambda function call locally
4
4
  module Jets::Controller::Middleware
5
5
  class Local
6
6
  extend Memoist
@@ -35,7 +35,7 @@ module Jets::Controller::Middleware
35
35
  # This can only really get called with the local server.
36
36
  run_polymophic_function
37
37
  else # Normal Jets request
38
- mimick_aws_lambda!(env, mimic.vars) unless on_aws?(env)
38
+ mimic_aws_lambda!(env, mimic.vars) unless on_aws?(env)
39
39
  @app.call(env)
40
40
  end
41
41
  end
@@ -60,7 +60,7 @@ module Jets::Controller::Middleware
60
60
  end
61
61
 
62
62
  # Modifies env the in the same way real call from AWS lambda would modify env
63
- def mimick_aws_lambda!(env, vars)
63
+ def mimic_aws_lambda!(env, vars)
64
64
  env.merge!(vars)
65
65
  env
66
66
  end
@@ -89,7 +89,7 @@ class Jets::Controller::Middleware::Local
89
89
  # posts/:id/edit => posts\/(.*)\/edit
90
90
 
91
91
  regexp_string = route_path.split('/').map do |s|
92
- s.include?(':') ? Jets::Route::CAPTURE_REGEX : s
92
+ s.include?(':') ? Jets::Router::Route::CAPTURE_REGEX : s
93
93
  end.join('\/')
94
94
  # make sure beginning and end of the string matches
95
95
  regexp_string = "^#{regexp_string}$"
@@ -18,6 +18,7 @@ module Jets::Controller::Middleware
18
18
  end
19
19
 
20
20
  def call
21
+ ENV['JETS_HOST'] = jets_host # Dirty to use what's essentially a global variable but unsure how else to achieve
21
22
  dup.call!
22
23
  end
23
24
 
@@ -29,7 +30,7 @@ module Jets::Controller::Middleware
29
30
  # Common setup logical at this point of middleware processing right before
30
31
  # calling any controller actions.
31
32
  def setup
32
- # We already recreated a mimicke rack env earlier as part of the very first
33
+ # We already recreated a mimic rack env earlier as part of the very first
33
34
  # middleware layer. However, by the time the rack env reaches the main middleware
34
35
  # it could had been updated by other middlewares. We update the env here again.
35
36
  @controller.request.set_env!(@env)
@@ -38,6 +39,11 @@ module Jets::Controller::Middleware
38
39
  @controller.session = @env['rack.session'] || {}
39
40
  end
40
41
 
42
+ def jets_host
43
+ default = "#{@env['rack.url_scheme']}://#{@env['HTTP_HOST']}"
44
+ Jets.config.helpers.host || default
45
+ end
46
+
41
47
  def self.call(env)
42
48
  instance = new(env)
43
49
  instance.call
@@ -77,7 +77,7 @@ module Jets::Controller::Rack
77
77
  #
78
78
  # Passes a these special variables so we have access to them in the middleware.
79
79
  # The controller instance is called in the Main middleware.
80
- # The lambda.* info is used by the Rack::Local middleware to create a mimicked
80
+ # The lambda.* info is used by the Rack::Local middleware to create a mimiced
81
81
  # controller for the local server.
82
82
  #
83
83
  def rack_vars(vars)
@@ -29,7 +29,7 @@ module Jets::Controller::Rack
29
29
  'QUERY_STRING' => query_string,
30
30
  'REMOTE_ADDR' => headers['X-Forwarded-For'],
31
31
  'REMOTE_HOST' => headers['Host'],
32
- 'REQUEST_METHOD' => @event['httpMethod'],
32
+ 'REQUEST_METHOD' => @event['httpMethod'] || 'GET', # useful to default to GET when testing with Lambda console
33
33
  'REQUEST_PATH' => @event['path'],
34
34
  'REQUEST_URI' => request_uri,
35
35
  'SCRIPT_NAME' => "",
@@ -26,11 +26,12 @@ module Jets::Controller::Rendering
26
26
  # x-jets-base64 to convert this Rack triplet to a API Gateway hash structure later
27
27
  headers["x-jets-base64"] = base64 ? 'yes' : 'no' # headers values must be Strings
28
28
 
29
- # Rails rendering does heavy lifting
30
29
  if drop_content_info?(status)
31
30
  body = StringIO.new
32
31
  else
33
-
32
+ # Rails rendering does heavy lifting
33
+ # _prefixes provided by jets/overrides/rails/action_controller.rb
34
+ ActionController::Base._prefixes = @controller.controller_paths
34
35
  renderer = ActionController::Base.renderer.new(renderer_options)
35
36
  body = renderer.render(render_options)
36
37
  body = StringIO.new(body)
@@ -39,16 +40,6 @@ module Jets::Controller::Rendering
39
40
  [status, headers, body] # triplet
40
41
  end
41
42
 
42
- # Example: posts/index
43
- def default_template_name
44
- "#{template_namespace}/#{@controller.meth}"
45
- end
46
-
47
- # PostsController => "posts" is the namespace
48
- def template_namespace
49
- @controller.class.to_s.sub('Controller','').underscore.pluralize
50
- end
51
-
52
43
  # default options:
53
44
  # https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/renderer.rb#L41-L47
54
45
  def renderer_options
@@ -74,6 +65,41 @@ module Jets::Controller::Rendering
74
65
  options
75
66
  end
76
67
 
68
+ def render_options
69
+ # normalize the template option
70
+ template = @options[:template]
71
+ if template and !template.include?('/')
72
+ template = "#{template_namespace}/#{template}"
73
+ end
74
+ template ||= default_template_name
75
+ # ready to override @options[:template]
76
+ @options[:template] = template if @options[:template]
77
+
78
+ render_options = {
79
+ template: template, # weird: template needs to be set no matter because it
80
+ # sets the name which is used in lookup_context.rb:209:in `normalize_name'
81
+ layout: @options[:layout],
82
+ assigns: controller_instance_variables,
83
+ # prefixes: ["posts"],
84
+ }
85
+ types = %w[json inline plain file xml body action].map(&:to_sym)
86
+ types.each do |type|
87
+ render_options[type] = @options[type] if @options[type]
88
+ end
89
+
90
+ render_options
91
+ end
92
+
93
+ # Example: posts/index
94
+ def default_template_name
95
+ "#{template_namespace}/#{@controller.meth}"
96
+ end
97
+
98
+ # PostsController => "posts" is the namespace
99
+ def template_namespace
100
+ @controller.class.to_s.sub('Controller','').underscore.pluralize
101
+ end
102
+
77
103
  # Takes headers and adds HTTP_ to front of the keys because that is what rack
78
104
  # does to the headers passed from a request. This seems to be the standard
79
105
  # when testing with curl and inspecting the headers in a Rack app. Example:
@@ -110,30 +136,7 @@ module Jets::Controller::Rendering
110
136
  results
111
137
  end
112
138
 
113
- def render_options
114
- # nomralize the template option
115
- template = @options[:template]
116
- if template and !template.include?('/')
117
- template = "#{template_namespace}/#{template}"
118
- end
119
- template ||= default_template_name
120
- # ready to override @options[:template]
121
- @options[:template] = template if @options[:template]
122
-
123
- render_options = {
124
- template: template, # weird: template needs to be set no matter because it
125
- # sets the name which is used in lookup_context.rb:209:in `normalize_name'
126
- layout: @options[:layout],
127
- assigns: controller_instance_variables,
128
- }
129
- types = %w[json inline plain file xml body action].map(&:to_sym)
130
- types.each do |type|
131
- render_options[type] = @options[type] if @options[type]
132
- end
133
-
134
- render_options
135
- end
136
-
139
+ # Pass controller instance variables from jets-based controller to ActionView scope
137
140
  def controller_instance_variables
138
141
  instance_vars = @controller.instance_variables.inject({}) do |vars, v|
139
142
  k = v.to_s.sub(/^@/,'') # @var => var
@@ -141,6 +144,9 @@ module Jets::Controller::Rendering
141
144
  vars
142
145
  end
143
146
  instance_vars[:event] = event
147
+ # jets internal variables
148
+ # So ActionView has access back to the jets controller
149
+ instance_vars[:_jets] = { controller: @controller }
144
150
  instance_vars
145
151
  end
146
152
 
@@ -199,7 +205,8 @@ module Jets::Controller::Rendering
199
205
  # Assign local variable because scope in the `:action_view do` block changes
200
206
  app_helper_classes = find_app_helper_classes
201
207
  ActiveSupport.on_load :action_view do
202
- include ApplicationHelper # include first
208
+ include Jets::Router::Helpers # internal routes helpers
209
+ include ApplicationHelper # include first
203
210
  app_helper_classes.each do |helper_class|
204
211
  include helper_class
205
212
  end
@@ -8,7 +8,8 @@ class Jets::Controller
8
8
  return @url unless add_stage?
9
9
 
10
10
  stage_name = Jets::Resource::ApiGateway::Deployment.stage_name
11
- "/#{stage_name}#{@url}"
11
+ stage_name_with_slashes = "/#{stage_name}/" # use to prevent stage name being added twice if url_for is called twice on the same string
12
+ @url.include?(stage_name_with_slashes) ? @url : "/#{stage_name}#{@url}"
12
13
  end
13
14
 
14
15
  def add_stage?
@@ -1,21 +1,85 @@
1
1
  # Piggy back off of Rails Generators.
2
2
  class Jets::Generator
3
- def self.invoke(generator, *args)
4
- new(generator, *args).run(:invoke)
5
- end
3
+ class << self
4
+ def invoke(generator, *args)
5
+ new(generator, *args).run(:invoke)
6
+ end
7
+
8
+ def revoke(generator, *args)
9
+ new(generator, *args).run(:revoke)
10
+ end
11
+
12
+ def help(args=ARGV)
13
+ require_generators
14
+
15
+ # `jets generate -h` results in:
16
+ #
17
+ # args = ["generate", "-h"]
18
+ #
19
+ args = args[1..-1] || []
20
+ help_flags = Thor::HELP_MAPPINGS + ["help"]
21
+ args.pop if help_flags.include?(args.last)
22
+ subcommand = args[0]
23
+
24
+ out = capture_stdout do
25
+ if subcommand
26
+ # Using invoke because it ensure the generator is configured properly
27
+ invoke(subcommand) # sub-level: jets generate scaffold -h
28
+ else
29
+ puts Jets::Commands::Help.text(:generate) # to trigger the regular Thor help
30
+ # Note: How to call the original top-level help menu from Rails. Keeping around in case its useful later:
31
+ # Rails::Generators.help # top-level: jets generate -h
32
+ end
33
+ end
34
+ out.gsub('rails','jets').gsub('Rails','Jets')
35
+ end
6
36
 
7
- def self.revoke(generator, *args)
8
- new(generator, *args).run(:revoke)
37
+ def capture_stdout
38
+ stdout_old = $stdout
39
+ io = StringIO.new
40
+ $stdout = io
41
+ yield
42
+ $stdout = stdout_old
43
+ io.string
44
+ end
45
+
46
+ def require_generators
47
+ # lazy require so Rails const is only defined when using generators
48
+ require "rails/generators"
49
+ require "rails/configuration"
50
+ require_active_job_generator
51
+ end
52
+
53
+ def require_active_job_generator
54
+ require "active_job"
55
+ require "rails/generators/job/job_generator"
56
+ # Override the source_root
57
+ Rails::Generators::JobGenerator.class_eval do
58
+ def self.source_root
59
+ File.expand_path("../generator/templates/active_job/job/templates", __FILE__)
60
+ end
61
+ end
62
+ end
9
63
  end
10
64
 
11
65
  def initialize(generator, *args)
12
66
  @generator, @args = generator, args
67
+ @args << '--pretend' if noop?
68
+ end
69
+
70
+ # Used to delegate noop option to Rails generator pretend option. Both work:
71
+ #
72
+ # jets generate scaffold user title:string --noop
73
+ # jets generate scaffold user title:string --pretend
74
+ #
75
+ # Grabbing directly from the ARGV because think its cleaner than passing options from
76
+ # Thor all the way down.
77
+ def noop?
78
+ ARGV.include?('--noop')
13
79
  end
14
80
 
15
81
  def run(behavior=:invoke)
16
- # lazy require so Rails const is only defined when using generators
17
- require "rails/generators"
18
- require "rails/configuration"
82
+ self.class.require_generators
19
83
  Rails::Generators.configure!(config)
20
84
  Rails::Generators.invoke(@generator, @args, behavior: behavior, destination_root: Jets.root)
21
85
  end
@@ -0,0 +1,6 @@
1
+ <% module_namespacing do -%>
2
+ class ApplicationJob < Jets::Job::Base
3
+ # Adjust to increase the default timeout for all Job classes
4
+ class_timeout 60
5
+ end
6
+ <% end -%>
@@ -0,0 +1,8 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %>Job < ApplicationJob
3
+ rate "10 hours" # every 10 hours
4
+ def dig
5
+ puts "done digging"
6
+ end
7
+ end
8
+ <% end -%>
@@ -1,17 +1,12 @@
1
- <%% editing = @event["path"].include?("edit") %>
2
- <%% action = editing ? "/<%= plural_table_name %>/#{<%= singular_table_name %>.id}" : "/<%= plural_table_name %>" %>
3
- <%%= form_tag(action) do %>
4
- <%% if editing -%>
5
- <input type="hidden" name="_method" value="put" />
6
- <%% end -%>
1
+ <%%= form_with(model: <%= model_resource_name %>, local: true) do |form| %>
7
2
  <%% if <%= singular_table_name %>.errors.any? %>
8
3
  <div id="error_explanation">
9
4
  <h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>
10
5
 
11
6
  <ul>
12
- <%% <%= singular_table_name %>.errors.full_messages.each do |message| %>
13
- <li><%%= message %></li>
14
- <%% end %>
7
+ <%% <%= singular_table_name %>.errors.full_messages.each do |message| %>
8
+ <li><%%= message %></li>
9
+ <%% end %>
15
10
  </ul>
16
11
  </div>
17
12
  <%% end %>
@@ -19,21 +14,21 @@
19
14
  <% attributes.each do |attribute| -%>
20
15
  <div class="field">
21
16
  <% if attribute.password_digest? -%>
22
- <%%= label_tag :password %>
23
- <%%= password_field_tag :password, <%= singular_table_name %>.<%= attribute.column_name %> %>
17
+ <%%= form.label :password %>
18
+ <%%= form.password_field :password %>
24
19
  </div>
25
20
 
26
21
  <div class="field">
27
- <%%= label_tag :password_confirmation %>
28
- <%%= password_field_tag :password_confirmation, <%= singular_table_name %>.<%= attribute.column_name %> %>
22
+ <%%= form.label :password_confirmation %>
23
+ <%%= form.password_field :password_confirmation %>
29
24
  <% else -%>
30
- <%%= label_tag :<%= attribute.column_name %> %>
31
- <%%= <%= attribute.field_type %>_tag "<%= "#{singular_table_name}[#{attribute.column_name}]" %>"<%= ", 'yes'" if attribute.field_type =~ /check_box/ %>, <%= singular_table_name %>.<%= attribute.column_name %> %>
25
+ <%%= form.label :<%= attribute.column_name %> %>
26
+ <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %> %>
32
27
  <% end -%>
33
28
  </div>
34
29
 
35
30
  <% end -%>
36
31
  <div class="actions">
37
- <%%= submit_tag("Submit") %>
32
+ <%%= form.submit %>
38
33
  </div>
39
34
  <%% end %>