class Tapioca::Compilers::Dsl::IdentityCache

`Tapioca::Compilers::DSL::IdentityCache` generates RBI files for Active Record models

that use `include IdentityCache`.

[`IdentityCache`](github.com/Shopify/identity_cache) is a blob level caching solution to plug into Active Record.

For example, with the following Active Record class:

~~~rb # post.rb class Post < ApplicationRecord

include IdentityCache

cache_index :blog_id
cache_index :title, unique: true
cache_index :title, :review_date, unique: true

end ~~~

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

~~~rbi # post.rbi # typed: true class Post

sig { params(blog_id: T.untyped, includes: T.untyped).returns(T::Array[::Post])
def fetch_by_blog_id(blog_id, includes: nil); end

sig { params(blog_ids: T.untyped, includes: T.untyped).returns(T::Array[::Post])
def fetch_multi_by_blog_id(index_values, includes: nil); end

sig { params(title: T.untyped, includes: T.untyped).returns(::Post) }
def fetch_by_title!(title, includes: nil); end

sig { params(title: T.untyped, includes: T.untyped).returns(T.nilable(::Post)) }
def fetch_by_title(title, includes: nil); end

sig { params(index_values: T.untyped, includes: T.untyped).returns(T::Array[::Post]) }
def fetch_multi_by_title(index_values, includes: nil); end

sig { params(title: T.untyped, review_date: T.untyped, includes: T.untyped).returns(T::Array[::Post]) }
def fetch_by_title_and_review_date!(title, review_date, includes: nil); end

sig { params(title: T.untyped, review_date: T.untyped, includes: T.untyped).returns(T::Array[::Post]) }
def fetch_by_title_and_review_date(title, review_date, includes: nil); end

end ~~~

Constants

COLLECTION_TYPE

Public Instance Methods

decorate(root, constant) click to toggle source
# File lib/tapioca/compilers/dsl/identity_cache.rb, line 72
def decorate(root, constant)
  caches = constant.send(:all_cached_associations)
  cache_indexes = constant.send(:cache_indexes)
  return if caches.empty? && cache_indexes.empty?

  root.create_path(constant) do |model|
    cache_manys = constant.send(:cached_has_manys)
    cache_ones = constant.send(:cached_has_ones)
    cache_belongs = constant.send(:cached_belongs_tos)

    cache_indexes.each do |field|
      create_fetch_by_methods(field, model, constant)
    end

    cache_manys.values.each do |field|
      create_fetch_field_methods(field, model, returns_collection: true)
    end

    cache_ones.values.each do |field|
      create_fetch_field_methods(field, model, returns_collection: false)
    end

    cache_belongs.values.each do |field|
      create_fetch_field_methods(field, model, returns_collection: false)
    end
  end
end
gather_constants() click to toggle source
# File lib/tapioca/compilers/dsl/identity_cache.rb, line 101
def gather_constants
  descendants_of(::ActiveRecord::Base).select do |klass|
    klass < ::IdentityCache::WithoutPrimaryIndex
  end
end

Private Instance Methods

create_aliased_fetch_by_methods(field, klass, constant) click to toggle source
# File lib/tapioca/compilers/dsl/identity_cache.rb, line 221
def create_aliased_fetch_by_methods(field, klass, constant)
  type, _ = ActiveRecordColumnTypeHelper.new(constant).type_for(field.alias_name.to_s)
  multi_type = type.delete_prefix("T.nilable(").delete_suffix(")").delete_prefix("::")
  length = field.key_fields.length
  suffix = field.send(:fetch_method_suffix)

  parameters = field.key_fields.map do |arg|
    create_param(arg.to_s, type: "T.untyped")
  end

  klass.create_method(
    "fetch_#{suffix}",
    class_method: true,
    parameters: parameters,
    return_type: type
  )

  if length == 1
    klass.create_method(
      "fetch_multi_#{suffix}",
      class_method: true,
      parameters: [create_param("keys", type: "T::Enumerable[T.untyped]")],
      return_type: COLLECTION_TYPE.call(multi_type)
    )
  end
end
create_fetch_by_methods(field, klass, constant) click to toggle source
# File lib/tapioca/compilers/dsl/identity_cache.rb, line 152
def create_fetch_by_methods(field, klass, constant)
  is_cache_index = field.instance_variable_defined?(:@attribute_proc)

  # Both `cache_index` and `cache_attribute` generate aliased methods
  create_aliased_fetch_by_methods(field, klass, constant)

  # If the method used was `cache_index` a few extra methods are created
  create_index_fetch_by_methods(field, klass, constant) if is_cache_index
end
create_fetch_field_methods(field, klass, returns_collection:) click to toggle source
# File lib/tapioca/compilers/dsl/identity_cache.rb, line 133
def create_fetch_field_methods(field, klass, returns_collection:)
  name = field.cached_accessor_name.to_s
  type = type_for_field(field, returns_collection: returns_collection)
  klass.create_method(name, return_type: type)

  if field.respond_to?(:cached_ids_name)
    klass.create_method(field.cached_ids_name, return_type: "T::Array[T.untyped]")
  elsif field.respond_to?(:cached_id_name)
    klass.create_method(field.cached_id_name, return_type: "T.untyped")
  end
end
create_index_fetch_by_methods(field, klass, constant) click to toggle source
# File lib/tapioca/compilers/dsl/identity_cache.rb, line 169
def create_index_fetch_by_methods(field, klass, constant)
  field_length = field.key_fields.length
  fields_name = field.key_fields.join("_and_")
  name = "fetch_by_#{fields_name}"
  parameters = field.key_fields.map do |arg|
    create_param(arg.to_s, type: "T.untyped")
  end
  parameters << create_kw_opt_param("includes", default: "nil", type: "T.untyped")

  if field.unique
    klass.create_method(
      "#{name}!",
      class_method: true,
      parameters: parameters,
      return_type: "::#{constant}"
    )

    klass.create_method(
      name,
      class_method: true,
      parameters: parameters,
      return_type: "T.nilable(::#{constant})"
    )
  else
    klass.create_method(
      name,
      class_method: true,
      parameters: parameters,
      return_type: COLLECTION_TYPE.call(constant)
    )
  end

  if field_length == 1
    klass.create_method(
      "fetch_multi_by_#{fields_name}",
      class_method: true,
      parameters: [
        create_param("index_values", type: "T::Enumerable[T.untyped]"),
        create_kw_opt_param("includes", default: "nil", type: "T.untyped"),
      ],
      return_type: COLLECTION_TYPE.call(constant)
    )
  end
end
type_for_field(field, returns_collection:) click to toggle source
# File lib/tapioca/compilers/dsl/identity_cache.rb, line 115
def type_for_field(field, returns_collection:)
  cache_type = field.reflection.compute_class(field.reflection.class_name)
  if returns_collection
    COLLECTION_TYPE.call(cache_type)
  else
    "T.nilable(::#{cache_type})"
  end
rescue ArgumentError
  "T.untyped"
end