Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Dependabot update strategy
version: 2
updates:
- package-ecosystem: bundler
directory: "/"
schedule:
interval: weekly
allow:
# Automatically keep all runtime dependencies updated
- dependency-name: "*"
dependency-type: "production"
versioning-strategy: lockfile-only
versioning-strategy: increase-if-necessary
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
51 changes: 14 additions & 37 deletions .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ on:
- actions-*
workflow_dispatch:

env:
BUNDLE_CLEAN: "true"
BUNDLE_PATH: vendor/bundle
BUNDLE_JOBS: 3
BUNDLE_RETRY: 3

jobs:
build:
runs-on: ubuntu-latest
Expand All @@ -38,36 +32,19 @@ jobs:
appraisal: "activerecord_7.1"
- ruby: "2.7"
appraisal: "activerecord_7.0"
- ruby: "2.5"
- ruby: "2.6"
appraisal: "activerecord_6.1"
steps:
- uses: actions/checkout@v6
- name: Set up Ruby ${{ matrix.ruby }}
uses: ruby/setup-ruby@v1
with:
ruby-version: "${{ matrix.ruby }}"
- name: Install packages
run: |
sudo apt-get update
sudo apt-get install libsqlite3-dev
- name: Setup bundler
if: matrix.bundler != ''
run: |
gem uninstall bundler --all
gem install bundler --no-document --version ${{ matrix.bundler }}
- name: Set Appraisal bundle
if: matrix.appraisal != ''
run: |
echo "using gemfile gemfiles/${{ matrix.appraisal }}.gemfile"
bundle config set gemfile "gemfiles/${{ matrix.appraisal }}.gemfile"
- name: Install gems
run: |
bundle install
- name: Run Tests
run: bundle exec rake
- name: standardrb
if: matrix.standardrb == true
run: bundle exec standardrb
- name: yard
if: matrix.yard == true
run: bundle exec yard doc --fail-on-warning
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Ruby CI
uses: bdurand/github-actions/ruby-ci@b7560883d5f8557f5d69fe8bd82721bbb5ed76df # v1.0.4
with:
ruby: ${{ matrix.ruby }}
appraisal: ${{ matrix.appraisal }}
bundler: ${{ matrix.bundler }}
standardrb: ${{ matrix.standardrb }}
yard: ${{ matrix.yard }}
frozen_strings: ${{ matrix.frozen_strings }}
34 changes: 27 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
.DS_Store

.bundle/
/vendor/bundle
/lib/bundler/man/

.rspec
.ruby-version
.yardoc/
.env
*.gem
*.rbc

/spec/reports/
/spec/examples.txt
.yardoc/
_yardoc/
/doc/
/rdoc/
/coverage/
/log/*.log
/pkg/
/tmp/

.ruby-version
.ruby-gemset
Gemfile.lock
coverage/
gemfiles/*.gemfile.lock
log/*.log
pkg/
rdoc/
doc/

.byebug_history

.claude/
.conductor/
.cursor/
.config
11 changes: 2 additions & 9 deletions .standard.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
format: progress

ruby_version: 2.5
ruby_version: 2.6

ignore:
- "**/*":
- Style/RedundantParentheses
- "spec/**/*":
- Lint/ConstantDefinitionInBlock
- Lint/UselessAssignment
format: progress
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 1.7.0

### Fixed

