standard_procedure_fabrik 0.1.1 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc634b02a078fda169f5f93bc1bd4c607b1245d3d5d866a9224ced85fb3576a1
4
- data.tar.gz: e57ba5a0e6cc50cd061fee1608e2fb096e7fab885e6389a3fd819c63530880bd
3
+ metadata.gz: af004e81e6ae299bb2cbf3d99f2b44a8f133b439292915ac016d1c4d4ca52233
4
+ data.tar.gz: 958c26fa0b8ab2b15e6bc0c4098cee7cb206f749f6e7ccda97074feca8515b4e
5
5
  SHA512:
6
- metadata.gz: 51e2f129f76aa9a3f08b8cd53b54891d8a5d6fdefb41baa03462c8227bea30a0c9497c716a620805e242dfcfff4f54e9b95e180cb4811b9e2915c5ca3a07ef44
7
- data.tar.gz: 964e3ceab133674e4366d98b36243d5d9c9e5b0642253a4a7c43e3300150560aa8c8f218ed87eec902c9136f3ca1e169e01c451dbab9f3cc9a5e82891457a581
6
+ metadata.gz: b104ef644aed04238e021d67a0e06c91961d4b68323bba5adc9a5814a429b3304558c0381fff018a3a6c302d2f766dc1e9513a62cd0246a5052a43376cec4073
7
+ data.tar.gz: 751f1a5ea6ba84141cd82f55ac70c3332fa3e34eddee32a25388e0880b76611334603e6604bbd06d557741998a31cbaa0eb6ce49ec60233d0ddbdbeaf123954c
@@ -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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [0.1.2] - 2024-12-29
2
+
3
+ - Bug fix - when using defaults and search keys together
4
+
1
5
  ## [0.1.1] - 2024-12-29
2
6
 
3
7
  - Pass the database through to defaults (so you can create dependent models)
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,16 +43,21 @@ 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.configure do
46
+ @db = Fabrik.db
47
+ @db.configure do
47
48
  with Person do
48
- defaults first_name: ->(db) { "Alice" }, last_name: ->(db) { Faker::Name.last_name }, age: ->(db) { 33 }
49
+ first_name { Faker::Name.first_name }
50
+ last_name { Faker::Name.last_name }
51
+ email { |person| Faker::Internet.email(name: person.first_name.downcase) }
52
+ age 33
49
53
  end
50
54
  end
51
55
 
52
- @alice = db.people.create last_name: "Anteater"
56
+ @alice = @db.people.create first_name: "Alice"
53
57
 
54
58
  puts @alice.first_name # => Alice
55
- puts @alice.last_name # => Anteater
59
+ puts @alice.last_name # => Hermann
60
+ puts @alice.email # => alice@some-domain.com
56
61
  puts @alice.age # => 33
57
62
  ```
58
63
 
@@ -63,16 +68,17 @@ When you've got a database packed with existing data, you don't want your seeds
63
68
  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
69
 
65
70
  ```ruby
66
- db.configure do
71
+ @db.configure do
67
72
  with Person do
68
- search_using :email
73
+ unique :email
69
74
  end
70
75
  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"
76
+ @alice = @db.people.create first_name: "Alice", last_name: "Aardvark", email: "alice@example.com"
77
+ @zac = @db.people.create first_name: "Zac", last_name: "Zebra", email: "alice@example.com"
73
78
 
74
79
  @alice == @zac # => true
75
80
  puts @zac.first_name # => Alice
81
+ puts @zac.last_name # => Aardvark
76
82
  ```
77
83
 
78
84
  ### Special processing
@@ -82,18 +88,18 @@ Some models are special. They can't live on their own. Their invariants won't
82
88
  So Fabrik lets you define specific processing that happens after a new record is created.
83
89
 
84
90
  ```ruby
85
- db.configure do
91
+ @db.configure do
86
92
  with Company do
87
- defaults name: -> { Faker::Company.name }
88
- after_create do |company, db|
89
- db.employees.create company: company, role: "CEO"
90
- end
93
+ name { Faker::Company.name }
94
+ after_create { |company| employees.create company: company, role: "CEO" }
91
95
  end
