oaken 0.2.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 160e746d1605f0e227c7084ce39ee6e05837cfe4288de735b4407c39a45d4fbe
4
- data.tar.gz: 9510c077a7f0f7bad0ebc56bfb6306cf2465253a04d6c19f28009a3552a47719
3
+ metadata.gz: 3d441d9ce6156b7fa0f30b188f1f1e09a065292fb520c8415e8eae3e3b3f6aef
4
+ data.tar.gz: b69bc4be147d5dca48ded82969b8abee63f10fd81b53015bf3c236d2abfcaadc
5
5
  SHA512:
6
- metadata.gz: 393bd015fadfb6e806a49f43be92fbf764d89c15577f91963361cef6d8221ba8cbd2992c0edba6b38920831dbe3299482b12c3c46e1b2f5b593617ce8c697ccf
7
- data.tar.gz: 3e6480a0ef119fa5929f44a6481359d7247ff4da4b4fcbc45225c90e98ad1c0897857f63d29c685b2a6af70f663724606360bb9487802fc9ac5995b8930ead9f
6
+ metadata.gz: 22c8eb550e6564429fed5302807d5586b77f8e19c2a5c097d168d23dec0bec25f8595db8a79ee654cdc24f67b7c1dbcb8c758c0dab38ffb03ccb823374e5ce94
7
+ data.tar.gz: 7dcd270301eb5994cdfac1c2630e9a0c27ba45df0bf851f54dc78e47273625e8500bf357649eab7f04481ae8bb4bc68144813eb831229bc66814df0196defe33
data/README.md CHANGED
@@ -1,22 +1,119 @@
1
1
  # Oaken
2
2
 
3
- The gem we're building in the Open Source Retreat https://kaspthrb.gumroad.com/l/open-source-retreat
3
+ Oaken is a new take on development and test data management for your Rails app. It blends the stability and storytelling from Fixtures with the dynamicness of FactoryBot/Fabricator.
4
4
 
5
- ## Installation
5
+ Fixtures are stable & help you build a story of how your app and its object graph exists along with edge cases, but the UX is unfortunately a nightmare.
6
+ To trace N associations, you have to open and read N different files — there's no way to group by scenario.
7
+
8
+ FactoryBot is spray & pray. You basically say “screw it, just give me the bare minimum I need to run this test”, which slows everything down because there’s no cohesion; and the Factories are always suspect in terms of completeness. Sure, I got the test to pass by wiring these 5 Factories together but did I miss something?
9
+
10
+ Oaken instead upgrades seeds in `db/seeds.rb`, so that you can put together scenarios & also reuse the development data in tests. That way the data you see in your development browser, is the same data you work with in tests to tie it more together — especially for people who are new to your codebase.
11
+
12
+ So you get the stability of named keys, a cohesive dataset, and a story like Fixtures. But the dynamics of FactoryBot as well. And unlike FactoryBot, you’re not making tons of one-off records to handle each case.
13
+
14
+ While Fixtures and FactoryBot both load data & truncate in tests, the end result is you end up writing less data back & forth to the database because you aren’t cobbling stuff together.
15
+
16
+ ## Setup
17
+
18
+ ### Starting in development
19
+
20
+ You can set it up in `db/seeds.rb`, like this:
21
+
22
+ ```ruby
23
+ Oaken.prepare do
24
+ seed :accounts, :data
25
+ end
26
+ ```
27
+
28
+ This will look for deeply nested files to load in `db/seeds` and `db/seeds/#{Rails.env}` within the `accounts` and `data` directories.
29
+
30
+ Here's what they could look like:
31
+
32
+ ```ruby
33
+ # db/seeds/accounts/kaspers_donuts.rb
34
+ donuts = accounts.create :kaspers_donuts, name: "Kasper's Donuts"
35
+
36
+ kasper = users.create :kasper, name: "Kasper", accounts: [donuts]
37
+ coworker = users.create :coworker, name: "Coworker", accounts: [donuts]
38
+
39
+ menu = menus.create account: donuts
40
+ plain_donut = menu_items.create menu: menu, name: "Plain", price_cents: 10_00
41
+ sprinkled_donut = menu_items.create menu: menu, name: "Sprinkled", price_cents: 10_10
42
+
43
+ supporter = users.create name: "Super Supporter"
44
+ orders.insert_all [user_id: supporter.id, item_id: plain_donut.id] * 10
45
+
46
+ orders.insert_all \
47
+ 10.times.map { { user_id: users.create(name: "Customer #{_1}").id, item_id: menu.items.sample.id } }
48
+ ```
49
+
50
+ ```ruby
51
+ # db/seeds/data/plans.rb
52
+ plans.upsert :basic, title: "Basic", price_cents: 10_00
53
+ ```
54
+
55
+ Seed files will generally use `create` and/or `insert`. Passing a symbol to name the record is useful when reusing the data in tests.
56
+
57
+ Now you can run `bin/rails db:seed` and `bin/rails db:seed:replant`.
58
+
59
+ ### Interlude: Directory Naming Conventions
6
60
 
