boba 0.0.6 → 0.0.8

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: 8819547e35a1566b8b772069f87ae189a4d1faa0bc7b3426e69c01b286fb372b
4
- data.tar.gz: 23e40a45b04a32ee32322d2123eabb3dc9e06829df556af916b1a2885cb04168
3
+ metadata.gz: c574e47f38c05d56093ec30f3f1328e28440c4ad2511567e0015106798569e44
4
+ data.tar.gz: cdeb013e429306a83dd5a41023fb73c311180d88e89923cccba55eff5a613dda
5
5
  SHA512:
6
- metadata.gz: a71b576ebd62ccfb4cde1a8383ad3dfccabb7a1f6812f35ad54ca8f3def3d1c014c493d86d7a973939c6f425a3a2eeebd62ec77df5c94b91840c1ada25b283d1
7
- data.tar.gz: '0417790d2c32e3faa556a22b6a9deab60581f3b6534f04451c87b89e74b4aa67ef0e3608eaf782a2343d83b1319e7461e3ca841a842dd557dda7ba83c78db4d2'
6
+ metadata.gz: 93de6c75462a9824d8cacd0fc0fec7ae246694eb0c63acf216cc0f04f3eb4fee94e9b98a77b6e65879eb8c11d41bf47fc06dae535b4767ecbab15150cc0c43ec
7
+ data.tar.gz: 2de09551f27c892aa5317aa133f30e67b70dfa9bfbc16e7c4474e089909ba7d3bf7d1178109f94f48b09a17d774425b3d51e1eb778629b2834135d1a66daad61
@@ -0,0 +1,38 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Boba
5
+ module ActiveRecord
6
+ module AttributeService
7
+ class << self
8
+ extend T::Sig
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)
13
+
14
+ constant.validators_on(attribute).any? do |validator|
15
+ next false unless validator.is_a?(::ActiveRecord::Validations::PresenceValidator)
16
+
17
+ !validator.options.key?(:if) && !validator.options.key?(:unless) && !validator.options.key?(:on)
18
+ end
19
+ end
20
+
21
+ sig { params(constant: T.class_of(::ActiveRecord::Base), column_name: String).returns(T::Boolean) }
22
+ def has_non_null_database_constraint?(constant, column_name)
23
+ column = constant.columns_hash[column_name]
24
+ return false if column.nil?
25
+
26
+ !column.null
27
+ rescue StandardError
28
+ false
29
+ end
30
+
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?
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,69 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative("attribute_service")
5
+
6
+ module Boba
7
+ module ActiveRecord
8
+ module ReflectionService
9
+ class << self
10
+ extend T::Sig
11
+
12
+ ReflectionType = T.type_alias do
13
+ T.any(::ActiveRecord::Reflection::ThroughReflection, ::ActiveRecord::Reflection::AssociationReflection)
14
+ end
15
+
16
+ sig { params(reflection: ReflectionType).returns(T::Boolean) }
17
+ def has_one_and_required_reflection?(reflection)
18
+ return false unless reflection.has_one?
19
+ return true if !!reflection.options[:required]
20
+ return true if reflection_required_by_database_constraint?(reflection)
21
+
22
+ reflection_required_by_validation?(reflection)
23
+ end
24
+
25
+ sig { params(reflection: ReflectionType).returns(T::Boolean) }
26
+ def belongs_to_and_non_optional_reflection?(reflection)
27
+ return false unless reflection.belongs_to?
28
+
29
+ optional = if reflection.options.key?(:required)
30
+ !reflection.options[:required]
31
+ else
32
+ reflection.options[:optional]
33
+ end
34
+ 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?(
48
+ reflection.active_record,
49
+ reflection.foreign_key,
50
+ )
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
+ return true if Boba::ActiveRecord::AttributeService.has_unconditional_presence_validator?(
57
+ reflection.active_record,
58
+ reflection.foreign_key,
59
+ )
60
+
61
+ Boba::ActiveRecord::AttributeService.has_unconditional_presence_validator?(
62
+ reflection.active_record,
63
+ reflection.name,
64
+ )
65
+ end
66
+ end
67
+ end
68
+ end
69
+ 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.6"
5
+ VERSION = "0.0.8"
6
6
  end
@@ -5,6 +5,8 @@ require "tapioca/dsl/compilers/active_record_associations"
5
5
 
6
6
  return unless defined?(Tapioca::Dsl::Compilers::ActiveRecordAssociations)
7
7
 
8
+ require "boba/active_record/reflection_service"
9
+
8
10
  module Tapioca
9
11
  module Dsl
10
12
  module Compilers
@@ -188,39 +190,14 @@ module Tapioca
188
190
  association_class = type_for(reflection)
189
191
  return as_nilable_type(association_class) unless association_type_option.persisted?
190
192
 
191
- if has_one_and_required_reflection?(reflection) || belongs_to_and_non_optional_reflection?(reflection)
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)
192
196
  association_class
193
197
  else
194
198
  as_nilable_type(association_class)
195
199
  end
196
200
  end
