rom-factory 0.10.2 → 0.11.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.action_hero.yml +21 -0
  3. data/.devtools/templates/changelog.erb +3 -0
  4. data/.devtools/templates/release.erb +36 -0
  5. data/.github/FUNDING.yml +1 -0
  6. data/.github/ISSUE_TEMPLATE/{---bug-report.md → bug-report.md} +6 -10
  7. data/.github/ISSUE_TEMPLATE/config.yml +5 -0
  8. data/.github/SUPPORT.md +3 -0
  9. data/.github/workflows/ci.yml +14 -23
  10. data/.github/workflows/docsite.yml +5 -4
  11. data/.github/workflows/rubocop.yml +46 -0
  12. data/.github/workflows/sync_configs.yml +17 -17
  13. data/.rubocop.yml +135 -16
  14. data/CHANGELOG.md +44 -0
  15. data/CODEOWNERS +1 -0
  16. data/CONTRIBUTING.md +3 -3
  17. data/Gemfile +20 -19
  18. data/Gemfile.devtools +6 -5
  19. data/LICENSE +1 -1
  20. data/README.md +3 -3
  21. data/Rakefile +1 -1
  22. data/benchmarks/basic.rb +10 -10
  23. data/changelog.yml +23 -0
  24. data/docsite/source/index.html.md +162 -1
  25. data/lib/rom/factory/attribute_registry.rb +2 -2
  26. data/lib/rom/factory/attributes/association.rb +52 -12
  27. data/lib/rom/factory/attributes/callable.rb +1 -1
  28. data/lib/rom/factory/attributes/value.rb +1 -1
  29. data/lib/rom/factory/attributes.rb +4 -6
  30. data/lib/rom/factory/builder/persistable.rb +3 -5
  31. data/lib/rom/factory/builder.rb +8 -18
  32. data/lib/rom/factory/constants.rb +1 -1
  33. data/lib/rom/factory/dsl.rb +36 -24
  34. data/lib/rom/factory/factories.rb +13 -15
  35. data/lib/rom/factory/registry.rb +2 -2
  36. data/lib/rom/factory/sequences.rb +1 -1
  37. data/lib/rom/factory/tuple_evaluator.rb +18 -22
  38. data/lib/rom/factory/version.rb +1 -1
  39. data/lib/rom/factory.rb +8 -5
  40. data/lib/rom-factory.rb +1 -1
  41. data/project.yml +1 -0
  42. data/rom-factory.gemspec +9 -10
  43. metadata +38 -24
  44. data/.github/ISSUE_TEMPLATE/----please-don-t-ask-for-support-via-issues.md +0 -10
  45. data/.github/ISSUE_TEMPLATE/----please-don-t-report-feature-requests-via-issues.md +0 -10
  46. data/.github/ISSUE_TEMPLATE/---a-detailed-bug-report.md +0 -30
  47. data/.github/ISSUE_TEMPLATE/---feature-request.md +0 -18
  48. data/.github/workflows/custom/ci.yml +0 -26
  49. data/Appraisals +0 -9
  50. data/LICENSE.txt +0 -21
data/Gemfile CHANGED
@@ -1,38 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source 'https://rubygems.org'
3
+ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
- eval_gemfile 'Gemfile.devtools'
7
+ eval_gemfile "Gemfile.devtools"
8
8
 
9
- gem 'faker', "~> #{ENV['FAKER'].eql?('faker-1') ? '1.7' : '2.8'}"
9
+ gem "faker", "~> 2.8"
10
10
 
11
- gem 'rake', '~> 12.0'
12
- gem 'rspec', '~> 3.0'
11
+ gem "rspec", "~> 3.0"
13
12
 
14
- gem 'rom', github: 'rom-rb/rom', branch: 'master' do
15
- gem 'rom-core'
13
+ git "https://github.com/rom-rb/rom.git", branch: "release-5.3" do
14
+ gem "rom-core"
15
+ gem "rom-changeset"
16
+ gem "rom-repository"
17
+ gem "rom"
16
18
  end