7
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
61
+ Oaken has some chosen directory conventions to help strengthen your understanding of your object graph:
62
+
63
+ - Have a directory for your top-level model, like `Account`, `Team`, `Organization`, that's why we have `db/seeds/accounts` above.
64
+ - `db/seeds/data` for any data tables, like the plans a SaaS app has.
65
+ - `db/seeds/tests/cases` for any specific cases that are only used in some tests, like `pagination.rb`.
66
+
67
+ ### Reusing data in tests
68
+
69
+ With the setup above, Oaken can reuse the same data in tests like this:
70
+
71
+ ```ruby
72
+ # test/test_helper.rb
73
+ class ActiveSupport::TestCase
74
+ include Oaken::TestSetup
75
+ end
76
+ ```
77
+
78
+ Now tests have access to `accounts.kaspers_donuts` and `users.kasper` etc. that were setup in the data scripts.
79
+
80
+ You can also load a specific seed, like this:
81
+
82
+ ```ruby
83
+ class PaginationTest < ActionDispatch::IntegrationTest
84
+ setup { seed "cases/pagination" }
85
+ end
86
+ ```
87
+
88
+ > [!NOTE]
89
+ > We're recommending having one-off seeds on an individual unit of work to help reinforce test isolation. Having some seed files be isolated also helps:
90
+ >
91
+ > - Reduce amount of junk data generated for unrelated tests
92
+ > - Make it easier to debug a particular test
93
+ > - Reduce test flakiness
94
+ > - Encourage writing seed files for specific edge-case scenarios
95
+
96
+ ### Fixtures Converter
97
+
98
+ You can convert your Rails fixtures to Oaken's seeds by running:
99
+
100
+ $ bin/rails generate oaken:convert:fixtures
101
+
102
+ This will convert anything in test/fixtures to db/seeds. E.g. `test/fixtures/users.yml` becomes `db/seeds/users.rb`.
103
+
104
+ ## Installation
8
105
 
9
106
  Install the gem and add to the application's Gemfile by executing:
10
107
 
11
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
108
+ $ bundle add oaken
12
109
 
13
110
  If bundler is not being used to manage dependencies, install the gem by executing:
14
111
 
15
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
112
+ $ gem install oaken
16
113
 
17
114
  ## Development
18
115
 
19
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
116
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `cd test/dummy` and `bin/rails test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
20
117
 
21
118
  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`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
22
119
 