197
-
198
- # Note - one can do more here. If the association's attribute has an unconditional presence validation, it
199
- # should also be considered required.
200
- sig { params(reflection: ReflectionType).returns(T::Boolean) }
201
- def has_one_and_required_reflection?(reflection)
202
- reflection.has_one? && !!reflection.options[:required]
203
- end
204
-
205
- # Note - one can do more here. If the FK defining the belongs_to association is non-nullable at the DB level, or
206
- # if the association's attribute has an unconditional presence validation, it should also be considered
207
- # non-optional.
208
- sig { params(reflection: ReflectionType).returns(T::Boolean) }
209
- def belongs_to_and_non_optional_reflection?(reflection)
210
- return false unless reflection.belongs_to?
211
-
212
- optional = if reflection.options.key?(:required)
213
- !reflection.options[:required]
214
- else
215
- reflection.options[:optional]
216
- end
217
-
218
- if optional.nil?
219
- !!reflection.active_record.belongs_to_required_by_default
220
- else
221
- !optional
222
- end
223
- end
224
201
  end
225
202
  end
226
203
  end
@@ -5,6 +5,7 @@ require "tapioca/dsl/compilers/active_record_columns"
5
5
 
6
6
  return unless defined?(Tapioca::Dsl::Compilers::ActiveRecordColumns)
7
7
 
8
+ require "boba/active_record/attribute_service"
8
9
  require "tapioca/dsl/helpers/active_record_column_type_helper"
9
10
 
10
11
  module Tapioca
@@ -150,10 +151,16 @@ module Tapioca
150
151
 
151
152
  sig { params(column_name: String).returns([String, String]) }
152
153
  def column_type_for(column_name)
153
- return ["T.untyped", "T.untyped"] if @column_type_option.untyped?
154
+ return ["T.untyped", "T.untyped"] if column_type_option.untyped?
154
155
 
155
- nilable_column = !has_non_null_database_constraint?(column_name) &&
156
- !has_unconditional_presence_validator?(column_name)
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
+ )
157
164
 
158
165
  column_type = @constant.attribute_types[column_name]
159
166
  getter_type = column_type_helper.send(
@@ -169,7 +176,8 @@ module Tapioca
169
176
  getter_type
170
177
  end
171
178
 
172
- if @column_type_option.persisted? && (virtual_attribute?(column_name) || !nilable_column)
179
+ virtual_attribute = Boba::ActiveRecord::AttributeService.virtual_attribute?(@constant, column_name)
180
+ if column_type_option.persisted? && (virtual_attribute || !nilable_column)
173
181
  [getter_type, setter_type]
174
182
  else
175
183
  getter_type = as_nilable_type(getter_type) unless column_type_helper.send(
@@ -180,30 +188,6 @@ module Tapioca
180
188
  end
181
189
  end
182
190
 
183
- sig { params(column_name: String).returns(T::Boolean) }
184
- def virtual_attribute?(column_name)
185
- @constant.columns_hash[column_name].nil?
186
- end
187
-
188
- sig { params(column_name: String).returns(T::Boolean) }
189
- def has_non_null_database_constraint?(column_name)
190
- column = @constant.columns_hash[column_name]
191
- return false if column.nil?
192
-
193
- !column.null
194
- end
195
-
196
- sig { params(column_name: String).returns(T::Boolean) }
197
- def has_unconditional_presence_validator?(column_name)
198
- return false unless @constant.respond_to?(:validators_on)
199
-
200
- @constant.validators_on(column_name).any? do |validator|
201
- next false unless validator.is_a?(ActiveRecord::Validations::PresenceValidator)
202
-
203
- !validator.options.key?(:if) && !validator.options.key?(:unless) && !validator.options.key?(:on)
204
- end
205
- end
206
-
207
191
  sig do
208
192
  params(
209
193
  klass: RBI::Scope,
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.6
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Angellist
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-12 00:00:00.000000000 Z
11
+ date: 2024-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sorbet-static-and-runtime
@@ -48,6 +48,8 @@ files:
48
48
  - LICENSE
49
49
  - README.md
50
50
  - lib/boba.rb
51
+ - lib/boba/active_record/attribute_service.rb
52
+ - lib/boba/active_record/reflection_service.rb
51
53
  - lib/boba/relations_railtie.rb
52
54
  - lib/boba/version.rb
53
55
  - lib/tapioca/dsl/compilers/active_record_associations_persisted.rb
@@ -59,9 +61,9 @@ licenses:
59
61
  - MIT
60
62
  metadata:
61
63
  bug_tracker_uri: https://github.com/angellist/boba/issues
62
- changelog_uri: https://github.com/angellist/boba/blob/0.0.6/History.md
64
+ changelog_uri: https://github.com/angellist/boba/blob/0.0.8/History.md
63
65
  homepage_uri: https://github.com/angellist/boba
64
- source_code_uri: https://github.com/angellist/boba/tree/0.0.6
66
+ source_code_uri: https://github.com/angellist/boba/tree/0.0.8
65
67
  rubygems_mfa_required: 'true'
66
68
  post_install_message:
67
69
  rdoc_options: []