cache bundle install at devcontainer build step

This commit is contained in:
Dylan Knutson
2025-07-20 18:24:51 +00:00
parent 8854dddb4a
commit 86203449ac
20 changed files with 3111 additions and 39 deletions

View File

@@ -19,5 +19,15 @@ RUN git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ru
RUN rbenv install 3.4.4 && \
rbenv global 3.4.4
# cache bundle install to make reloading the devcontainer faster
RUN mkdir -p /tmp/bundle-install-cache && \
chown -R vscode:vscode /tmp/bundle-install-cache
WORKDIR /tmp/bundle-install-cache
COPY lib/has_aux_table/version.rb /tmp/bundle-install-cache/lib/has_aux_table/version.rb
COPY Gemfile.lock Gemfile has-aux-table.gemspec /tmp/bundle-install-cache/
RUN BUNDLE_FROZEN=true MAKE="make -j$(nproc)" bundle install --jobs $(nproc)
# convenience aliases
RUN echo 'alias rspec="bundle exec rspec"' >> ~/.bashrc
RUN echo 'alias tapioca="bundle exec tapioca"' >> ~/.bashrc
RUN echo 'alias srb="bundle exec srb"' >> ~/.bashrc

View File

@@ -4,7 +4,8 @@
"name": "Ruby",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"build": {
"dockerfile": "Dockerfile"
"dockerfile": "Dockerfile",
"context": ".."
},
"features": {
"ghcr.io/devcontainers-extra/features/npm-package:1": {
@@ -18,10 +19,11 @@
"aliariff.vscode-erb-beautify",
"KoichiSasada.vscode-rdbg",
"qwtel.sqlite-viewer",
"ms-azuretools.vscode-docker"
"ms-azuretools.vscode-docker",
"ryanluker.vscode-coverage-gutters"
]
}
},
}
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
/spec/reports/
/tmp/
/vendor/
/.ruby-lsp/
# rspec failure tracking
.rspec_status

View File