@@ -30,4 +127,21 @@ The gem is available as open source under the terms of the [MIT License](https:/
30
127
 
31
128
  ## Code of Conduct
32
129
 
33
- Everyone interacting in the Oaken project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/kaspth/oaken/blob/main/CODE_OF_CONDUCT.md).
130
+ Everyone interacting in the Oaken project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/kaspth/oaken/blob/main/CODE_OF_CONDUCT.md).
131
+
132
+ ## Support
133
+
134
+ Initial development is supported in part by:
135
+
136
+ <a href="https://arrows.to">
137
+ <img src="https://user-images.githubusercontent.com/56947/258236465-06c692a7-738e-44bd-914e-fecc697317ce.png" />
138
+ </a>
139
+
140
+ And by:
141
+
142
+ - [Alexandre Ruban](https://github.com/alexandreruban)
143
+ - [Lars Kronfält](https://github.com/larkro)
144
+ - [Manuel Costa Reis](https://github.com/manuelfcreis)
145
+ - [Thomas Cannon](https://github.com/tcannonfodder)
146
+
147
+ As a sponsor you're welcome to submit a pull request to add your own name here.
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators/base"
4
+ require "yaml"
5
+
6
+ module Oaken::Convert; end
7
+ class Oaken::Convert::Fixture
8
+ attr_reader :model_name, :name
9
+
10
+ def initialize(model_name, name, attributes)
11
+ @model_name, @name, @attributes = model_name.tr("/", "_"), name, attributes
12
+ @plural = @model_name
13
+ @singular = @model_name.singularize
14
+ end
15
+
16
+ def extract_dependents(fixtures)
17
+ @dependents = fixtures.select { _1.reference(plural, singular) == name }
18
+ fixtures.replace fixtures - dependents
19
+
20
+ dependents.each { _1.extract_dependents fixtures }
21
+ end
22
+
23
+ def reference(plural, singular)
24
+ @referenced = [plural, :plural] if attributes[plural]
25
+ @referenced = [singular, :singular] if attributes[singular]
26
+ attributes[@referenced&.first]
27
+ end
28
+
29
+ def render(delimiter: "\n")
30
+ [render_self, dependents&.map { _1.render delimiter: nil }].join(delimiter)
31
+ end
32
+
33
+ private
34
+ attr_reader :attributes, :dependents
35
+ attr_reader :plural, :singular
36
+
37
+ def render_self
38
+ "#{model_name}.create :#{name}, #{convert_hash(attributes)}\n".tap do
39
+ _1.prepend "#{name} = " if dependents&.any?
40
+ end
41
+ end
42
+
43
+ def convert_hash(hash)
44
+ hash.map { |k, v| "#{k}: #{recursive_convert(v, key: k)}" }.join(", ")
45
+ end
46
+
47
+ def recursive_convert(input, key: nil)
48
+ case input
49
+ when Hash then "{ #{convert_hash(input)} }"
50
+ when Array then input.map { recursive_convert _1 }.join(", ")
51
+ when Integer then input
52
+ else
53
+ if key == @referenced&.first
54
+ @referenced.last == :plural ? "[#{input}]" : input
55
+ else
56
+ "\"#{input}\""
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ class Oaken::Convert::FixturesGenerator < Rails::Generators::Base
63
+ desc "Converts fixtures to Oaken seeds in db/seeds/test"
64
+ source_root File.expand_path("templates", __dir__)
65
+
66
+ class_option :root_model, required: true
67
+ class_option :keeps, type: :boolean, default: true
68
+
69
+ def prepare
70
+ @root_model = ActiveModel::Name.new(options[:root_model].constantize)
71
+ empty_directory_with_keep_file "db/seeds/data"
72
+ empty_directory_with_keep_file "db/seeds/test/cases"
73
+ end
74
+
75
+ def parse
76
+ @fixtures = Dir.glob("test/fixtures/**/*.yml").to_h do |path|
77
+ model_name = path.delete_prefix("test/fixtures/").chomp(".yml")
78
+ [model_name, YAML.load_file(path).presence&.map { Oaken::Convert::Fixture.new(model_name, _1, _2) }]
79
+ rescue Psych::SyntaxError
80
+ say "Skipped #{path} due to ERB content or other YAML parsing issues.", :yellow
81
+ end.tap(&:compact_blank!)
82
+ end
83
+
84
+ def prepend_prepare_to_seeds
85
+ namespaces = @fixtures.keys.filter_map { _1.classify if _1.include?("/") }.uniq.sort
86
+
87
+ code = +"Oaken.prepare do\n"
88
+ code << " register #{namespaces.join(", ")}\n\n" if namespaces.any?
89
+ code << " seed :#{@root_model.plural}, :data\n"
90
+ code << "end\n"
91
+
92
+ inject_into_file "db/seeds.rb", code, before: /\A/
93
+ end
94
+
95
+ def convert_all
96
+ roots = @fixtures.delete(@root_model.collection)
97
+ @fixtures = @fixtures.values.flatten
98
+
99
+ roots.each do |fixture|
100
+ fixture.extract_dependents @fixtures
101
+ create_file "db/seeds/test/#{@root_model.plural}/#{fixture.name}.rb", fixture.render.chomp
102
+ end
103
+
104
+ @fixtures.group_by(&:model_name).each do |model_name, fixtures|
105
+ create_file "db/seeds/test/data/#{model_name}.rb", fixtures.map(&:render).join.chomp
106
+ end
107
+ end
108
+
109
+ private
110
+ def empty_directory_with_keep_file(name)
111
+ empty_directory name
112
+ create_file "#{name}/.keep" if options[:keeps]
113
+ end
114
+ end
@@ -0,0 +1,5 @@
1
+ class Oaken::Railtie < Rails::Railtie
2
+ initializer "oaken.lookup_paths" do
3
+ Oaken.lookup_paths << "db/seeds/#{Rails.env}"
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ RSpec.configure do |config|
2
+ config.include(Oaken::Seeds)
3
+
4
+ config.use_transactional_fixtures = true
5
+
6
+ config.before(:suite) do
7
+ # Mimic fixtures by truncating before inserting.
8
+ ActiveRecord::Tasks::DatabaseTasks.truncate_all
9
+ Oaken.load_seed
10
+ end
11
+ end
@@ -0,0 +1,83 @@
1
+ module Oaken::Seeds
2
+ extend self
3
+
4
+ def self.method_missing(name, ...)
5
+ if type = name.to_s.classify.safe_constantize
6
+ register type
7
+ public_send(name, ...)
8
+ else
9
+ super
10
+ end
11
+ end
12
+ def self.respond_to_missing?(name, ...) = name.to_s.classify.safe_constantize || super
13
+
14
+ def self.register(*types)
15
+ types.each do |type|
16
+ stored = provider.new(type) and define_method(stored.key) { stored }
17
+ end
18
+ end
19
+ def self.provider = Oaken::Stored::ActiveRecord
20
+
21
+ class << self
22
+ # Set up a general seed rule or perform a one-off seed for a test file.
23
+ #
24
+ # You can set up a general seed rule in `db/seeds.rb` like this:
25
+ #
26
+ # Oaken.prepare do
27
+ # seed :accounts # Seeds from `db/seeds/accounts/**/*.rb` and `db/seeds/<Rails.env>/accounts/**/*.rb`
28
+ # end
29
+ #
30
+ # Then if you need a test specific scenario, we recommend putting them in `db/seeds/test/cases`.
31
+ #
32
+ # Say you have `db/seeds/test/cases/pagination.rb`, you can load it like this:
33
+ #
34
+ # # test/integration/pagination_test.rb
35
+ # class PaginationTest < ActionDispatch::IntegrationTest
36
+ # setup { seed "cases/pagination" }
37
+ # end
38
+ def seed(*directories)
39
+ Oaken.lookup_paths.product(directories).each do |path, directory|
40
+ load_from Pathname(path).join(directory.to_s)
41
+ end
42
+ end
43
+
44
+ private def load_from(path)
45
+ @loader = Oaken::Loader.new path
46
+ @loader.load_onto self
47
+ ensure
48
+ @loader = nil
49
+ end
50
+
51
+ # `section` is purely for decorative purposes to carve up `Oaken.prepare` and seed files.
52
+ #
53
+ # Oaken.prepare do
54
+ # section :roots # Just the very few top-level models like Accounts and Users.
55
+ # users.defaults email_address: -> { Faker::Internet.email }, webauthn_id: -> { SecureRandom.hex }
56
+ #
57
+ # section :stems # Models building on the roots.
58
+ #
59
+ # section :leafs # Remaining models, bulk of them, hanging off root and stem models.
60
+ #
61
+ # section do
62
+ # seed :accounts, :data
63
+ # end
64
+ # end
65
+ #
66
+ # Since `section` is defined as `def section(*, **) = yield if block_given?`, you can use
67
+ # all of Ruby's method signature flexibility to help communicate structure better.
68
+ #
69
+ # Use positional and keyword arguments, or use blocks to indent them, or combine them all.
70
+ def section(*, **)
71
+ yield if block_given?
72
+ end
73
+ end
74
+
75
+ # Call `seed` in tests to load individual case files:
76
+ #
77
+ # class PaginationTest < ActionDispatch::IntegrationTest
78
+ # setup do
79
+ # seed "cases/pagination" # Loads `db/seeds/{,test}/cases/pagination{,**/*}.rb`
80
+ # end
81
+ # end
82
+ delegate :seed, to: Oaken::Seeds
83
+ end
@@ -0,0 +1,45 @@
1
+ class Oaken::Stored::ActiveRecord
2
+ def initialize(type)
3
+ @type, @key = type, type.table_name
4
+ @attributes = {}
5
+ end
6
+ attr_reader :type, :key
7
+ delegate :transaction, to: :type # For multi-db setups to help open a transaction on secondary connections.
8
+ delegate :find, :insert_all, :pluck, to: :type
9
+
10
+ def defaults(**attributes)
11
+ @attributes = @attributes.merge(attributes)
12
+ @attributes
13
+ end
14
+
15
+ def create(label = nil, unique_by: nil, **attributes)
16
+ attributes = @attributes.merge(attributes)
17
+ attributes.transform_values! { _1.respond_to?(:call) ? _1.call : _1 }
18
+
19
+ finders = attributes.slice(*unique_by)
20
+ record = type.find_by(finders)&.tap { _1.update!(**attributes) } if finders.any?
21
+ record ||= type.create!(**attributes)
22
+
23
+ label label => record if label
24
+ record
25
+ end
26
+
27
+ def upsert(label = nil, unique_by: nil, **attributes)
28
+ attributes = @attributes.merge(attributes)
29
+ attributes.transform_values! { _1.respond_to?(:call) ? _1.call : _1 }
30
+
31
+ type.new(attributes).validate!
32
+ record = type.new(id: type.upsert(attributes, unique_by: unique_by, returning: :id).rows.first.first)
33
+ label label => record if label
34
+ record
35
+ end
36
+
37
+ def label(**labels)
38
+ # TODO: Fix hardcoding of db/seeds instead of using Oaken.lookup_paths
39
+ location = caller_locations(1, 6).find { _1.path.match? /db\/seeds\// }
40
+
41
+ labels.each do |label, record|
42
+ class_eval "def #{label} = find(#{record.id})", location.path, location.lineno
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,24 @@
1
+ module Oaken::TestSetup
2
+ include Oaken::Seeds
3
+
4
+ def self.included(klass)
5
+ klass.fixtures # Rely on fixtures to setup a shared connection pool and wrap tests in transactions.
6
+ klass.parallelize_setup { Oaken.load_seed } # No need to truncate as parallel test databases are always empty.
7
+ klass.prepend BeforeSetup
8
+ end
9
+
10
+ module BeforeSetup
11
+ # We must inject late enough to call `should_parallelize?`, but before fixtures' `before_setup`.
12
+ #
13
+ # So we prepend into `before_setup` and later `super` to have fixtures wrap tests in transactions.
14
+ def before_setup
15
+ unless Minitest.parallel_executor.send(:should_parallelize?)
16
+ ActiveRecord::Tasks::DatabaseTasks.truncate_all # Mimic fixtures by truncating before inserting.
17
+ Oaken.load_seed
18
+ end
19
+
20
+ Oaken::TestSetup::BeforeSetup.remove_method :before_setup # Only run once, so remove before passing to fixtures in `super`.
21
+ super
22
+ end
23
+ end
24
+ end
data/lib/oaken/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Oaken
4
- VERSION = "0.2.0"
4
+ VERSION = "0.6.0"
5
5
  end
data/lib/oaken.rb CHANGED
@@ -1,8 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "oaken/version"
3
+ require "oaken/version"
4
+ require "pathname"
4
5
 
5
6
  module Oaken
6
7
  class Error < StandardError; end
7
- # Your code goes here...
8
+
9
+ autoload :Seeds, "oaken/seeds"
10
+ autoload :TestSetup, "oaken/test_setup"
11
+
12
+ module Stored
13
+ autoload :ActiveRecord, "oaken/stored/active_record"
14
+ end
15
+
16
+ singleton_class.attr_reader :lookup_paths
17
+ @lookup_paths = ["db/seeds"]
18
+
19
+ class Loader
20
+ def initialize(path)
21
+ @entries = Pathname.glob("#{path}{,/**/*}.{rb,sql}").sort
22
+ end
23
+
24
+ def load_onto(seeds)
25
+ @entries.each do |path|
26
+ ActiveRecord::Base.transaction do
27
+ case path.extname
28
+ when ".rb" then seeds.class_eval path.read, path.to_s
29
+ when ".sql" then ActiveRecord::Base.connection.execute path.read
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def self.prepare(&block) = Seeds.instance_eval(&block)
37
+ def self.load_seed = Rails.application.load_seed
8
38
  end
39
+
40
+ require_relative "oaken/railtie" if defined?(Rails::Railtie)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oaken
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kasper Timm Hansen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-20 00:00:00.000000000 Z
11
+ date: 2024-05-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -22,14 +22,20 @@ files:
22
22
  - LICENSE.txt
23
23
  - README.md
24
24
  - Rakefile
25
+ - lib/generators/oaken/convert/fixtures_generator.rb
25
26
  - lib/oaken.rb
27
+ - lib/oaken/railtie.rb
28
+ - lib/oaken/rspec_setup.rb
29
+ - lib/oaken/seeds.rb
30
+ - lib/oaken/stored/active_record.rb
31
+ - lib/oaken/test_setup.rb
26
32
  - lib/oaken/version.rb
27
- homepage: https://kaspthrb.gumroad.com/l/open-source-retreat-summer-2023
33
+ homepage: https://github.com/kaspth/oaken
28
34
  licenses:
29
35
  - MIT
30
36
  metadata:
31
37
  allowed_push_host: https://rubygems.org
32
- homepage_uri: https://kaspthrb.gumroad.com/l/open-source-retreat-summer-2023
38
+ homepage_uri: https://github.com/kaspth/oaken
33
39
  source_code_uri: https://github.com/kaspth/oaken
34
40
  changelog_uri: https://github.com/kaspth/oaken/blob/main/CHANGELOG.md
35
41
  post_install_message:
@@ -47,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
47
53
  - !ruby/object:Gem::Version
48
54
  version: '0'
49
55
  requirements: []
50
- rubygems_version: 3.4.17
56
+ rubygems_version: 3.5.6
51
57
  signing_key:
52
58
  specification_version: 4
53
59
  summary: Oaken aims to blend your Fixtures/Factories and levels up your database seeds.