Skip to content

Commit

Permalink
Make it possible to extend list of id types for ActiveRecord relations
Browse files Browse the repository at this point in the history
By moving the list of id types to a constant, we are now making it possible to extend the list of id types for ActiveRecord relations in gem or application level "compilers".

All that a "compiler" needs to do is to read the list of id types from the `Tapioca::DSL::Compilers::ActiveRecordRelations::ID_TYPES` constant, add the new id type to the list and then assign the result back to `Tapioca::DSL::Compilers::ActiveRecordRelations::ID_TYPES`.
  • Loading branch information
paracycle committed Jul 22, 2024
1 parent 8678e2a commit aeb6eef
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 16 deletions.
38 changes: 22 additions & 16 deletions lib/tapioca/dsl/compilers/active_record_relations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,25 @@ class ActiveRecordRelations < Compiler

ConstantType = type_member { { fixed: T.class_of(::ActiveRecord::Base) } }

# From ActiveRecord::ConnectionAdapter::Quoting#quote, minus nil
ID_TYPES = T.let(
[
"String",
"Symbol",
"::ActiveSupport::Multibyte::Chars",
"T::Boolean",
"BigDecimal",
"Numeric",
"::ActiveRecord::Type::Binary::Data",
"::ActiveRecord::Type::Time::Value",
"Date",
"Time",
"::ActiveSupport::Duration",
"T::Class[T.anything]",
].to_set.freeze,
T::Set[String],
)

sig { override.void }
def decorate
create_classes_and_includes
Expand Down Expand Up @@ -663,27 +682,14 @@ def create_common_methods
return_type: "T::Boolean",
)
when :find
# From ActiveRecord::ConnectionAdapter::Quoting#quote, minus nil
id_types = [
"String",
"Symbol",
"::ActiveSupport::Multibyte::Chars",
"T::Boolean",
"BigDecimal",
"Numeric",
"::ActiveRecord::Type::Binary::Data",
"::ActiveRecord::Type::Time::Value",
"Date",
"Time",
"::ActiveSupport::Duration",
"T::Class[T.anything]",
].to_set
id_types = ID_TYPES

if constant.table_exists?
primary_key_type = constant.type_for_attribute(constant.primary_key)
type = Tapioca::Dsl::Helpers::ActiveModelTypeHelper.type_for(primary_key_type)
type = RBIHelper.as_non_nilable_type(type)
id_types << type if type != "T.untyped"

id_types = ID_TYPES.union([type]) if type != "T.untyped"
end

id_types = "T.any(#{id_types.to_a.join(", ")})"
Expand Down
65 changes: 65 additions & 0 deletions spec/tapioca/cli/dsl_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

module Tapioca
class DslSpec < SpecWithProject
include Tapioca::Helpers::Test::Template

describe "cli::dsl" do
before(:all) do
@project.write!("config/application.rb", <<~RB)
Expand Down Expand Up @@ -2050,6 +2052,10 @@ class Post
end

describe "custom compilers" do
after do
project.remove!("sorbet/rbi/dsl")
end

it "must load custom compilers from gems" do
@project.write!("lib/post.rb", <<~RB)
class Post
Expand Down Expand Up @@ -2179,6 +2185,65 @@ module GeneratedBar; end
assert_empty_stderr(result)
assert_success_status(result)
end

it "must be able to modify behaviour of existing compilers" do
@project.write!("lib/post.rb", <<~RB)
::ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
class Post < ActiveRecord::Base
end
RB

foo = mock_gem("foo", "0.0.1") do
write!("lib/tapioca/dsl/compilers/active_record_relation_ext.rb", <<~RB)
class Tapioca::Dsl::Compilers::ActiveRecordRelations
ID_TYPES = ID_TYPES.union(["Foo"]).freeze
end
RB
end

@project.require_mock_gem(foo)
@project.require_real_gem("activerecord", require: "active_record")
@project.require_real_gem("sqlite3", "1.7.3")
@project.bundle_install!

result = @project.tapioca("dsl Post")

assert_stdout_equals(<<~OUT, result)
Loading DSL extension classes... Done
Loading Rails application... Done
Loading DSL compiler classes... Done
Compiling DSL RBI files...
create sorbet/rbi/dsl/post.rbi
Done
Checking generated RBI files... Done
No errors found
All operations performed in working directory.
Please review changes and commit them.
OUT

assert_project_file_includes("sorbet/rbi/dsl/post.rbi", indented(<<~RBI, 4))
sig do
params(
args: T.any(String, Symbol, ::ActiveSupport::Multibyte::Chars, T::Boolean, BigDecimal, Numeric, ::ActiveRecord::Type::Binary::Data, ::ActiveRecord::Type::Time::Value, Date, Time, ::ActiveSupport::Duration, T::Class[T.anything], Foo)
).returns(::Post)
end
sig do
params(
args: T::Array[T.any(String, Symbol, ::ActiveSupport::Multibyte::Chars, T::Boolean, BigDecimal, Numeric, ::ActiveRecord::Type::Binary::Data, ::ActiveRecord::Type::Time::Value, Date, Time, ::ActiveSupport::Duration, T::Class[T.anything], Foo)]
).returns(T::Enumerable[::Post])
end
sig { params(args: NilClass, block: T.proc.params(object: ::Post).void).returns(T.nilable(::Post)) }
def find(args = nil, &block); end
RBI

assert_empty_stderr(result)
assert_success_status(result)
end
end

describe "custom extensions" do
Expand Down

0 comments on commit aeb6eef

Please sign in to comment.