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
@@ -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 %>