92
96
  with Employee do
93
- defaults first_name: -> { Faker::Name.first_name }, last_name: -> { Faker::Name.last_name }, role: -> { "Cleaner" }
97
+ first_name { Faker::Name.first_name }
98
+ last_name { Faker::Name.last_name }
99
+ role "Cleaner"
94
100
  end
95
101
  end
96
- db.companies.create name: "MegaCorp"
102
+ @db.companies.create name: "MegaCorp"
97
103
  puts Company.find_by(name: "MegaCorp").employees.size # => 1
98
104
  ```
99
105
 
@@ -104,9 +110,9 @@ You've created a load of models. And you need to reference them to create more
104
110
  So Fabrik let's you give your models a label. And then refer back to that model using the label.
105
111
 
106
112
  ```ruby
107
- db.people.create :alice, first_name: "Alice"
113
+ @db.people.create :alice, first_name: "Alice"
108
114
 
109
- puts db.people[:alice] # => Alice
115
+ puts @db.people.alice # => Alice
110
116
  ```
111
117
 
112
118
  ## Classes and naming
@@ -120,7 +126,7 @@ class Intergalactic::Spaceship < ApplicationRecord
120
126
  # whatever
121
127
  end
122
128
 
123
- db.intergalactic_spaceships.create :ufo
129
+ @db.intergalactic_spaceships.create :ufo
124
130
 
125
131
  puts Intergalactic::Spaceship.count # => 1
126
132
  ```
@@ -130,13 +136,13 @@ Or maybe you're writing a Rails engine and all your classes are namespaced. Typ
130
136
  So Fabrik lets you register an alternative name for your classes.
131
137
 
132
138
  ```ruby
133
- db.configure do
139
+ @db.configure do
134
140
  with MyAccountsPackageEngine::Invoice, as: :invoice do
135
- defaults due_date: ->(db) { 7.days.from_now }
141
+ due_date { 7.days.from_now }
136
142
  end
137
143
  end
138
144
 
139
- db.invoices.create :overdue_invoice, due_date: 1.day.ago
145
+ @invoice = @db.invoices.create
140
146
  ```
141
147
 
142
148
  ## Installation
@@ -151,15 +157,34 @@ If you're only using it for tests, add it to your `test` group. If you're using
151
157
 
152
158
  ## Usage
153
159
 
154
- ### Localised (for tests)
160
+ ### Global
161
+
162
+ Most of the time, you can use the global `Fabrik.db` instance.
163
+
164
+ ```ruby
165
+ Fabrik.configure do
166
+ with Person do
167
+ first_name { Faker::Name.first_name }
168
+ last_name { Faker::Name.last_name }
169
+ email { Faker::Internet.email }
170
+ end
171
+ end
172
+
173
+ Fabrik.db.people.create :alice, first_name: "Alice"
174
+ ```
175
+
176
+ 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.
177
+
178
+ ### Localised
155
179
 
156
180
  Create an instance of a [Fabrik::Database](/lib/fabrik/database.rb), configure it and use it.
157
181
 
158
182
  ```ruby
159
- db = Fabrik::Database.new
160
- db.configure do
183
+ db = Fabrik::Database.new do
161
184
  with Person do
162
- defaults first_name: ->(db) { Faker::Name.first_name }, last_name: ->(db) { Faker::Name.last_name }, email: ->(db) { Faker::Internet.email }
185
+ first_name { Faker::Name.first_name }
186
+ last_name { Faker::Name.last_name }
187
+ email { Faker::Internet.email }
163
188
  end
164
189
  end
165
190
 
@@ -170,9 +195,8 @@ In an RSpec:
170
195
 
171
196
  ```ruby
172
197
  RSpec.describe "Whatever" do
173
- let(:db) { Fabrik::Database.new }
174
- before do
175
- db.configure do
198
+ let(:db) do
199
+ Fabrik::Database.new do
176
200
  # ... whatever
177
201
  end
178
202
  end
@@ -181,21 +205,6 @@ end
181
205
 