17
19
 
18
20
  group :test do
19
- gem 'rom-sql', github: 'rom-rb/rom-sql', branch: 'master'
20
- gem 'inflecto'
21
- gem 'pry-byebug', '~> 3.8', platforms: :ruby
22
- gem 'pry', '~> 0.12.0', '<= 0.13'
21
+ gem "pry", "~> 0.12.0", "<= 0.13"
22
+ gem "pry-byebug", "~> 3.8", platforms: :ruby
23
+ gem "rom-sql", github: "rom-rb/rom-sql", branch: "release-3.6"
23
24
 
24
- gem 'pg', '~> 0.21', platforms: :ruby
25
- gem 'jdbc-postgres', platforms: :jruby
25
+ gem "jdbc-postgres", platforms: :jruby
26
+ gem "pg", "~> 0.21", platforms: :ruby
26
27
  end
27
28
 
28
29
  group :tools do
29
- gem 'byebug', platform: :mri
30
- gem 'redcarpet' # for yard
30
+ gem "byebug", platform: :mri
31
+ gem "redcarpet" # for yard
31
32
  end
32
33
 
33
34
  group :benchmarks do
34
- gem 'activerecord'
35
- gem 'benchmark-ips'
36
- gem 'factory_bot'
37
- gem 'fabrication'
35
+ gem "activerecord"
36
+ gem "benchmark-ips"
37
+ gem "fabrication"
38
+ gem "factory_bot"
38
39
  end
data/Gemfile.devtools CHANGED
@@ -2,18 +2,19 @@
2
2
 
3
3
  # this file is managed by rom-rb/devtools project
4
4
 
5
+ gem "rake", ">= 12.3.3"
6
+
5
7
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
8
 
7
9
  group :test do
8
- # 0.18.x breaks codacy result parser
9
- gem "simplecov", "0.17.1", require: false, platforms: :ruby
10
-
11
- gem "codacy-coverage", require: false, platforms: :ruby
10
+ gem "simplecov", require: false, platforms: :ruby
11
+ gem "simplecov-cobertura", require: false, platforms: :ruby
12
+ gem "rexml", require: false
12
13
 
13
14
  gem "warning" if RUBY_VERSION >= "2.4.0"
14
15
  end
15
16
 
16
17
  group :tools do
17
18
  # this is the same version that we use on codacy
18
- gem "rubocop", "0.71.0"
19
+ gem "rubocop", "1.26.1"
19
20
  end
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2020 rom-rb team
3
+ Copyright (c) 2015-2021 rom-rb team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -14,14 +14,14 @@
14
14
 
15
15
  ## Links
16
16
 
