Skip to content

Commit 135194c

Browse files
committed
Support signature generation for ActionMailer
ActionMailer automatically generates class methods from the instance methods user defined via method_missing. For example, `AccountMailer.welcome_mail` will be generated from `AccountMail#welcome_mail` that is defined by users. This starts to support signature generation for auto-generated class methods for mailer classes dynamically.
1 parent 5c94b64 commit 135194c

File tree

13 files changed

+193
-4
lines changed

13 files changed

+193
-4
lines changed

lib/rbs_rails.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
require_relative "rbs_rails/version"
66
require_relative "rbs_rails/util"
7+
require_relative 'rbs_rails/action_mailer'
78
require_relative 'rbs_rails/active_record'
89
require_relative 'rbs_rails/path_helpers'
910
require_relative 'rbs_rails/dependency_builder'

lib/rbs_rails/action_mailer.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
module RbsRails
2+
module ActionMailer
3+
def self.class_to_rbs(klass)
4+
Generator.new(klass).generate
5+
end
6+
7+
class Generator
8+
def initialize(klass)
9+
@klass = klass
10+
@klass_name = Util.module_name(klass, abs: false)
11+
end
12+
13+
def generate
14+
Util.format_rbs klass_decl
15+
end
16+
17+
private def klass_decl
18+
<<~RBS
19+
#{header}
20+
#{methods}
21+
#{footer}
22+
RBS
23+
end
24+
25+
private def header
26+
namespace = +''
27+
klass_name(abs: false).split('::').map do |mod_name|
28+
namespace += "::#{mod_name}"
29+
mod_object = Object.const_get(namespace)
30+
case mod_object
31+
when Class
32+
# @type var superclass: Class
33+
superclass = _ = mod_object.superclass
34+
superclass_name = Util.module_name(superclass, abs: false)
35+
36+
"class #{mod_name} < ::#{superclass_name}"
37+
when Module
38+
"module #{mod_name}"
39+
else
40+
raise 'unreachable'
41+
end
42+
end.join("\n")
43+
end
44+
45+
private def methods
46+
klass.action_methods.map do |method_name|
47+
"def self.#{method_name}: (*untyped) -> ActionMailer::MessageDelivery"
48+
end.join("\n")
49+
end
50+
51+
private def footer
52+
"end\n" * klass_name(abs: false).split('::').size
53+
end
54+
55+
private def klass_name(abs: true)
56+
abs ? "::#{@klass_name}" : @klass_name
57+
end
58+
59+
private
60+
attr_reader :klass
61+
end
62+
end
63+
end

lib/rbs_rails/rake_task.rb

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ def initialize(name = :rbs_rails, &block)
1515
block.call(self) if block
1616

1717
def_generate_rbs_for_models
18+
def_generate_rbs_for_mailers
1819
def_generate_rbs_for_path_helpers
1920
def_all
2021
end
2122

2223
def def_all
2324
desc 'Run all tasks of rbs_rails'
2425

25-
deps = [:"#{name}:generate_rbs_for_models", :"#{name}:generate_rbs_for_path_helpers"]
26+
deps = [:"#{name}:generate_rbs_for_models", :"#{name}:generate_rbs_for_mailers", :"#{name}:generate_rbs_for_path_helpers"]
2627
task("#{name}:all": deps)
2728
end
2829

@@ -34,7 +35,7 @@ def def_generate_rbs_for_models
3435
Rails.application.eager_load!
3536

3637
dep_builder = DependencyBuilder.new
37-
38+
3839
::ActiveRecord::Base.descendants.each do |klass|
3940
next unless RbsRails::ActiveRecord.generatable?(klass)
4041
next if ignore_model_if&.call(klass)
@@ -53,6 +54,23 @@ def def_generate_rbs_for_models
5354
end
5455
end
5556