182
206
  In a minitest ... I don't know, I've not used minitest in years but I'm sure it's easy enough.
183
207
 
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
208
 
200
209
  ## Development
201
210
 
@@ -0,0 +1 @@
1
+ 939adbdd3a1922e7dc52f5125c9511bc6eb0bdc77cde5a54464b7f56506b6bf309828aac8df062d0ac8da29bcce5a0ee66affc56b20c0112df52aaa0764f736b
@@ -0,0 +1 @@
1
+ 8d4ca660ab0691aa90bf0a3c61e8ef85765b746d5d575eeec1225bc6dc3c4fbb8fe8945e7020ec037f46f0d3a5fe6dc0f34b55bc1c0a364d949684220a7a15e9
@@ -9,7 +9,9 @@ module Fabrik
9
9
 
10
10
  def register(klass, as: nil, &block)
11
11
  blueprint_name = (as.nil? ? blueprint_name_for(klass) : as.to_s.pluralize).to_sym
12
- @blueprints[blueprint_name] = Blueprint.new(klass, &block)
12
+ blueprint = Blueprint.new(klass, &block)
13
+ @blueprints[blueprint_name] = blueprint
14
+ @blueprints[klass] = blueprint
13
15
 
14
16
  define_singleton_method blueprint_name do
15
17
  proxy_for(blueprint_name)
@@ -19,25 +21,24 @@ module Fabrik
19
21
  end
20
22
  alias_method :with, :register
21
23
 
22
- def defaults_for(klass) = blueprint_for(klass).default_attributes
24
+ def defaults_for(klass) = @blueprints[klass].default_attributes
23
25
 
24
- def search_keys_for(klass) = blueprint_for(klass).search_keys
26
+ def unique_keys_for(klass) = @blueprints[klass].unique_keys
25
27
 
26
- def after_create_for(klass) = blueprint_for(klass).callback
28
+ def after_create_for(klass) = @blueprints[klass].callback
27
29
 
28
30
  def method_missing(method_name, *, &block) = (klass = class_from(method_name)).nil? ? super : register(klass)
29
31
 
30
32
  def respond_to_missing?(method_name, include_private = false) = !class_from(method_name).nil? || super
31
33
 
32
- def initialize
34
+ def initialize &config
33
35
  @blueprints = {}
34
36
  @records = {}
37
+ instance_eval(&config) unless config.nil?
35
38
  end
36
39
 
37
40
  private def blueprint_name_for(klass) = klass.name.split("::").map(&:underscore).join("_").pluralize
38
41
 
39
- private def blueprint_for(klass) = @blueprints.values.find { |bp| bp.klass == klass }
40
-
41
42
  private def proxy_for(blueprint_name)
42
43
  @records[blueprint_name] ||= Proxy.new(self, @blueprints[blueprint_name])
43
44
  end
@@ -51,32 +52,42 @@ module Fabrik
51
52
  end
52
53
 
53
54
  class Blueprint
54
- attr_reader :klass, :default_attributes, :search_keys, :callback
55
+ attr_reader :klass, :default_attributes, :unique_keys, :callback
55
56
 
56
57
  def defaults(**default_attributes) = @default_attributes = default_attributes
57
58
 
58
- def search_using(*keys) = @search_keys = keys
59
+ def unique(*keys) = @unique_keys = keys
59
60
 
60
61
  def after_create(&block) = @callback = block
61
62
 
62
- def call_after_create(record, db) = @callback&.call(record, db)
63
+ def call_after_create(record, db) = @callback.nil? ? nil : db.instance_exec(record, &@callback)
64
+
65
+ def method_missing(method_name, *args, &block)
66
+ @default_attributes[method_name.to_sym] = args.first.nil? ? block : ->(_) { args.first }
67
+ end
68
+
69
+ def respond_to_missing?(method_name, include_private = false) = @default_attributes.key?(method_name.to_sym) || super
63
70
 
64
71
  def initialize(klass, &block)
65
72
  @klass = klass
66
73
  @default_attributes = {}
67
- @search_keys = []
74
+ @unique_keys = []
68
75
  instance_eval(&block) if block_given?
