tapioca 0.5.6 → 0.6.0

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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +114 -23
  3. data/lib/tapioca/cli.rb +188 -64
  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_relations.rb +703 -0
  7. data/lib/tapioca/compilers/dsl/active_record_scope.rb +43 -13
  8. data/lib/tapioca/compilers/dsl/active_record_typed_store.rb +2 -4
  9. data/lib/tapioca/compilers/dsl/base.rb +25 -42
  10. data/lib/tapioca/compilers/dsl/extensions/frozen_record.rb +29 -0
  11. data/lib/tapioca/compilers/dsl/frozen_record.rb +37 -0
  12. data/lib/tapioca/compilers/dsl/helper/active_record_constants.rb +27 -0
  13. data/lib/tapioca/compilers/dsl/param_helper.rb +52 -0
  14. data/lib/tapioca/compilers/dsl/rails_generators.rb +120 -0
  15. data/lib/tapioca/compilers/dsl_compiler.rb +32 -6
  16. data/lib/tapioca/compilers/sorbet.rb +2 -0
  17. data/lib/tapioca/compilers/symbol_table/symbol_generator.rb +47 -46
  18. data/lib/tapioca/executor.rb +79 -0
  19. data/lib/tapioca/gemfile.rb +23 -0
  20. data/lib/tapioca/generators/base.rb +11 -18
  21. data/lib/tapioca/generators/dsl.rb +33 -38
  22. data/lib/tapioca/generators/gem.rb +50 -29
  23. data/lib/tapioca/generators/init.rb +41 -16
  24. data/lib/tapioca/generators/todo.rb +6 -6
  25. data/lib/tapioca/helpers/cli_helper.rb +26 -0
  26. data/lib/tapioca/helpers/config_helper.rb +84 -0
  27. data/lib/tapioca/helpers/test/content.rb +51 -0
  28. data/lib/tapioca/helpers/test/isolation.rb +125 -0
  29. data/lib/tapioca/helpers/test/template.rb +34 -0
  30. data/lib/tapioca/internal.rb +3 -2
  31. data/lib/tapioca/rbi_ext/model.rb +12 -9
  32. data/lib/tapioca/reflection.rb +13 -0
  33. data/lib/tapioca/trackers/autoload.rb +70 -0
  34. data/lib/tapioca/trackers/constant_definition.rb +42 -0
  35. data/lib/tapioca/trackers/mixin.rb +78 -0
  36. data/lib/tapioca/trackers.rb +14 -0
  37. data/lib/tapioca/version.rb +1 -1
  38. data/lib/tapioca.rb +28 -2
  39. metadata +19 -7
  40. data/lib/tapioca/config.rb +0 -45
  41. data/lib/tapioca/config_builder.rb +0 -73
  42. data/lib/tapioca/constant_locator.rb +0 -40
@@ -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