17
- * [User documentation](http://rom-rb.org/learn/rom-factory)
18
- * [API documentation](http://rubydoc.info/gems/rom-factory)
17
+ * [User documentation](https://rom-rb.org/learn/factory)
18
+ * [API documentation](https://rubydoc.info/gems/rom-factory)
19
19
 
20
20
  ## Supported Ruby versions
21
21
 
22
22
  This library officially supports the following Ruby versions:
23
23
 
24
- * MRI >= `2.4`
24
+ * MRI >= `2.5`
25
25
  * jruby >= `9.2`
26
26
 
27
27
  ## License
data/Rakefile CHANGED
@@ -5,4 +5,4 @@ require "rspec/core/rake_task"
5
5
 
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
- task :default => :spec
8
+ task default: :spec
data/benchmarks/basic.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rom-factory'
4
- require 'rom-core'
5
- require 'active_record'
6
- require 'factory_bot'
7
- require 'fabrication'
8
- require 'benchmark/ips'
3
+ require "rom-factory"
4
+ require "rom-core"
5
+ require "active_record"
6
+ require "factory_bot"
7
+ require "fabrication"
8
+ require "benchmark/ips"
9
9
 
10
- DATABASE_URL = 'postgres://localhost/rom_factory_bench'.freeze
10
+ DATABASE_URL = "postgres://localhost/rom_factory_bench"
11
11
 
12
12
  rom = ROM.container(:sql, DATABASE_URL) do |conf|
13
13
  conf.default.connection.create_table?(:users) do
@@ -50,15 +50,15 @@ Fabricator(:user) do
50
50
  end
51
51
 
52
52
  Benchmark.ips do |x|
53
- x.report('rom-factory persisted struct') do
53
+ x.report("rom-factory persisted struct") do
54
54
  factory[:user]
55
55
  end
56
56
 
57
- x.report('factory_bot') do
57
+ x.report("factory_bot") do
58
58
  FactoryBot.create(:user)
59
59
  end
60
60
 
61
- x.report('fabrication') do
61
+ x.report("fabrication") do
62
62
  Fabricate(:user)
63
63
  end
64
64
 
data/changelog.yml CHANGED
@@ -1,4 +1,27 @@
1
1
  ---
2
+ - version: 0.11.0
3
+ date: 2022-11-11
4
+ added:
5
+ - Support for one-to-one associations (@ianks)
6
+ - "[internal] cache for Faker constants (@flash-gordon)"
7
+ changed:
8
+ - |
9
+ [BREAKING] attributes are always passed as keywords (@alassek)
10
+ This may affect your code in places where attributes are passed as hashes.
11
+ Places like
12
+ ```ruby
13
+ user_attributes = { name: 'Jane' }
14
+ Factory[:user, user_attributes]
15
+ ```
16
+ must be updated to
17
+ ```ruby
18
+ user_attributes = { name: 'Jane' }
19
+ Factory[:user, **user_attributes]
20
+ ```
21
+ - "Upgraded to the latest versions of dry-rb dependencies, compatible with rom 5.3 (@flash-gordon)"
22
+ - Support for Faker 1.x was dropped (@alassek)
23
+ fixed:
24
+ - Support for plural Faker generators (@wuarmin)
2
25
  - version: 0.10.2
3
26
  date: "2020-04-05"
4
27
  fixed:
@@ -5,4 +5,165 @@ chapter: Factory
5
5
 
6
6
  # rom-factory
7
7
 
8
- TODO: write docs :)
8
+ `rom-factory` provides a simple API for creating and persisting `ROM::Struct`'s. If you already know FactoryBot you'll definitely understand the concept.
9
+
10
+ ## Installation
11
+
12
+ First of all you need to define a ROM Container for Factory. For example if you are using `rspec`, you can add these lines to `spec_helper.rb`. Also you need to require here all files with Factories.
13
+
14
+ ```ruby
15
+ Factory = ROM::Factory.configure do |config|
16
+ config.rom = my_rom_container
17
+ end
18
+
19
+ Dir[File.dirname(__FILE__) + '/support/factories/*.rb'].each { |file| require file }
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### Define factory
25
+
26
+ ```ruby
27
+ # 'spec/support/factories/users.rb'
28
+
29
+ Factory.define(:user) do |f|
30
+ f.name 'John'
31
+ f.age 42
32
+ end
33
+ ```
34
+ #### Specify relations
35
+
36
+ You can specify ROM relation if you want. It'll be pluralized factory name by default.
37
+
38
+ ```ruby
39
+ Factory.define(:user, relation: :people) do |f|
40
+ f.name 'John'
41
+ f.age 42
42
+ end
43
+ ```
44
+
45
+ #### Specify namespace for your structs
46
+
47
+ Struct `User` will be find in MyApp::Entities namespace
48
+
49
+ ```ruby
50
+ Factory.define(:user, struct_namespace: MyApp::Entities) do |f|
51
+ # ...
52
+ end
53
+ ```
54
+
55
+ #### Sequences
56
+
57
+ You can use sequences for uniq fields
58
+
59
+ ```ruby
60
+ Factory.define(:user) do |f|
61
+ f.name 'John'
62
+ f.sequence(:email) { |n| "john#{n}@example.com" }
63
+ end
64
+ ```
65
+
66
+ #### Timestamps
67
+
68
+ ```ruby
69
+ Factory.define(:user) do |f|
70
+ f.name 'John'
71
+ f.timestamps
72
+ # same as
73
+ # f.created_at { Time.now }
74
+ # f.updated_at { Time.now }
75
+ end
76
+ ```
77
+
78
+ #### Associations
79
+
80
+ * belongs_to
81
+
82
+ ```ruby
83
+ Factory.define(:group) do |f|
84
+ f.name 'Admins'
85
+ end
86
+
87
+ Factory.define(:user) do |f|
88
+ f.name 'John'
89
+ f.association(:user)
90
+ end
91
+ ```
92
+
93
+ * has_many
94
+
95
+ ```ruby
96
+ Factory.define(:group) do |f|
97
+ f.name 'Admins'
98
+ f.association(:user, count: 2)
99
+ end
100
+
101
+ Factory.define(:user) do |f|
102
+ f.name 'John'
103
+ end
104
+ ```
105
+
106
+ #### Extend already existing factory
107
+
108
+ ```ruby
109
+ Factory.define(:user) do |f|
110
+ f.name 'John'
111
+ f.admin false
112
+ end
113
+
114
+ Factory.define(admin: :user) do |f|
115
+ f.admin true
116
+ end
117
+
118
+ # Factory.structs(:admin)
119
+ ```
120
+
121
+ #### Traits
122
+
123
+ ```ruby
124
+ Factory.define(:user) do |f|
125
+ f.name 'John'
126
+ f.admin false
127
+
128
+ f.trait :with_age do |t|
129
+ t.age 42
130
+ end
131
+ end
132
+
133
+ # Factory.structs(:user, :with_age)
134
+ ```
135
+
136
+ #### Build-in [Faker](https://github.com/faker-ruby/faker) objects
137
+
138
+ ```ruby
139
+ Factory.define(:user) do |f|
140
+ f.email { fake(:internet, :email) }
141
+ end
142
+ ```
143
+
144
+ #### Dependent attributes
145
+
146
+ Attributes can be based on the values of other attributes:
147
+
148
+ ```ruby
149
+ Factory.define(:user) do |f|
150
+ f.full_name { fake(:name) }
151
+ # Dependent attributes are inferred from the block parameter names:
152
+ f.login { |full_name| full_name.downcase.gsub(/\s+/, '_') }
153
+ # Works with sequences too:
154
+ f.sequence(:email) { |n, login| "#{login}-#{n}@example.com" }
155
+ end
156
+ ```
157
+
158
+ ### Build and persist objects
159
+
160
+ ```ruby
161
+ # Create in-memory object
162
+ Factory.structs[:user]
163
+
164
+ # Persist struct in database
165
+ Factory[:user]
166
+
167
+ # Override attributes
168
+ Factory[:user, age: 24]
169
+ ```
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'tsort'
3
+ require "tsort"
4
4
 
5
5
  module ROM
6
6
  module Factory
@@ -47,7 +47,7 @@ module ROM
47
47
 
48
48
  # @api private
49
49
  def associations
50
- self.class.new(elements.select { |e| e.kind_of?(Attributes::Association::Core) })
50
+ self.class.new(elements.select { |e| e.is_a?(Attributes::Association::Core) })
51
51
  end
52
52
 
53
53
  private
@@ -5,10 +5,9 @@ module ROM::Factory
5
5
  # @api private
6
6
  module Association
7
7
  class << self
8
- def new(assoc, builder, *args)
9
- const_get(assoc.definition.type).new(assoc, builder, *args)
8
+ def new(assoc, builder, *traits, **options)
9
+ const_get(assoc.definition.type).new(assoc, builder, *traits, **options)
10
10
  end
11
- ruby2_keywords(:new) if respond_to?(:ruby2_keywords, true)
12
11
  end
13
12
 
14
13
  # @api private
@@ -23,6 +22,11 @@ module ROM::Factory
23
22
  @options = options
24
23
  end
25
24
 
25
+ # @api private
26
+ def through?
27
+ false
28
+ end
29
+
26
30
  # @api private
27
31
  def builder
28
32
  @__builder__ ||= @builder_proc.call
@@ -56,7 +60,7 @@ module ROM::Factory
56
60
  else
57
61
  builder.struct(*traits)
58
62
  end
59
- tuple = { name => struct }
63
+ tuple = {name => struct}
60
64
  assoc.associate(tuple, struct)
61
65
  end
62
66
  end
@@ -78,13 +82,13 @@ module ROM::Factory
78
82
  association_hash = assoc.associate(attrs, parent)
79
83
 
80
84
  if persist
81
- builder.persistable.create(*traits, association_hash)
85
+ builder.persistable.create(*traits, **association_hash)
82
86
  else
83
- builder.struct(*traits, attrs.merge(association_hash))
87
+ builder.struct(*traits, **attrs, **association_hash)
84
88
  end
85
89
  end
86
90
 
87
- { name => structs }
91
+ {name => structs}
88
92
  end
89
93
 
90
94
  # @api private
@@ -103,22 +107,22 @@ module ROM::Factory
103
107
  # @api private
104
108
  def call(attrs = EMPTY_HASH, parent, persist: true)
105
109
  # do not associate if count is 0
106
- return { name => nil } if count.zero?
110
+ return {name => nil} if count.zero?
107
111
 
108
112
  return if attrs.key?(name)
109
113
 
110
114
  association_hash = assoc.associate(attrs, parent)
111
115
 
112
116
  struct = if persist
113
- builder.persistable.create(*traits, association_hash)
117
+ builder.persistable.create(*traits, **association_hash)
114
118
  else
115
119
  belongs_to_name = Dry::Core::Inflector.singularize(assoc.source_alias)
116
- belongs_to_associations = { belongs_to_name.to_sym => parent }
120
+ belongs_to_associations = {belongs_to_name.to_sym => parent}
117
121
  final_attrs = attrs.merge(association_hash).merge(belongs_to_associations)
118
- builder.struct(*traits, final_attrs)
122
+ builder.struct(*traits, **final_attrs)
119
123
  end
120
124
 
121
- { name => struct }
125
+ {name => struct}
122
126
  end
123
127
 
124
128
  # @api private
@@ -126,6 +130,42 @@ module ROM::Factory
126
130
  options.fetch(:count, 1)
127
131
  end
128
132
  end
133
+
134
+ class OneToOneThrough < Core
135
+ def call(attrs = EMPTY_HASH, parent, persist: true)
136
+ return if attrs.key?(name)
137
+
138
+ struct = if persist && attrs[tpk]
139
+ attrs
140
+ elsif persist
141
+ builder.persistable.create(*traits, **attrs)
142
+ else
143
+ builder.struct(*traits, **attrs)
144
+ end
145
+
146
+ assoc.persist([parent], struct) if persist
147
+
148
+ {name => struct}
149
+ end
150
+
151
+ def dependency?(rel)
152
+ assoc.source == rel
153
+ end
154
+
155
+ def through?
156
+ true
157
+ end
158
+
159
+ private
160
+
161
+ def count
162
+ options.fetch(:count, 1)
163
+ end
164
+
165
+ def tpk
166
+ assoc.target.primary_key
167
+ end
168
+ end
129
169
  end
130
170
  end
131
171
  end
@@ -16,7 +16,7 @@ module ROM::Factory
16
16
  # @api private
17
17
  def call(attrs, *args)
18
18
  result = attrs[name] || dsl.instance_exec(*args, &block)
19
- { name => result }
19
+ {name => result}
20
20
  end
21
21
 
22
22
  # @api private
@@ -16,7 +16,7 @@ module ROM::Factory
16
16
  def call(attrs = EMPTY_HASH)
17
17
  return if attrs.key?(name)
18
18
 
19
- { name => value }
19
+ {name => value}
20
20
  end
21
21
 
22
22
  # @api private
@@ -1,14 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/constants'
4
-
5
3
  module ROM
6
4
  module Factory
7
5
  include Dry::Core::Constants
8
6
  end
9
7
  end
10
8
 
11
- require 'rom/factory/attributes/value'
12
- require 'rom/factory/attributes/callable'
13
- require 'rom/factory/attributes/sequence'
14
- require 'rom/factory/attributes/association'
9
+ require "rom/factory/attributes/value"
10
+ require "rom/factory/attributes/callable"
11
+ require "rom/factory/attributes/sequence"
12
+ require "rom/factory/attributes/association"
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'delegate'
3
+ require "delegate"
4
4
 
5
5
  module ROM
6
6
  module Factory
@@ -21,10 +21,8 @@ module ROM
21
21
  end
22
22
 
23
23
  # @api private
24
- def create(*args)
25
- traits, attrs = builder.extract_tuple(args)
26
-
27
- tuple = tuple(*traits, attrs)
24
+ def create(*traits, **attrs)
25
+ tuple = tuple(*traits, **attrs)
28
26
  validate_keys(traits, attrs)
29
27
  persisted = persist(tuple)
30
28
 
@@ -1,11 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/core/constants'
4
-
5
- require 'rom/struct'
6
- require 'rom/initializer'
7
- require 'rom/factory/tuple_evaluator'
8
- require 'rom/factory/builder/persistable'
3
+ require "rom/struct"
4
+ require "rom/initializer"
5
+ require "rom/factory/tuple_evaluator"
6
+ require "rom/factory/builder/persistable"
9
7
 
10
8
  module ROM::Factory
11
9
  # @api private
@@ -31,18 +29,15 @@ module ROM::Factory
31
29
  option :struct_namespace, reader: false
32
30
 
33
31
  # @api private
34
- def tuple(*args)
35
- traits, attrs = extract_tuple(args)
36
-
32
+ def tuple(*traits, **attrs)
37
33
  tuple_evaluator.defaults(traits, attrs)
38
34
  end
39
35
 
40
36
  # @api private
41
- def struct(*args)
42
- traits, attrs = extract_tuple(args)
37
+ def struct(*traits, **attrs)
43
38
  validate_keys(traits, attrs, allow_associations: true)
44
39
 
45
- tuple_evaluator.struct(*args)
40
+ tuple_evaluator.struct(*traits, **attrs)
46
41
  end
47
42
  alias_method :create, :struct
48
43
 
@@ -75,18 +70,13 @@ module ROM::Factory
75
70
  tuple_evaluator.relation
76
71
  end
77
72
 
78
- # @api private
79
- def extract_tuple(args)
80
- tuple_evaluator.extract_tuple(args)
81
- end
82
-
83
73
  # @api private
84
74
  def validate_keys(traits, tuple, allow_associations: false)
85
75
  schema_keys = relation.schema.attributes.map(&:name)
86
76
  assoc_keys = tuple_evaluator.assoc_names(traits)
87
77
  unknown_keys = tuple.keys - schema_keys - assoc_keys
88
78
 
89
- unknown_keys = unknown_keys - relation.schema.associations.to_h.keys if allow_associations
79
+ unknown_keys -= relation.schema.associations.to_h.keys if allow_associations
90
80
 
91
81
  raise UnknownFactoryAttributes, unknown_keys unless unknown_keys.empty?
92
82
  end
@@ -10,7 +10,7 @@ module ROM
10
10
 
11
11
  class UnknownFactoryAttributes < StandardError
12
12
  def initialize(attrs)
13
- super("Unknown attributes: #{attrs.join(', ')}")
13
+ super("Unknown attributes: #{attrs.join(", ")}")
14
14
  end
15
15
  end
16
16
  end