69
76
  end
70
77
  end
71
78
 
72
79
  class Proxy < SimpleDelegator
73
80
  def create(label = nil, **attributes)
74
- (@blueprint.search_keys.any? ? find_or_create_record(attributes) : create_record(attributes)).tap do |record|
81
+ (@blueprint.unique_keys.any? ? find_or_create_record(attributes) : create_record(attributes)).tap do |record|
75
82
  @records[label.to_sym] = record if label
76
83
  end
77
84
  end
78
85
 
79
- def [](label) = @records[label.to_sym]
86
+ def method_missing(method_name, *args, &block)
87
+ @records[method_name.to_sym]
88
+ end
89
+
90
+ def respond_to_missing?(method_name, include_private = false) = @@records.key?(label.to_sym) || super
80
91
 
81
92
  def initialize(db, blueprint)
82
93
  @db = db
@@ -85,15 +96,18 @@ module Fabrik
85
96
  super(klass)
86
97
  end
87
98
 
88
- private def search_keys = @blueprint.search_keys
99
+ private def unique_keys = @blueprint.unique_keys
89
100
 
90
101
  private def klass = @blueprint.klass
91
102
 
92
103
  private def default_attributes = @blueprint.default_attributes
93
104
 
94
- private def find_or_create_record(attributes) = find_record(attributes) || create_record(attributes)
105
+ private def find_or_create_record(attributes)
106
+ attributes = attributes_with_defaults(attributes)
107
+ find_record(attributes) || create_record(attributes)
108
+ end
95
109
 
96
- private def find_record(attributes) = attributes.slice(*search_keys).empty? ? nil : klass.find_by(**attributes.slice(*search_keys))
110
+ private def find_record(attributes) = attributes.slice(*unique_keys).empty? ? nil : klass.find_by(**attributes.slice(*unique_keys))
97
111
 
98
112
  private def create_record(attributes)
99
113
  klass.create(**attributes_with_defaults(attributes)).tap do |record|
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fabrik
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
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.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rahoul Baruah
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-12-29 00:00:00.000000000 Z
10
+ date: 2025-02-06 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -24,20 +23,6 @@ dependencies:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
25
  version: 6.0.0
27
- - !ruby/object:Gem::Dependency
28
- name: standard-procedure-plumbing
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
26
  description: Fixtures, fittings and seeds
42
27
  email:
43
28
  - rahoulb@echodek.co
@@ -45,14 +30,18 @@ executables: []
45
30
  extensions: []
46
31
  extra_rdoc_files: []
47
32
  files:
33
+ - ".devcontainer/devcontainer.json"
48
34
  - ".rspec"
49
35
  - ".standard.yml"
50
36
  - CHANGELOG.md
37
+ - Guardfile
51
38
  - LICENSE
52
39
  - README.md
53
40
  - Rakefile
54
41
  - checksums/standard_procedure_fabrik-0.1.0.gem.sha512
55
42
  - checksums/standard_procedure_fabrik-0.1.1.gem.sha512
43
+ - checksums/standard_procedure_fabrik-0.1.2.gem.sha512
44
+ - checksums/standard_procedure_fabrik-0.2.0.gem.sha512
56
45
  - lib/fabrik.rb
57
46
  - lib/fabrik/database.rb
58
47
  - lib/fabrik/version.rb
@@ -65,7 +54,6 @@ metadata:
65
54
  homepage_uri: https://www.theartandscienceofruby.com
66
55
  source_code_uri: https://github.com/standard-procedure/fabrik
67
56
  changelog_uri: https://github.com/standard-procedure/fabrik/blob/main/CHANGELOG.md
68
- post_install_message:
69
57
  rdoc_options: []
70
58
  require_paths:
71
59
  - lib
@@ -80,8 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
68
  - !ruby/object:Gem::Version
81
69
  version: '0'
82
70
  requirements: []
83
- rubygems_version: 3.5.22
84
- signing_key:
71
+ rubygems_version: 3.6.2
85
72
  specification_version: 4
86
73
  summary: Fabrik
87
74
  test_files: []