Compare commits
3 Commits
6ba5a70bb5
...
86203449ac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86203449ac | ||
|
|
8854dddb4a | ||
|
|
198ab946d7 |
33
.devcontainer/Dockerfile
Normal file
33
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
FROM mcr.microsoft.com/devcontainers/base:debian-12
|
||||
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -qqy \
|
||||
build-essential \
|
||||
autoconf \
|
||||
libssl-dev \
|
||||
libyaml-dev \
|
||||
zlib1g-dev \
|
||||
libffi-dev \
|
||||
libgmp-dev \
|
||||
rustc \
|
||||
watchman \
|
||||
libsqlite3-dev \
|
||||
pkg-config
|
||||
USER vscode
|
||||
RUN git clone https://github.com/rbenv/rbenv.git ~/.rbenv
|
||||
ENV PATH="/home/vscode/.rbenv/bin:/home/vscode/.rbenv/shims:$PATH"
|
||||
RUN echo 'eval "$(rbenv init - --no-rehash bash)"' >> ~/.bashrc
|
||||
RUN git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
|
||||
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
|
||||
@@ -3,7 +3,10 @@
|
||||
{
|
||||
"name": "Ruby",
|
||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
||||
"image": "mcr.microsoft.com/devcontainers/ruby:1-3.4-bullseye",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"context": ".."
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-extra/features/npm-package:1": {
|
||||
"package": "backlog.md"
|
||||
@@ -16,16 +19,16 @@
|
||||
"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.
|
||||
// "forwardPorts": [],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "sudo apt update && sudo apt install --no-install-recommends --no-install-suggests -qy watchman",
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,6 +6,8 @@
|
||||
/pkg/
|
||||
/spec/reports/
|
||||
/tmp/
|
||||
/vendor/
|
||||
/.ruby-lsp/
|
||||
|
||||
# rspec failure tracking
|
||||
.rspec_status
|
||||
|
||||
9
.vscode/launch.json
vendored
9
.vscode/launch.json
vendored
@@ -6,17 +6,14 @@
|
||||
"configurations": [
|
||||
{
|
||||
"type": "rdbg",
|
||||
"name": "Debug current file with rdbg",
|
||||
"name": "rdbg - demo_functionality.rb",
|
||||
"request": "launch",
|
||||
"script": "${file}",
|
||||
"askParameters": true,
|
||||
"rdbgPath": "~/.rbenv/shims/rdbg"
|
||||
"script": "demo_functionality.rb",
|
||||
},
|
||||
{
|
||||
"type": "rdbg",
|
||||
"name": "Attach with rdbg",
|
||||
"name": "rdbg - attach to rspec",
|
||||
"request": "attach",
|
||||
"rdbgPath": "~/.rbenv/shims/rdbg"
|
||||
}
|
||||
]
|
||||
}
|
||||
11
.vscode/settings.json
vendored
11
.vscode/settings.json
vendored
@@ -2,11 +2,16 @@
|
||||
"editor.formatOnSave": true,
|
||||
"workbench.editor.titleScrollbarSizing": "large",
|
||||
"rubyLsp.formatter": "syntax_tree",
|
||||
"rubyLsp.featureFlags": {
|
||||
"fullTestDiscovery": true
|
||||
},
|
||||
"rubyLsp.addonSettings": {
|
||||
"Ruby LSP RSpec": {
|
||||
"debug": true
|
||||
}
|
||||
},
|
||||
"[ruby]": {
|
||||
"editor.defaultFormatter": "Shopify.ruby-lsp"
|
||||
},
|
||||
"rubyLsp.rubyVersionManager": {
|
||||
"rbenvExecutablePath": "/usr/local/share/rbenv/bin/rbenv",
|
||||
},
|
||||
"rdbg.useBundler": false,
|
||||
}
|
||||
6
Gemfile
6
Gemfile
@@ -12,6 +12,12 @@ group :development do
|
||||
gem "bundler-audit"
|
||||
gem "lefthook"
|
||||
gem "tapioca", "~> 0.17"
|
||||
gem "ruby-lsp-rspec", require: false
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem "simplecov", require: false
|
||||
gem "simplecov-lcov", require: false
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
|
||||
18
Gemfile.lock
18
Gemfile.lock
@@ -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)
|
||||
@@ -51,6 +52,7 @@ GEM
|
||||
pp (>= 0.6.0)
|
||||
rdoc (>= 4.0.0)
|
||||
reline (>= 0.4.2)
|
||||
language_server-protocol (3.17.0.5)
|
||||
lefthook (1.12.2)
|
||||
logger (1.7.0)
|
||||
method_source (1.1.0)
|
||||
@@ -96,7 +98,20 @@ GEM
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.13.0)
|
||||
rspec-support (3.13.4)
|
||||
ruby-lsp (0.25.0)
|
||||
language_server-protocol (~> 3.17.0)
|
||||
prism (>= 1.2, < 2.0)
|
||||
rbs (>= 3, < 5)
|
||||
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)
|
||||
@@ -148,6 +163,9 @@ DEPENDENCIES
|
||||
pry
|
||||
rake (~> 13.0)
|
||||
rspec (~> 3.0)
|
||||
ruby-lsp-rspec
|
||||
simplecov
|
||||
simplecov-lcov
|
||||
sorbet-runtime
|
||||
sorbet-static-and-runtime
|
||||
sqlite3 (~> 1.4)
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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)
|
||||
@@ -58,6 +72,7 @@ module HasAuxTable
|
||||
setup_relation_extensions!(config)
|
||||
setup_attribute_getter_setter_hooks!(config)
|
||||
setup_enum_hook!(config)
|
||||
setup_update_counter_hook!(config)
|
||||
|
||||
config
|
||||
end
|
||||
@@ -236,14 +251,15 @@ module HasAuxTable
|
||||
read_attribute
|
||||
_write_attribute
|
||||
write_attribute
|
||||
_assign_attribute
|
||||
].each do |method_name|
|
||||
method = self.instance_method(method_name)
|
||||
self.define_method(method_name) do |name, *args, **kwargs, &block|
|
||||
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
|
||||
@@ -413,6 +429,31 @@ module HasAuxTable
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
sig { params(config: AuxTableConfig).void }
|
||||
def setup_update_counter_hook!(config)
|
||||
self.define_singleton_method(:update_counters) do |id, counters|
|
||||
T.bind(self, T.class_of(ActiveRecord::Base))
|
||||
main_counters = {}
|
||||
aux_counters = {}
|
||||
opts = {}
|
||||
counters.each do |k, v|
|
||||
is_aux = config.aux.is_column?(k)
|
||||
is_main = config.main.is_column?(k)
|
||||
if !is_aux && !is_main
|
||||
opts[k] = v
|
||||
elsif is_aux
|
||||
aux_counters[k] = v
|
||||
elsif is_main
|
||||
main_counters[k] = v
|
||||
end
|
||||
end
|
||||
super(id, main_counters.merge(opts)) if main_counters.any?
|
||||
if aux_counters.any?
|
||||
config.aux.klass.update_counters(id, aux_counters.merge(opts))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
mixes_in_class_methods(ClassMethods)
|
||||
end
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
5
sorbet/rbi/annotations/activesupport.rbi
vendored
5
sorbet/rbi/annotations/activesupport.rbi
vendored
@@ -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
377
sorbet/rbi/gems/docile@1.4.1.rbi
generated
Normal 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)
|
||||
9
sorbet/rbi/gems/language_server-protocol@3.17.0.5.rbi
generated
Normal file
9
sorbet/rbi/gems/language_server-protocol@3.17.0.5.rbi
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for types exported from the `language_server-protocol` gem.
|
||||
# Please instead update this file by running `bin/tapioca gem language_server-protocol`.
|
||||
|
||||
|
||||
# THIS IS AN EMPTY RBI FILE.
|
||||
# see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem
|
||||
15
sorbet/rbi/gems/ruby-lsp-rspec@0.1.26.rbi
generated
Normal file
15
sorbet/rbi/gems/ruby-lsp-rspec@0.1.26.rbi
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for types exported from the `ruby-lsp-rspec` gem.
|
||||
# Please instead update this file by running `bin/tapioca gem ruby-lsp-rspec`.
|
||||
|
||||
|
||||
# source://ruby-lsp-rspec//lib/ruby_lsp_rspec/version.rb#4
|
||||
module RubyLsp; end
|
||||
|
||||
# source://ruby-lsp-rspec//lib/ruby_lsp_rspec/version.rb#5
|
||||
module RubyLsp::RSpec; end
|
||||
|
||||
# source://ruby-lsp-rspec//lib/ruby_lsp_rspec/version.rb#6
|
||||
RubyLsp::RSpec::VERSION = T.let(T.unsafe(nil), String)
|
||||
9
sorbet/rbi/gems/ruby-lsp@0.25.0.rbi
generated
Normal file
9
sorbet/rbi/gems/ruby-lsp@0.25.0.rbi
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
# typed: true
|
||||
|
||||
# DO NOT EDIT MANUALLY
|
||||
# This is an autogenerated file for types exported from the `ruby-lsp` gem.
|
||||
# Please instead update this file by running `bin/tapioca gem ruby-lsp`.
|
||||
|
||||
|
||||
# THIS IS AN EMPTY RBI FILE.
|
||||
# see https://github.com/Shopify/tapioca#manually-requiring-parts-of-a-gem
|
||||
96
sorbet/rbi/gems/simplecov-html@0.13.2.rbi
generated
Normal file
96
sorbet/rbi/gems/simplecov-html@0.13.2.rbi
generated
Normal 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
127
sorbet/rbi/gems/simplecov-lcov@0.8.0.rbi
generated
Normal 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
2149
sorbet/rbi/gems/simplecov@0.22.0.rbi
generated
Normal file
File diff suppressed because it is too large
Load Diff
9
sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi
generated
Normal file
9
sorbet/rbi/gems/simplecov_json_formatter@0.1.4.rbi
generated
Normal 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
|
||||
24
sorbet/rbi/shims/simplecov.rbi
Normal file
24
sorbet/rbi/shims/simplecov.rbi
Normal 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
|
||||
@@ -2,8 +2,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe HasAuxTable do
|
||||
before(:all) { SpecHelper.initialize_spec_schema! }
|
||||
|
||||
# Car class will be defined after schema setup
|
||||
|
||||
it "has a version number" do
|
||||
@@ -23,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
|
||||
@@ -108,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)
|
||||
@@ -144,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")
|
||||
@@ -363,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
|
||||
@@ -379,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
|
||||
@@ -691,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")
|
||||
@@ -710,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")
|
||||
@@ -718,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
|
||||
|
||||
@@ -738,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
|
||||
@@ -770,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)
|
||||
|
||||
@@ -848,4 +877,38 @@ RSpec.describe HasAuxTable do
|
||||
end
|
||||
}.not_to raise_error
|
||||
end
|
||||
|
||||
describe "counter cache" do
|
||||
before do
|
||||
@reader = Reader.create!(name: "John Doe", reading_speed: 100)
|
||||
@book =
|
||||
Book.create!(title: "The Great Gatsby", author: "F. Scott Fitzgerald")
|
||||
end
|
||||
|
||||
it "updates counter caches that are on the aux model" do
|
||||
@reader.read_books << @book
|
||||
expect(@reader.read_book_joins_count).to eq(1)
|
||||
expect(@reader.read_book_joins.count).to eq(1)
|
||||
end
|
||||
|
||||
it "updates counter caches that are on the main table of an aux model" do
|
||||
end
|
||||
|
||||
it "updates counter caches on a non-aux model" do
|
||||
@reader.read_books << @book
|
||||
expect(@book.read_book_joins_count).to eq(1)
|
||||
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
|
||||
|
||||
@@ -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(
|
||||
@@ -12,6 +33,8 @@ ActiveRecord::Base.establish_connection(
|
||||
database: ":memory:"
|
||||
)
|
||||
|
||||
require_relative "spec_models"
|
||||
|
||||
RSpec.configure do |config|
|
||||
# Enable flags like --only-failures and --next-failure
|
||||
config.example_status_persistence_file_path = ".rspec_status"
|
||||
@@ -19,6 +42,8 @@ RSpec.configure do |config|
|
||||
# Disable RSpec exposing methods globally on `Module` and `main`
|
||||
config.disable_monkey_patching!
|
||||
|
||||
config.backtrace_inclusion_patterns = [/\bactiverecord\b/]
|
||||
|
||||
config.expect_with :rspec do |c|
|
||||
c.syntax = :expect
|
||||
end
|
||||
@@ -58,82 +83,4 @@ module SpecHelper
|
||||
|
||||
query_count
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def self.initialize_spec_schema!
|
||||
# Set up the database schema for testing
|
||||
ActiveRecord::Schema.define do
|
||||
create_table :vehicle_lots do |t|
|
||||
t.string :name
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_base_table :vehicles do |t|
|
||||
t.string :name
|
||||
t.references :vehicle_lot, foreign_key: { to_table: :vehicle_lots }
|
||||
t.timestamps
|
||||
|
||||
t.create_aux :car do |t|
|
||||
t.string :fuel_type
|
||||
t.decimal :engine_size, precision: 3, scale: 1
|
||||
end
|
||||
|
||||
t.create_aux :boat do |t|
|
||||
t.boolean :only_freshwater
|
||||
end
|
||||
|
||||
t.create_aux :plane do |t|
|
||||
t.integer :engine_type
|
||||
end
|
||||
end
|
||||
|
||||
create_base_table :people do |t|
|
||||
t.string :name
|
||||
t.timestamps
|
||||
|
||||
t.create_aux :driver do |t|
|
||||
t.integer :license_number
|
||||
t.references :car,
|
||||
foreign_key: {
|
||||
to_table: :vehicles_car_aux,
|
||||
primary_key: :base_table_id
|
||||
}
|
||||
end
|
||||
|
||||
t.create_aux :captain do |t|
|
||||
t.references :boat,
|
||||
null: false,
|
||||
foreign_key: {
|
||||
to_table: :vehicles_boat_aux,
|
||||
primary_key: :base_table_id
|
||||
}
|
||||
end
|
||||
|
||||
t.create_aux :passenger do |t|
|
||||
t.references :boat,
|
||||
null: false,
|
||||
foreign_key: {
|
||||
to_table: :vehicles_boat_aux,
|
||||
primary_key: :base_table_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
create_base_table :utensils do |t|
|
||||
t.string :name
|
||||
t.string :material
|
||||
t.timestamps
|
||||
|
||||
t.create_aux :fork do |t|
|
||||
t.integer :num_tongs
|
||||
end
|
||||
|
||||
t.create_aux :spoon do |t|
|
||||
t.string :curvature
|
||||
end
|
||||
end
|
||||
|
||||
require_relative "spec_models"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,121 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
extend T::Sig
|
||||
|
||||
# Set up the database schema for testing
|
||||
ActiveRecord::Schema.define do
|
||||
create_table :vehicle_lots do |t|
|
||||
t.string :name
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_base_table :vehicles do |t|
|
||||
t.string :name
|
||||
t.references :vehicle_lot, foreign_key: { to_table: :vehicle_lots }
|
||||
t.timestamps
|
||||
|
||||
t.create_aux :car do |t|
|
||||
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
|
||||
|
||||
t.create_aux :plane do |t|
|
||||
t.integer :engine_type
|
||||
end
|
||||
end
|
||||
|
||||
create_base_table :people do |t|
|
||||
t.string :name
|
||||
t.integer :friends_count
|
||||
t.integer :lovers_count
|
||||
t.timestamps
|
||||
|
||||
t.create_aux :driver do |t|
|
||||
t.integer :license_number, index: true, null: false
|
||||
t.references :car,
|
||||
foreign_key: {
|
||||
to_table: :vehicles_car_aux,
|
||||
primary_key: :base_table_id
|
||||
}
|
||||
end
|
||||
|
||||
t.create_aux :captain do |t|
|
||||
t.references :boat,
|
||||
null: false,
|
||||
foreign_key: {
|
||||
to_table: :vehicles_boat_aux,
|
||||
primary_key: :base_table_id
|
||||
}
|
||||
end
|
||||
|
||||
t.create_aux :passenger do |t|
|
||||
t.references :boat,
|
||||
null: false,
|
||||
foreign_key: {
|
||||
to_table: :vehicles_boat_aux,
|
||||
primary_key: :base_table_id
|
||||
}
|
||||
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
|
||||
t.timestamps
|
||||
|
||||
t.create_aux :fork do |t|
|
||||
t.integer :num_tongs
|
||||
end
|
||||
|
||||
t.create_aux :spoon do |t|
|
||||
t.string :curvature
|
||||
end
|
||||
end
|
||||
|
||||
create_aux_table :people, :reader do |t|
|
||||
t.integer :reading_speed
|
||||
t.integer :read_book_joins_count
|
||||
end
|
||||
|
||||
create_table :books do |t|
|
||||
t.string :title
|
||||
t.string :author
|
||||
t.integer :pages
|
||||
t.integer :read_book_joins_count
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :read_book_joins,
|
||||
id: false,
|
||||
primary_key: %i[book_id reader_id] do |t|
|
||||
t.references :book, foreign_key: { to_table: :books }
|
||||
t.references :reader,
|
||||
foreign_key: {
|
||||
to_table: :people_reader_aux,
|
||||
primary_key: :base_table_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class Vehicle < ActiveRecord::Base
|
||||
include HasAuxTable
|
||||
@@ -27,13 +143,82 @@ class Plane < Vehicle
|
||||
end
|
||||
|
||||
class Person < ActiveRecord::Base
|
||||
extend T::Sig
|
||||
include HasAuxTable
|
||||
self.table_name = "people"
|
||||
|
||||
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
|
||||
@@ -59,3 +244,71 @@ module Kitchen
|
||||
aux_table :spoon
|
||||
end
|
||||
end
|
||||
|
||||
# Non-aux table model that has_and_belongs_to_many w/ counter cache
|
||||
class Book < ActiveRecord::Base
|
||||
has_many :read_book_joins, inverse_of: :book
|
||||
has_many :readers, through: :read_book_joins
|
||||
end
|
||||
|
||||
# Aux table model that has_and_belongs_to_many w/ counter cache
|
||||
class Reader < Person
|
||||
aux_table :reader
|
||||
has_many :read_book_joins, inverse_of: :reader
|
||||
has_many :read_books, through: :read_book_joins, source: :book
|
||||
end
|
||||
|
||||
# The join table for the has_and_belongs_to_many association between Reader and Book
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user