standard_procedure_fabrik 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.devcontainer/devcontainer.json +31 -0
- data/Guardfile +22 -0
- data/README.md +58 -43
- data/checksums/standard_procedure_fabrik-0.2.0.gem.sha512 +1 -0
- data/checksums/standard_procedure_fabrik-0.2.1.gem.sha512 +1 -0
- data/lib/fabrik/database.rb +31 -17
- data/lib/fabrik/version.rb +1 -1
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 60c8bb1ad20f4486e99e2ce3e3ef51895d30ddae6d64aa4f66e88dc27462cfe6
|
4
|
+
data.tar.gz: a1e4156f37806ead8bf2212a927192a851749abb16b55aac671ca7a99d42e2ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad07c2c432fc4f59d1b01df21c8f71c41384bb4d99b53e8d2ad34fa8796724b4f12140fd5a49369a38b0624095272b79b6a15c35091b9adaee988c7a47e63f68
|
7
|
+
data.tar.gz: caca476f05265786a15ccdb16ed280236a0d5177d647ca5e2d18826bb05026442c7c0d4d48fcadc3aa5789b67a7380938ddf53d41199ebcc388b0f6cae1711f9
|
@@ -0,0 +1,31 @@
|
|
1
|
+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
2
|
+
// README at: https://github.com/devcontainers/templates/tree/main/src/ruby
|
3
|
+
{
|
4
|
+
"name": "Ruby",
|
5
|
+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
6
|
+
"image": "mcr.microsoft.com/devcontainers/ruby:1-3.3-bullseye",
|
7
|
+
// Features to add to the dev container. More info: https://containers.dev/features.
|
8
|
+
// "features": {},
|
9
|
+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
10
|
+
// "forwardPorts": [],
|
11
|
+
// Use 'postCreateCommand' to run commands after the container is created.
|
12
|
+
// "postCreateCommand": "ruby --version",
|
13
|
+
"customizations": {
|
14
|
+
"vscode": {
|
15
|
+
"extensions": [
|
16
|
+
"Shopify.ruby-extensions-pack",
|
17
|
+
"testdouble.vscode-standard-ruby",
|
18
|
+
"manuelpuyol.erb-linter",
|
19
|
+
"Shopify.ruby-lsp",
|
20
|
+
"aki77.rails-db-schema",
|
21
|
+
"miguel-savignano.ruby-symbols",
|
22
|
+
"sibiraj-s.vscode-scss-formatter",
|
23
|
+
"Thadeu.vscode-run-rspec-file",
|
24
|
+
"Cronos87.yaml-symbols",
|
25
|
+
"aliariff.vscode-erb-beautify"
|
26
|
+
]
|
27
|
+
}
|
28
|
+
}
|
29
|
+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
30
|
+
// "remoteUser": "root"
|
31
|
+
}
|
data/Guardfile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
group :development do
|
2
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
3
|
+
watch(%r{^spec/.+_spec.rb$})
|
4
|
+
watch(%r{^lib/(.+).rb$}) { "spec" }
|
5
|
+
end
|
6
|
+
|
7
|
+
guard :bundler do
|
8
|
+
require "guard/bundler"
|
9
|
+
require "guard/bundler/verify"
|
10
|
+
helper = Guard::Bundler::Verify.new
|
11
|
+
|
12
|
+
files = ["Gemfile"]
|
13
|
+
files += Dir["*.gemspec"] if files.any? { |f| helper.uses_gemspec?(f) }
|
14
|
+
|
15
|
+
# Assume files are symlinked from somewhere
|
16
|
+
files.each { |file| watch(helper.real_path(file)) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
guard :standardrb, fix: true, all_on_start: true, progress: true do
|
21
|
+
watch(/.+.rb$/)
|
22
|
+
end
|
data/README.md
CHANGED
@@ -43,19 +43,30 @@ When you're writing a spec, you probably only care about one aspect of the model
|
|
43
43
|
So Fabrik allows you to set default attributes; then when you're creating a model, you can specify the ones you're interested in and ignore all the rest.
|
44
44
|
|
45
45
|
```ruby
|
46
|
-
db.
|
46
|
+
@db = Fabrik.db
|
47
|
+
@db.configure do
|
47
48
|
with Person do
|
48
|
-
|
49
|
+
first_name { Faker::Name.first_name }
|
50
|
+
last_name { Faker::Name.last_name }
|
51
|
+
email { |person| Faker::Internet.email(name: person.first_name) }
|
52
|
+
age 33
|
49
53
|
end
|
50
54
|
end
|
51
55
|
|
52
|
-
@alice = db.people.create
|
56
|
+
@alice = @db.people.create first_name: "Alice"
|
53
57
|
|
54
58
|
puts @alice.first_name # => Alice
|
55
|
-
puts @alice.last_name # =>
|
59
|
+
puts @alice.last_name # => Hermann
|
60
|
+
puts @alice.email # => alice@some-domain.com
|
56
61
|
puts @alice.age # => 33
|
57
62
|
```
|
58
63
|
|
64
|
+
When specifying a default, you can either use a fixed value - for example `age 33`.
|
65
|
+
|
66
|
+
Or you can pass a block and use a dynamic value - for example `last_name { Faker::Name.last_name }`.
|
67
|
+
|
68
|
+
If you pass a block, you can also access any attributes that have been generated so far - in this case we are accessing `person.first_name`. Whilst attributes that were supplied in the call to `create` are generally safe, relying on values that were generated dynamically may not be. The default value generators _should_ be called in the order of declaration, giving you access to dynamic values declared beforehand - but this is not guaranteed. It's generally safer to avoid references when generating default values.
|
69
|
+
|
59
70
|
### Uniqueness
|
60
71
|
|
61
72
|
When you've got a database packed with existing data, you don't want your seeds to fail because of a uniqueness constraint. Or your tests to fail because suddenly they're finding two records when they were only expecting one.
|
@@ -63,16 +74,17 @@ When you've got a database packed with existing data, you don't want your seeds
|
|
63
74
|
So Fabrik lets you define what makes a model unique. Then when you create it, it checks for an existing record first and only creates a new one if the original is not found.
|
64
75
|
|
65
76
|
```ruby
|
66
|
-
db.configure do
|
77
|
+
@db.configure do
|
67
78
|
with Person do
|
68
|
-
|
79
|
+
unique :email
|
69
80
|
end
|
70
81
|
end
|
71
|
-
@alice = db.people.create first_name: "Alice", last_name: "Aardvark", email: "alice@example.com"
|
72
|
-
@zac = db.people.create first_name: "Zac", last_name: "Zebra", email: "alice@example.com"
|
82
|
+
@alice = @db.people.create first_name: "Alice", last_name: "Aardvark", email: "alice@example.com"
|
83
|
+
@zac = @db.people.create first_name: "Zac", last_name: "Zebra", email: "alice@example.com"
|
73
84
|
|
74
85
|
@alice == @zac # => true
|
75
86
|
puts @zac.first_name # => Alice
|
87
|
+
puts @zac.last_name # => Aardvark
|
76
88
|
```
|
77
89
|
|
78
90
|
### Special processing
|
@@ -82,18 +94,18 @@ Some models are special. They can't live on their own. Their invariants won't
|
|
82
94
|
So Fabrik lets you define specific processing that happens after a new record is created.
|
83
95
|
|
84
96
|
```ruby
|
85
|
-
db.configure do
|
97
|
+
@db.configure do
|
86
98
|
with Company do
|
87
|
-
|
88
|
-
after_create
|
89
|
-
db.employees.create company: company, role: "CEO"
|
90
|
-
end
|
99
|
+
name { Faker::Company.name }
|
100
|
+
after_create { |company| employees.create company: company, role: "CEO" }
|
91
101
|
end
|
92
102
|
with Employee do
|
93
|
-
|
103
|
+
first_name { Faker::Name.first_name }
|
104
|
+
last_name { Faker::Name.last_name }
|
105
|
+
role "Cleaner"
|
94
106
|
end
|
95
107
|
end
|
96
|
-
db.companies.create name: "MegaCorp"
|
108
|
+
@db.companies.create name: "MegaCorp"
|
97
109
|
puts Company.find_by(name: "MegaCorp").employees.size # => 1
|
98
110
|
```
|
99
111
|
|
@@ -104,9 +116,9 @@ You've created a load of models. And you need to reference them to create more
|
|
104
116
|
So Fabrik let's you give your models a label. And then refer back to that model using the label.
|
105
117
|
|
106
118
|
```ruby
|
107
|
-
db.people.create :alice, first_name: "Alice"
|
119
|
+
@db.people.create :alice, first_name: "Alice"
|
108
120
|
|
109
|
-
puts db.people
|
121
|
+
puts @db.people.alice # => Alice
|
110
122
|
```
|
111
123
|
|
112
124
|
## Classes and naming
|
@@ -120,7 +132,7 @@ class Intergalactic::Spaceship < ApplicationRecord
|
|
120
132
|
# whatever
|
121
133
|
end
|
122
134
|
|
123
|
-
db.intergalactic_spaceships.create :ufo
|
135
|
+
@db.intergalactic_spaceships.create :ufo
|
124
136
|
|
125
137
|
puts Intergalactic::Spaceship.count # => 1
|
126
138
|
```
|
@@ -130,13 +142,13 @@ Or maybe you're writing a Rails engine and all your classes are namespaced. Typ
|
|
130
142
|
So Fabrik lets you register an alternative name for your classes.
|
131
143
|
|
132
144
|
```ruby
|
133
|
-
db.configure do
|
145
|
+
@db.configure do
|
134
146
|
with MyAccountsPackageEngine::Invoice, as: :invoice do
|
135
|
-
|
147
|
+
due_date { 7.days.from_now }
|
136
148
|
end
|
137
149
|
end
|
138
150
|
|
139
|
-
db.invoices.create
|
151
|
+
@invoice = @db.invoices.create
|
140
152
|
```
|
141
153
|
|
142
154
|
## Installation
|
@@ -151,15 +163,34 @@ If you're only using it for tests, add it to your `test` group. If you're using
|
|
151
163
|
|
152
164
|
## Usage
|
153
165
|
|
154
|
-
###
|
166
|
+
### Global
|
167
|
+
|
168
|
+
Most of the time, you can use the global `Fabrik.db` instance.
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
Fabrik.configure do
|
172
|
+
with Person do
|
173
|
+
first_name { Faker::Name.first_name }
|
174
|
+
last_name { Faker::Name.last_name }
|
175
|
+
email { Faker::Internet.email }
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
Fabrik.db.people.create :alice, first_name: "Alice"
|
180
|
+
```
|
181
|
+
|
182
|
+
Watch out - because this uses a global instance, it's not thread-safe. That's not an issue if you're just using it for database seeds or in most test runners (single-threaded or parallelised with multiple processes). But it *might* cause problems if you're using threads to parallelise your tests, or you're reconfiguring while your application is running.
|
183
|
+
|
184
|
+
### Localised
|
155
185
|
|
156
186
|
Create an instance of a [Fabrik::Database](/lib/fabrik/database.rb), configure it and use it.
|
157
187
|
|
158
188
|
```ruby
|
159
|
-
db = Fabrik::Database.new
|
160
|
-
db.configure do
|
189
|
+
db = Fabrik::Database.new do
|
161
190
|
with Person do
|
162
|
-
|
191
|
+
first_name { Faker::Name.first_name }
|
192
|
+
last_name { Faker::Name.last_name }
|
193
|
+
email { Faker::Internet.email }
|
163
194
|
end
|
164
195
|
end
|
165
196
|
|
@@ -170,9 +201,8 @@ In an RSpec:
|
|
170
201
|
|
171
202
|
```ruby
|
172
203
|
RSpec.describe "Whatever" do
|
173
|
-
let(:db)
|
174
|
-
|
175
|
-
db.configure do
|
204
|
+
let(:db) do
|
205
|
+
Fabrik::Database.new do
|
176
206
|
# ... whatever
|
177
207
|
end
|
178
208
|
end
|
@@ -181,21 +211,6 @@ end
|
|
181
211
|
|
182
212
|
In a minitest ... I don't know, I've not used minitest in years but I'm sure it's easy enough.
|
183
213
|
|
184
|
-
### Global (for seeds and generally creating stuff)
|
185
|
-
|
186
|
-
Configure the global `Fabrik::Database`, then use it.
|
187
|
-
|
188
|
-
```ruby
|
189
|
-
Fabrik.configure do
|
190
|
-
with Person do
|
191
|
-
defaults first_name: ->(db) { Faker::Name.first_name }, last_name: ->(db) { Faker::Name.last_name }, email: ->(db) { Faker::Internet.email }
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
Fabrik.db.people.create :alice, first_name: "Alice"
|
196
|
-
```
|
197
|
-
|
198
|
-
Watch out - because this uses a global instance, it's not thread-safe. That's not an issue if you're just using it for database seeds or in most test runners(single-threaded or parallelised with multiple processes). But it *might* cause problems if you're using threads to parallelise your tests, or you're reconfiguring while your application is running.
|
199
214
|
|
200
215
|
## Development
|
201
216
|
|
@@ -0,0 +1 @@
|
|
1
|
+
8d4ca660ab0691aa90bf0a3c61e8ef85765b746d5d575eeec1225bc6dc3c4fbb8fe8945e7020ec037f46f0d3a5fe6dc0f34b55bc1c0a364d949684220a7a15e9
|
@@ -0,0 +1 @@
|
|
1
|
+
0809cf506fe2cbf6b519100fb12c6799380f14991374e6cd2508fc33a6a1141e35f70d9e8b5d01a525df1cec660f99dc1e0f65c1e737c4001c482e76ed2f9553
|
data/lib/fabrik/database.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require "active_support/core_ext/string"
|
4
4
|
require "delegate"
|
5
|
+
require "ostruct"
|
5
6
|
|
6
7
|
module Fabrik
|
7
8
|
class Database
|
@@ -9,7 +10,9 @@ module Fabrik
|
|
9
10
|
|
10
11
|
def register(klass, as: nil, &block)
|
11
12
|
blueprint_name = (as.nil? ? blueprint_name_for(klass) : as.to_s.pluralize).to_sym
|
12
|
-
|
13
|
+
blueprint = Blueprint.new(klass, &block)
|
14
|
+
@blueprints[blueprint_name] = blueprint
|
15
|
+
@blueprints[klass] = blueprint
|
13
16
|
|
14
17
|
define_singleton_method blueprint_name do
|
15
18
|
proxy_for(blueprint_name)
|
@@ -19,25 +22,24 @@ module Fabrik
|
|
19
22
|
end
|
20
23
|
alias_method :with, :register
|
21
24
|
|
22
|
-
def defaults_for(klass) =
|
25
|
+
def defaults_for(klass) = @blueprints[klass].default_attributes
|
23
26
|
|
24
|
-
def
|
27
|
+
def unique_keys_for(klass) = @blueprints[klass].unique_keys
|
25
28
|
|
26
|
-
def after_create_for(klass) =
|
29
|
+
def after_create_for(klass) = @blueprints[klass].callback
|
27
30
|
|
28
31
|
def method_missing(method_name, *, &block) = (klass = class_from(method_name)).nil? ? super : register(klass)
|
29
32
|
|
30
33
|
def respond_to_missing?(method_name, include_private = false) = !class_from(method_name).nil? || super
|
31
34
|
|
32
|
-
def initialize
|
35
|
+
def initialize &config
|
33
36
|
@blueprints = {}
|
34
37
|
@records = {}
|
38
|
+
instance_eval(&config) unless config.nil?
|
35
39
|
end
|
36
40
|
|
37
41
|
private def blueprint_name_for(klass) = klass.name.split("::").map(&:underscore).join("_").pluralize
|
38
42
|
|
39
|
-
private def blueprint_for(klass) = @blueprints.values.find { |bp| bp.klass == klass }
|
40
|
-
|
41
43
|
private def proxy_for(blueprint_name)
|
42
44
|
@records[blueprint_name] ||= Proxy.new(self, @blueprints[blueprint_name])
|
43
45
|
end
|
@@ -51,32 +53,42 @@ module Fabrik
|
|
51
53
|
end
|
52
54
|
|
53
55
|
class Blueprint
|
54
|
-
attr_reader :klass, :default_attributes, :
|
56
|
+
attr_reader :klass, :default_attributes, :unique_keys, :callback
|
55
57
|
|
56
58
|
def defaults(**default_attributes) = @default_attributes = default_attributes
|
57
59
|
|
58
|
-
def
|
60
|
+
def unique(*keys) = @unique_keys = keys
|
59
61
|
|
60
62
|
def after_create(&block) = @callback = block
|
61
63
|
|
62
|
-
def call_after_create(record, db) = @callback
|
64
|
+
def call_after_create(record, db) = @callback.nil? ? nil : db.instance_exec(record, &@callback)
|
65
|
+
|
66
|
+
def method_missing(method_name, *args, &block)
|
67
|
+
@default_attributes[method_name.to_sym] = args.first.nil? ? block : ->(_) { args.first }
|
68
|
+
end
|
69
|
+
|
70
|
+
def respond_to_missing?(method_name, include_private = false) = @default_attributes.key?(method_name.to_sym) || super
|
63
71
|
|
64
72
|
def initialize(klass, &block)
|
65
73
|
@klass = klass
|
66
74
|
@default_attributes = {}
|
67
|
-
@
|
68
|
-
instance_eval(&block)
|
75
|
+
@unique_keys = []
|
76
|
+
instance_eval(&block) unless block.nil?
|
69
77
|
end
|
70
78
|
end
|
71
79
|
|
72
80
|
class Proxy < SimpleDelegator
|
73
81
|
def create(label = nil, **attributes)
|
74
|
-
(@blueprint.
|
82
|
+
(@blueprint.unique_keys.any? ? find_or_create_record(attributes) : create_record(attributes)).tap do |record|
|
75
83
|
@records[label.to_sym] = record if label
|
76
84
|
end
|
77
85
|
end
|
78
86
|
|
79
|
-
def
|
87
|
+
def method_missing(method_name, *args, &block)
|
88
|
+
@records[method_name.to_sym]
|
89
|
+
end
|
90
|
+
|
91
|
+
def respond_to_missing?(method_name, include_private = false) = @@records.key?(label.to_sym) || super
|
80
92
|
|
81
93
|
def initialize(db, blueprint)
|
82
94
|
@db = db
|
@@ -85,7 +97,7 @@ module Fabrik
|
|
85
97
|
super(klass)
|
86
98
|
end
|
87
99
|
|
88
|
-
private def
|
100
|
+
private def unique_keys = @blueprint.unique_keys
|
89
101
|
|
90
102
|
private def klass = @blueprint.klass
|
91
103
|
|
@@ -96,7 +108,7 @@ module Fabrik
|
|
96
108
|
find_record(attributes) || create_record(attributes)
|
97
109
|
end
|
98
110
|
|
99
|
-
private def find_record(attributes) = attributes.slice(*
|
111
|
+
private def find_record(attributes) = attributes.slice(*unique_keys).empty? ? nil : klass.find_by(**attributes.slice(*unique_keys))
|
100
112
|
|
101
113
|
private def create_record(attributes)
|
102
114
|
klass.create(**attributes_with_defaults(attributes)).tap do |record|
|
@@ -106,7 +118,9 @@ module Fabrik
|
|
106
118
|
|
107
119
|
private def attributes_with_defaults attributes
|
108
120
|
attributes_to_generate = default_attributes.keys - attributes.keys
|
109
|
-
attributes_to_generate.each_with_object(
|
121
|
+
attributes_to_generate.each_with_object(OpenStruct.new(**attributes)) do |name, generated_attributes|
|
122
|
+
generated_attributes[name] = default_attributes[name].nil? ? nil : @db.instance_exec(generated_attributes, &default_attributes[name])
|
123
|
+
end.to_h.merge(attributes)
|
110
124
|
end
|
111
125
|
end
|
112
126
|
end
|
data/lib/fabrik/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: standard_procedure_fabrik
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rahoul Baruah
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-02-06 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activesupport
|
@@ -25,7 +24,7 @@ dependencies:
|
|
25
24
|
- !ruby/object:Gem::Version
|
26
25
|
version: 6.0.0
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
27
|
+
name: ostruct
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
30
|
- - ">="
|
@@ -45,15 +44,19 @@ executables: []
|
|
45
44
|
extensions: []
|
46
45
|
extra_rdoc_files: []
|
47
46
|
files:
|
47
|
+
- ".devcontainer/devcontainer.json"
|
48
48
|
- ".rspec"
|
49
49
|
- ".standard.yml"
|
50
50
|
- CHANGELOG.md
|
51
|
+
- Guardfile
|
51
52
|
- LICENSE
|
52
53
|
- README.md
|
53
54
|
- Rakefile
|
54
55
|
- checksums/standard_procedure_fabrik-0.1.0.gem.sha512
|
55
56
|
- checksums/standard_procedure_fabrik-0.1.1.gem.sha512
|
56
57
|
- checksums/standard_procedure_fabrik-0.1.2.gem.sha512
|
58
|
+
- checksums/standard_procedure_fabrik-0.2.0.gem.sha512
|
59
|
+
- checksums/standard_procedure_fabrik-0.2.1.gem.sha512
|
57
60
|
- lib/fabrik.rb
|
58
61
|
- lib/fabrik/database.rb
|
59
62
|
- lib/fabrik/version.rb
|
@@ -66,7 +69,6 @@ metadata:
|
|
66
69
|
homepage_uri: https://www.theartandscienceofruby.com
|
67
70
|
source_code_uri: https://github.com/standard-procedure/fabrik
|
68
71
|
changelog_uri: https://github.com/standard-procedure/fabrik/blob/main/CHANGELOG.md
|
69
|
-
post_install_message:
|
70
72
|
rdoc_options: []
|
71
73
|
require_paths:
|
72
74
|
- lib
|
@@ -81,8 +83,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
83
|
- !ruby/object:Gem::Version
|
82
84
|
version: '0'
|
83
85
|
requirements: []
|
84
|
-
rubygems_version: 3.
|
85
|
-
signing_key:
|
86
|
+
rubygems_version: 3.6.2
|
86
87
|
specification_version: 4
|
87
88
|
summary: Fabrik
|
88
89
|
test_files: []
|