boba 0.0.12 → 0.0.14
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.
- checksums.yaml +4 -4
- data/README.md +12 -6
- data/lib/boba/active_record/attribute_service.rb +35 -10
- data/lib/boba/active_record/reflection_service.rb +21 -25
- data/lib/boba/options/association_type_option.rb +50 -0
- data/lib/boba/relations_railtie.rb +19 -4
- data/lib/boba/version.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_associations_persisted.rb +5 -50
- data/lib/tapioca/dsl/compilers/active_record_columns_persisted.rb +1 -8
- data/lib/tapioca/dsl/compilers/active_record_relation_types.rb +67 -0
- data/lib/tapioca/dsl/compilers/attr_json.rb +5 -5
- data/lib/tapioca/dsl/compilers/kaminari.rb +85 -0
- data/lib/tapioca/dsl/compilers/money_rails.rb +41 -4
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb49413d3cfe01c4ea9d7cf283c50f03183f521e198b0af96e30fd0ec95fb7d4
|
4
|
+
data.tar.gz: 7f2dd02645cce05f8204a710b6a787e0aa7271fc902bea0f4c0e0174ffe7cf99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87d9819bf315f321c8aaf3e33f6681606cc980384b2a7f61ceda298097cc955a291e91a1b7ef7e0c1e2e3763570cf79d244e1eebfb07766404bb60283732c8bd
|
7
|
+
data.tar.gz: 5cd15d8a5d1a16eba4258240ce240c17063dc5b538dfb9346bf1209e5ab0ae18af667ca710b2e2c0e086261c9b110abc7f6dfac78122fbaacc567b53125afe82
|
data/README.md
CHANGED
@@ -36,7 +36,8 @@ If you'd like to use relation types in your sigs that are less broad than `Activ
|
|
36
36
|
gem 'boba'
|
37
37
|
```
|
38
38
|
|
39
|
-
The railtie will automatically define the `PrivateRelation`
|
39
|
+
The railtie will automatically define the `PrivateRelation`, `PrivateAssociationRelation`, and `PrivateCollectionProxy` constants on each model that inherits from `ActiveRecord::Base`. These are defined as their corresponding private `ActiveRecord` classes, so runtime type checking works as expected. They can then be used in typing, like so:
|
40
|
+
|
40
41
|
```ruby
|
41
42
|
class Post < ::ActiveRecord::Base
|
42
43
|
scope :recent -> { where('created_at > ?', Date.current) }
|
@@ -49,7 +50,7 @@ sig { params(author: Author).returns(Post::PrivateRelation) }
|
|
49
50
|
def posts_from_author(author); end
|
50
51
|
```
|
51
52
|
|
52
|
-
and the following should not raise
|
53
|
+
and the following should not raise a Sorbet error:
|
53
54
|
|
54
55
|
```ruby
|
55
56
|
sig { params(author: Author).returns(Post::PrivateRelation) }
|
@@ -58,6 +59,15 @@ def recent_posts_from_author(author)
|
|
58
59
|
end
|
59
60
|
```
|
60
61
|
|
62
|
+
Boba also defines a type alias `RelationType` on each such class, which is defined as the union of the three relation types. This is useful because the relation types are often used interchangeably and so you may expect to return or pass any of the three classes as an argument. To use this, you will also need to use the `ActiveRecordRelationTypes` compiler to generate the type alias in the signatures as well (or define them manually in shims).
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
sig { params(author: Author).returns(Post::RelationType) }
|
66
|
+
def recent_posts_from_author(author)
|
67
|
+
posts_from_author(author).recent
|
68
|
+
end
|
69
|
+
```
|
70
|
+
|
61
71
|
## Contributing
|
62
72
|
|
63
73
|
Bugs and feature requests are welcome and should be [filed as issues on github](https://github.com/angellist/boba/issues).
|
@@ -71,7 +81,3 @@ Since Boba is intended to be used alongside Tapioca and the compilers provided b
|
|
71
81
|
Contributed compilers should be well documented, and named after and include a link or reference to the Gem, DSL, or other module they implement RBIs for.
|
72
82
|
|
73
83
|
Compilers for Gems, DSLs, or modules that are not publicly available will not be accepted.
|
74
|
-
|
75
|
-
## Todo
|
76
|
-
|
77
|
-
1. Specs & spec harness
|
@@ -7,15 +7,22 @@ module Boba
|
|
7
7
|
class << self
|
8
8
|
extend T::Sig
|
9
9
|
|
10
|
-
sig
|
11
|
-
|
12
|
-
|
10
|
+
sig do
|
11
|
+
params(
|
12
|
+
constant: T.class_of(::ActiveRecord::Base),
|
13
|
+
attribute: String,
|
14
|
+
column_name: String,
|
15
|
+
).returns(T::Boolean)
|
16
|
+
end
|
17
|
+
def nilable_attribute?(constant, attribute, column_name: attribute)
|
18
|
+
return false if has_non_null_database_constraint?(constant, column_name)
|
13
19
|
|
14
|
-
constant
|
15
|
-
|
20
|
+
!has_unconditional_presence_validator?(constant, attribute)
|
21
|
+
end
|
16
22
|
|
17
|
-
|
18
|
-
|
23
|
+
sig { params(constant: T.class_of(::ActiveRecord::Base), column_name: String).returns(T::Boolean) }
|
24
|
+
def virtual_attribute?(constant, column_name)
|
25
|
+
constant.columns_hash[column_name].nil?
|
19
26
|
end
|
20
27
|
|
21
28
|
sig { params(constant: T.class_of(::ActiveRecord::Base), column_name: String).returns(T::Boolean) }
|
@@ -28,9 +35,27 @@ module Boba
|
|
28
35
|
false
|
29
36
|
end
|
30
37
|
|
31
|
-
sig { params(constant: T.class_of(::ActiveRecord::Base),
|
32
|
-
def
|
33
|
-
constant.
|
38
|
+
sig { params(constant: T.class_of(::ActiveRecord::Base), attribute: String).returns(T::Boolean) }
|
39
|
+
def has_unconditional_presence_validator?(constant, attribute)
|
40
|
+
return false unless constant.respond_to?(:validators_on)
|
41
|
+
|
42
|
+
constant.validators_on(attribute).any? do |validator|
|
43
|
+
unconditional_presence_validator?(validator)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
sig { params(validator: ActiveModel::Validator).returns(T::Boolean) }
|
50
|
+
def unconditional_presence_validator?(validator)
|
51
|
+
return false unless validator.is_a?(::ActiveRecord::Validations::PresenceValidator)
|
52
|
+
|
53
|
+
unconditional_validator?(validator)
|
54
|
+
end
|
55
|
+
|
56
|
+
sig { params(validator: ActiveModel::Validator).returns(T::Boolean) }
|
57
|
+
def unconditional_validator?(validator)
|
58
|
+
!validator.options.key?(:if) && !validator.options.key?(:unless) && !validator.options.key?(:on)
|
34
59
|
end
|
35
60
|
end
|
36
61
|
end
|
@@ -13,13 +13,24 @@ module Boba
|
|
13
13
|
T.any(::ActiveRecord::Reflection::ThroughReflection, ::ActiveRecord::Reflection::AssociationReflection)
|
14
14
|
end
|
15
15
|
|
16
|
+
sig { params(reflection: ReflectionType).returns(T::Boolean) }
|
17
|
+
def required_reflection?(reflection)
|
18
|
+
return true if has_one_and_required_reflection?(reflection)
|
19
|
+
|
20
|
+
belongs_to_and_non_optional_reflection?(reflection)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
16
25
|
sig { params(reflection: ReflectionType).returns(T::Boolean) }
|
17
26
|
def has_one_and_required_reflection?(reflection)
|
18
27
|
return false unless reflection.has_one?
|
19
28
|
return true if !!reflection.options[:required]
|
20
|
-
return true if reflection_required_by_database_constraint?(reflection)
|
21
29
|
|
22
|
-
|
30
|
+
Boba::ActiveRecord::AttributeService.has_unconditional_presence_validator?(
|
31
|
+
reflection.active_record,
|
32
|
+
reflection.name.to_s,
|
33
|
+
)
|
23
34
|
end
|
24
35
|
|
25
36
|
sig { params(reflection: ReflectionType).returns(T::Boolean) }
|
@@ -32,36 +43,21 @@ module Boba
|
|
32
43
|
reflection.options[:optional]
|
33
44
|
end
|
34
45
|
return !optional unless optional.nil?
|
35
|
-
return true if
|
36
|
-
return true if reflection_required_by_validation?(reflection)
|
37
|
-
|
38
|
-
# nothing defined, so fall back to the default active record config
|
39
|
-
!!reflection.active_record.belongs_to_required_by_default
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
# check for non-nullable database constraint on the foreign key
|
45
|
-
sig { params(reflection: ReflectionType).returns(T::Boolean) }
|
46
|
-
def reflection_required_by_database_constraint?(reflection)
|
47
|
-
Boba::ActiveRecord::AttributeService.has_non_null_database_constraint?(
|
46
|
+
return true if Boba::ActiveRecord::AttributeService.has_non_null_database_constraint?(
|
48
47
|
reflection.active_record,
|
49
|
-
reflection.foreign_key,
|
48
|
+
reflection.foreign_key.to_s,
|
50
49
|
)
|
51
|
-
end
|
52
|
-
|
53
|
-
# check for presence validator on the foreign key or on the association
|
54
|
-
sig { params(reflection: ReflectionType).returns(T::Boolean) }
|
55
|
-
def reflection_required_by_validation?(reflection)
|
56
50
|
return true if Boba::ActiveRecord::AttributeService.has_unconditional_presence_validator?(
|
57
51
|
reflection.active_record,
|
58
|
-
reflection.foreign_key,
|
52
|
+
reflection.foreign_key.to_s,
|
59
53
|
)
|
60
|
-
|
61
|
-
Boba::ActiveRecord::AttributeService.has_unconditional_presence_validator?(
|
54
|
+
return true if Boba::ActiveRecord::AttributeService.has_unconditional_presence_validator?(
|
62
55
|
reflection.active_record,
|
63
|
-
reflection.name,
|
56
|
+
reflection.name.to_s,
|
64
57
|
)
|
58
|
+
|
59
|
+
# nothing defined, so fall back to the default active record config
|
60
|
+
!!reflection.active_record.belongs_to_required_by_default
|
65
61
|
end
|
66
62
|
end
|
67
63
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Boba
|
5
|
+
module Options
|
6
|
+
class AssociationTypeOption < T::Enum
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
enums do
|
10
|
+
Nilable = new("nilable")
|
11
|
+
Persisted = new("persisted")
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
extend T::Sig
|
16
|
+
|
17
|
+
sig do
|
18
|
+
params(
|
19
|
+
options: T::Hash[String, T.untyped],
|
20
|
+
block: T.proc.params(value: String, default_association_type_option: AssociationTypeOption).void,
|
21
|
+
).returns(AssociationTypeOption)
|
22
|
+
end
|
23
|
+
def from_options(options, &block)
|
24
|
+
association_type_option = Nilable
|
25
|
+
value = options["ActiveRecordAssociationTypes"]
|
26
|
+
|
27
|
+
if value
|
28
|
+
if has_serialized?(value)
|
29
|
+
association_type_option = from_serialized(value)
|
30
|
+
else
|
31
|
+
block.call(value, association_type_option)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
association_type_option
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
sig { returns(T::Boolean) }
|
40
|
+
def persisted?
|
41
|
+
self == AssociationTypeOption::Persisted
|
42
|
+
end
|
43
|
+
|
44
|
+
sig { returns(T::Boolean) }
|
45
|
+
def nilable?
|
46
|
+
self == AssociationTypeOption::Nilable
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -6,15 +6,30 @@ require "rails/railtie"
|
|
6
6
|
class Boba::RelationsRailtie < Rails::Railtie
|
7
7
|
railtie_name(:boba)
|
8
8
|
|
9
|
-
initializer("boba.
|
9
|
+
initializer("boba.add_private_relation_constants") do
|
10
10
|
ActiveSupport.on_load(:active_record) do
|
11
11
|
module AciveRecordInheritDefineRelationTypes
|
12
12
|
def inherited(child)
|
13
13
|
super(child)
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
# Tapioca defines these three classes for each active record model as proxies for the actual AR internal
|
16
|
+
# classes. In order to be able to use these as types in signatures, we need to expose them as actual constants
|
17
|
+
# at runtime. Tapioca intentionally obfuscates these classes because they're private, so exposing them is
|
18
|
+
# _slightly_ dangerous in that someone could do something naughty. But we're not super worried about it.
|
19
|
+
child.const_set("PrivateRelation", child.const_get(:ActiveRecord_Relation))
|
20
|
+
child.const_set("PrivateAssociationRelation", child.const_get(:ActiveRecord_AssociationRelation))
|
21
|
+
child.const_set("PrivateCollectionProxy", child.const_get(:ActiveRecord_Associations_CollectionProxy))
|
22
|
+
|
23
|
+
# Expose a common type so that signatures can be typed to the broader `RelationType` since the three are often
|
24
|
+
# used interchangeably.
|
25
|
+
relation_type = T.type_alias do
|
26
|
+
T.any(
|
27
|
+
child.const_get(:PrivateRelation),
|
28
|
+
child.const_get(:PrivateAssociationRelation),
|
29
|
+
child.const_get(:PrivateCollectionProxy),
|
30
|
+
)
|
31
|
+
end
|
32
|
+
child.const_set(:RelationType, relation_type)
|
18
33
|
end
|
19
34
|
end
|
20
35
|
|
data/lib/boba/version.rb
CHANGED
@@ -6,6 +6,7 @@ require "tapioca/dsl/compilers/active_record_associations"
|
|
6
6
|
return unless defined?(Tapioca::Dsl::Compilers::ActiveRecordAssociations)
|
7
7
|
|
8
8
|
require "boba/active_record/reflection_service"
|
9
|
+
require "boba/options/association_type_option"
|
9
10
|
|
10
11
|
module Tapioca
|
11
12
|
module Dsl
|
@@ -53,62 +54,18 @@ module Tapioca
|
|
53
54
|
class ActiveRecordAssociationsPersisted < ::Tapioca::Dsl::Compilers::ActiveRecordAssociations
|
54
55
|
extend T::Sig
|
55
56
|
|
56
|
-
class AssociationTypeOption < T::Enum
|
57
|
-
extend T::Sig
|
58
|
-
|
59
|
-
enums do
|
60
|
-
Nilable = new("nilable")
|
61
|
-
Persisted = new("persisted")
|
62
|
-
end
|
63
|
-
|
64
|
-
class << self
|
65
|
-
extend T::Sig
|
66
|
-
|
67
|
-
sig do
|
68
|
-
params(
|
69
|
-
options: T::Hash[String, T.untyped],
|
70
|
-
block: T.proc.params(value: String, default_association_type_option: AssociationTypeOption).void,
|
71
|
-
).returns(AssociationTypeOption)
|
72
|
-
end
|
73
|
-
def from_options(options, &block)
|
74
|
-
association_type_option = Nilable
|
75
|
-
value = options["ActiveRecordAssociationTypes"]
|
76
|
-
|
77
|
-
if value
|
78
|
-
if has_serialized?(value)
|
79
|
-
association_type_option = from_serialized(value)
|
80
|
-
else
|
81
|
-
block.call(value, association_type_option)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
association_type_option
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
sig { returns(T::Boolean) }
|
90
|
-
def persisted?
|
91
|
-
self == AssociationTypeOption::Persisted
|
92
|
-
end
|
93
|
-
|
94
|
-
sig { returns(T::Boolean) }
|
95
|
-
def nilable?
|
96
|
-
self == AssociationTypeOption::Nilable
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
57
|
private
|
101
58
|
|
102
|
-
sig { returns(AssociationTypeOption) }
|
59
|
+
sig { returns(Boba::Options::AssociationTypeOption) }
|
103
60
|
def association_type_option
|
104
61
|
@association_type_option ||= T.let(
|
105
|
-
AssociationTypeOption.from_options(options) do |value, default_association_type_option|
|
62
|
+
Boba::Options::AssociationTypeOption.from_options(options) do |value, default_association_type_option|
|
106
63
|
add_error(<<~MSG.strip)
|
107
64
|
Unknown value for compiler option `ActiveRecordAssociationTypes` given: `#{value}`.
|
108
65
|
Proceeding with the default value: `#{default_association_type_option.serialize}`.
|
109
66
|
MSG
|
110
67
|
end,
|
111
|
-
T.nilable(AssociationTypeOption),
|
68
|
+
T.nilable(Boba::Options::AssociationTypeOption),
|
112
69
|
)
|
113
70
|
end
|
114
71
|
|
@@ -190,9 +147,7 @@ module Tapioca
|
|
190
147
|
association_class = type_for(reflection)
|
191
148
|
return as_nilable_type(association_class) unless association_type_option.persisted?
|
192
149
|
|
193
|
-
if Boba::ActiveRecord::ReflectionService.
|
194
|
-
association_class
|
195
|
-
elsif Boba::ActiveRecord::ReflectionService.belongs_to_and_non_optional_reflection?(reflection)
|
150
|
+
if Boba::ActiveRecord::ReflectionService.required_reflection?(reflection)
|
196
151
|
association_class
|
197
152
|
else
|
198
153
|
as_nilable_type(association_class)
|
@@ -153,14 +153,7 @@ module Tapioca
|
|
153
153
|
def column_type_for(column_name)
|
154
154
|
return ["T.untyped", "T.untyped"] if column_type_option.untyped?
|
155
155
|
|
156
|
-
nilable_column =
|
157
|
-
@constant,
|
158
|
-
column_name,
|
159
|
-
)
|
160
|
-
nilable_column &&= !Boba::ActiveRecord::AttributeService.has_unconditional_presence_validator?(
|
161
|
-
@constant,
|
162
|
-
column_name,
|
163
|
-
)
|
156
|
+
nilable_column = Boba::ActiveRecord::AttributeService.nilable_attribute?(@constant, column_name)
|
164
157
|
|
165
158
|
column_type = @constant.attribute_types[column_name]
|
166
159
|
getter_type = column_type_helper.send(
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
return unless defined?(ActiveRecord::Base)
|
5
|
+
|
6
|
+
require "tapioca/dsl/helpers/active_record_constants_helper"
|
7
|
+
require "tapioca/dsl/compilers/active_record_relations"
|
8
|
+
|
9
|
+
return unless defined?(Tapioca::Dsl::Compilers::ActiveRecordRelations)
|
10
|
+
|
11
|
+
module Tapioca
|
12
|
+
module Dsl
|
13
|
+
module Compilers
|
14
|
+
# `Tapioca::Dsl::Compilers::ActiveRecordRelationTypes` extends
|
15
|
+
# `Tapioca::Dsl::Compilers::ActiveRecordRelationTypes` to generate a `RelationType` type alias for each class.
|
16
|
+
# This type alias is defined a runtime through the Boba railtie, and is useful for typing signatures to accept or
|
17
|
+
# return relations. For instance, with the following `ActiveRecord::Base` subclass:
|
18
|
+
# ~~~rb
|
19
|
+
# class Post < ApplicationRecord
|
20
|
+
# end
|
21
|
+
# ~~~
|
22
|
+
#
|
23
|
+
# This compiler will produce the RBI file `post.rbi` with the following content:
|
24
|
+
# ~~~rbi
|
25
|
+
# # post.rbi
|
26
|
+
# # typed: true
|
27
|
+
#
|
28
|
+
# class Post
|
29
|
+
# RelationType = T.any(PrivateRelation, PrivateAssociationRelation, PrivateCollectionProxy)
|
30
|
+
# end
|
31
|
+
# ~~~
|
32
|
+
# So that the following method will accept any of the private relation types as an argument:
|
33
|
+
# ~~~rb
|
34
|
+
# sig { params(posts: Post::RelationType).void }
|
35
|
+
# def process_posts(posts)
|
36
|
+
# # ...
|
37
|
+
# end
|
38
|
+
# ~~~
|
39
|
+
class ActiveRecordRelationTypes < Compiler
|
40
|
+
extend T::Sig
|
41
|
+
|
42
|
+
ConstantType = type_member { { fixed: T.class_of(::ActiveRecord::Base) } }
|
43
|
+
|
44
|
+
sig { override.void }
|
45
|
+
def decorate
|
46
|
+
root.create_path(constant) do |rbi_class|
|
47
|
+
relation_type_alias = "T.any(" \
|
48
|
+
"#{Tapioca::Dsl::Helpers::ActiveRecordConstantsHelper::RelationClassName}, " \
|
49
|
+
"#{Tapioca::Dsl::Helpers::ActiveRecordConstantsHelper::AssociationRelationClassName}, " \
|
50
|
+
"#{Tapioca::Dsl::Helpers::ActiveRecordConstantsHelper::AssociationsCollectionProxyClassName}" \
|
51
|
+
")"
|
52
|
+
rbi_class.create_type_variable("RelationType", type: "T.type_alias { #{relation_type_alias} }")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class << self
|
57
|
+
extend T::Sig
|
58
|
+
|
59
|
+
sig { override.returns(T::Enumerable[Module]) }
|
60
|
+
def gather_constants
|
61
|
+
Tapioca::Dsl::Compilers::ActiveRecordRelations.gather_constants
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
1
|
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
3
|
|
4
|
-
return
|
4
|
+
return unless defined?(AttrJson::Record)
|
5
5
|
|
6
6
|
module Tapioca
|
7
7
|
module Dsl
|
@@ -39,7 +39,7 @@ module Tapioca
|
|
39
39
|
# Class methods module is already defined in the gem rbi, so just reference it here.
|
40
40
|
ClassMethodsModuleName = "AttrJson::Record::ClassMethods"
|
41
41
|
InstanceMethodModuleName = "AttrJsonGeneratedMethods"
|
42
|
-
ConstantType = type_member {{ fixed: T.any(T.class_of(::AttrJson::Record), T.class_of(::AttrJson::Model)) }}
|
42
|
+
ConstantType = type_member { { fixed: T.any(T.class_of(::AttrJson::Record), T.class_of(::AttrJson::Model)) } }
|
43
43
|
|
44
44
|
class << self
|
45
45
|
extend T::Sig
|
@@ -65,7 +65,7 @@ module Tapioca
|
|
65
65
|
private
|
66
66
|
|
67
67
|
def decorate_attributes(rbi_scope)
|
68
|
-
|
68
|
+
constant.attr_json_registry
|
69
69
|
.definitions
|
70
70
|
.sort_by(&:name) # this is annoying, but we need to sort to force consistent ordering or the rbi checks fail
|
71
71
|
.each do |definition|
|
@@ -122,7 +122,7 @@ module Tapioca
|
|
122
122
|
|
123
123
|
sorbet_type = "::#{sorbet_type}"
|
124
124
|
sorbet_type = "T::Array[#{sorbet_type}]" if array
|
125
|
-
sorbet_type = "T.nilable(#{sorbet_type})" if nilable #
|
125
|
+
sorbet_type = "T.nilable(#{sorbet_type})" if nilable # TODO: improve this
|
126
126
|
|
127
127
|
sorbet_type
|
128
128
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
return unless defined?(Kaminari)
|
5
|
+
|
6
|
+
require "tapioca/dsl/helpers/active_record_constants_helper"
|
7
|
+
|
8
|
+
module Tapioca
|
9
|
+
module Dsl
|
10
|
+
module Compilers
|
11
|
+
# `Tapioca::Dsl::Compilers::Kaminari` decorates RBI files for models
|
12
|
+
# using Kaminari.
|
13
|
+
#
|
14
|
+
# For example, with Kaminari installed and the following `ActiveRecord::Base` subclass:
|
15
|
+
#
|
16
|
+
# ~~~rb
|
17
|
+
# class Post < ApplicationRecord
|
18
|
+
# end
|
19
|
+
# ~~~
|
20
|
+
#
|
21
|
+
# This compiler will produce the RBI file `post.rbi` with the following content:
|
22
|
+
#
|
23
|
+
# ~~~rbi
|
24
|
+
# # post.rbi
|
25
|
+
# # typed: true
|
26
|
+
# class Post
|
27
|
+
# extend GeneratedRelationMethods
|
28
|
+
#
|
29
|
+
# module GeneratedRelationMethods
|
30
|
+
# sig do
|
31
|
+
# params(
|
32
|
+
# num: T.any(Integer, String)
|
33
|
+
# ).returns(T.all(PrivateRelation, Kaminari::PageScopeMethods, Kaminari::ActiveRecordRelationMethods))
|
34
|
+
# end
|
35
|
+
# def page(num = nil); end
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
# ~~~
|
39
|
+
class Kaminari < Tapioca::Dsl::Compiler
|
40
|
+
extend T::Sig
|
41
|
+
include Helpers::ActiveRecordConstantsHelper
|
42
|
+
|
43
|
+
ConstantType = type_member { { fixed: T.class_of(::ActiveRecord::Base) } }
|
44
|
+
|
45
|
+
sig { override.void }
|
46
|
+
def decorate
|
47
|
+
root.create_path(constant) do |model|
|
48
|
+
target_modules.each do |module_name, return_type|
|
49
|
+
model.create_module(module_name).create_method(
|
50
|
+
::Kaminari.config.page_method_name.to_s,
|
51
|
+
parameters: [create_opt_param("num", type: "T.any(Integer, String)", default: "nil")],
|
52
|
+
return_type: "T.all(#{return_type}, Kaminari::PageScopeMethods, Kaminari::ActiveRecordRelationMethods)",
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
model.create_extend(RelationMethodsModuleName)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class << self
|
61
|
+
extend T::Sig
|
62
|
+
|
63
|
+
sig { override.returns(T::Enumerable[Module]) }
|
64
|
+
def gather_constants
|
65
|
+
descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
sig { returns(T::Array[[String, String]]) }
|
72
|
+
def target_modules
|
73
|
+
if compiler_enabled?("ActiveRecordRelations")
|
74
|
+
[
|
75
|
+
[RelationMethodsModuleName, RelationClassName],
|
76
|
+
[AssociationRelationMethodsModuleName, AssociationRelationClassName],
|
77
|
+
]
|
78
|
+
else
|
79
|
+
[[RelationMethodsModuleName, "T.untyped"]]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -4,6 +4,8 @@
|
|
4
4
|
return unless defined?(MoneyRails)
|
5
5
|
|
6
6
|
require "tapioca/helpers/rbi_helper"
|
7
|
+
require "tapioca/dsl/helpers/active_record_column_type_helper"
|
8
|
+
require "boba/active_record/attribute_service"
|
7
9
|
|
8
10
|
module Tapioca
|
9
11
|
module Dsl
|
@@ -37,7 +39,14 @@ module Tapioca
|
|
37
39
|
extend T::Sig
|
38
40
|
include RBIHelper
|
39
41
|
|
40
|
-
ConstantType = type_member
|
42
|
+
ConstantType = type_member do
|
43
|
+
{
|
44
|
+
fixed: T.all(
|
45
|
+
T.class_of(::MoneyRails::ActiveRecord::Monetizable),
|
46
|
+
::MoneyRails::ActiveRecord::Monetizable::ClassMethods,
|
47
|
+
),
|
48
|
+
}
|
49
|
+
end
|
41
50
|
|
42
51
|
class << self
|
43
52
|
extend T::Sig
|
@@ -48,6 +57,21 @@ module Tapioca
|
|
48
57
|
end
|
49
58
|
end
|
50
59
|
|
60
|
+
ColumnTypeOption = Tapioca::Dsl::Helpers::ActiveRecordColumnTypeHelper::ColumnTypeOption
|
61
|
+
|
62
|
+
sig { returns(ColumnTypeOption) }
|
63
|
+
def column_type_option
|
64
|
+
@column_type_option ||= T.let(
|
65
|
+
ColumnTypeOption.from_options(options) do |value, default_column_type_option|
|
66
|
+
add_error(<<~MSG.strip)
|
67
|
+
Unknown value for compiler option `ActiveRecordColumnTypes` given: `#{value}`.
|
68
|
+
Proceeding with the default value: `#{default_column_type_option.serialize}`.
|
69
|
+
MSG
|
70
|
+
end,
|
71
|
+
T.nilable(ColumnTypeOption),
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
51
75
|
sig { override.void }
|
52
76
|
def decorate
|
53
77
|
return if constant.monetized_attributes.empty?
|
@@ -57,10 +81,23 @@ module Tapioca
|
|
57
81
|
instance_module = RBI::Module.new(instance_module_name)
|
58
82
|
|
59
83
|
constant.monetized_attributes.each do |attribute_name, column_name|
|
60
|
-
|
84
|
+
if column_type_option.untyped?
|
85
|
+
type_name = "T.untyped"
|
86
|
+
else
|
87
|
+
type_name = "::Money"
|
88
|
+
|
89
|
+
nilable_attribute = if constant < ::ActiveRecord::Base && column_type_option.persisted?
|
90
|
+
Boba::ActiveRecord::AttributeService.nilable_attribute?(
|
91
|
+
T.cast(constant, T.class_of(::ActiveRecord::Base)),
|
92
|
+
attribute_name,
|
93
|
+
column_name: column_name,
|
94
|
+
)
|
95
|
+
else
|
96
|
+
true
|
97
|
+
end
|
61
98
|
|
62
|
-
|
63
|
-
|
99
|
+
type_name = as_nilable_type(type_name) if nilable_attribute
|
100
|
+
end
|
64
101
|
|
65
102
|
# Model: monetize :amount_cents
|
66
103
|
# => amount
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: boba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Angellist
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sorbet-static-and-runtime
|
@@ -50,11 +50,14 @@ files:
|
|
50
50
|
- lib/boba.rb
|
51
51
|
- lib/boba/active_record/attribute_service.rb
|
52
52
|
- lib/boba/active_record/reflection_service.rb
|
53
|
+
- lib/boba/options/association_type_option.rb
|
53
54
|
- lib/boba/relations_railtie.rb
|
54
55
|
- lib/boba/version.rb
|
55
56
|
- lib/tapioca/dsl/compilers/active_record_associations_persisted.rb
|
56
57
|
- lib/tapioca/dsl/compilers/active_record_columns_persisted.rb
|
58
|
+
- lib/tapioca/dsl/compilers/active_record_relation_types.rb
|
57
59
|
- lib/tapioca/dsl/compilers/attr_json.rb
|
60
|
+
- lib/tapioca/dsl/compilers/kaminari.rb
|
58
61
|
- lib/tapioca/dsl/compilers/money_rails.rb
|
59
62
|
- lib/tapioca/dsl/compilers/paperclip.rb
|
60
63
|
- lib/tapioca/dsl/compilers/state_machines_extended.rb
|
@@ -63,9 +66,9 @@ licenses:
|
|
63
66
|
- MIT
|
64
67
|
metadata:
|
65
68
|
bug_tracker_uri: https://github.com/angellist/boba/issues
|
66
|
-
changelog_uri: https://github.com/angellist/boba/blob/0.0.
|
69
|
+
changelog_uri: https://github.com/angellist/boba/blob/0.0.14/History.md
|
67
70
|
homepage_uri: https://github.com/angellist/boba
|
68
|
-
source_code_uri: https://github.com/angellist/boba/tree/0.0.
|
71
|
+
source_code_uri: https://github.com/angellist/boba/tree/0.0.14
|
69
72
|
rubygems_mfa_required: 'true'
|
70
73
|
post_install_message:
|
71
74
|
rdoc_options: []
|