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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2742aab097f1a8977498b4b654829b4a6b9d54e98320a4e56a2c1419aeeb53ad
4
- data.tar.gz: 34394ab7faa6707a2afcd4fa03d24fedc5ae84471c0f461b37ac70de5ef51ca5
3
+ metadata.gz: 60c8bb1ad20f4486e99e2ce3e3ef51895d30ddae6d64aa4f66e88dc27462cfe6
4
+ data.tar.gz: a1e4156f37806ead8bf2212a927192a851749abb16b55aac671ca7a99d42e2ec
5
5
  SHA512:
6
- metadata.gz: c049dacfdaa1a044e70510216f244d3008e441f3cf950439e7862c7ed165a915e0f6b42787a3afdcca4d50be3bfd1408668308c6cab176196ffc26cad5053481
7
- data.tar.gz: e814649e75c6cd9ea0a69d6705205b62ef91bc34b4bf545d093c616924bebe2ee84bfea09df4d0298dc294c759d44c7f4fdb7f6ee752c2a9b568e0dd7bcbdb04
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.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) }
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
 
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
- search_using :email
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
- defaults name: -> { Faker::Company.name }
88
- after_create do |company, db|
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
- defaults first_name: -> { Faker::Name.first_name }, last_name: -> { Faker::Name.last_name }, role: -> { "Cleaner" }
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[:alice] # => Alice
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
- defaults due_date: ->(db) { 7.days.from_now }
147
+ due_date { 7.days.from_now }
136
148
  end
137
149
  end
138
150
 
139
- db.invoices.create :overdue_invoice, due_date: 1.day.ago
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
- ### Localised (for tests)
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
- defaults first_name: ->(db) { Faker::Name.first_name }, last_name: ->(db) { Faker::Name.last_name }, email: ->(db) { Faker::Internet.email }
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) { Fabrik::Database.new }
174
- before do
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
@@ -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
- @blueprints[blueprint_name] = Blueprint.new(klass, &block)
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) = blueprint_for(klass).default_attributes
25
+ def defaults_for(klass) = @blueprints[klass].default_attributes
23
26
 
24
- def search_keys_for(klass) = blueprint_for(klass).search_keys
27
+ def unique_keys_for(klass) = @blueprints[klass].unique_keys
25
28
 
26
- def after_create_for(klass) = blueprint_for(klass).callback
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, :search_keys, :callback
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 search_using(*keys) = @search_keys = keys
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&.call(record, db)
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
- @search_keys = []
68
- instance_eval(&block) if block_given?
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.search_keys.any? ? find_or_create_record(attributes) : create_record(attributes)).tap do |record|
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 [](label) = @records[label.to_sym]
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 search_keys = @blueprint.search_keys
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(*search_keys).empty? ? nil : klass.find_by(**attributes.slice(*search_keys))
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({}) { |name, generated_attributes| generated_attributes[name] = default_attributes[name].call(@db) }.merge(attributes)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fabrik
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.1"
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.2
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: 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
@@ -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: standard-procedure-plumbing
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.5.22
85
- signing_key:
86
+ rubygems_version: 3.6.2
86
87
  specification_version: 4
87
88
  summary: Fabrik
88
89
  test_files: []