tapioca 0.4.10 → 0.4.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +3 -2
- data/lib/tapioca/compilers/dsl/action_controller_helpers.rb +38 -39
- data/lib/tapioca/compilers/dsl/action_mailer.rb +4 -4
- data/lib/tapioca/compilers/dsl/active_record_associations.rb +80 -48
- data/lib/tapioca/compilers/dsl/active_record_columns.rb +60 -65
- data/lib/tapioca/compilers/dsl/active_record_enum.rb +27 -23
- data/lib/tapioca/compilers/dsl/active_record_scope.rb +18 -17
- data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +8 -7
- data/lib/tapioca/compilers/dsl/active_resource.rb +5 -4
- data/lib/tapioca/compilers/dsl/active_support_current_attributes.rb +6 -7
- data/lib/tapioca/compilers/dsl/base.rb +1 -1
- data/lib/tapioca/compilers/dsl/frozen_record.rb +25 -25
- data/lib/tapioca/compilers/dsl/{active_record_identity_cache.rb → identity_cache.rb} +11 -10
- data/lib/tapioca/compilers/dsl/protobuf.rb +1 -1
- data/lib/tapioca/compilers/dsl/sidekiq_worker.rb +83 -0
- data/lib/tapioca/compilers/dsl/smart_properties.rb +1 -1
- data/lib/tapioca/compilers/dsl/state_machines.rb +75 -73
- data/lib/tapioca/compilers/dsl/url_helpers.rb +9 -5
- data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +69 -40
- data/lib/tapioca/gemfile.rb +30 -22
- data/lib/tapioca/generator.rb +4 -0
- data/lib/tapioca/version.rb +1 -1
- metadata +18 -3
@@ -15,8 +15,7 @@ module Tapioca
|
|
15
15
|
module Compilers
|
16
16
|
module Dsl
|
17
17
|
# `Tapioca::Compilers::Dsl::ActiveRecordEnum` decorates RBI files for subclasses of
|
18
|
-
# `ActiveRecord::Base` which declare `enum` fields
|
19
|
-
# (see https://api.rubyonrails.org/classes/ActiveRecord/Enum.html).
|
18
|
+
# `ActiveRecord::Base` which declare [`enum` fields](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html).
|
20
19
|
#
|
21
20
|
# For example, with the following `ActiveRecord::Base` subclass:
|
22
21
|
#
|
@@ -32,26 +31,30 @@ module Tapioca
|
|
32
31
|
# # post.rbi
|
33
32
|
# # typed: true
|
34
33
|
# class Post
|
35
|
-
#
|
36
|
-
# def all_title!; end
|
34
|
+
# include EnumMethodsModule
|
37
35
|
#
|
38
|
-
#
|
39
|
-
#
|
36
|
+
# module EnumMethodsModule
|
37
|
+
# sig { void }
|
38
|
+
# def all_title!; end
|
40
39
|
#
|
41
|
-
#
|
42
|
-
#
|
40
|
+
# sig { returns(T::Boolean) }
|
41
|
+
# def all_title?; end
|
43
42
|
#
|
44
|
-
#
|
45
|
-
#
|
43
|
+
# sig { returns(T::Hash[T.any(String, Symbol), Integer]) }
|
44
|
+
# def self.title_types; end
|
46
45
|
#
|
47
|
-
#
|
48
|
-
#
|
46
|
+
# sig { void }
|
47
|
+
# def book_title!; end
|
49
48
|
#
|
50
|
-
#
|
51
|
-
#
|
49
|
+
# sig { returns(T::Boolean) }
|
50
|
+
# def book_title?; end
|
52
51
|
#
|
53
|
-
#
|
54
|
-
#
|
52
|
+
# sig { void }
|
53
|
+
# def web_title!; end
|
54
|
+
#
|
55
|
+
# sig { returns(T::Boolean) }
|
56
|
+
# def web_title?; end
|
57
|
+
# end
|
55
58
|
# end
|
56
59
|
# ~~~
|
57
60
|
class ActiveRecordEnum < Base
|
@@ -61,17 +64,18 @@ module Tapioca
|
|
61
64
|
def decorate(root, constant)
|
62
65
|
return if constant.defined_enums.empty?
|
63
66
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
67
|
+
root.path(constant) do |model|
|
68
|
+
module_name = "EnumMethodsModule"
|
69
|
+
|
70
|
+
model.create_module(module_name) do |mod|
|
71
|
+
generate_instance_methods(constant, mod)
|
72
|
+
end
|
68
73
|
|
69
|
-
|
70
|
-
k.create_include(module_name)
|
74
|
+
model.create_include(module_name)
|
71
75
|
|
72
76
|
constant.defined_enums.each do |name, enum_map|
|
73
77
|
type = type_for_enum(enum_map)
|
74
|
-
create_method(
|
78
|
+
create_method(model, name.pluralize, class_method: true, return_type: type)
|
75
79
|
end
|
76
80
|
end
|
77
81
|
end
|
@@ -12,9 +12,9 @@ end
|
|
12
12
|
module Tapioca
|
13
13
|
module Compilers
|
14
14
|
module Dsl
|
15
|
-
# `Tapioca::Compilers::Dsl::ActiveRecordScope` decorates RBI files for
|
16
|
-
# `ActiveRecord::Base` which declare
|
17
|
-
# (
|
15
|
+
# `Tapioca::Compilers::Dsl::ActiveRecordScope` decorates RBI files for
|
16
|
+
# subclasses of `ActiveRecord::Base` which declare
|
17
|
+
# [`scope` fields](https://api.rubyonrails.org/classes/ActiveRecord/Scoping/Named/ClassMethods.html#method-i-scope).
|
18
18
|
#
|
19
19
|
# For example, with the following `ActiveRecord::Base` subclass:
|
20
20
|
#
|
@@ -31,15 +31,15 @@ module Tapioca
|
|
31
31
|
# # post.rbi
|
32
32
|
# # typed: true
|
33
33
|
# class Post
|
34
|
-
# extend
|
35
|
-
# end
|
34
|
+
# extend GeneratedRelationMethods
|
36
35
|
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
36
|
+
# module GeneratedRelationMethods
|
37
|
+
# sig { params(args: T.untyped, blk: T.untyped).returns(T.untyped) }
|
38
|
+
# def private_kind(*args, &blk); end
|
40
39
|
#
|
41
|
-
#
|
42
|
-
#
|
40
|
+
# sig { params(args: T.untyped, blk: T.untyped).returns(T.untyped) }
|
41
|
+
# def public_kind(*args, &blk); end
|
42
|
+
# end
|
43
43
|
# end
|
44
44
|
# ~~~
|
45
45
|
class ActiveRecordScope < Base
|
@@ -55,15 +55,16 @@ module Tapioca
|
|
55
55
|
scope_method_names = constant.send(:generated_relation_methods).instance_methods(false)
|
56
56
|
return if scope_method_names.empty?
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
58
|
+
root.path(constant) do |model|
|
59
|
+
module_name = "GeneratedRelationMethods"
|
60
|
+
|
61
|
+
model.create_module(module_name) do |mod|
|
62
|
+
scope_method_names.each do |scope_method|
|
63
|
+
generate_scope_method(scope_method, mod)
|
64
|
+
end
|
62
65
|
end
|
63
|
-
end
|
64
66
|
|
65
|
-
|
66
|
-
k.create_extend(module_name)
|
67
|
+
model.create_extend(module_name)
|
67
68
|
end
|
68
69
|
end
|
69
70
|
|
@@ -15,8 +15,8 @@ end
|
|
15
15
|
module Tapioca
|
16
16
|
module Compilers
|
17
17
|
module Dsl
|
18
|
-
# `Tapioca::Compilers::DSL::ActiveRecordTypedStore` generates RBI files for
|
19
|
-
# `ActiveRecord::TypedStore`
|
18
|
+
# `Tapioca::Compilers::DSL::ActiveRecordTypedStore` generates RBI files for Active Record models that use
|
19
|
+
# [`ActiveRecord::TypedStore`](https://github.com/byroot/activerecord-typedstore) features.
|
20
20
|
#
|
21
21
|
# For example, with the following ActiveRecord class:
|
22
22
|
#
|
@@ -101,13 +101,14 @@ module Tapioca
|
|
101
101
|
stores = constant.typed_stores
|
102
102
|
return if stores.values.flat_map(&:accessors).empty?
|
103
103
|
|
104
|
-
root.path(constant) do |
|
104
|
+
root.path(constant) do |model|
|
105
105
|
stores.values.each do |store_data|
|
106
106
|
store_data.accessors.each do |accessor|
|
107
107
|
field = store_data.fields[accessor]
|
108
108
|
type = type_for(field.type_sym)
|
109
109
|
type = "T.nilable(#{type})" if field.null && type != "T.untyped"
|
110
|
-
|
110
|
+
|
111
|
+
generate_methods(model, field.name.to_s, type)
|
111
112
|
end
|
112
113
|
end
|
113
114
|
end
|
@@ -141,13 +142,13 @@ module Tapioca
|
|
141
142
|
|
142
143
|
sig do
|
143
144
|
params(
|
145
|
+
klass: Parlour::RbiGenerator::Namespace,
|
144
146
|
name: String,
|
145
|
-
type: String
|
146
|
-
klass: Parlour::RbiGenerator::Namespace
|
147
|
+
type: String
|
147
148
|
)
|
148
149
|
.void
|
149
150
|
end
|
150
|
-
def generate_methods(name, type
|
151
|
+
def generate_methods(klass, name, type)
|
151
152
|
klass.create_method(
|
152
153
|
"#{name}=",
|
153
154
|
parameters: [Parlour::RbiGenerator::Parameter.new(name, type: type)],
|
@@ -13,8 +13,8 @@ module Tapioca
|
|
13
13
|
module Compilers
|
14
14
|
module Dsl
|
15
15
|
# `Tapioca::Compilers::Dsl::ActiveResource` decorates RBI files for subclasses of
|
16
|
-
# `ActiveResource::Base` which declare
|
17
|
-
#
|
16
|
+
# [`ActiveResource::Base`](https://github.com/rails/activeresource) which declare
|
17
|
+
# `schema` fields.
|
18
18
|
#
|
19
19
|
# For example, with the following `ActiveResource::Base` subclass:
|
20
20
|
#
|
@@ -71,9 +71,10 @@ module Tapioca
|
|
71
71
|
end
|
72
72
|
def decorate(root, constant)
|
73
73
|
return if constant.schema.blank?
|
74
|
-
|
74
|
+
|
75
|
+
root.path(constant) do |resource|
|
75
76
|
constant.schema.each do |attribute, type|
|
76
|
-
create_schema_methods(
|
77
|
+
create_schema_methods(resource, attribute, type)
|
77
78
|
end
|
78
79
|
end
|
79
80
|
end
|
@@ -13,9 +13,8 @@ module Tapioca
|
|
13
13
|
module Compilers
|
14
14
|
module Dsl
|
15
15
|
# `Tapioca::Compilers::Dsl::ActiveSupportCurrentAttributes` decorates RBI files for all
|
16
|
-
# subclasses of
|
17
|
-
#
|
18
|
-
# To add attributes see https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html
|
16
|
+
# subclasses of
|
17
|
+
# [`ActiveSupport::CurrentAttributes`](https://api.rubyonrails.org/classes/ActiveSupport/CurrentAttributes.html).
|
19
18
|
#
|
20
19
|
# For example, with the following singleton class
|
21
20
|
#
|
@@ -76,20 +75,20 @@ module Tapioca
|
|
76
75
|
instance_methods = instance_methods_for(constant) - dynamic_methods
|
77
76
|
return if dynamic_methods.empty? && instance_methods.empty?
|
78
77
|
|
79
|
-
root.path(constant) do |
|
78
|
+
root.path(constant) do |current_attributes|
|
80
79
|
dynamic_methods.each do |method|
|
81
80
|
method = method.to_s
|
82
81
|
# We want to generate each method both on the class
|
83
|
-
generate_method(
|
82
|
+
generate_method(current_attributes, method, class_method: true)
|
84
83
|
# and on the instance
|
85
|
-
generate_method(
|
84
|
+
generate_method(current_attributes, method, class_method: false)
|
86
85
|
end
|
87
86
|
|
88
87
|
instance_methods.each do |method|
|
89
88
|
# instance methods are only elevated to class methods
|
90
89
|
# no need to add separate instance methods for them
|
91
90
|
method = constant.instance_method(method)
|
92
|
-
create_method_from_def(
|
91
|
+
create_method_from_def(current_attributes, method, class_method: true)
|
93
92
|
end
|
94
93
|
end
|
95
94
|
end
|
@@ -61,7 +61,7 @@ module Tapioca
|
|
61
61
|
end
|
62
62
|
def create_method(namespace, name, options = {})
|
63
63
|
return unless valid_method_name?(name)
|
64
|
-
T.unsafe(namespace).create_method(name, options)
|
64
|
+
T.unsafe(namespace).create_method(name, **options)
|
65
65
|
end
|
66
66
|
|
67
67
|
# Create a Parlour method inside `namespace` from its Ruby definition
|
@@ -12,8 +12,8 @@ end
|
|
12
12
|
module Tapioca
|
13
13
|
module Compilers
|
14
14
|
module Dsl
|
15
|
-
# `Tapioca::Compilers::Dsl::FrozenRecord` generates RBI files for subclasses of
|
16
|
-
# (
|
15
|
+
# `Tapioca::Compilers::Dsl::FrozenRecord` generates RBI files for subclasses of
|
16
|
+
# [`FrozenRecord::Base`](https://github.com/byroot/frozen_record).
|
17
17
|
#
|
18
18
|
# For example, with the following FrozenRecord class:
|
19
19
|
#
|
@@ -41,27 +41,27 @@ module Tapioca
|
|
41
41
|
# # Student.rbi
|
42
42
|
# # typed: strong
|
43
43
|
# class Student
|
44
|
-
# include
|
45
|
-
# end
|
44
|
+
# include FrozenRecordAttributeMethods
|
46
45
|
#
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
46
|
+
# module FrozenRecordAttributeMethods
|
47
|
+
# sig { returns(T.untyped) }
|
48
|
+
# def first_name; end
|
50
49
|
#
|
51
|
-
#
|
52
|
-
#
|
50
|
+
# sig { returns(T::Boolean) }
|
51
|
+
# def first_name?; end
|
53
52
|
#
|
54
|
-
#
|
55
|
-
#
|
53
|
+
# sig { returns(T.untyped) }
|
54
|
+
# def id; end
|
56
55
|
#
|
57
|
-
#
|
58
|
-
#
|
56
|
+
# sig { returns(T::Boolean) }
|
57
|
+
# def id?; end
|
59
58
|
#
|
60
|
-
#
|
61
|
-
#
|
59
|
+
# sig { returns(T.untyped) }
|
60
|
+
# def last_name; end
|
62
61
|
#
|
63
|
-
#
|
64
|
-
#
|
62
|
+
# sig { returns(T::Boolean) }
|
63
|
+
# def last_name?; end
|
64
|
+
# end
|
65
65
|
# end
|
66
66
|
# ~~~
|
67
67
|
class FrozenRecord < Base
|
@@ -72,17 +72,17 @@ module Tapioca
|
|
72
72
|
attributes = constant.attributes
|
73
73
|
return if attributes.empty?
|
74
74
|
|
75
|
-
|
75
|
+
root.path(constant) do |record|
|
76
|
+
module_name = "FrozenRecordAttributeMethods"
|
76
77
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
78
|
+
record.create_module(module_name) do |mod|
|
79
|
+
attributes.each do |attribute|
|
80
|
+
create_method(mod, "#{attribute}?", return_type: 'T::Boolean')
|
81
|
+
create_method(mod, attribute.to_s, return_type: 'T.untyped')
|
82
|
+
end
|
81
83
|
end
|
82
|
-
end
|
83
84
|
|
84
|
-
|
85
|
-
klass.create_include(module_name)
|
85
|
+
record.create_include(module_name)
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
@@ -15,11 +15,12 @@ end
|
|
15
15
|
module Tapioca
|
16
16
|
module Compilers
|
17
17
|
module Dsl
|
18
|
-
# `Tapioca::Compilers::DSL::
|
18
|
+
# `Tapioca::Compilers::DSL::IdentityCache` generates RBI files for Active Record models
|
19
19
|
# that use `include IdentityCache`.
|
20
|
-
# `IdentityCache` is a blob level caching solution
|
20
|
+
# [`IdentityCache`](https://github.com/Shopify/identity_cache) is a blob level caching solution
|
21
|
+
# to plug into Active Record.
|
21
22
|
#
|
22
|
-
# For example, with the following
|
23
|
+
# For example, with the following Active Record class:
|
23
24
|
#
|
24
25
|
# ~~~rb
|
25
26
|
# # post.rb
|
@@ -61,7 +62,7 @@ module Tapioca
|
|
61
62
|
# def fetch_by_title_and_review_date(title, review_date, includes: nil); end
|
62
63
|
# end
|
63
64
|
# ~~~
|
64
|
-
class
|
65
|
+
class IdentityCache < Base
|
65
66
|
extend T::Sig
|
66
67
|
|
67
68
|
COLLECTION_TYPE = T.let(
|
@@ -82,25 +83,25 @@ module Tapioca
|
|
82
83
|
cache_indexes = constant.send(:cache_indexes)
|
83
84
|
return if caches.empty? && cache_indexes.empty?
|
84
85
|
|
85
|
-
root.path(constant) do |
|
86
|
+
root.path(constant) do |model|
|
86
87
|
cache_manys = constant.send(:cached_has_manys)
|
87
88
|
cache_ones = constant.send(:cached_has_ones)
|
88
89
|
cache_belongs = constant.send(:cached_belongs_tos)
|
89
90
|
|
90
91
|
cache_indexes.each do |field|
|
91
|
-
create_fetch_by_methods(field,
|
92
|
+
create_fetch_by_methods(field, model, constant)
|
92
93
|
end
|
93
94
|
|
94
95
|
cache_manys.values.each do |field|
|
95
|
-
create_fetch_field_methods(field,
|
96
|
+
create_fetch_field_methods(field, model, returns_collection: true)
|
96
97
|
end
|
97
98
|
|
98
99
|
cache_ones.values.each do |field|
|
99
|
-
create_fetch_field_methods(field,
|
100
|
+
create_fetch_field_methods(field, model, returns_collection: false)
|
100
101
|
end
|
101
102
|
|
102
103
|
cache_belongs.values.each do |field|
|
103
|
-
create_fetch_field_methods(field,
|
104
|
+
create_fetch_field_methods(field, model, returns_collection: false)
|
104
105
|
end
|
105
106
|
end
|
106
107
|
end
|
@@ -108,7 +109,7 @@ module Tapioca
|
|
108
109
|
sig { override.returns(T::Enumerable[Module]) }
|
109
110
|
def gather_constants
|
110
111
|
::ActiveRecord::Base.descendants.select do |klass|
|
111
|
-
klass < IdentityCache::WithoutPrimaryIndex
|
112
|
+
klass < ::IdentityCache::WithoutPrimaryIndex
|
112
113
|
end
|
113
114
|
end
|
114
115
|
|
@@ -12,7 +12,7 @@ module Tapioca
|
|
12
12
|
module Compilers
|
13
13
|
module Dsl
|
14
14
|
# `Tapioca::Compilers::Dsl::Protobuf` decorates RBI files for subclasses of
|
15
|
-
# `Google::Protobuf::MessageExts`
|
15
|
+
# [`Google::Protobuf::MessageExts`](https://github.com/protocolbuffers/protobuf/tree/master/ruby).
|
16
16
|
#
|
17
17
|
# For example, with the following "cart.rb" file:
|
18
18
|
#
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "parlour"
|
5
|
+
|
6
|
+
begin
|
7
|
+
require "sidekiq"
|
8
|
+
rescue LoadError
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
module Tapioca
|
13
|
+
module Compilers
|
14
|
+
module Dsl
|
15
|
+
# `Tapioca::Compilers::Dsl::SidekiqWorker` generates RBI files classes that include
|
16
|
+
# [`Sidekiq::Worker`](https://github.com/mperham/sidekiq/wiki/Getting-Started).
|
17
|
+
#
|
18
|
+
# For example, with the following class that includes `Sidekiq::Worker`:
|
19
|
+
#
|
20
|
+
# ~~~rb
|
21
|
+
# class NotifierWorker
|
22
|
+
# include Sidekiq::Worker
|
23
|
+
# def perform(customer_id)
|
24
|
+
# # ...
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
# ~~~
|
28
|
+
#
|
29
|
+
# this generator will produce the RBI file `notifier_worker.rbi` with the following content:
|
30
|
+
#
|
31
|
+
# ~~~rbi
|
32
|
+
# # notifier_worker.rbi
|
33
|
+
# # typed: true
|
34
|
+
# class NotifierWorker
|
35
|
+
# sig { params(customer_id: T.untyped).returns(String) }
|
36
|
+
# def self.perform_async(customer_id); end
|
37
|
+
#
|
38
|
+
# sig { params(interval: T.any(DateTime, Time), customer_id: T.untyped).returns(String) }
|
39
|
+
# def self.perform_at(interval, customer_id); end
|
40
|
+
#
|
41
|
+
# sig { params(interval: Numeric, customer_id: T.untyped).returns(String) }
|
42
|
+
# def self.perform_in(interval, customer_id); end
|
43
|
+
# end
|
44
|
+
# ~~~
|
45
|
+
class SidekiqWorker < Base
|
46
|
+
extend T::Sig
|
47
|
+
|
48
|
+
sig { override.params(root: Parlour::RbiGenerator::Namespace, constant: T.class_of(::Sidekiq::Worker)).void }
|
49
|
+
def decorate(root, constant)
|
50
|
+
return unless constant.instance_methods.include?(:perform)
|
51
|
+
|
52
|
+
root.path(constant) do |worker|
|
53
|
+
method_def = constant.instance_method(:perform)
|
54
|
+
|
55
|
+
async_params = compile_method_parameters_to_parlour(method_def)
|
56
|
+
|
57
|
+
# `perform_at` and is just an alias for `perform_in` so both methods technically
|
58
|
+
# accept a datetime, time, or numeric but we're typing them differently so they
|
59
|
+
# semantically make sense.
|
60
|
+
at_params = [
|
61
|
+
Parlour::RbiGenerator::Parameter.new('interval', type: 'T.any(DateTime, Time)'),
|
62
|
+
*async_params,
|
63
|
+
]
|
64
|
+
in_params = [
|
65
|
+
Parlour::RbiGenerator::Parameter.new('interval', type: 'Numeric'),
|
66
|
+
*async_params,
|
67
|
+
]
|
68
|
+
|
69
|
+
create_method(worker, 'perform_async', parameters: async_params, return_type: 'String', class_method: true)
|
70
|
+
create_method(worker, 'perform_at', parameters: at_params, return_type: 'String', class_method: true)
|
71
|
+
create_method(worker, 'perform_in', parameters: in_params, return_type: 'String', class_method: true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
sig { override.returns(T::Enumerable[Module]) }
|
76
|
+
def gather_constants
|
77
|
+
classes = T.cast(ObjectSpace.each_object(Class), T::Enumerable[Class])
|
78
|
+
classes.select { |c| c < Sidekiq::Worker }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|