Skip to content

Instantly share code, notes, and snippets.

@chaffeqa
Last active April 3, 2023 16:19
Show Gist options
  • Save chaffeqa/6c74348df87df337141f4648089b8319 to your computer and use it in GitHub Desktop.
Save chaffeqa/6c74348df87df337141f4648089b8319 to your computer and use it in GitHub Desktop.
ActiveSupport::Concern and Sorbet RBI solution
# typed: true
module AllCachedModelConcern
extend ActiveSupport::Concern
included do
after_save(:clear_all_cached!)
after_destroy(:clear_all_cached!)
end
def clear_all_cached!
self.class.all_cached_backend.delete(self.class.all_cached_key) if self.class.all_cached_backend
end
class_methods do
def all_cached_key
"#{self}:all_cached"
end
def cached_find(id)
cached_find_all([id]).first
end
def cached_find!(id)
cached_find(id) || raise(ActiveRecord::RecordNotFound, "Couldn't find #{self} with ID=#{id}")
end
def cached_find_all(ids)
all_cached.select { |record| ids.map(&:to_i).include?(record.id) }
end
def all_cached
if all_cached_backend.nil?
order("id ASC").to_a
else
all_cached_backend.fetch(all_cached_key, expires_in: 5.minutes) { order("id ASC").to_a }
end
end
def all_cached_backend
Rails.cache
end
end
end
# typed: true
module AllCachedModelConcern
extend ::ActiveSupport::Concern
extend T::Sig
extend T::Helpers
requires_ancestor { T.class_of(ActiveRecord::Base) }
requires_ancestor { Kernel }
sig { void }
def clear_all_cached!; end
module ClassMethods
extend T::Sig
extend T::Helpers
requires_ancestor { T.class_of(ActiveRecord::Base) }
requires_ancestor { Kernel }
TIdParam = T.type_alias { T.any(String, Integer) }
TSelf = T.type_alias { T.self_type }
sig { returns(String) }
def all_cached_key; end
sig { params(id: TIdParam).returns(T.nilable(TSelf)) }
def cached_find(id); end
sig { params(id: TIdParam).returns(TSelf) }
def cached_find!(id); end
sig { returns(T::Array[TSelf]) }
def all_cached; end
sig { params(ids: T::Array[TIdParam]).returns(T::Array[TSelf]) }
def cached_find_all(ids); end
sig { returns(ActiveSupport::Cache::Store) }
def all_cached_backend; end
end
mixes_in_class_methods(ClassMethods)
end
@chaffeqa
Copy link
Author

chaffeqa commented Apr 3, 2023

Couple of issues:

included do doesn't work

Method `after_destroy` does not exist on `T.class_of(AllCachedModelConcern)`

self_type doesn't work

Expression does not have a fully-defined type (Did you reference another class's type members?)

@paracycle
Copy link

included do ... end does work, but there is no way for Sorbet to know what the type of self inside that block is going to be. You need to add a T.bind(self, ActiveRecord::Base) inside the block to make Sorbet understand what the binding of self is. But, you can't do that in RBI files.

As a matter of fact, you won't be able to type your application too much without having to use T.unsafe or T.let or T.cast or T.bind inside your code. That's a problem RBS also has today.

As for T.self_type, I am not sure I see that being problematic with inline signatures: https://sorbet.run/?arg=--enable-experimental-requires-ancestor#%23%20typed%3A%20true%0A%0Amodule%20Foo%0A%20%20%20%20extend%20T%3A%3AHelpers%0A%0A%20%20%20%20module%20ClassMethods%0A%20%20%20%20%20%20%20%20extend%20T%3A%3ASig%0A%0A%20%20%20%20%20%20%20%20TSelf%20%3D%20T.type_alias%20%7B%20T.self_type%20%7D%0A%20%20%20%20%20%20%20%20sig%20%7B%20returns%28T.self_type%29%20%7D%0A%20%20%20%20%20%20%20%20def%20foo%3B%20self%3B%20end%0A%0A%20%20%20%20%20%20%20%20sig%20%7B%20returns%28TSelf%29%20%7D%0A%20%20%20%20%20%20%20%20def%20bar%3B%20self%3B%20end%0A%20%20%20%20end%0A%0A%20%20%20%20mixes_in_class_methods%28ClassMethods%29%0Aend%0A%0Aclass%20Bar%0A%20%20%20%20include%20Foo%0Aend%0A%0A%0AT.reveal_type%20Bar.foo%0AT.reveal_type%20Bar.bar

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment