Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ActiveStorage getter and setter generator for has_one_attached and has_many_attached #416

Merged
merged 10 commits into from
Aug 12, 2021
98 changes: 98 additions & 0 deletions lib/tapioca/compilers/dsl/active_storage.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# typed: strict
# frozen_string_literal: true

begin
require "active_storage"
require "active_storage/reflection"
rescue LoadError
return
end

module Tapioca
module Compilers
module Dsl
# `Tapioca::Compilers::Dsl::ActiveStorage` decorates RBI files for subclasses of
# `ActiveRecord::Base` that declare [one](https://edgeguides.rubyonrails.org/active_storage_overview.html#has-one-attached)
# or [many](https://edgeguides.rubyonrails.org/active_storage_overview.html#has-many-attached) attachments.
#
# For example, with the following `ActiveRecord::Base` subclass:
#
# ~~~rb
# class Post < ApplicationRecord
# has_one_attached :photo
# has_many_attached :blogs
# end
# ~~~
#
# this generator will produce the RBI file `post.rbi` with the following content:
#
# ~~~rbi
# # typed: strong
#
# class Post
# sig { returns(ActiveStorage::Attached::Many) }
# def blogs; end
#
# sig { params(attachable: T.untyped).returns(T.untyped) }
# def blogs=(attachable); end
#
# sig { returns(ActiveStorage::Attached::One) }
# def photo; end
#
# sig { params(attachable: T.untyped).returns(T.untyped) }
# def photo=(attachable); end
# end
# ~~~
class ActiveStorage < Base
extend T::Sig

sig do
override.params(root: RBI::Tree,
constant: T.all(Module, ::ActiveStorage::Reflection::ActiveRecordExtensions::ClassMethods)).void
end
def decorate(root, constant)
return if constant.reflect_on_all_attachments.empty?

root.create_path(constant) do |scope|
constant.reflect_on_all_attachments.each do |reflection|
type = type_of(reflection)
name = reflection.name.to_s
scope.create_method(
name,
return_type: type
)
scope.create_method(
"#{name}=",
parameters: [create_param("attachable", type: "T.untyped")],
Copy link
Contributor Author

@mojanjz mojanjz Aug 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it seems like attachable can be any of the types mentioned here

return_type: "T.untyped"
)
end
end
end

sig { override.returns(T::Enumerable[Module]) }
def gather_constants
ActiveRecord::Base.descendants
.reject(&:abstract_class?)
.grep(::ActiveStorage::Reflection::ActiveRecordExtensions::ClassMethods)
end

private

sig do
params(reflection: ActiveRecord::Reflection::MacroReflection).returns(String)
end
def type_of(reflection)
paracycle marked this conversation as resolved.
Show resolved Hide resolved
case reflection
when ::ActiveStorage::Reflection::HasOneAttachedReflection
"ActiveStorage::Attached::One"
when ::ActiveStorage::Reflection::HasManyAttachedReflection
"ActiveStorage::Attached::Many"
else
"T.untyped"
end
end
end
end
end
end
34 changes: 34 additions & 0 deletions manual/generator_activestorage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## ActiveStorage

`Tapioca::Compilers::Dsl::ActiveStorage` decorates RBI files for subclasses of
`ActiveRecord::Base` that declare [one](https://edgeguides.rubyonrails.org/active_storage_overview.html#has-one-attached)
or [many](https://edgeguides.rubyonrails.org/active_storage_overview.html#has-many-attached) attachments.

For example, with the following `ActiveRecord::Base` subclass:

~~~rb
class Post < ApplicationRecord
has_one_attached :photo
has_many_attached :blogs
end
~~~

this generator will produce the RBI file `post.rbi` with the following content:

~~~rbi
# typed: strong

class Post
sig { returns(ActiveStorage::Attached::Many) }
def blogs; end

sig { params(attachable: T.untyped).returns(T.untyped) }
def blogs=(attachable); end

sig { returns(ActiveStorage::Attached::One) }
def photo; end

sig { params(attachable: T.untyped).returns(T.untyped) }
def photo=(attachable); end
end
~~~
1 change: 1 addition & 0 deletions manual/generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ In the following section you will find all available DSL generators:
* [ActiveRecordScope](generator_activerecordscope.md)
* [ActiveRecordTypedStore](generator_activerecordtypedstore.md)
* [ActiveResource](generator_activeresource.md)
* [ActiveStorage](generator_activestorage.md)
* [ActiveSupportConcern](generator_activesupportconcern.md)
* [ActiveSupportCurrentAttributes](generator_activesupportcurrentattributes.md)
* [Config](generator_config.md)
Expand Down
Loading