57+
def def_generate_rbs_for_mailers
58+
desc 'Generate RBS files for Active Mailer mailers'
59+
task("#{name}:generate_rbs_for_mailers": :environment) do
60+
require 'rbs_rails'
61+
62+
Rails.application.eager_load!
63+
64+
::ActionMailer::Base.descendants.each do |klass|
65+
path = signature_root_dir / "app/mailers/#{klass.name.underscore}.rbs"
66+
path.dirname.mkpath
67+
68+
sig = RbsRails::ActionMailer.class_to_rbs(klass)
69+
path.write sig
70+
end
71+
end
72+
end
73+
5674
def def_generate_rbs_for_path_helpers
5775
desc 'Generate RBS files for path helpers'
5876
task("#{name}:generate_rbs_for_path_helpers": :environment) do

sig/rbs_rails/action_mailer.rbs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module RbsRails
2+
module ActionMailer
3+
def self.class_to_rbs: (untyped klass) -> String
4+
5+
class Generator
6+
@klass_name: String
7+
8+
def initialize: (singleton(ActionMailer::Base) klass) -> void
9+
10+
def generate: () -> String
11+
12+
def klass_decl: () -> String
13+
14+
def header: () -> String
15+
16+
def methods: () -> String
17+
18+
def footer: () -> String
19+
20+
def klass_name: (?abs: boolish) -> String
21+
22+
private
23+
24+
attr_reader klass: singleton(ActionMailer::Base)
25+
end
26+
end
27+
end

sig/rbs_rails/rake_task.rbs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ module RbsRails
1717

1818
def def_generate_rbs_for_models: () -> void
1919

20+
def def_generate_rbs_for_mailers: () -> void
21+
2022
def def_generate_rbs_for_path_helpers: () -> void
2123

2224
private

test/app/Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ gem 'bcrypt'
1010

1111
gem 'rbs_rails', path: '../../'
1212

13+
# to run Rails6.1 under Ruby 3.1+
14+
gem 'mail', '>= 2.8.0'
15+
1316
# to run Rails6.1 under Ruby 3.4
1417
gem 'base64'
1518
gem 'bigdecimal'

test/app/Gemfile.lock

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ GEM
7676
builder (3.2.4)
7777
concurrent-ruby (1.1.10)
7878
crass (1.0.6)
79+
date (3.3.4)
7980
drb (2.2.0)
8081
ruby2_keywords
8182
erubi (1.10.0)
@@ -86,15 +87,27 @@ GEM
8687
loofah (2.21.3)
8788
crass (~> 1.0.2)
8889
nokogiri (>= 1.12.0)
89-
mail (2.7.1)
90+
mail (2.8.1)
9091
mini_mime (>= 0.1.1)
92+
net-imap
93+
net-pop
94+
net-smtp
9195
marcel (1.0.1)
9296
method_source (1.0.0)
9397
mini_mime (1.0.3)
9498
mini_portile2 (2.8.2)
9599
minitest (5.17.0)
96100
msgpack (1.4.2)
97101
mutex_m (0.2.0)
102+
net-imap (0.4.10)
103+
date
104+
net-protocol
105+
net-pop (0.1.2)
106+
net-protocol
107+
net-protocol (0.2.2)
108+
timeout
109+
net-smtp (0.4.0.1)
110+
net-protocol
98111
nio4r (2.5.8)
99112
nokogiri (1.15.2)
100113
mini_portile2 (~> 2.8.2)
@@ -147,6 +160,7 @@ GEM
147160
sprockets (>= 3.0.0)
148161
sqlite3 (1.4.2)
149162
thor (1.1.0)
163+
timeout (0.4.1)
150164
tzinfo (2.0.5)
151165
concurrent-ruby (~> 1.0)
152166
websocket-driver (0.7.3)
@@ -163,6 +177,7 @@ DEPENDENCIES
163177
bigdecimal
164178
bootsnap
165179
drb
180+
mail (>= 2.8.0)
166181
mutex_m
167182
psych (< 4)
168183
puma
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class ApplicationMailer < ActionMailer::Base
2+
default from: '[email protected]'
3+
layout 'mailer'
4+
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
class UserMailer < ApplicationMailer
2+
default from: '[email protected]'
3+
4+
def welcome_email
5+
@user = params[:user]
6+
@url = 'http://example.com/login'
7+
mail(to: @user.email, subject: 'Welcome to My Awesome Site')
8+
end
9+
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<%= yield %>

0 commit comments

Comments
 (0)