tapioca 0.5.3 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +114 -23
  3. data/lib/tapioca/cli.rb +188 -65
  4. data/lib/tapioca/compilers/dsl/active_record_associations.rb +94 -8
  5. data/lib/tapioca/compilers/dsl/active_record_columns.rb +5 -4
  6. data/lib/tapioca/compilers/dsl/active_record_enum.rb +1 -1
  7. data/lib/tapioca/compilers/dsl/active_record_relations.rb +703 -0
  8. data/lib/tapioca/compilers/dsl/active_record_scope.rb +43 -13
  9. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +39 -33
  10. data/lib/tapioca/compilers/dsl/base.rb +26 -42
  11. data/lib/tapioca/compilers/dsl/extensions/frozen_record.rb +29 -0
  12. data/lib/tapioca/compilers/dsl/frozen_record.rb +37 -0
  13. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +27 -0
  14. data/lib/tapioca/compilers/dsl/identity_cache.rb +0 -1
  15. data/lib/tapioca/compilers/dsl/param_helper.rb +52 -0
  16. data/lib/tapioca/compilers/dsl/rails_generators.rb +120 -0
  17. data/lib/tapioca/compilers/dsl_compiler.rb +34 -6
  18. data/lib/tapioca/compilers/sorbet.rb +2 -0
  19. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +51 -48
  20. data/lib/tapioca/executor.rb +79 -0
  21. data/lib/tapioca/gemfile.rb +28 -4
  22. data/lib/tapioca/generators/base.rb +11 -18
  23. data/lib/tapioca/generators/dsl.rb +33 -38
  24. data/lib/tapioca/generators/gem.rb +64 -34
  25. data/lib/tapioca/generators/init.rb +41 -16
  26. data/lib/tapioca/generators/todo.rb +6 -6
  27. data/lib/tapioca/helpers/cli_helper.rb +26 -0
  28. data/lib/tapioca/helpers/config_helper.rb +84 -0
  29. data/lib/tapioca/helpers/test/content.rb +51 -0
  30. data/lib/tapioca/helpers/test/isolation.rb +125 -0
  31. data/lib/tapioca/helpers/test/template.rb +34 -0
  32. data/lib/tapioca/internal.rb +3 -2
  33. data/lib/tapioca/rbi_ext/model.rb +13 -10
  34. data/lib/tapioca/reflection.rb +13 -0
  35. data/lib/tapioca/trackers/autoload.rb +70 -0
  36. data/lib/tapioca/trackers/constant_definition.rb +42 -0
  37. data/lib/tapioca/trackers/mixin.rb +78 -0
  38. data/lib/tapioca/trackers.rb +14 -0
  39. data/lib/tapioca/version.rb +1 -1
  40. data/lib/tapioca.rb +28 -2
  41. metadata +37 -13
  42. data/lib/tapioca/config.rb +0 -45
  43. data/lib/tapioca/config_builder.rb +0 -73
  44. data/lib/tapioca/constant_locator.rb +0 -34
@@ -7,6 +7,8 @@ rescue LoadError
7
7
  return
8
8
  end
9
9
 
10
+ require "tapioca/compilers/dsl/helper/active_record_constants"
11
+
10
12
  module Tapioca
11
13
  module Compilers
12
14
  module Dsl
@@ -99,6 +101,23 @@ module Tapioca
99
101
  # ~~~
100
102
  class ActiveRecordAssociations < Base
101
103
  extend T::Sig
104
+ include Helper::ActiveRecordConstants
105
+
106
+ class SourceReflectionError < StandardError
107
+ end
108
+
109
+ class MissingConstantError < StandardError
110
+ extend T::Sig
111
+
112
+ sig { returns(String) }
113
+ attr_reader :class_name
114
+
115
+ sig { params(class_name: String).void }
116
+ def initialize(class_name)
117
+ @class_name = class_name
118
+ super
119
+ end
120
+ end
102
121
 
103
122
  ReflectionType = T.type_alias do
104
123
  T.any(::ActiveRecord::Reflection::ThroughReflection, ::ActiveRecord::Reflection::AssociationReflection)
@@ -109,14 +128,12 @@ module Tapioca
109
128
  return if constant.reflections.empty?
110
129
 
111
130
  root.create_path(constant) do |model|
112
- module_name = "GeneratedAssociationMethods"
113
-
114
- model.create_module(module_name) do |mod|
131
+ model.create_module(AssociationMethodsModuleName) do |mod|
115
132
  populate_nested_attribute_writers(mod, constant)
116
133
  populate_associations(mod, constant)
117
134
  end
118
135
 
119
- model.create_include(module_name)
136
+ model.create_include(AssociationMethodsModuleName)
120
137
  end
121
138
  end
122
139
 
@@ -146,6 +163,14 @@ module Tapioca
146
163
  else
147
164
  populate_single_assoc_getter_setter(mod, constant, association_name, reflection)
148
165
  end
166
+ rescue SourceReflectionError
167
+ add_error(<<~MSG.strip)
168
+ Cannot generate association `#{reflection.name}` on `#{constant}` since the source of the through association is missing.
169
+ MSG
170
+ rescue MissingConstantError => error
171
+ add_error(<<~MSG.strip)
172
+ Cannot generate association `#{declaration(reflection)}` on `#{constant}` since the constant `#{error.class_name}` does not exist.
173
+ MSG
149
174
  end
150
175
  end
151
176
 
@@ -241,11 +266,61 @@ module Tapioca
241
266
  ).returns(String)