- `sync_table_data!` now retries once on `ActiveRecord::RecordNotUnique` errors caused by a concurrent sync in another process inserting the same rows.
- `sync_table_data!` with `delete_missing: true` now raises an `ArgumentError` instead of deleting every row in the table when the data files contain no rows.
- `sync_table_data!` now raises an `ArgumentError` when a data file row has no value for the key attribute. Previously such rows were collapsed into a single record with a blank key.
- `sync_table_data!` now returns an empty array instead of `nil` when the table does not exist.
- Fixed broken cycle detection in the autosave association check during syncs that could cause infinite recursion on cyclic autosave associations.
- Syncing a single table inheritance subclass no longer inserts duplicate or wrongly typed rows for records defined in the base class' data files. Existing rows are matched without the inheritance type condition, and new rows default to the type of the class whose data file defines them.
- Single table inheritance subclasses now share the support table state defined on their base class regardless of load order. Previously class methods like `instance_names` and `protected_instance?` raised `NoMethodError` and named instance helpers could be missing on subclasses.
- `protected_instance?` and `instance_keys` now include data files added to single table inheritance subclasses and no longer return stale results when data files are added after their values were first computed.
- `sync_table_data!` with `delete_missing: true` no longer deletes rows that are managed by data files added to single table inheritance subclasses.
- Records are now merged in strict data file order, so later files take precedence even when named instance files are mixed with list format files.
- Data files that override attributes on a named instance are now merged by the instance name rather than only by the key attribute. Previously an override that did not repeat the key attribute value was treated as a new record and inserted as an extra row on every sync.
- Named instance helper methods and `named_instance_data` now return the merged values that are synced to the database. Previously the helpers permanently returned the values from the first file that defined the named instance.
- Entries under a name beginning with an underscore are treated as anonymous records even when the value is a single hash. Previously two files reusing the same underscore name each with a single hash were merged into one record, silently dropping rows.
- YAML data files can now use anchors/aliases and date/time values. Previously these raised `Psych::AliasesNotEnabled` or `Psych::DisallowedClass` errors.
- Generated predicate methods (e.g. `record.active?`) now cast the data file value to the attribute type before comparing, so they no longer silently return `false` when the types differ (always the case for CSV data files, where all values are strings).
- `named_instance` now raises an `ActiveRecord::RecordNotFound` error for undefined named instances instead of querying the database for a `nil` key.
- `named_instance_attribute_helpers` can now be called again with an attribute that was already registered without raising an `ArgumentError`.
- Modifications to memoized class-level state are now synchronized to avoid races on Ruby implementations without a global interpreter lock.
- Setting `config.support_table.auto_sync = false` before the gem is loaded is no longer overwritten back to `true` by the Railtie.
- Data file names containing extra dots no longer break the class name detection used by `SupportTableData.sync_all!` to eager load models.
- Error messages for invalid named instance definitions now include the model class name instead of repeating the instance name.
- The `:compact` YARD format now emits macros that YARD can actually expand. Previously the generated docs resolved to methods with no description, `@return`, or `@raise` tags and documented the predicate methods as class methods. Macro names are also namespaced per class so models do not overwrite each other's macros.
- The documentation tasks now remove duplicated generated YARD doc blocks (e.g. left over from a bad merge) instead of corrupting the model source file.
- The documentation tasks no longer report success when a model raises an `ArgumentError` for an invalid named instance definition.

## 1.6.1

### Fixed
Expand Down
25 changes: 16 additions & 9 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ source "https://rubygems.org"

gemspec

gem "rspec", "~> 3.0"
gem "rake"
gem "irb"
gem "sqlite3"
gem "appraisal"
gem "standard", "~>1.0"
gem "pry-byebug"
gem "yard"
gem "csv"
# Exclude development-only gems from dependabot.
unless ENV["DEPENDABOT"]
gem "sqlite3"
gem "csv"

gem "rake"
gem "rspec", "~> 3.11"

# Exclude development-only gems from the gemfiles generated by appraisal.
unless defined?(::Appraisal::Gemfile) && is_a?(::Appraisal::Gemfile)
gem "appraisal", require: false
gem "standard", require: false
gem "simplecov", require: false
gem "yard", require: false
end
end
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class Status < ApplicationRecord

You cannot update the value of the key attribute in a record in the data file. If you do, a new record will be created and the existing record will be left unchanged.

When you add multiple data files, records defined in more than one file are merged together, with later files taking precedence. Records in files defined as a hash of named instances are matched by the instance name, so an override file only needs to specify the attributes it is changing. Records in files defined as a list are matched by the key attribute value, which must be repeated in each file.

You can specify data files as relative paths. This can be done by setting the `SupportTableData.data_directory` value. You can override this value for a model by setting the `support_table_data_directory` attribute on its class. Otherwise, relative file paths will be resolved from the current working directory. You must define the directory to load relative files from before loading your model classes.

In a Rails application, `SupportTableData.data_directory` will be automatically set to `db/support_tables/`. This can be overridden by setting the `config.support_table.data_directory` option in the Rails application configuration.
Expand Down Expand Up @@ -203,7 +205,7 @@ A good practice is to add a check to your CI pipeline to ensure the documentatio
Each model can choose how its YARD docs are generated by setting `support_table_yard_docs` to one of three values:

- `:full` — verbose comment block per generated method (the default)
- `:compact` — shared `@!macro` definitions at the top of the documentation block plus a short `@!method` / `@!macro` pair per generated method. IDEs and `yard doc` resolve the macros into the same per-method documentation as `:full`. Useful when a model has many named instances and the verbose comment block is too long to be useful inline.
- `:compact` — shared `@!macro` definitions plus a short comment block per generated method that expands them. YARD resolves it to exactly the same methods and return types as `:full`. Useful when a model has many named instances and the verbose comment block is too long to be useful inline.
- `:none` — generate no YARD docs for this model. The rake task will also strip any previously generated YARD docs from the source file.

