boba 0.0.13 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c88ef5656da55d5a864ad1bf3b5231907260c62f2239964f47921368dbffea8
4
- data.tar.gz: c32f3fe6f3063407146db5559cee2d6714474e9f69eb769ce39278c961908537
3
+ metadata.gz: cb49413d3cfe01c4ea9d7cf283c50f03183f521e198b0af96e30fd0ec95fb7d4
4
+ data.tar.gz: 7f2dd02645cce05f8204a710b6a787e0aa7271fc902bea0f4c0e0174ffe7cf99
5
5
  SHA512:
6
- metadata.gz: ba9b4ccc7ffb27011848c62428a8cbed4cf9556bc3b63c04a4db867ed08ca12c2f16f48bc9f4c1f0ade04bd769d5033a977bc1e34f85115995c31dcdd65d21e0
7
- data.tar.gz: dfe9d28722a6191722271c44b64ba2aec62eca438534b9617b2806d460c35c171635a662d6a6033d96220de6f3ff40cf0d4dc5e4122643ecad4fe3b6b6ac9b52
6
+ metadata.gz: 87d9819bf315f321c8aaf3e33f6681606cc980384b2a7f61ceda298097cc955a291e91a1b7ef7e0c1e2e3763570cf79d244e1eebfb07766404bb60283732c8bd
7
+ data.tar.gz: 5cd15d8a5d1a16eba4258240ce240c17063dc5b538dfb9346bf1209e5ab0ae18af667ca710b2e2c0e086261c9b110abc7f6dfac78122fbaacc567b53125afe82
data/README.md CHANGED
@@ -81,7 +81,3 @@ Since Boba is intended to be used alongside Tapioca and the compilers provided b
81
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.
82
82
 
83
83
  Compilers for Gems, DSLs, or modules that are not publicly available will not be accepted.
84
-
85
- ## Todo
86
-
87
- 1. Specs & spec harness
@@ -7,15 +7,22 @@ module Boba
7
7
  class << self
8
8
  extend T::Sig
9
9
 
10
- sig { params(constant: T.class_of(::ActiveRecord::Base), attribute: String).returns(T::Boolean) }
11
- def has_unconditional_presence_validator?(constant, attribute)
12
- return false unless constant.respond_to?(:validators_on)
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.validators_on(attribute).any? do |validator|
15
- next false unless validator.is_a?(::ActiveRecord::Validations::PresenceValidator)
20
+ !has_unconditional_presence_validator?(constant, attribute)
21
+ end
16
22
 
17
- !validator.options.key?(:if) && !validator.options.key?(:unless) && !validator.options.key?(:on)
18
- end
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), column_name: String).returns(T::Boolean) }
32
- def virtual_attribute?(constant, column_name)
33
- constant.columns_hash[column_name].nil?
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
- reflection_required_by_validation?(reflection)
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 reflection_required_by_database_constraint?(reflection)
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
data/lib/boba/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Boba
5
- VERSION = "0.0.13"
5
+ VERSION = "0.0.14"
6
6
  end
@@ -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.has_one_and_required_reflection?(reflection)
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 = !Boba::ActiveRecord::AttributeService.has_non_null_database_constraint?(
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(
@@ -11,10 +11,10 @@ return unless defined?(Tapioca::Dsl::Compilers::ActiveRecordRelations)
11
11
  module Tapioca
12
12
  module Dsl
13
13
  module Compilers
14
- # `Tapioca::Dsl::Compilers::ActiveRecordRelationTypes` extends `Tapioca::Dsl::Compilers::ActiveRecordRelationTypes`
15
- # to generate a `RelationType` type alias for each class. This type alias is defined a runtime through the Boba
16
- # railtie, and is useful for typing signatures to accept or return relations. For instance, with the following
17
- # `ActiveRecord::Base` subclass:
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
18
  # ~~~rb
19
19
  # class Post < ApplicationRecord
20
20
  # 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 { { fixed: T.class_of(::MoneyRails::ActiveRecord::Monetizable) } }
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
- column = T.unsafe(constant).columns_hash[column_name]
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
- type_name = "::Money"
63
- type_name = as_nilable_type(type_name) if column.nil? || !!column.null
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.13
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-12-01 00:00:00.000000000 Z
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,12 +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
57
58
  - lib/tapioca/dsl/compilers/active_record_relation_types.rb
58
59
  - lib/tapioca/dsl/compilers/attr_json.rb
60
+ - lib/tapioca/dsl/compilers/kaminari.rb
59
61
  - lib/tapioca/dsl/compilers/money_rails.rb
60
62
  - lib/tapioca/dsl/compilers/paperclip.rb
61
63
  - lib/tapioca/dsl/compilers/state_machines_extended.rb
@@ -64,9 +66,9 @@ licenses:
64
66
  - MIT
65
67
  metadata:
66
68
  bug_tracker_uri: https://github.com/angellist/boba/issues
67
- changelog_uri: https://github.com/angellist/boba/blob/0.0.13/History.md
69
+ changelog_uri: https://github.com/angellist/boba/blob/0.0.14/History.md
68
70
  homepage_uri: https://github.com/angellist/boba
69
- source_code_uri: https://github.com/angellist/boba/tree/0.0.13
71
+ source_code_uri: https://github.com/angellist/boba/tree/0.0.14
70
72
  rubygems_mfa_required: 'true'
71
73
  post_install_message:
72
74
  rdoc_options: []