242
267
  end
243
268
  def type_for(constant, reflection)
269
+ validate_reflection!(reflection)
270
+
244
271
  return "T.untyped" if !constant.table_exists? || polymorphic_association?(reflection)
245
272
 
246
273
  T.must(qualified_name_of(reflection.klass))
247
274
  end
248
275
 
276
+ sig do
277
+ params(
278
+ reflection: ReflectionType
279
+ ).void
280
+ end
281
+ def validate_reflection!(reflection)
282
+ # Check existence of source reflection, first, since, calling
283
+ # `.klass` also tries to go through the source reflection
284
+ # and fails with a cryptic error, otherwise.
285
+ if reflection.through_reflection?
286
+ raise SourceReflectionError unless reflection.source_reflection
287
+ end
288
+
289
+ # For non-polymorphic reflections, `.klass` should not be raising
290
+ # a `NameError`.
291
+ unless reflection.polymorphic?
292
+ reflection.klass
293
+ end
294
+ rescue NameError
295
+ class_name = if reflection.through_reflection?
296
+ reflection.send(:delegate_reflection).class_name
297
+ else
298
+ reflection.class_name
299
+ end
300
+
301
+ raise MissingConstantError, class_name
302
+ end
303
+
304
+ sig { params(reflection: ReflectionType).returns(T.nilable(String)) }
305
+ def declaration(reflection)
306
+ case reflection
307
+ when ActiveRecord::Reflection::HasOneReflection
308
+ "has_one :#{reflection.name}"
309
+ when ActiveRecord::Reflection::HasManyReflection
310
+ "has_many :#{reflection.name}"
311
+ when ActiveRecord::Reflection::HasAndBelongsToManyReflection
312
+ "has_and_belongs_to_many :#{reflection.name}"
313
+ when ActiveRecord::Reflection::BelongsToReflection
314
+ "belongs_to :#{reflection.name}"
315
+ when ActiveRecord::Reflection::ThroughReflection
316
+ delegate_reflection = reflection.send(:delegate_reflection)
317
+ declaration = declaration(delegate_reflection)
318
+ through_name = delegate_reflection.options[:through]
319
+
320
+ "#{declaration}, through: :#{through_name}"
321
+ end
322
+ end
323
+
249
324
  sig do
250
325
  params(
251
326
  constant: T.class_of(ActiveRecord::Base),
@@ -253,11 +328,22 @@ module Tapioca
253
328
  ).returns(String)
254
329
  end
255
330
  def relation_type_for(constant, reflection)
256
- "ActiveRecord::Associations::CollectionProxy" if !constant.table_exists? ||
257
- polymorphic_association?(reflection)
331
+ validate_reflection!(reflection)
332
+
333
+ relations_enabled = generator_enabled?("ActiveRecordRelations")
334
+ polymorphic_association = !constant.table_exists? || polymorphic_association?(reflection)
258
335
 
259
- # Change to: "::#{reflection.klass.name}::ActiveRecord_Associations_CollectionProxy"
260
- "::ActiveRecord::Associations::CollectionProxy[#{qualified_name_of(reflection.klass)}]"
336
+ if relations_enabled
337
+ if polymorphic_association
338
+ "ActiveRecord::Associations::CollectionProxy"
339
+ else
340
+ "#{qualified_name_of(reflection.klass)}::#{AssociationsCollectionProxyClassName}"
341
+ end
342
+ elsif polymorphic_association
343
+ "ActiveRecord::Associations::CollectionProxy[T.untyped]"
344
+ else
345
+ "::ActiveRecord::Associations::CollectionProxy[#{qualified_name_of(reflection.klass)}]"
346
+ end
261
347
  end
262
348
 
263
349
  sig do
@@ -7,6 +7,8 @@ rescue LoadError
7
7
  return
8
8
  end
9
9
 
10
+ require "tapioca/compilers/dsl/helper/active_record_constants"
11
+
10
12
  module Tapioca
11
13
  module Compilers
12
14
  module Dsl
@@ -96,15 +98,14 @@ module Tapioca
96
98
  # ~~~
97
99
  class ActiveRecordColumns < Base
98
100
  extend T::Sig
101
+ include Helper::ActiveRecordConstants
99
102
 
100
103
  sig { override.params(root: RBI::Tree, constant: T.class_of(ActiveRecord::Base)).void }
101
104
  def decorate(root, constant)
102
105
  return unless constant.table_exists?
103
106
 
104
107
  root.create_path(constant) do |model|
105
- module_name = "GeneratedAttributeMethods"
106
-
107
- model.create_module(module_name) do |mod|
108
+ model.create_module(AttributeMethodsModuleName) do |mod|
108
109
  constant.columns_hash.each_key do |column_name|
109
110
  column_name = column_name.to_s
110
111
  add_methods_for_attribute(mod, constant, column_name)
@@ -121,7 +122,7 @@ module Tapioca
121
122
  end
122
123
  end
123
124
 
124
- model.create_include(module_name)
125
+ model.create_include(AttributeMethodsModuleName)
125
126
  end
126
127
  end
127
128
 
@@ -80,7 +80,7 @@ module Tapioca
80
80
 
81
81
  sig { override.returns(T::Enumerable[Module]) }
82
82
  def gather_constants
83
- descendants_of(::ActiveRecord::Base).reject(&:abstract_class?)
83
+ descendants_of(::ActiveRecord::Base)
84
84
  end
85
85
 
86
86
  private