oaken 0.5.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +49 -26
- data/lib/generators/oaken/convert/fixtures_generator.rb +1 -1
- data/lib/oaken/railtie.rb +1 -21
- data/lib/oaken/rspec_setup.rb +10 -0
- data/lib/oaken/seeds.rb +91 -22
- data/lib/oaken/stored/active_record.rb +23 -18
- data/lib/oaken/test_setup.rb +24 -0
- data/lib/oaken/version.rb +1 -1
- data/lib/oaken.rb +8 -42
- metadata +5 -4
- data/lib/oaken/entry.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81f5e7a3ea1f5236b6e2726d1ecd24dee5698b59dc5e73f80ce618181b1dffe1
|
4
|
+
data.tar.gz: 6c4628800cdc44acd516a8e51a708fdee3d63ce506679fa5bc742b337da5c725
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5257b630ef4ac76db5b9fceb161df47d71e1df3481d1fdcd45fb1ac35319a72568d084f6e48a158cca3217512382890d5919111f329b08798f388e46d4ff5014
|
7
|
+
data.tar.gz: 2b91b6caeede127715691ecdeab3b8355ba273d9105b11f52579f7c4da2dcfefb7bfbc243ee4cc7b7de2af6401e95e195b4c377d31502ddd0162043e243c07aa
|
data/README.md
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
# Oaken
|
2
2
|
|
3
|
-
Oaken is
|
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
|
+
|
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.
|
4
15
|
|
5
16
|
## Setup
|
6
17
|
|
@@ -16,7 +27,7 @@ end
|
|
16
27
|
|
17
28
|
This will look for deeply nested files to load in `db/seeds` and `db/seeds/#{Rails.env}` within the `accounts` and `data` directories.
|
18
29
|
|
19
|
-
Here's what they could look like
|
30
|
+
Here's what they could look like:
|
20
31
|
|
21
32
|
```ruby
|
22
33
|
# db/seeds/accounts/kaspers_donuts.rb
|
@@ -38,12 +49,12 @@ orders.insert_all \
|
|
38
49
|
|
39
50
|
```ruby
|
40
51
|
# db/seeds/data/plans.rb
|
41
|
-
plans.
|
52
|
+
plans.upsert :basic, title: "Basic", price_cents: 10_00
|
42
53
|
```
|
43
54
|
|
44
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.
|
45
56
|
|
46
|
-
Now you can run `bin/rails db:seed`
|
57
|
+
Now you can run `bin/rails db:seed` and `bin/rails db:seed:replant`.
|
47
58
|
|
48
59
|
### Interlude: Directory Naming Conventions
|
49
60
|
|
@@ -53,45 +64,57 @@ Oaken has some chosen directory conventions to help strengthen your understandin
|
|
53
64
|
- `db/seeds/data` for any data tables, like the plans a SaaS app has.
|
54
65
|
- `db/seeds/tests/cases` for any specific cases that are only used in some tests, like `pagination.rb`.
|
55
66
|
|
67
|
+
### Using default attributes
|
68
|
+
|
69
|
+
You can set up default attributes that's applied to created/inserted records at different levels, like this:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
Oaken.prepare do
|
73
|
+
# Assign broad global defaults for every type.
|
74
|
+
defaults name: -> { Faker::Name.name }, public_key: -> { SecureRandom.hex }
|
75
|
+
|
76
|
+
# Assign a more specific default on one type, which overrides the global default above.
|
77
|
+
accounts.defaults name: -> { Faker::Business.name }
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
> [!TIP]
|
82
|
+
> `defaults` are particularly well suited for assigning generated data with [Faker](https://github.com/faker-ruby/faker).
|
83
|
+
|
56
84
|
### Reusing data in tests
|
57
85
|
|
58
|
-
With the setup above, Oaken can reuse the same data in tests like
|
86
|
+
With the setup above, Oaken can reuse the same data in tests like this:
|
59
87
|
|
60
88
|
```ruby
|
61
89
|
# test/test_helper.rb
|
62
90
|
class ActiveSupport::TestCase
|
63
|
-
include Oaken
|
64
|
-
|
65
|
-
# Override Minitest::Test#run to wrap each test in a transaction.
|
66
|
-
def run
|
67
|
-
result = nil
|
68
|
-
ActiveRecord::Base.transaction(requires_new: true) do
|
69
|
-
result = super
|
70
|
-
raise ActiveRecord::Rollback
|
71
|
-
end
|
72
|
-
result
|
73
|
-
end
|
91
|
+
include Oaken::TestSetup
|
74
92
|
end
|
75
93
|
```
|
76
94
|
|
77
95
|
Now tests have access to `accounts.kaspers_donuts` and `users.kasper` etc. that were setup in the data scripts.
|
78
96
|
|
97
|
+
> [!NOTE]
|
98
|
+
> For RSpec, you can put this in `spec/rails_helper.rb`:
|
99
|
+
> ```ruby
|
100
|
+
> require "oaken/rspec_setup"
|
101
|
+
> ```
|
102
|
+
|
79
103
|
You can also load a specific seed, like this:
|
80
104
|
|
81
105
|
```ruby
|
82
106
|
class PaginationTest < ActionDispatch::IntegrationTest
|
83
|
-
seed "cases/pagination"
|
107
|
+
setup { seed "cases/pagination" }
|
84
108
|
end
|
85
109
|
```
|
86
110
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
```
|
111
|
+
> [!NOTE]
|
112
|
+
> 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:
|
113
|
+
>
|
114
|
+
> - Reduce amount of junk data generated for unrelated tests
|
115
|
+
> - Make it easier to debug a particular test
|
116
|
+
> - Reduce test flakiness
|
117
|
+
> - Encourage writing seed files for specific edge-case scenarios
|
95
118
|
|
96
119
|
### Fixtures Converter
|
97
120
|
|
@@ -113,7 +136,7 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
113
136
|
|
114
137
|
## Development
|
115
138
|
|
116
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `
|
139
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/rails test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
117
140
|
|
118
141
|
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).
|
119
142
|
|
@@ -86,7 +86,7 @@ class Oaken::Convert::FixturesGenerator < Rails::Generators::Base
|
|
86
86
|
|
87
87
|
code = +"Oaken.prepare do\n"
|
88
88
|
code << " register #{namespaces.join(", ")}\n\n" if namespaces.any?
|
89
|
-
code << "
|
89
|
+
code << " seed :#{@root_model.plural}, :data\n"
|
90
90
|
code << "end\n"
|
91
91
|
|
92
92
|
inject_into_file "db/seeds.rb", code, before: /\A/
|
data/lib/oaken/railtie.rb
CHANGED
@@ -1,25 +1,5 @@
|
|
1
1
|
class Oaken::Railtie < Rails::Railtie
|
2
|
-
initializer "oaken.
|
2
|
+
initializer "oaken.lookup_paths" do
|
3
3
|
Oaken.lookup_paths << "db/seeds/#{Rails.env}"
|
4
|
-
Oaken.store_path = Oaken.store_path.join(Rails.env)
|
5
|
-
end
|
6
|
-
|
7
|
-
rake_tasks do
|
8
|
-
namespace :oaken do
|
9
|
-
task("reset") { Oaken.store_path.rmtree }
|
10
|
-
task("reset:all") { Oaken.store_path.dirname.rmtree }
|
11
|
-
|
12
|
-
task "reset:include_test" do
|
13
|
-
# Some db: tasks in development also manipulate the test database.
|
14
|
-
Oaken.store_path.sub("development", "test").rmtree if Rails.env.development?
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
task "db:drop" => ["oaken:reset", "oaken:reset:include_test"]
|
19
|
-
task "db:purge" => ["oaken:reset", "oaken:reset:include_test"]
|
20
|
-
task "db:purge:all" => ["oaken:reset:all"]
|
21
|
-
|
22
|
-
# db:seed:replant runs trunacte_all, after trial-and-error we need to hook into that and not the replant task.
|
23
|
-
task "db:truncate_all" => "oaken:purge"
|
24
4
|
end
|
25
5
|
end
|
data/lib/oaken/seeds.rb
CHANGED
@@ -1,41 +1,110 @@
|
|
1
1
|
module Oaken::Seeds
|
2
2
|
extend self
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
# Allow assigning defaults across different types.
|
5
|
+
def self.defaults(**defaults) = attributes.merge!(**defaults)
|
6
|
+
def self.defaults_for(*keys) = attributes.slice(*keys)
|
7
|
+
def self.attributes = @attributes ||= {}.with_indifferent_access
|
7
8
|
|
9
|
+
# Oaken's main auto-registering logic.
|
10
|
+
#
|
11
|
+
# So when you first call e.g. `accounts.create`, we'll hit `method_missing` here
|
12
|
+
# and automatically call `register Account`.
|
13
|
+
#
|
14
|
+
# We'll also match partial and full nested namespaces like in this order:
|
15
|
+
#
|
16
|
+
# accounts => Account
|
17
|
+
# account_jobs => AccountJob | Account::Job
|
18
|
+
# account_job_tasks => AccountJobTask | Account::JobTask | Account::Job::Task
|
19
|
+
#
|
20
|
+
# If you have classes that don't follow this naming convention, you must call `register` manually.
|
8
21
|
def self.method_missing(meth, ...)
|
9
|
-
name = meth.to_s
|
10
|
-
|
11
|
-
|
12
|
-
|
22
|
+
name = meth.to_s.classify
|
23
|
+
name = name.sub!(/(?<=[a-z])(?=[A-Z])/, "::") until name.nil? or type = name.safe_constantize
|
24
|
+
|
25
|
+
if type
|
26
|
+
register type
|
27
|
+
public_send(meth, ...)
|
13
28
|
else
|
14
29
|
super
|
15
30
|
end
|
16
31
|
end
|
32
|
+
def self.respond_to_missing?(name, ...) = name.to_s.classify.safe_constantize || super
|
17
33
|
|
18
|
-
|
19
|
-
|
34
|
+
# Register a model class to be accessible as an instance method via `include Oaken::Seeds`.
|
35
|
+
# Note: Oaken's auto-register via `method_missing` means it's less likely you need to call this manually.
|
36
|
+
#
|
37
|
+
# register Account, Account::Job, Account::Job::Task
|
38
|
+
#
|
39
|
+
# Oaken uses the `table_name` of the passed classes for the method names, e.g. here they'd be
|
40
|
+
# `accounts`, `account_jobs`, and `account_job_tasks`, respectively.
|
41
|
+
def self.register(*types)
|
42
|
+
types.each do |type|
|
43
|
+
stored = provider.new(type) and define_method(stored.key) { stored }
|
44
|
+
end
|
20
45
|
end
|
21
46
|
def self.provider = Oaken::Stored::ActiveRecord
|
22
47
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
48
|
+
class << self
|
49
|
+
# Set up a general seed rule or perform a one-off seed for a test file.
|
50
|
+
#
|
51
|
+
# You can set up a general seed rule in `db/seeds.rb` like this:
|
52
|
+
#
|
53
|
+
# Oaken.prepare do
|
54
|
+
# seed :accounts # Seeds from `db/seeds/accounts/**/*.rb` and `db/seeds/<Rails.env>/accounts/**/*.rb`
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Then if you need a test specific scenario, we recommend putting them in `db/seeds/test/cases`.
|
58
|
+
#
|
59
|
+
# Say you have `db/seeds/test/cases/pagination.rb`, you can load it like this:
|
60
|
+
#
|
61
|
+
# # test/integration/pagination_test.rb
|
62
|
+
# class PaginationTest < ActionDispatch::IntegrationTest
|
63
|
+
# setup { seed "cases/pagination" }
|
64
|
+
# end
|
27
65
|
def seed(*directories)
|
28
|
-
Oaken.lookup_paths.each do |path|
|
29
|
-
|
30
|
-
@loader = Oaken::Loader.new Pathname(path).join(directory.to_s)
|
31
|
-
@loader.load_onto Oaken::Seeds
|
32
|
-
end
|
66
|
+
Oaken.lookup_paths.product(directories).each do |path, directory|
|
67
|
+
load_from Pathname(path).join(directory.to_s)
|
33
68
|
end
|
34
69
|
end
|
35
|
-
end
|
36
|
-
extend Loading
|
37
70
|
|
38
|
-
|
39
|
-
|
71
|
+
private def load_from(path)
|
72
|
+
@loader = Oaken::Loader.new path
|
73
|
+
@loader.load_onto self
|
74
|
+
ensure
|
75
|
+
@loader = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
# `section` is purely for decorative purposes to carve up `Oaken.prepare` and seed files.
|
79
|
+
#
|
80
|
+
# Oaken.prepare do
|
81
|
+
# section :roots # Just the very few top-level models like Accounts and Users.
|
82
|
+
# users.defaults email_address: -> { Faker::Internet.email }, webauthn_id: -> { SecureRandom.hex }
|
83
|
+
#
|
84
|
+
# section :stems # Models building on the roots.
|
85
|
+
#
|
86
|
+
# section :leafs # Remaining models, bulk of them, hanging off root and stem models.
|
87
|
+
#
|
88
|
+
# section do
|
89
|
+
# seed :accounts, :data
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# Since `section` is defined as `def section(*, **) = yield if block_given?`, you can use
|
94
|
+
# all of Ruby's method signature flexibility to help communicate structure better.
|
95
|
+
#
|
96
|
+
# Use positional and keyword arguments, or use blocks to indent them, or combine them all.
|
97
|
+
def section(*, **)
|
98
|
+
yield if block_given?
|
99
|
+
end
|
40
100
|
end
|
101
|
+
|
102
|
+
# Call `seed` in tests to load individual case files:
|
103
|
+
#
|
104
|
+
# class PaginationTest < ActionDispatch::IntegrationTest
|
105
|
+
# setup do
|
106
|
+
# seed "cases/pagination" # Loads `db/seeds/{,test}/cases/pagination{,**/*}.rb`
|
107
|
+
# end
|
108
|
+
# end
|
109
|
+
delegate :seed, to: Oaken::Seeds
|
41
110
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
-
class Oaken::Stored::ActiveRecord
|
2
|
-
def initialize(type
|
3
|
-
|
4
|
-
@attributes =
|
1
|
+
class Oaken::Stored::ActiveRecord
|
2
|
+
def initialize(type)
|
3
|
+
@type, @key = type, type.table_name
|
4
|
+
@attributes = Oaken::Seeds.defaults_for(*type.column_names)
|
5
5
|
end
|
6
|
+
attr_reader :type, :key
|
6
7
|
delegate :transaction, to: :type # For multi-db setups to help open a transaction on secondary connections.
|
7
8
|
delegate :find, :insert_all, :pluck, to: :type
|
8
9
|
|
@@ -11,30 +12,34 @@ class Oaken::Stored::ActiveRecord < Struct.new(:type, :key)
|
|
11
12
|
@attributes
|
12
13
|
end
|
13
14
|
|
14
|
-
def create(
|
15
|
-
lineno = caller_locations(1, 1).first.lineno
|
16
|
-
|
15
|
+
def create(label = nil, unique_by: nil, **attributes)
|
17
16
|
attributes = @attributes.merge(attributes)
|
18
17
|
attributes.transform_values! { _1.respond_to?(:call) ? _1.call : _1 }
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
19
|
+
finders = attributes.slice(*unique_by)
|
20
|
+
record = type.find_by(finders)&.tap { _1.update!(**attributes) } if finders.any?
|
21
|
+
record ||= type.create!(**attributes)
|
24
22
|
|
25
|
-
|
26
|
-
|
23
|
+
label label => record if label
|
24
|
+
record
|
25
|
+
end
|
27
26
|
|
27
|
+
def upsert(label = nil, unique_by: nil, **attributes)
|
28
28
|
attributes = @attributes.merge(attributes)
|
29
29
|
attributes.transform_values! { _1.respond_to?(:call) ? _1.call : _1 }
|
30
30
|
|
31
31
|
type.new(attributes).validate!
|
32
|
-
type.
|
33
|
-
|
34
|
-
|
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
35
|
end
|
36
36
|
|
37
|
-
def
|
38
|
-
Oaken
|
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
|
39
44
|
end
|
40
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
data/lib/oaken.rb
CHANGED
@@ -6,64 +6,30 @@ require "pathname"
|
|
6
6
|
module Oaken
|
7
7
|
class Error < StandardError; end
|
8
8
|
|
9
|
-
autoload :Seeds,
|
10
|
-
autoload :
|
9
|
+
autoload :Seeds, "oaken/seeds"
|
10
|
+
autoload :TestSetup, "oaken/test_setup"
|
11
11
|
|
12
12
|
module Stored
|
13
13
|
autoload :ActiveRecord, "oaken/stored/active_record"
|
14
14
|
end
|
15
15
|
|
16
|
-
class Inflector
|
17
|
-
def tableize(string)
|
18
|
-
string.gsub(/(?<=[a-z])(?=[A-Z])/, "_").gsub("::", "_").tap(&:downcase!) << "s"
|
19
|
-
end
|
20
|
-
|
21
|
-
def classify(string)
|
22
|
-
string.chomp("s").gsub(/_([a-z])/) { $1.upcase }.sub(/^\w/, &:upcase)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
singleton_class.attr_accessor :inflector
|
27
|
-
@inflector = Inflector.new
|
28
|
-
|
29
16
|
singleton_class.attr_reader :lookup_paths
|
30
17
|
@lookup_paths = ["db/seeds"]
|
31
18
|
|
32
|
-
singleton_class.attr_accessor :store_path
|
33
|
-
@store_path = Pathname.new "tmp/oaken/store"
|
34
|
-
|
35
19
|
class Loader
|
36
|
-
attr_reader :entry
|
37
|
-
|
38
20
|
def initialize(path)
|
39
|
-
@entries
|
21
|
+
@entries = Pathname.glob("#{path}{,/**/*}.rb").sort
|
40
22
|
end
|
41
23
|
|
42
|
-
def load_onto(seeds)
|
43
|
-
|
44
|
-
|
45
|
-
@entry.load_onto seeds
|
24
|
+
def load_onto(seeds) = @entries.each do |path|
|
25
|
+
ActiveRecord::Base.transaction do
|
26
|
+
seeds.class_eval path.read, path.to_s
|
46
27
|
end
|
47
28
|
end
|
48
29
|
end
|
49
30
|
|
50
|
-
def self.
|
51
|
-
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.prepare(&block)
|
55
|
-
store_path.rmtree if ENV["OAKEN_RESET"]
|
56
|
-
Seeds.instance_eval(&block)
|
57
|
-
Seeds
|
58
|
-
end
|
59
|
-
|
60
|
-
def self.seeds
|
61
|
-
unless defined?(@loaded)
|
62
|
-
@loaded = true
|
63
|
-
Rails.application.load_seed
|
64
|
-
end
|
65
|
-
Seeds
|
66
|
-
end
|
31
|
+
def self.prepare(&block) = Seeds.instance_eval(&block)
|
32
|
+
def self.load_seed = Rails.application.load_seed
|
67
33
|
end
|
68
34
|
|
69
35
|
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.
|
4
|
+
version: 0.7.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:
|
11
|
+
date: 2024-06-30 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -24,10 +24,11 @@ files:
|
|
24
24
|
- Rakefile
|
25
25
|
- lib/generators/oaken/convert/fixtures_generator.rb
|
26
26
|
- lib/oaken.rb
|
27
|
-
- lib/oaken/entry.rb
|
28
27
|
- lib/oaken/railtie.rb
|
28
|
+
- lib/oaken/rspec_setup.rb
|
29
29
|
- lib/oaken/seeds.rb
|
30
30
|
- lib/oaken/stored/active_record.rb
|
31
|
+
- lib/oaken/test_setup.rb
|
31
32
|
- lib/oaken/version.rb
|
32
33
|
homepage: https://github.com/kaspth/oaken
|
33
34
|
licenses:
|
@@ -52,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
52
53
|
- !ruby/object:Gem::Version
|
53
54
|
version: '0'
|
54
55
|
requirements: []
|
55
|
-
rubygems_version: 3.
|
56
|
+
rubygems_version: 3.5.10
|
56
57
|
signing_key:
|
57
58
|
specification_version: 4
|
58
59
|
summary: Oaken aims to blend your Fixtures/Factories and levels up your database seeds.
|
data/lib/oaken/entry.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
require "digest/md5"
|
2
|
-
require "pstore"
|
3
|
-
|
4
|
-
class Oaken::Entry < DelegateClass(PStore)
|
5
|
-
def self.store_accessor(name)
|
6
|
-
define_method(name) { self[name] } and define_method("#{name}=") { |value| self[name] = value }
|
7
|
-
end
|
8
|
-
store_accessor :checksum
|
9
|
-
store_accessor :readers
|
10
|
-
|
11
|
-
def self.within(directory)
|
12
|
-
Pathname.glob("#{directory}{,/**/*}.{rb,sql}").sort.map { new _1 }
|
13
|
-
end
|
14
|
-
|
15
|
-
def initialize(pathname)
|
16
|
-
@file, @pathname = pathname.to_s, pathname
|
17
|
-
@computed_checksum = Digest::MD5.hexdigest(@pathname.read)
|
18
|
-
|
19
|
-
prepared_store_path = Oaken.store_path.join(pathname).tap { _1.dirname.mkpath }
|
20
|
-
super PStore.new(prepared_store_path)
|
21
|
-
end
|
22
|
-
|
23
|
-
def load_onto(seeds)
|
24
|
-
transaction do
|
25
|
-
if replay?
|
26
|
-
puts "Replaying #{@file}…"
|
27
|
-
readers.each do |key, *args|
|
28
|
-
define_reader(seeds.send(key), *args)
|
29
|
-
end
|
30
|
-
else
|
31
|
-
reset
|
32
|
-
|
33
|
-
case @pathname.extname
|
34
|
-
in ".rb" then seeds.class_eval @pathname.read, @file
|
35
|
-
in ".sql" then ActiveRecord::Base.connection.execute @pathname.read
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def transaction(&block)
|
42
|
-
super do
|
43
|
-
Oaken.transaction(&block)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def replay?
|
48
|
-
checksum == @computed_checksum
|
49
|
-
end
|
50
|
-
|
51
|
-
def reset
|
52
|
-
self.checksum = @computed_checksum
|
53
|
-
self.readers = Set.new
|
54
|
-
end
|
55
|
-
|
56
|
-
def define_reader(stored, name, id, lineno)
|
57
|
-
stored.instance_eval "def #{name}; find #{id}; end", @file, lineno
|
58
|
-
readers << [stored.key, name, id, lineno]
|
59
|
-
end
|
60
|
-
end
|