diff --git a/.github/workflows/ruby_test.yml b/.github/workflows/ruby_test.yml index 6471a3f..036fa4f 100644 --- a/.github/workflows/ruby_test.yml +++ b/.github/workflows/ruby_test.yml @@ -12,12 +12,20 @@ jobs: name: Rspec runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0' + ruby: [ "3.1", "3.2", "3.3", "3.4", "4.0" ] steps: - name: Checkout Repository uses: sanger/.github/.github/actions/setup/checkout@master - - name: Setup Ruby - uses: sanger/.github/.github/actions/setup/ruby@master + - name: Setup Ruby ${{ matrix.ruby }} + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true # runs 'bundle install' and caches installed gems automatically - name: Run rspec run: bundle exec rspec @@ -25,7 +33,7 @@ jobs: - name: Upload coverage reports to Codecov uses: sanger/.github/.github/actions/tests/codecov@master with: - name: ${{ github.run_id }}_${{ github.job }}_${{ github.event_name }} + name: ${{ github.run_id }}_${{ github.job }}_${{ github.event_name }}_${{ matrix.ruby }} token: ${{ secrets.CODECOV_TOKEN }} flags: ${{ github.event_name }} disable-search: true diff --git a/.rubocop.yml b/.rubocop.yml index c29e870..d737d1b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,5 @@ --- -inherit_from: ".rubocop_todo.yml" - # The behaviour of RuboCop can be controlled via the .rubocop.yml # configuration file. It makes it possible to enable/disable # certain cops (checks) and to alter their behaviour if they accept @@ -13,12 +11,15 @@ inherit_from: ".rubocop_todo.yml" # # See https://docs.rubocop.org/rubocop/latest/configuration.html +inherit_from: ".rubocop_todo.yml" + plugins: - rubocop-rake - rubocop-rspec AllCops: NewCops: enable + TargetRubyVersion: 3.1 Gemspec/DevelopmentDependencies: EnforcedStyle: gemspec @@ -41,3 +42,7 @@ RSpec/NestedGroups: # The single-line syntax can be clearer than the multi-line version for multiple definitions Style/EmptyClassDefinition: Enabled: false + +# Only use shorthand hash syntax when all keys match the variables for better readability +Style/HashSyntax: + EnforcedShorthandSyntax: consistent diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 601fba3..7ff2a52 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2026-04-29 17:06:59 UTC using RuboCop version 1.86.1. +# on 2026-04-29 18:48:04 UTC using RuboCop version 1.86.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -12,13 +12,6 @@ Gemspec/RequireMFA: Exclude: - 'record_loader.gemspec' -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. -Style/GuardClause: - Exclude: - - 'record_loader.gemspec' - # Offense count: 3 # This cop supports unsafe autocorrection (--autocorrect-all). Style/ReduceToHash: diff --git a/.ruby-version b/.ruby-version index 818bd47..9cec716 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.0.6 +3.1.6 diff --git a/CHANGELOG.md b/CHANGELOG.md index ba89a46..49740d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,27 @@ Keeps track of notable changes. Please remember to add new behaviours to the Unreleased section to make new releases easy. +## [Unreleased] + +- [Added] Support for Ruby 3.1 + +## [3.0.0] + +- [Changed] Version numbering to follow [Ruby Gem versioning guidelines](https://guides.rubygems.org/patterns/), loosely tracking Ruby versions. There are no functional changes in this release. + +## [0.3.0] + +- [Breaking] Remove support for Ruby 2.5 +- [Added] Add support for Ruby 3.0 + ## [0.2.0] - [Added] `RecordLoader.export_attributes` for easy generation of yaml from - existing data + existing data - [Added] Improved feedback if exceptions raised during record creation - [Added] Improved templated yml files to use attributes from table - [Changed] Update name of yaml files generated as part of tests. - No changes are required to existing loaders. + No changes are required to existing loaders. - [Fixed] Default yaml files correctly templated ## [0.1.1] diff --git a/Gemfile.lock b/Gemfile.lock index 3196b27..9607bb8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,19 +1,25 @@ PATH remote: . specs: - record_loader (0.3.0) + record_loader (3.1.0) + psych (~> 5.0) GEM remote: https://rubygems.org/ specs: ast (2.4.3) coderay (1.1.3) + date (3.5.1) diff-lcs (1.5.0) docile (1.4.1) json (2.19.4) language_server-protocol (3.17.0.5) lint_roller (1.1.0) + logger (1.7.0) + mdtoc (0.3.1) + sorbet-runtime method_source (1.0.0) + ostruct (0.6.3) parallel (1.28.0) parser (3.3.11.1) ast (~> 2.4.1) @@ -22,6 +28,9 @@ GEM pry (0.14.2) coderay (~> 1.1) method_source (~> 1.0) + psych (5.3.1) + date + stringio racc (1.8.1) rainbow (3.1.1) rake (13.0.6) @@ -67,6 +76,8 @@ GEM simplecov-html (0.13.2) simplecov-lcov (0.9.0) simplecov_json_formatter (0.1.4) + sorbet-runtime (0.6.13169) + stringio (3.2.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) @@ -76,7 +87,10 @@ PLATFORMS ruby DEPENDENCIES - bundler (~> 2.3) + bundler (~> 2.5) + logger (~> 1.7) + mdtoc (~> 0.3.1) + ostruct (~> 0.6.3) pry (~> 0.14) rake (~> 13.0) record_loader! @@ -89,4 +103,4 @@ DEPENDENCIES yard (~> 0.9) BUNDLED WITH - 2.3.21 + 2.5.9 diff --git a/README.md b/README.md index 93b1f75..dc9d743 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,28 @@ your production and development environments. While written with ActiveRecord/Rails in mind, it is possible to use RecordLoader in different environments. + +* [RecordLoader](README.md#recordloader) + * [Key features](README.md#key-features) + * [Version compatibility](README.md#version-compatibility) + * [Installation](README.md#installation) + * [How to run](README.md#how-to-run) + * [How to generate new records loaders for your project (Rails)](README.md#how-to-generate-new-records-loaders-for-your-project-rails) + * [An example loader](README.md#an-example-loader) + * [Dev and Wip files](README.md#dev-and-wip-files) + * [RecordLoader Dependencies](README.md#recordloader-dependencies) + * [Triggering on deployment](README.md#triggering-on-deployment) + * [Within Sanger PSD](README.md#within-sanger-psd) + * [Non Rails Environments](README.md#non-rails-environments) + * [Development](README.md#development) + * [Setup](README.md#setup) + * [Testing](README.md#testing) + * [Releasing](README.md#releasing) + * [Contributing](README.md#contributing) + * [License](README.md#license) + * [Updating the table of contents](README.md#updating-the-table-of-contents) + + ## Key features - Produce testable, reproducible data migrations across multiple environments @@ -18,6 +40,24 @@ While written with ActiveRecord/Rails in mind, it is possible to use RecordLoade - Keep work-in-progress isolated with .wip.yml files - Rails generators to quickly create new record loaders +## Version compatibility + +If using with Rails: + +| Rails Version | RecordLoader Version | +| ------------- | -------------------- | +| 7.x and above | 1.x | +| 6.x and below | 0.3.0*, 1.0.x* | + +_\* Pin psych < 4_ + +If using with Ruby: + +| Ruby Version | RecordLoader Version | +| ------------ | -------------------- | +| 3.x | 0.3.0, 1.0.x | +| 2.5 - 2.7 | 0.2.0 | + ## Installation Add this line to your application's Gemfile: @@ -82,62 +122,62 @@ Suppose you want to create a loader to maintain a selection of product types. Yo This will create several files: -#### `lib/tasks/record_loader.rake` +- `lib/tasks/record_loader.rake` -Adds the record_loader:all rake task which can be used to trigger all record loaders. + Adds the record_loader:all rake task which can be used to trigger all record loaders. -#### `lib/record_loader/application_record_loader.rb` +- `lib/record_loader/application_record_loader.rb` -Application specific base class for customization. + Application specific base class for customization. -#### `config/record_loader/product_types/default_records.yml` +- `config/record_loader/product_types/default_records.yml` -Example yaml file to begin populating with your record information. Record Loaders will load all yaml files from within -this directory, so it is possible to separate your records into multiple different files for better organization. -In addition yaml files ending in `.dev.yml` and `.wip.yml` exhibit special behaviour. -See [dev and wip files](#dev-and-wip). + Example yaml file to begin populating with your record information. Record Loaders will load all yaml files from within + this directory, so it is possible to separate your records into multiple different files for better organization. + In addition yaml files ending in `.dev.yml` and `.wip.yml` exhibit special behaviour. + See [dev and wip files](#dev-and-wip). -#### `lib/record_loader/product_type_loader.rb` +- `lib/record_loader/product_type_loader.rb` -The actual loader. It will look something like this: + The actual loader. It will look something like this: -```ruby -# frozen_string_literal: true -# This file was automatically generated via `rails g record_loader` - -# RecordLoader handles automatic population and updating of database records -# across different environments -# @see https://rubydoc.info/github/sanger/record_loader/ -module RecordLoader - # Creates the specified plate types if they are not present - class ProductTypeLoader < ApplicationRecordLoader - config_folder 'product_types' - - def create_or_update!(name, options) - ProductType.create_with(options).find_or_create_by!(name: name) + ```ruby + # frozen_string_literal: true + # This file was automatically generated via `rails g record_loader` + + # RecordLoader handles automatic population and updating of database records + # across different environments + # @see https://rubydoc.info/github/sanger/record_loader/ + module RecordLoader + # Creates the specified plate types if they are not present + class ProductTypeLoader < ApplicationRecordLoader + config_folder 'product_types' + + def create_or_update!(name, options) + ProductType.create_with(options).find_or_create_by!(name: name) + end end end -end -``` + ``` -The `config_folder` specifies which directory under `config/record_loader` will be used to source the yaml files. -The method `create_or_update!` will create the actual records, and should be idempotent (ie. calling it multiple times will -have the same effect as calling it once). `create_or_update!` will be called once for each entry in the yaml files, -with the first argument being the key, and the second argument being the value, usually a hash of options. + The `config_folder` specifies which directory under `config/record_loader` will be used to source the yaml files. + The method `create_or_update!` will create the actual records, and should be idempotent (ie. calling it multiple times will + have the same effect as calling it once). `create_or_update!` will be called once for each entry in the yaml files, + with the first argument being the key, and the second argument being the value, usually a hash of options. -#### `lib/record_loader/tasks/record_loader/product_type.rake` +- `lib/record_loader/tasks/record_loader/product_type.rake` -This contains the `record_loader:product_type` which will trigger the record loader, and also ensures that -`record_loader:product_type` will get invoked on calling `record_loader:all`. + This contains the `record_loader:product_type` which will trigger the record loader, and also ensures that + `record_loader:product_type` will get invoked on calling `record_loader:all`. -#### `spec/data/record_loader/product_types/product_types_basic.yml` +- `spec/data/record_loader/product_types/product_types_basic.yml` -A basic configuration for testing the loader. Tests use a separate directory to avoid coupling your specs to the data. + A basic configuration for testing the loader. Tests use a separate directory to avoid coupling your specs to the data. -#### `spec/lib/record_loader/product_type_loader_spec.rb` +- `spec/lib/record_loader/product_type_loader_spec.rb` -A basic rspec spec file for testing your loader. By default this just confirms that your loader creates the -expected number of records, and that it is idempotent. + A basic rspec spec file for testing your loader. By default this just confirms that your loader creates the + expected number of records, and that it is idempotent. ## Dev and Wip files @@ -206,11 +246,20 @@ See {RecordLoader::Adapter} for information about custom adapters. ## Development -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can -also run `bin/console` for an interactive prompt that will allow you to experiment. +### Setup + +After checking out the repo, run `bin/setup` to install dependencies. + +### Testing + +Then, run `bundle rspec` to run the unit tests. +You can also run `bin/console` for an interactive prompt that will allow you to experiment. +To install this gem onto your local machine, run `bundle exec rake install`. -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the -version number in `version.rb`, ensure the CHANGELOG.md is updated and that everything is committed. +### Releasing + +To release a new version, update the version number in `version.rb`, ensure the CHANGELOG.md is updated +and that everything is committed. Then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). @@ -221,3 +270,12 @@ Bug reports and pull requests are welcome on GitHub at 5.0' + # Development dependencies - spec.add_development_dependency 'bundler', '~> 2.3' + spec.add_development_dependency 'bundler', '~> 2.5' + spec.add_development_dependency 'logger', '~> 1.7' + spec.add_development_dependency 'mdtoc', '~> 0.3.1' + spec.add_development_dependency 'ostruct', '~> 0.6.3' spec.add_development_dependency 'pry', '~> 0.14' spec.add_development_dependency 'rake', '~> 13.0' spec.add_development_dependency 'rspec', '~> 3.12' @@ -50,4 +52,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'simplecov', '~> 0.22' spec.add_development_dependency 'simplecov-lcov', '~> 0.9' spec.add_development_dependency 'yard', '~> 0.9' + + # For more information and examples about making a new gem, check out our + # guide at: https://bundler.io/guides/creating_gem.html end diff --git a/spec/record_loader/base_spec.rb b/spec/record_loader/base_spec.rb index 5223bd0..c452962 100644 --- a/spec/record_loader/base_spec.rb +++ b/spec/record_loader/base_spec.rb @@ -50,6 +50,22 @@ def create_or_update_called end end + describe '#initialize' do + let(:dev) { false } + + context 'when loading a string' do + let(:selected_files) { ['000_example'] } + + it 'loads the config from the specified file' do + expect(record_loader.instance_variable_get(:@config)) + .to eq({ + 'Example a' => { 'key_a' => 'value a' }, + 'Example b' => { 'key_a' => 'value b' } + }) + end + end + end + describe '#create' do before { record_loader.create! } diff --git a/spec/record_loader/filter/standard_spec.rb b/spec/record_loader/filter/standard_spec.rb index d772d8a..e122e5c 100644 --- a/spec/record_loader/filter/standard_spec.rb +++ b/spec/record_loader/filter/standard_spec.rb @@ -3,7 +3,7 @@ require 'record_loader/filter/standard' RSpec.describe RecordLoader::Filter::Standard, type: :model do - subject(:filter) { described_class.new(dev: dev, wip_list: wip_list) } + subject(:filter) { described_class.new(dev:, wip_list:) } def record_file(name) RecordLoader::RecordFile.new(Pathname.new(name))