@@ -2,6 +2,9 @@
"editor.formatOnSave": true,
"workbench.editor.titleScrollbarSizing": "large",
"rubyLsp.formatter": "syntax_tree",
"rubyLsp.featureFlags": {
"fullTestDiscovery": true
},
"rubyLsp.addonSettings": {
"Ruby LSP RSpec": {
"debug": true

View File

@@ -15,6 +15,11 @@ group :development do
gem "ruby-lsp-rspec", require: false
end
group :test do
gem "simplecov", require: false
gem "simplecov-lcov", require: false
end
group :development, :test do
gem "rspec", "~> 3.0"
gem "sorbet-static-and-runtime"

View File

@@ -41,6 +41,7 @@ GEM
irb (~> 1.10)
reline (>= 0.3.8)
diff-lcs (1.6.2)
docile (1.4.1)
drb (2.2.3)
erb (5.0.1)
erubi (1.13.1)
@@ -104,6 +105,13 @@ GEM
ruby-lsp-rspec (0.1.26)
ruby-lsp (~> 0.25.0)
securerandom (0.4.1)
simplecov (0.22.0)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.2)
simplecov-lcov (0.8.0)
simplecov_json_formatter (0.1.4)
sorbet (0.5.12222)
sorbet-static (= 0.5.12222)
sorbet-runtime (0.5.12222)
@@ -156,6 +164,8 @@ DEPENDENCIES
rake (~> 13.0)
rspec (~> 3.0)
ruby-lsp-rspec
simplecov
simplecov-lcov
sorbet-runtime
sorbet-static-and-runtime
sqlite3 (~> 1.4)

View File

@@ -109,8 +109,6 @@ class E621Post < Post
belongs_to :creator, class_name: "E621User", inverse_of: :created_posts
end
puts FaPost.inspect
fa_user = FaUser.create!(username: "Alice", url_name: "alice")
fa_user_id = fa_user.id
raise if fa_user.id.nil?

View File

@@ -35,6 +35,20 @@ module HasAuxTable
include RelationExtensions
sig do
params(column_name: T.any(String, Symbol)).returns(
T.nilable(AuxTableConfig)
)
end
def aux_table_for(column_name)
@aux_table_configs ||=
T.let({}, T.nilable(T::Hash[Symbol, AuxTableConfig]))
@aux_table_configs.values.find do |config|
config.aux.is_column?(column_name)
end
end
# Main DSL method for defining auxiliary tables
sig { params(aux_name: T.any(String, Symbol)).returns(AuxTableConfig) }
def aux_table(aux_name)
@@ -243,7 +257,9 @@ module HasAuxTable
T.bind(self, ActiveRecord::Base)
if config.aux.column_names.include?(name.to_s)
target = config.aux_model_for(self)
T.unsafe(target).send(method_name, name, *args, **kwargs, &block)
ret =
T.unsafe(target).send(method_name, name, *args, **kwargs, &block)
ret
else
T.unsafe(method).bind(self).call(name, *args, **kwargs, &block)
end

View File

@@ -194,22 +194,6 @@ module HasAuxTable
end
end
sig do
params(
relation: T.any(ActiveRecord::Relation, T.class_of(ActiveRecord::Base)),
conditions: T::Hash[String, T.untyped]
).returns(ActiveRecord::Relation)
end
def apply_split_conditions!(relation, conditions)
main_conditions, aux_conditions =
self.aux.partition_by_columns(conditions)
relation = relation.where(main_conditions) if main_conditions.any?
if aux_conditions.any?
relation = relation.where(aux_association_name => aux_conditions)
end
relation
end
sig do
params(conditions: T::Hash[String, T.untyped]).returns(
T::Hash[String, T.untyped]

View File

@@ -1,6 +1,68 @@
# typed: true
# frozen_string_literal: true
require "active_record/associations/association_scope"
class ActiveRecord::Associations::AssociationScope
def get_chain(reflection, association, tracker)
name = reflection.name
chain =
T.let(
[
ActiveRecord::Reflection::RuntimeReflection.new(
reflection,
association
)
],
T.untyped
)
reflection
.chain
.drop(1)
.each do |refl|
refl_klass = T.cast(refl.klass, T.class_of(ActiveRecord::Base))
if refl_klass.is_a?(HasAuxTable::ClassMethods) &&
(aux_config = refl_klass.aux_table_for(refl.foreign_key))
aliased_table = aux_config.aux.klass.arel_table
chain << ReflectionProxy.new(refl, aliased_table)
else
aliased_table =
tracker.aliased_table_for(refl.klass.arel_table) do
refl.alias_candidate(name)
end
chain << ReflectionProxy.new(refl, aliased_table)
end
end
chain
end
def scope(association)
klass = association.klass
reflection = association.reflection
scope = klass.unscoped
owner = association.owner
chain = get_chain(reflection, association, scope.alias_tracker)
scope.extending! reflection.extensions
scope = add_constraints(scope, owner, chain)
scope.limit!(1) unless reflection.collection?
chain.each do |refl|
klass = refl.klass
next unless klass.is_a?(HasAuxTable::ClassMethods)
next unless aux_config = klass.aux_table_for(refl.join_primary_key)
aux_table = aux_config.aux.klass.table_name
main_table = aux_config.main.klass.table_name
fkey = "'#{aux_table}'.'#{aux_config.foreign_key}'"
pkey = "'#{main_table}'.'#{aux_config.primary_key}'"
scope.joins!("INNER JOIN '#{main_table}' ON #{fkey} = #{pkey}")
end if association.is_a?(
ActiveRecord::Associations::HasManyThroughAssociation
)
scope
end
end
module HasAuxTable
module RelationExtensions
extend T::Sig
@@ -73,11 +135,7 @@ module HasAuxTable
:bind_attribute,
true
) do |original, name, value, &block|
if aux_config.aux.is_column?(name)
aux_config.aux_bind_attribute(name, value, &block)
else
original.call(name, value, &block)
end
aux_config.aux_bind_attribute(name, value, &block)
end
end
end

View File

@@ -461,3 +461,8 @@ class ActiveSupport::ErrorReporter
sig { params(error: T.any(Exception, String), severity: T.nilable(Symbol), context: T::Hash[Symbol, T.untyped], source: T.nilable(String)).void }
def unexpected(error, severity: T.unsafe(nil), context: T.unsafe(nil), source: T.unsafe(nil)); end
end
module ActiveSupport::Testing::Assertions
sig { type_parameters(:Block).params(block: T.proc.returns(T.type_parameter(:Block))).returns(T.type_parameter(:Block)) }
def assert_nothing_raised(&block); end
end

377
sorbet/rbi/gems/docile@1.4.1.rbi generated Normal file
View File

@@ -0,0 +1,377 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for types exported from the `docile` gem.
# Please instead update this file by running `bin/tapioca gem docile`.
# Docile keeps your Ruby DSLs tame and well-behaved.
#
# source://docile//lib/docile/version.rb#3
module Docile
extend ::Docile::Execution
private
# Execute a block in the context of an object whose methods represent the
# commands in a DSL.
#
# Use this method to execute an *imperative* DSL, which means that:
#
# 1. Each command mutates the state of the DSL context object
# 2. The return value of each command is ignored
# 3. The final return value is the original context object
#
# @example Use a String as a DSL
# Docile.dsl_eval("Hello, world!") do
# reverse!
# upcase!
# end
# #=> "!DLROW ,OLLEH"
# @example Use an Array as a DSL
# Docile.dsl_eval([]) do
# push 1
# push 2
# pop
# push 3
# end
# #=> [1, 3]
# @note Use with an *imperative* DSL (commands modify the context object)
# @param dsl [Object] context object whose methods make up the DSL
# @param args [Array] arguments to be passed to the block
# @param block [Proc] the block of DSL commands to be executed against the
# `dsl` context object
# @return [Object] the `dsl` context object after executing the block
#
# source://docile//lib/docile.rb#45
def dsl_eval(dsl, *args, **_arg2, &block); end
# Execute a block in the context of an immutable object whose methods,
# and the methods of their return values, represent the commands in a DSL.
#
# Use this method to execute a *functional* DSL, which means that:
#
# 1. The original DSL context object is never mutated
# 2. Each command returns the next DSL context object
# 3. The final return value is the value returned by the last command
#
# @example Use a frozen String as a DSL
# Docile.dsl_eval_immutable("I'm immutable!".freeze) do
# reverse
# upcase
# end
# #=> "!ELBATUMMI M'I"
# @example Use a Float as a DSL
# Docile.dsl_eval_immutable(84.5) do
# fdiv(2)
# floor
# end
# #=> 42
# @note Use with a *functional* DSL (commands return successor
# context objects)
# @param dsl [Object] immutable context object whose methods make up the
# initial DSL
# @param args [Array] arguments to be passed to the block
# @param block [Proc] the block of DSL commands to be executed against the
# `dsl` context object and successor return values
# @return [Object] the return value of the final command in the block
#
# source://docile//lib/docile.rb#128
def dsl_eval_immutable(dsl, *args, **_arg2, &block); end
# Execute a block in the context of an object whose methods represent the
# commands in a DSL, and return *the block's return value*.
#
# Use this method to execute an *imperative* DSL, which means that:
#
# 1. Each command mutates the state of the DSL context object
# 2. The return value of each command is ignored
# 3. The final return value is the original context object
#
# @example Use a String as a DSL
# Docile.dsl_eval_with_block_return("Hello, world!") do
# reverse!
# upcase!
# first
# end
# #=> "!"
# @example Use an Array as a DSL
# Docile.dsl_eval_with_block_return([]) do
# push "a"
# push "b"
# pop
# push "c"
# length
# end
# #=> 2
# @note Use with an *imperative* DSL (commands modify the context object)
# @param dsl [Object] context object whose methods make up the DSL
# @param args [Array] arguments to be passed to the block
# @param block [Proc] the block of DSL commands to be executed against the
# `dsl` context object
# @return [Object] the return value from executing the block
#
# source://docile//lib/docile.rb#87
def dsl_eval_with_block_return(dsl, *args, **_arg2, &block); end
class << self
# Execute a block in the context of an object whose methods represent the
# commands in a DSL.
#
# Use this method to execute an *imperative* DSL, which means that:
#
# 1. Each command mutates the state of the DSL context object
# 2. The return value of each command is ignored
# 3. The final return value is the original context object
#
# @example Use a String as a DSL
# Docile.dsl_eval("Hello, world!") do
# reverse!
# upcase!
# end
# #=> "!DLROW ,OLLEH"
# @example Use an Array as a DSL
# Docile.dsl_eval([]) do
# push 1
# push 2
# pop
# push 3
# end
# #=> [1, 3]
# @note Use with an *imperative* DSL (commands modify the context object)
# @param dsl [Object] context object whose methods make up the DSL
# @param args [Array] arguments to be passed to the block
# @param block [Proc] the block of DSL commands to be executed against the
# `dsl` context object
# @return [Object] the `dsl` context object after executing the block
#
# source://docile//lib/docile.rb#51
def dsl_eval(dsl, *args, **_arg2, &block); end
# Execute a block in the context of an immutable object whose methods,
# and the methods of their return values, represent the commands in a DSL.
#
# Use this method to execute a *functional* DSL, which means that:
#
# 1. The original DSL context object is never mutated
# 2. Each command returns the next DSL context object
# 3. The final return value is the value returned by the last command
#
# @example Use a frozen String as a DSL
# Docile.dsl_eval_immutable("I'm immutable!".freeze) do
# reverse
# upcase
# end
# #=> "!ELBATUMMI M'I"
# @example Use a Float as a DSL
# Docile.dsl_eval_immutable(84.5) do
# fdiv(2)
# floor
# end
# #=> 42
# @note Use with a *functional* DSL (commands return successor
# context objects)
# @param dsl [Object] immutable context object whose methods make up the
# initial DSL
# @param args [Array] arguments to be passed to the block
# @param block [Proc] the block of DSL commands to be executed against the
# `dsl` context object and successor return values
# @return [Object] the return value of the final command in the block
#
# source://docile//lib/docile.rb#133
def dsl_eval_immutable(dsl, *args, **_arg2, &block); end
# Execute a block in the context of an object whose methods represent the
# commands in a DSL, and return *the block's return value*.
#
# Use this method to execute an *imperative* DSL, which means that:
#
# 1. Each command mutates the state of the DSL context object
# 2. The return value of each command is ignored
# 3. The final return value is the original context object
#
# @example Use a String as a DSL
# Docile.dsl_eval_with_block_return("Hello, world!") do
# reverse!
# upcase!
# first
# end
# #=> "!"
# @example Use an Array as a DSL
# Docile.dsl_eval_with_block_return([]) do
# push "a"
# push "b"
# pop
# push "c"
# length
# end
# #=> 2
# @note Use with an *imperative* DSL (commands modify the context object)
# @param dsl [Object] context object whose methods make up the DSL
# @param args [Array] arguments to be passed to the block
# @param block [Proc] the block of DSL commands to be executed against the
# `dsl` context object
# @return [Object] the return value from executing the block
#
# source://docile//lib/docile.rb#94
def dsl_eval_with_block_return(dsl, *args, **_arg2, &block); end
end
end
# This is used to remove entries pointing to Docile's source files
# from {Exception#backtrace} and {Exception#backtrace_locations}.
#
# If {NoMethodError} is caught then the exception object will be extended
# by this module to add filter functionalities.
#
# @api private
#
# source://docile//lib/docile/backtrace_filter.rb#11
module Docile::BacktraceFilter
# @api private
#
# source://docile//lib/docile/backtrace_filter.rb#14
def backtrace; end
# @api private
#
# source://docile//lib/docile/backtrace_filter.rb#19
def backtrace_locations; end
end
# @api private
#
# source://docile//lib/docile/backtrace_filter.rb#12
Docile::BacktraceFilter::FILTER_PATTERN = T.let(T.unsafe(nil), Regexp)
# Operates in the same manner as {FallbackContextProxy}, but replacing
# the primary `receiver` object with the result of each proxied method.
#
# This is useful for implementing DSL evaluation for immutable context
# objects.
#
#
# @api private
# @see Docile.dsl_eval_immutable
#
# source://docile//lib/docile/chaining_fallback_context_proxy.rb#17
class Docile::ChainingFallbackContextProxy < ::Docile::FallbackContextProxy
# Proxy methods as in {FallbackContextProxy#method_missing}, replacing
# `receiver` with the returned value.
#
# @api private
#
# source://docile//lib/docile/chaining_fallback_context_proxy.rb#20
def method_missing(method, *args, **_arg2, &block); end
end
# A namespace for functions relating to the execution of a block against a
# proxy object.
#
# @api private
#
# source://docile//lib/docile/execution.rb#8
module Docile::Execution
private
# Execute a block in the context of an object whose methods represent the
# commands in a DSL, using a specific proxy class.
#
# @api private
# @param dsl [Object] context object whose methods make up the
# (initial) DSL
# @param proxy_type [FallbackContextProxy, ChainingFallbackContextProxy] which class to instantiate as proxy context
# @param args [Array] arguments to be passed to the block
# @param block [Proc] the block of DSL commands to be executed
# @return [Object] the return value of the block
#
# source://docile//lib/docile/execution.rb#19
def exec_in_proxy_context(dsl, proxy_type, *args, **_arg3, &block); end
class << self
# Execute a block in the context of an object whose methods represent the
# commands in a DSL, using a specific proxy class.
#
# @api private
# @param dsl [Object] context object whose methods make up the
# (initial) DSL
# @param proxy_type [FallbackContextProxy, ChainingFallbackContextProxy] which class to instantiate as proxy context
# @param args [Array] arguments to be passed to the block
# @param block [Proc] the block of DSL commands to be executed
# @return [Object] the return value of the block
#
# source://docile//lib/docile/execution.rb#51
def exec_in_proxy_context(dsl, proxy_type, *args, **_arg3, &block); end
end
end
# A proxy object with a primary receiver as well as a secondary
# fallback receiver.
#
# Will attempt to forward all method calls first to the primary receiver,
# and then to the fallback receiver if the primary does not handle that
# method.
#
# This is useful for implementing DSL evaluation in the context of an object.
#
#
# @api private
# @see Docile.dsl_eval
#
# source://docile//lib/docile/fallback_context_proxy.rb#20
class Docile::FallbackContextProxy
# @api private
# @param receiver [Object] the primary proxy target to which all methods
# initially will be forwarded
# @param fallback [Object] the fallback proxy target to which any methods
# not handled by `receiver` will be forwarded
# @return [FallbackContextProxy] a new instance of FallbackContextProxy
#
# source://docile//lib/docile/fallback_context_proxy.rb#46
def initialize(receiver, fallback); end
# @api private
# @return [Array<Symbol>] Instance variable names, excluding
# {NON_PROXIED_INSTANCE_VARIABLES}
#
# source://docile//lib/docile/fallback_context_proxy.rb#85
def instance_variables; end
# Proxy all methods, excluding {NON_PROXIED_METHODS}, first to `receiver`
# and then to `fallback` if not found.
#
# @api private
#
# source://docile//lib/docile/fallback_context_proxy.rb#91
def method_missing(method, *args, **_arg2, &block); end
end
# The set of methods which will **not** fallback from the block's context
# to the dsl object.
#
# @api private
#
# source://docile//lib/docile/fallback_context_proxy.rb#30
Docile::FallbackContextProxy::NON_FALLBACK_METHODS = T.let(T.unsafe(nil), Set)
# The set of instance variables which are local to this object and hidden.
# All other instance variables will be copied in and out of this object
# from the scope in which this proxy was created.
#
# @api private
#
# source://docile//lib/docile/fallback_context_proxy.rb#35
Docile::FallbackContextProxy::NON_PROXIED_INSTANCE_VARIABLES = T.let(T.unsafe(nil), Set)
# The set of methods which will **not** be proxied, but instead answered
# by this object directly.
#
# @api private
#
# source://docile//lib/docile/fallback_context_proxy.rb#23
Docile::FallbackContextProxy::NON_PROXIED_METHODS = T.let(T.unsafe(nil), Set)
# The current version of this library
#
# source://docile//lib/docile/version.rb#5
Docile::VERSION = T.let(T.unsafe(nil), String)

View File

@@ -0,0 +1,96 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for types exported from the `simplecov-html` gem.
# Please instead update this file by running `bin/tapioca gem simplecov-html`.
# source://simplecov-html//lib/simplecov-html.rb#15
module SimpleCov; end
# source://simplecov-html//lib/simplecov-html.rb#16
module SimpleCov::Formatter; end
# source://simplecov-html//lib/simplecov-html.rb#17
class SimpleCov::Formatter::HTMLFormatter
# @return [HTMLFormatter] a new instance of HTMLFormatter
#
# source://simplecov-html//lib/simplecov-html.rb#26
def initialize; end
# source://simplecov-html//lib/simplecov-html.rb#33
def format(result); end
private
# source://simplecov-html//lib/simplecov-html.rb#93
def asset_inline(name); end
# source://simplecov-html//lib/simplecov-html.rb#79
def asset_output_path; end
# source://simplecov-html//lib/simplecov-html.rb#87
def assets_path(name); end
# @return [Boolean]
#
# source://simplecov-html//lib/simplecov-html.rb#48
def branchable_result?; end
# source://simplecov-html//lib/simplecov-html.rb#124
def coverage_css_class(covered_percent); end
# source://simplecov-html//lib/simplecov-html.rb#120
def covered_percent(percent); end
# Returns a table containing the given source files
#
# source://simplecov-html//lib/simplecov-html.rb#111
def formatted_file_list(title, source_files); end
# Returns the html for the given source_file
#
# source://simplecov-html//lib/simplecov-html.rb#104
def formatted_source_file(source_file); end
# Return a (kind of) unique id for the source file given. Uses SHA1 on path for the id
#
# source://simplecov-html//lib/simplecov-html.rb#145
def id(source_file); end
# @return [Boolean]
#
# source://simplecov-html//lib/simplecov-html.rb#55
def line_status?(source_file, line); end
# source://simplecov-html//lib/simplecov-html.rb#157
def link_to_source_file(source_file); end
# source://simplecov-html//lib/simplecov-html.rb#63
def output_message(result); end
# source://simplecov-html//lib/simplecov-html.rb#75
def output_path; end
# source://simplecov-html//lib/simplecov-html.rb#153
def shortened_filename(source_file); end
# source://simplecov-html//lib/simplecov-html.rb#134
def strength_css_class(covered_strength); end
# Returns the an erb instance for the template of given name
#
# source://simplecov-html//lib/simplecov-html.rb#71
def template(name); end
# source://simplecov-html//lib/simplecov-html.rb#149
def timeago(time); end
end
# Only have a few content types, just hardcode them
#
# source://simplecov-html//lib/simplecov-html.rb#19
SimpleCov::Formatter::HTMLFormatter::CONTENT_TYPES = T.let(T.unsafe(nil), Hash)
# source://simplecov-html//lib/simplecov-html/version.rb#6
SimpleCov::Formatter::HTMLFormatter::VERSION = T.let(T.unsafe(nil), String)

127
sorbet/rbi/gems/simplecov-lcov@0.8.0.rbi generated Normal file
View File

@@ -0,0 +1,127 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for types exported from the `simplecov-lcov` gem.
# Please instead update this file by running `bin/tapioca gem simplecov-lcov`.
# source://simplecov-lcov//lib/simplecov-lcov.rb#7
module SimpleCov; end
# source://simplecov-lcov//lib/simplecov-lcov.rb#8
module SimpleCov::Formatter; end
# Custom Formatter to generate lcov style coverage for simplecov
#
# source://simplecov-lcov//lib/simplecov-lcov.rb#10
class SimpleCov::Formatter::LcovFormatter
# generate lcov style coverage.
# ==== Args
# _result_ :: [SimpleCov::Result] abcoverage result instance.
#
# source://simplecov-lcov//lib/simplecov-lcov.rb#14
def format(result); end
private
# source://simplecov-lcov//lib/simplecov-lcov.rb#62
def create_output_directory!; end
# source://simplecov-lcov//lib/simplecov-lcov.rb#121
def filtered_branches(file); end
# source://simplecov-lcov//lib/simplecov-lcov.rb#117
def filtered_lines(file); end
# source://simplecov-lcov//lib/simplecov-lcov.rb#125
def format_branch(branch, branch_idx); end
# source://simplecov-lcov//lib/simplecov-lcov.rb#101
def format_branches(file); end
# source://simplecov-lcov//lib/simplecov-lcov.rb#84
def format_file(file); end
# source://simplecov-lcov//lib/simplecov-lcov.rb#130
def format_line(line); end
# source://simplecov-lcov//lib/simplecov-lcov.rb#111
def format_lines(file); end
# source://simplecov-lcov//lib/simplecov-lcov.rb#50
def lcov_results_path; end
# source://simplecov-lcov//lib/simplecov-lcov.rb#46
def output_directory; end
# source://simplecov-lcov//lib/simplecov-lcov.rb#79
def output_filename(filename); end
# @return [Boolean]
#
# source://simplecov-lcov//lib/simplecov-lcov.rb#54
def report_with_single_file?; end
# source://simplecov-lcov//lib/simplecov-lcov.rb#58
def single_report_path; end
# source://simplecov-lcov//lib/simplecov-lcov.rb#67
def write_lcov!(file); end
# source://simplecov-lcov//lib/simplecov-lcov.rb#73
def write_lcov_to_single_file!(files); end
class << self
# @yield [@config]
#
# source://simplecov-lcov//lib/simplecov-lcov.rb#27
def config; end
# source://simplecov-lcov//lib/simplecov-lcov.rb#33
def report_with_single_file=(value); end
end
end
# source://simplecov-lcov//lib/simple_cov_lcov/configuration.rb#1
module SimpleCovLcov; end
# source://simplecov-lcov//lib/simple_cov_lcov/configuration.rb#2
class SimpleCovLcov::Configuration
# source://simplecov-lcov//lib/simple_cov_lcov/configuration.rb#24
def lcov_file_name; end
# Sets the attribute lcov_file_name
#
# @param value the value to set the attribute lcov_file_name to.
#
# source://simplecov-lcov//lib/simple_cov_lcov/configuration.rb#5
def lcov_file_name=(_arg0); end
# source://simplecov-lcov//lib/simple_cov_lcov/configuration.rb#11
def output_directory; end
# Sets the attribute output_directory
#
# @param value the value to set the attribute output_directory to.
#
# source://simplecov-lcov//lib/simple_cov_lcov/configuration.rb#4
def output_directory=(_arg0); end
# Sets the attribute report_with_single_file
#
# @param value the value to set the attribute report_with_single_file to.
#
# source://simplecov-lcov//lib/simple_cov_lcov/configuration.rb#3
def report_with_single_file=(_arg0); end
# @return [Boolean]
#
# source://simplecov-lcov//lib/simple_cov_lcov/configuration.rb#7
def report_with_single_file?; end
# source://simplecov-lcov//lib/simple_cov_lcov/configuration.rb#20
def single_report_path; end
# source://simplecov-lcov//lib/simple_cov_lcov/configuration.rb#15
def single_report_path=(new_path); end
end

2149
sorbet/rbi/gems/simplecov@0.22.0.rbi generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,9 @@
# typed: true
# DO NOT EDIT MANUALLY
# This is an autogenerated file for types exported from the `simplecov_json_formatter` gem.
# Please instead update this file by running `bin/tapioca gem simplecov_json_formatter`.
# THIS IS AN EMPTY RBI FILE.
# see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem

View File

@@ -0,0 +1,24 @@
# typed: strict
# frozen_string_literal: true
module SimpleCov
sig do
params(profile: T.untyped, block: T.proc.bind(SimpleCov).void).returns(
T.nilable(T.any(Symbol, Integer))
)
end
def self.start(profile = nil, &block)
end
sig { params(filter: T.untyped).void }
def add_filter(filter)
end
sig { params(level: T.nilable(T.any(Symbol, Integer))).void }
def enable_coverage(level = nil)
end
sig { params(formatter: T.untyped).void }
def formatter(formatter)
end
end

View File

@@ -21,6 +21,10 @@ RSpec.describe HasAuxTable do
end
describe "column reporting" do
it "reports columns of the base class" do
expect(Vehicle.inspect).to include("name")
end
it "reports the correct columns on the string repr of the class" do
expect(Car.inspect).to include("fuel_type")
end
@@ -106,7 +110,7 @@ RSpec.describe HasAuxTable do
end
it "can set association on aux record" do
driver = Driver.create!(name: "John Doe")
driver = Driver.create!(name: "John Doe", license_number: 12_345)
car = Car.create!(name: "Honda Civic")
driver.car = car
expect(driver.car).to eq(car)
@@ -142,6 +146,20 @@ RSpec.describe HasAuxTable do
expect(plane.engine_type).to eq("turboprop")
end
describe "validations" do
it "validates the main record" do
driver = Driver.create!(name: "John Doe", license_number: 12_345)
expect(driver.valid?).to be_truthy
driver.name = nil
expect(driver.valid?).to be_falsey
end
it "validates through an association" do
car = Car.create!(name: "Honda Civic")
car.drivers.create!(name: "John Doe", license_number: 12_345)
end
end
describe "#changed?" do
it "returns true if the main record changes" do
car = Car.create!(name: "Honda Civic")
@@ -361,10 +379,7 @@ RSpec.describe HasAuxTable do
end
it "works with chained where clauses" do
# Chain where clauses with auxiliary columns
efficient_cars = Car.where(fuel_type: "hybrid").where(engine_size: 1.8)
expect(efficient_cars.length).to eq(1)
expect(efficient_cars.first.name).to eq("Toyota Prius")
end
@@ -377,6 +392,12 @@ RSpec.describe HasAuxTable do
car_names = cars.map(&:name).sort
expect(car_names).to eq(["Tesla Model 3", "Toyota Prius"])
end
it "works when sql is passed to where" do
cars = Car.where("fuel_type = 'hybrid'")
expect(cars.length).to eq(1)
expect(cars.first.name).to eq("Toyota Prius")
end
end
describe "query performance and optimization" do
@@ -689,7 +710,7 @@ RSpec.describe HasAuxTable do
describe "nested associations" do
it "can create a driver through the association" do
driver = @car.drivers.create!(name: "John Doe")
driver = @car.drivers.create!(name: "John Doe", license_number: 123_456)
expect(driver.car).to eq(@car)
expect(driver.car_id).to eq(@car.id)
expect(driver.car.fuel_type).to eq("hybrid")
@@ -708,7 +729,8 @@ RSpec.describe HasAuxTable do
end
it "can create a driver directly" do
driver = Driver.create!(car: @car, name: "John Doe")
driver =
Driver.create!(car: @car, name: "John Doe", license_number: 123_456)
expect(driver.car).to eq(@car)
expect(driver.car_id).to eq(@car.id)
expect(driver.car.fuel_type).to eq("hybrid")
@@ -716,12 +738,12 @@ RSpec.describe HasAuxTable do
end
it "can be accessed through the association" do
driver = @car.drivers.create!(name: "John Doe")
driver = @car.drivers.create!(name: "John Doe", license_number: 123_456)
expect(@car.drivers).to eq([driver])
end
it "can be destroyed through the association" do
driver = @car.drivers.create!(name: "John Doe")
driver = @car.drivers.create!(name: "John Doe", license_number: 123_456)
expect { driver.destroy }.to change { @car.reload.drivers.count }.by(-1)
end
@@ -736,6 +758,15 @@ RSpec.describe HasAuxTable do
d = drivers.find_by(license_number: 123_456)
expect(d.id).to eq(driver.id)
end
# it "can create STI models through associations" do
# user =
# TwitterUser.create!(last_login_at: Time.now, twitter_handle: "a_user")
# post = user.posts.create!(title: "twitter post")
# expect(post.user).to eq(user)
# expect(post.user_id).to eq(user.id)
# expect(post.user.twitter_handle).to eq("a_user")
# end
end
describe "#reload" do
@@ -768,7 +799,7 @@ RSpec.describe HasAuxTable do
it "reloads associations" do
expect(@car.drivers.length).to eq(0)
Driver.create!(car: @car, name: "Billy Kid")
Driver.create!(car: @car, name: "Billy Kid", license_number: 123_456)
expect(@car.drivers.length).to eq(0)
expect(@car.drivers.count).to eq(1)
@@ -869,4 +900,15 @@ RSpec.describe HasAuxTable do
expect(@book.read_book_joins.count).to eq(1)
end
end
describe "joins model with aux tables" do
it "can create a join record" do
doctor = Person.create!(name: "Dr. John Doe")
patient = Person.create!(name: "Jane Doe")
assoc = doctor.patients
assoc << patient
expect(doctor.patients.count).to eq(1)
expect(patient.doctors.count).to eq(1)
end
end
end

View File

@@ -1,10 +1,31 @@
# typed: strict
# frozen_string_literal: true
require "simplecov"
require "simplecov-lcov"
SimpleCov::Formatter::LcovFormatter.config do |c|
c.report_with_single_file = true
c.lcov_file_name = "lcov.info"
end
SimpleCov.start do
enable_coverage :branch
add_filter "spec"
formatter(
SimpleCov::Formatter::MultiFormatter.new(
[
SimpleCov::Formatter::LcovFormatter, # Add Lcov as an output when generating code coverage report
SimpleCov::Formatter::HTMLFormatter # Add other outputs for the code coverage report
]
)
)
end
require "pry"
require "active_record"
require "active_record/errors"
require "has_aux_table"
require "pry"
# Configure ActiveRecord to use in-memory SQLite database
ActiveRecord::Base.establish_connection(
@@ -21,7 +42,7 @@ RSpec.configure do |config|
# Disable RSpec exposing methods globally on `Module` and `main`
config.disable_monkey_patching!
# config.backtrace_inclusion_patterns = [/\bactiverecord\b/]
config.backtrace_inclusion_patterns = [/\bactiverecord\b/]
config.expect_with :rspec do |c|
c.syntax = :expect

View File

@@ -18,7 +18,9 @@ ActiveRecord::Schema.define do
t.string :fuel_type
t.decimal :engine_size, precision: 3, scale: 1
end
end
change_base_table :vehicles do |t|
t.create_aux :boat do |t|
t.boolean :only_freshwater
end
@@ -35,7 +37,7 @@ ActiveRecord::Schema.define do
t.timestamps
t.create_aux :driver do |t|
t.integer :license_number
t.integer :license_number, index: true, null: false
t.references :car,
foreign_key: {
to_table: :vehicles_car_aux,
@@ -62,6 +64,20 @@ ActiveRecord::Schema.define do
end
end
create_base_table :relationship_joins do |t|
t.create_aux :doctor_patient do |t|
t.integer :num_exams
t.references :doctor, foreign_key: { to_table: :people }
t.references :patient, foreign_key: { to_table: :people }
end
t.create_aux :employer_employee do |t|
t.boolean :signed_nda
t.references :employer, foreign_key: { to_table: :people }
t.references :employee, foreign_key: { to_table: :people }
end
end
create_base_table :utensils do |t|
t.string :name
t.string :material
@@ -127,12 +143,82 @@ class Plane < Vehicle
end
class Person < ActiveRecord::Base
extend T::Sig
include HasAuxTable
validates :name, presence: true, uniqueness: true
sig do
params(
associations: [Symbol, Symbol],
table_name: Symbol,
model_class_name: T.any(Symbol, String, T.class_of(ActiveRecord::Base)),
join_class_name: T.any(Symbol, String, T.class_of(ActiveRecord::Base))
).void
end
def self.has_and_belongs_to_many_through(
associations,
table_name:,
model_class_name:,
join_class_name:
)
from_assoc_plural, to_assoc_plural = associations
from_assoc_class_name, to_assoc_class_name =
model_class_name,
model_class_name
from_assoc_singular = from_assoc_plural.to_s.singularize
from_join_assoc_name = :"#{from_assoc_singular}_#{table_name}"
to_assoc_singular = to_assoc_plural.to_s.singularize
to_join_assoc_name = :"#{to_assoc_singular}_#{table_name}"
has_many(
from_join_assoc_name,
primary_key: primary_key,
foreign_key: "#{from_assoc_singular}_id",
inverse_of: from_assoc_singular,
class_name: join_class_name
)
has_many(
to_assoc_plural,
through: from_join_assoc_name,
source: to_assoc_singular,
class_name: to_assoc_class_name
)
has_many(
to_join_assoc_name,
primary_key: primary_key,
foreign_key: "#{to_assoc_singular}_id",
inverse_of: to_assoc_singular,
class_name: join_class_name
)
has_many(
from_assoc_plural,
through: to_join_assoc_name,
source: from_assoc_singular,
class_name: from_assoc_class_name
)
end
has_and_belongs_to_many_through(
%i[doctors patients],
model_class_name: "Person",
join_class_name: "DoctorPatientJoin",
table_name: :doctor_patient_joins
)
has_and_belongs_to_many_through(
%i[employers employees],
model_class_name: "Person",
join_class_name: "EmployerEmployeeJoin",
table_name: :employer_employee_joins
)
end
class Driver < Person
aux_table :driver
belongs_to :car, optional: true
validates :license_number, presence: true, uniqueness: true
end
class Captain < Person
@@ -177,3 +263,52 @@ class ReadBookJoin < ActiveRecord::Base
belongs_to :book, counter_cache: true
belongs_to :reader, counter_cache: true
end
# Join table that has aux records
class RelationshipJoin < ActiveRecord::Base
include HasAuxTable
end
class DoctorPatientJoin < RelationshipJoin
aux_table :doctor_patient
belongs_to :doctor, class_name: "Person"
belongs_to :patient, class_name: "Person"
end
class EmployerEmployeeJoin < RelationshipJoin
aux_table :employer_employee
belongs_to :employer, class_name: "Person"
belongs_to :employee, class_name: "Person"
end
class User < ActiveRecord::Base
include HasAuxTable
has_many :posts, inverse_of: :user, class_name: "Post"
end
# class TwitterUser < User
# aux_table :twitter
# validates :twitter_handle, presence: true
# has_many :posts, inverse_of: :user, class_name: "TwitterPost"
# end
# class RedditUser < User
# aux_table :reddit
# validates :reddit_handle, presence: true
# has_many :posts, inverse_of: :user, class_name: "RedditPost"
# end
# class Post < ActiveRecord::Base
# include HasAuxTable
# belongs_to :user, inverse_of: :posts
# end
# class TwitterPost < Post
# aux_table :twitter
# belongs_to :user, inverse_of: :posts, class_name: "TwitterUser"
# end
# class RedditPost < Post
# aux_table :reddit
# belongs_to :user, inverse_of: :posts, class_name: "RedditUser"
# end