```ruby
Expand Down Expand Up @@ -279,6 +281,12 @@ SupportTableData.sync_all!(delete_missing: true)
> [!CAUTION]
> Use `delete_missing` with care. It will delete any records in the table that are not defined in the data files, which may include user-created data or fail due to foreign key constraints.

As a safeguard, `sync_table_data!` will raise an `ArgumentError` rather than delete anything when `delete_missing` is enabled but the data files contain no rows (for instance, when a data file was accidentally emptied or truncated).

Rows managed by data files added to a single table inheritance subclass are never deleted by a sync on the base class, even if the subclass has not been loaded yet. Each candidate row is resolved to its own class through the inheritance column before it is deleted, so the subclass is loaded on demand and gets to declare which rows it owns.

It is recommended to add a unique database index on the key attribute column. Concurrent syncs from multiple processes (for example, parallel deployment jobs) could otherwise insert duplicate rows. If a sync hits a uniqueness violation from a concurrent insert, it will automatically retry once to pick up the other process' changes.

The number of records contained in data files should be fairly small (ideally fewer than 100). It is possible to load just a subset of rows in a large table because only the rows listed in the data files will be synced. You can use this feature if your table allows user-entered data, but has a few rows that must exist for the code to work.

Loading data is done inside a database transaction. No changes will be persisted to the database unless all rows for a model can be synced.
Expand Down
29 changes: 27 additions & 2 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# frozen_string_literal: true

begin
require "bundler/setup"
rescue LoadError
Expand All @@ -13,10 +15,33 @@ task :verify_release_branch do
end
end

Rake::Task[:release].enhance([:verify_release_branch])
Rake::Task[:release].prerequisites.prepend("verify_release_branch")

require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)

task default: :spec
task default: [:spec]

namespace :appraisal do
desc "Update the appraisal gemfiles"
task :update do
Dir.glob("gemfiles/*.gemfile*") do |file|
File.delete(file) if File.file?(file)
end

system("bundle exec appraisal generate") || abort("appraisal generate failed")

Dir.glob("gemfiles/*.gemfile") do |file|
puts "Locking #{file}"
Bundler.with_unbundled_env do
system(
{
"BUNDLE_GEMFILE" => file
},
"bundle", "lock", "--update"
) || abort("appraisal lock failed on #{file}")
end
end
end
end
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.6.1
1.7.0
8 changes: 2 additions & 6 deletions gemfiles/activerecord_6.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

source "https://rubygems.org"

gem "rspec", "~> 3.0"
gem "rake"
gem "sqlite3", "~> 1.4.0"
gem "appraisal"
gem "standard", "~>1.0"
gem "pry-byebug"
gem "yard"
gem "csv"
gem "rake"
gem "rspec", "~> 3.11"
gem "activerecord", "~> 6.1.0"
gem "concurrent-ruby", "1.3.4"

Expand Down
8 changes: 2 additions & 6 deletions gemfiles/activerecord_7.0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

source "https://rubygems.org"

gem "rspec", "~> 3.0"
gem "rake"
gem "sqlite3", "~> 1.4.0"
gem "appraisal"
gem "standard", "~>1.0"
gem "pry-byebug"
gem "yard"
gem "csv"
gem "rake"
gem "rspec", "~> 3.11"
gem "activerecord", "~> 7.0.0"
gem "concurrent-ruby", "1.3.4"

Expand Down
8 changes: 2 additions & 6 deletions gemfiles/activerecord_7.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

source "https://rubygems.org"

gem "rspec", "~> 3.0"
gem "rake"
gem "sqlite3", "~> 1.4.0"
gem "appraisal"
gem "standard", "~>1.0"
gem "pry-byebug"
gem "yard"
gem "csv"
gem "rake"
gem "rspec", "~> 3.11"
gem "activerecord", "~> 7.0.0"
gem "concurrent-ruby", "1.3.4"

Expand Down
8 changes: 2 additions & 6 deletions gemfiles/activerecord_7.2.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

source "https://rubygems.org"

gem "rspec", "~> 3.0"
gem "rake"
gem "sqlite3", "~> 1.4.0"
gem "appraisal"
gem "standard", "~>1.0"
gem "pry-byebug"
gem "yard"
gem "csv"
gem "rake"
gem "rspec", "~> 3.11"
gem "activerecord", "~> 7.0.0"
gem "concurrent-ruby", "1.3.4"

Expand Down
8 changes: 2 additions & 6 deletions gemfiles/activerecord_8.0.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

source "https://rubygems.org"

gem "rspec", "~> 3.0"
gem "rake"
gem "sqlite3", "~> 2.5.0"
gem "appraisal"
gem "standard", "~>1.0"
gem "pry-byebug"
gem "yard"
gem "csv"
gem "rake"
gem "rspec", "~> 3.11"
gem "activerecord", "~> 8.0.0"

gemspec path: "../"
8 changes: 2 additions & 6 deletions gemfiles/activerecord_8.1.gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

source "https://rubygems.org"

gem "rspec", "~> 3.0"
gem "rake"
gem "sqlite3", "~> 2.9.0"
gem "appraisal"
gem "standard", "~>1.0"
gem "pry-byebug"
gem "yard"
gem "csv"
gem "rake"
gem "rspec", "~> 3.11"
gem "activerecord", "~> 8.1.0"

gemspec path: "../"
Loading