rom-factory 0.10.2 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
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