vorpal 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c866f3e83daac73f28e5c9a53875b73d7f8db95cbf437200f0b0c69cce81d317
4
- data.tar.gz: e1e91f848552e1f3710849370978d4a1e322e65b12c65bebcd995b76960ba44e
3
+ metadata.gz: cf920ae279d48b7b9db84a79c77eeb521602a885c8fe88e8f2121e47a5a46e41
4
+ data.tar.gz: 9e97596434cf65e598279005a0bb325cadde5d7a6d3f2180ea5a828432f89760
5
5
  SHA512:
6
- metadata.gz: 5bcd5518f56b4c43f4180d3f63c75c6186c1bd89bee939b8410e656f3df411920238eaf51da4a590367f929507c3bdb1ada98cf972d6ebf6785432417018b9a2
7
- data.tar.gz: 85e53e6946ea0d0f06e14d8913ac3eba45668b3d08bfc0036a789620ea05af587cb5a97991467eadfea1f76d5dd3fccaf71c96aeec2345327d9538e714e0e720
6
+ metadata.gz: d7c07c613b24c7d342be0361e33bda6db6cff0270222db1b7ca8018cf141fb623b2583e49a0d39db51d53389225e527d6a5fd74f133edc6f7653309e86644be4
7
+ data.tar.gz: 492d62d23506de0e26ffdc8e525169dcb78d6c1060dc9309c9828e9865f2341e8f01cc84fe95a059d01d89462c62292a67ebcfc4148ce4ace770884a56e5e04c
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Vorpal [![Build Status](https://travis-ci.com/nulogy/vorpal.svg?branch=master)](https://travis-ci.com/nulogy/vorpal) [![Code Climate](https://codeclimate.com/github/nulogy/vorpal/badges/gpa.svg)](https://codeclimate.com/github/nulogy/vorpal) [![Code Coverage](https://codecov.io/gh/nulogy/vorpal/branch/master/graph/badge.svg)](https://codecov.io/gh/nulogy/vorpal/branch/master)
1
+ # Vorpal [![Build Status](https://travis-ci.com/nulogy/vorpal.svg?branch=main)](https://travis-ci.com/nulogy/vorpal) [![Code Climate](https://codeclimate.com/github/nulogy/vorpal/badges/gpa.svg)](https://codeclimate.com/github/nulogy/vorpal) [![Code Coverage](https://codecov.io/gh/nulogy/vorpal/branch/main/graph/badge.svg)](https://codecov.io/gh/nulogy/vorpal/branch/main)
2
2
 
3
3
  Separate your domain model from your persistence mechanism. Some problems call for a really sharp tool.
4
4
 
@@ -45,27 +45,21 @@ Or install it yourself as:
45
45
  Start with a domain model of POROs and AR::Base objects that form an aggregate:
46
46
 
47
47
  ```ruby
48
- class Tree; end
49
-
50
48
  class Branch
51
- include Virtus.model
52
-
53
- attribute :id, Integer
54
- attribute :length, Decimal
55
- attribute :diameter, Decimal
56
- attribute :tree, Tree
49
+ attr_accessor :id
50
+ attr_accessor :length
51
+ attr_accessor :diameter
52
+ attr_accessor :tree
57
53
  end
58
54
 
59
- class Gardener < ActiveRecord::Base
55
+ class Gardener
60
56
  end
61
57
 
62
58
  class Tree
63
- include Virtus.model
64
-
65
- attribute :id, Integer
66
- attribute :name, String
67
- attribute :gardener, Gardener
68
- attribute :branches, Array[Branch]
59
+ attr_accessor :id
60
+ attr_accessor :name
61
+ attr_accessor :gardener
62
+ attr_accessor :branches
69
63
  end
70
64
  ```
71
65
 
@@ -162,9 +156,35 @@ TreeRepository.destroy(dead_tree)
162
156
  TreeRepository.destroy_by_id(dead_tree_id)
163
157
  ```
164
158
 
159
+ ### Ids
160
+
161
+ Vorpal by default will use auto-incrementing Integers from a DB sequence for ids. However, UUID v4 ids are also
162
+ supported:
163
+
164
+ ```ruby
165
+ Vorpal.define do
166
+ # UUID v4 id!
167
+ map Tree, primary_key_type: :uuid do
168
+ # ..
169
+ end
170
+
171
+ # Also a UUID v4 id, the Rails Way!
172
+ map Trunk, id: :uuid do
173
+ # ..
174
+ end
175
+
176
+ # If you feel the need to specify an auto-incrementing integer id.
177
+ map Branch, primary_key_type: :serial do
178
+ # ..
179
+ end
180
+ end
181
+ ```
182
+
183
+ CAVEAT: Vorpal currently does NOT SUPPORT anyone but Vorpal setting the id of an entity!
184
+
165
185
  ## API Documentation
166
186
 
167
- http://rubydoc.info/github/nulogy/vorpal/master/frames
187
+ http://rubydoc.info/github/nulogy/vorpal/main/frames
168
188
 
169
189
  ## Caveats
170
190
  It also does not do some things that you might expect from other ORMs:
@@ -181,9 +201,8 @@ It also does not do some things that you might expect from other ORMs:
181
201
  1. Only supports PostgreSQL.
182
202
 
183
203
  ## Future Enhancements
184
- * Support for UUID primary keys.
204
+ * Support for clients to set UUID-based ids.
185
205
  * Nicer DSL for specifying attributes that have different names in the domain model than in the DB.
186
- * Show how to implement POROs without using Virtus (it is unsupported and can be crazy slow)
187
206
  * Aggregate updated_at.
188
207
  * Better support for value objects.
189
208
 
@@ -201,7 +220,7 @@ It also does not do some things that you might expect from other ORMs:
201
220
 
202
221
  **A.** Create a method on a [Repository](http://martinfowler.com/eaaCatalog/repository.html)! They have full access to the DB/ORM so you can use [Arel](https://github.com/rails/arel) and go [crazy](http://asciicasts.com/episodes/239-activerecord-relation-walkthrough) or use direct SQL if you want.
203
222
 
204
- For example, use the [#query](https://rubydoc.info/github/nulogy/vorpal/master/Vorpal/AggregateMapper#query-instance_method) method on the [AggregateMapper](https://rubydoc.info/github/nulogy/vorpal/master/Vorpal/AggregateMapper) to access the underyling [ActiveRecordRelation](https://api.rubyonrails.org/classes/ActiveRecord/Relation.html):
223
+ For example, use the [#query](https://rubydoc.info/github/nulogy/vorpal/main/Vorpal/AggregateMapper#query-instance_method) method on the [AggregateMapper](https://rubydoc.info/github/nulogy/vorpal/main/Vorpal/AggregateMapper) to access the underyling [ActiveRecordRelation](https://api.rubyonrails.org/classes/ActiveRecord/Relation.html):
205
224
 
206
225
  ```ruby
207
226
  def find_special_ones
@@ -0,0 +1,71 @@
1
+ require 'equalizer'
2
+
3
+ module Vorpal
4
+ module Config
5
+ # @private
6
+ class ClassConfig
7
+ include Equalizer.new(:domain_class, :db_class)
8
+ attr_reader :serializer, :deserializer, :domain_class, :db_class, :primary_key_type, :local_association_configs
9
+ attr_accessor :has_manys, :belongs_tos, :has_ones
10
+
11
+ ALLOWED_PRIMARY_KEY_TYPE_OPTIONS = [:serial, :uuid]
12
+
13
+ def initialize(attrs)
14
+ @has_manys = []
15
+ @belongs_tos = []
16
+ @has_ones = []
17
+ @local_association_configs = []
18
+
19
+ @serializer = attrs[:serializer]
20
+ @deserializer = attrs[:deserializer]
21
+ @domain_class = attrs[:domain_class]
22
+ @db_class = attrs[:db_class]
23
+ @primary_key_type = attrs[:primary_key_type]
24
+ raise "Invalid primary_key_type: '#{@primary_key_type}'" unless ALLOWED_PRIMARY_KEY_TYPE_OPTIONS.include?(@primary_key_type)
25
+ end
26
+
27
+ def build_db_object(attributes)
28
+ db_class.new(attributes)
29
+ end
30
+
31
+ def set_db_object_attributes(db_object, attributes)
32
+ db_object.attributes = attributes
33
+ end
34
+
35
+ def get_db_object_attributes(db_object)
36
+ symbolize_keys(db_object.attributes)
37
+ end
38
+
39
+ def serialization_required?
40
+ domain_class.superclass.name != 'ActiveRecord::Base'
41
+ end
42
+
43
+ def serialize(object)
44
+ serializer.serialize(object)
45
+ end
46
+
47
+ def deserialize(db_object)
48
+ attributes = get_db_object_attributes(db_object)
49
+ serialization_required? ? deserializer.deserialize(domain_class.new, attributes) : db_object
50
+ end
51
+
52
+ def set_attribute(db_object, attribute, value)
53
+ db_object.send("#{attribute}=", value)
54
+ end
55
+
56
+ def get_attribute(db_object, attribute)
57
+ db_object.send(attribute)
58
+ end
59
+
60
+ private
61
+
62
+ def symbolize_keys(hash)
63
+ result = {}
64
+ hash.each_key do |key|
65
+ result[key.to_sym] = hash[key]
66
+ end
67
+ result
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,13 +1,13 @@
1
1
  require 'vorpal/util/hash_initialization'
2
2
  require 'vorpal/exceptions'
3
+ require 'vorpal/config/class_config'
3
4
  require 'equalizer'
4
5
 
5
6
  module Vorpal
6
7
  # @private
7
- class MasterConfig
8
- def initialize(class_configs)
9
- @class_configs = class_configs
10
- initialize_association_configs
8
+ class MainConfig
9
+ def initialize
10
+ @class_configs = []
11
11
  end
12
12
 
13
13
  def config_for(clazz)
@@ -20,7 +20,9 @@ module Vorpal
20
20
  @class_configs.detect { |conf| conf.db_class == db_object.class }
21
21
  end
22
22
 
23
- private
23
+ def add_class_config(class_config)
24
+ @class_configs << class_config
25
+ end
24
26
 
25
27
  def initialize_association_configs
26
28
  association_configs = {}
@@ -50,6 +52,8 @@ module Vorpal
50
52
  end
51
53
  end
52
54
 
55
+ private
56
+
53
57
  def build_association_config(association_configs, local_config, fk, fk_type)
54
58
  association_config = AssociationConfig.new(local_config, fk, fk_type)
55
59
  if association_configs[association_config]
@@ -126,67 +130,6 @@ module Vorpal
126
130
  end
127
131
  end
128
132
 
129
- # @private
130
- class ClassConfig
131
- include Equalizer.new(:domain_class, :db_class)
132
- attr_reader :serializer, :deserializer, :domain_class, :db_class, :local_association_configs
133
- attr_accessor :has_manys, :belongs_tos, :has_ones
134
-
135
- def initialize(attrs)
136
- @has_manys = []
137
- @belongs_tos = []
138
- @has_ones = []
139
- @local_association_configs = []
140
-
141
- attrs.each do |k,v|
142
- instance_variable_set("@#{k}", v)
143
- end
144
- end
145
-
146
- def build_db_object(attributes)
147
- db_class.new(attributes)
148
- end
149
-
150
- def set_db_object_attributes(db_object, attributes)
151
- db_object.attributes = attributes
152
- end
153
-
154
- def get_db_object_attributes(db_object)
155
- symbolize_keys(db_object.attributes)
156
- end
157
-
158
- def serialization_required?
159
- domain_class.superclass.name != 'ActiveRecord::Base'
160
- end
161
-
162
- def serialize(object)
163
- serializer.serialize(object)
164
- end
165
-
166
- def deserialize(db_object)
167
- attributes = get_db_object_attributes(db_object)
168
- serialization_required? ? deserializer.deserialize(domain_class.new, attributes) : db_object
169
- end
170
-
171
- def set_attribute(db_object, attribute, value)
172
- db_object.send("#{attribute}=", value)
173
- end
174
-
175
- def get_attribute(db_object, attribute)
176
- db_object.send(attribute)
177
- end
178
-
179
- private
180
-
181
- def symbolize_keys(hash)
182
- result = {}
183
- hash.each_key do |key|
184
- result[key.to_sym] = hash[key]
185
- end
186
- result
187
- end
188
- end
189
-
190
133
  # @private
191
134
  class ForeignKeyInfo
192
135
  include Equalizer.new(:fk_column, :fk_type_column, :fk_type)
@@ -16,74 +16,32 @@ module Vorpal
16
16
  @defaults_generator = DefaultsGenerator.new(clazz, db_driver)
17
17
  end
18
18
 
19
- # Maps the given attributes to and from the domain object and the DB. Not needed
20
- # if a serializer and deserializer were provided.
19
+ # @private
21
20
  def attributes(*attributes)
22
21
  @attributes.concat(attributes)
23
22
  end
24
23
 
25
- # Defines a one-to-many association to another type where the foreign key is stored on the child.
26
- #
27
- # In Object-Oriented programming, associations are *directed*. This means that they can only be
28
- # traversed in one direction: from the type that defines the association (the one with the
29
- # getter) to the type that is associated. They end that defines the association is called the
30
- # 'Parent' and the end that is associated is called the 'Child'.
31
- #
32
- # @param name [String] Name of the association getter.
33
- # @param options [Hash]
34
- # @option options [Boolean] :owned (True) True if the child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
35
- # @option options [String] :fk (Parent class name converted to snakecase and appended with a '_id') The name of the DB column on the child that contains the foreign key reference to the parent.
36
- # @option options [String] :fk_type The name of the DB column on the child that contains the parent class name. Only needed when there is an association from the child side that is polymorphic.
37
- # @option options [Class] :child_class (name converted to a Class) The child class.
24
+ # @private
38
25
  def has_many(name, options={})
39
- @has_manys << {name: name}.merge(options)
26
+ @has_manys << build_has_many({name: name}.merge(options))
40
27
  end
41
28
 
42
- # Defines a one-to-one association to another type where the foreign key
43
- # is stored on the child.
44
- #
45
- # In Object-Oriented programming, associations are *directed*. This means that they can only be
46
- # traversed in one direction: from the type that defines the association (the one with the
47
- # getter) to the type that is associated. They end that defines the association is called the
48
- # 'Parent' and the end that is associated is called the 'Child'.
49
- #
50
- # @param name [String] Name of the association getter.
51
- # @param options [Hash]
52
- # @option options [Boolean] :owned (True) True if the child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
53
- # @option options [String] :fk (Parent class name converted to snakecase and appended with a '_id') The name of the DB column on the child that contains the foreign key reference to the parent.
54
- # @option options [String] :fk_type The name of the DB column on the child that contains the parent class name. Only needed when there is an association from the child side that is polymorphic.
55
- # @option options [Class] :child_class (name converted to a Class) The child class.
29
+ # @private
56
30
  def has_one(name, options={})
57
- @has_ones << {name: name}.merge(options)
31
+ @has_ones << build_has_one({name: name}.merge(options))
58
32
  end
59
33
 
60
- # Defines a one-to-one association with another type where the foreign key
61
- # is stored on the parent.
62
- #
63
- # This association can be polymorphic. I.E. children can be of different types.
64
- #
65
- # In Object-Oriented programming, associations are *directed*. This means that they can only be
66
- # traversed in one direction: from the type that defines the association (the one with the
67
- # getter) to the type that is associated. They end that defines the association is called the
68
- # 'Parent' and the end that is associated is called the 'Child'.
69
- #
70
- # @param name [String] Name of the association getter.
71
- # @param options [Hash]
72
- # @option options [Boolean] :owned (True) True if the child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
73
- # @option options [String] :fk (Child class name converted to snakecase and appended with a '_id') The name of the DB column on the parent that contains the foreign key reference to the child.
74
- # @option options [String] :fk_type The name of the DB column on the parent that contains the child class name. Only needed when the association is polymorphic.
75
- # @option options [Class] :child_class (name converted to a Class) The child class.
76
- # @option options [[Class]] :child_classes The list of possible classes that can be children. This is for polymorphic associations. Takes precedence over `:child_class`.
34
+ # @private
77
35
  def belongs_to(name, options={})
78
- @belongs_tos << {name: name}.merge(options)
36
+ @belongs_tos << build_belongs_to({name: name}.merge(options))
79
37
  end
80
38
 
81
39
  # @private
82
40
  def build
83
41
  class_config = build_class_config
84
- class_config.has_manys = build_has_manys
85
- class_config.has_ones = build_has_ones
86
- class_config.belongs_tos = build_belongs_tos
42
+ class_config.has_manys = @has_manys
43
+ class_config.has_ones = @has_ones
44
+ class_config.belongs_tos = @belongs_tos
87
45
 
88
46
  class_config
89
47
  end
@@ -96,18 +54,15 @@ module Vorpal
96
54
  private
97
55
 
98
56
  def build_class_config
99
- Vorpal::ClassConfig.new(
57
+ Vorpal::Config::ClassConfig.new(
100
58
  domain_class: @domain_class,
101
59
  db_class: @class_options[:to] || @defaults_generator.build_db_class(@class_options[:table_name]),
102
60
  serializer: @class_options[:serializer] || @defaults_generator.serializer(attributes_with_id),
103
61
  deserializer: @class_options[:deserializer] || @defaults_generator.deserializer(attributes_with_id),
62
+ primary_key_type: @class_options[:primary_key_type] || @class_options[:id] || :serial,
104
63
  )
105
64
  end
106
65
 
107
- def build_has_manys
108
- @has_manys.map { |options| build_has_many(options) }
109
- end
110
-
111
66
  def build_has_many(options)
112
67
  options[:child_class] ||= @defaults_generator.child_class(options[:name])
113
68
  options[:fk] ||= @defaults_generator.foreign_key(@domain_class.name)
@@ -115,10 +70,6 @@ module Vorpal
115
70
  Vorpal::HasManyConfig.new(options)
116
71
  end
117
72
 
118
- def build_has_ones
119
- @has_ones.map { |options| build_has_one(options) }
120
- end
121
-
122
73
  def build_has_one(options)
123
74
  options[:child_class] ||= @defaults_generator.child_class(options[:name])
124
75
  options[:fk] ||= @defaults_generator.foreign_key(@domain_class.name)
@@ -126,10 +77,6 @@ module Vorpal
126
77
  Vorpal::HasOneConfig.new(options)
127
78
  end
128
79
 
129
- def build_belongs_tos
130
- @belongs_tos.map { |options| build_belongs_to(options) }
131
- end
132
-
133
80
  def build_belongs_to(options)
134
81
  child_class = options[:child_classes] || options[:child_class] || @defaults_generator.child_class(options[:name])
135
82
  options[:child_classes] = Array(child_class)
@@ -4,54 +4,143 @@ require 'vorpal/driver/postgresql'
4
4
 
5
5
  module Vorpal
6
6
  module Dsl
7
- module Configuration
8
-
9
- # Configures and creates a {Engine} instance.
10
- #
11
- # @param options [Hash] Global configuration options for the engine instance.
12
- # @option options [Object] :db_driver (Object that will be used to interact with the DB.)
13
- # Must be duck-type compatible with {Postgresql}.
7
+ # Implements the Vorpal DSL.
14
8
  #
15
- # @return [Engine] Instance of the mapping engine.
16
- def define(options={}, &block)
17
- master_config = build_config(&block)
18
- db_driver = options.fetch(:db_driver, Driver::Postgresql.new)
19
- Engine.new(db_driver, master_config)
20
- end
21
-
22
- # Maps a domain class to a relational table.
9
+ # ```ruby
10
+ # engine = Vorpal.define do
11
+ # map Tree do
12
+ # attributes :name
13
+ # belongs_to :trunk
14
+ # has_many :branches
15
+ # end
23
16
  #
24
- # @param domain_class [Class] Type of the domain model to be mapped
25
- # @param options [Hash] Configure how to map the domain model
26
- # @option options [String] :to
27
- # Class of the ActiveRecord object that will map this domain class to the DB.
28
- # Optional, if one is not specified, it will be generated.
29
- # @option options [Object] :serializer (map the {ConfigBuilder#attributes} directly)
30
- # Object that will convert the domain objects into a hash.
17
+ # map Trunk do
18
+ # attributes :length
19
+ # has_one :tree
20
+ # end
31
21
  #
32
- # Must have a `(Hash) serialize(Object)` method.
33
- # @option options [Object] :deserializer (map the {ConfigBuilder#attributes} directly)
34
- # Object that will set a hash of attribute_names->values onto a new domain
35
- # object.
22
+ # map Branch do
23
+ # attributes :length
24
+ # belongs_to :tree
25
+ # end
26
+ # end
36
27
  #
37
- # Must have a `(Object) deserialize(Object, Hash)` method.
38
- def map(domain_class, options={}, &block)
39
- @class_configs << build_class_config(domain_class, options, &block)
40
- end
28
+ # mapper = engine.mapper_for(Tree)
29
+ # ```
30
+ module Configuration
31
+ # Configures and creates a {Engine} instance.
32
+ #
33
+ # @param options [Hash] Global configuration options for the engine instance.
34
+ # @option options [Object] :db_driver (Object that will be used to interact with the DB.)
35
+ # Must be duck-type compatible with {Postgresql}.
36
+ #
37
+ # @return [Engine] Instance of the mapping engine.
38
+ def define(options={}, &block)
39
+ @main_config = MainConfig.new
40
+ instance_exec(&block)
41
+ @main_config.initialize_association_configs
42
+ db_driver = options.fetch(:db_driver, Driver::Postgresql.new)
43
+ engine = Engine.new(db_driver, @main_config)
44
+ @main_config = nil # make sure this MainConfig is never re-used by accident.
45
+ engine
46
+ end
41
47
 
42
- # @private
43
- def build_class_config(domain_class, options={}, &block)
44
- builder = ConfigBuilder.new(domain_class, options, Driver::Postgresql.new)
45
- builder.instance_exec(&block) if block_given?
46
- builder.build
47
- end
48
+ # Maps a domain class to a relational table.
49
+ #
50
+ # @param domain_class [Class] Type of the domain model to be mapped
51
+ # @param options [Hash] Configure how to map the domain model
52
+ # @option options [String] :to
53
+ # Class of the ActiveRecord object that will map this domain class to the DB.
54
+ # Optional, if one is not specified, it will be generated.
55
+ # @option options [Object] :serializer (map the {ConfigBuilder#attributes} directly)
56
+ # Object that will convert the domain objects into a hash.
57
+ #
58
+ # Must have a `(Hash) serialize(Object)` method.
59
+ # @option options [Object] :deserializer (map the {ConfigBuilder#attributes} directly)
60
+ # Object that will set a hash of attribute_names->values onto a new domain
61
+ # object.
62
+ #
63
+ # Must have a `(Object) deserialize(Object, Hash)` method.
64
+ # @option options [Symbol] :primary_key_type [:serial, :uuid] (:serial)
65
+ # The type of primary key for the class. :serial for auto-incrementing integer, :uuid for a UUID
66
+ # @option options [Symbol] :id
67
+ # Same as :primary_key_type. Exists for compatibility with the Rails API.
68
+ def map(domain_class, options={}, &block)
69
+ class_config = build_class_config(domain_class, options, &block)
70
+ @main_config.add_class_config(class_config)
71
+ class_config
72
+ end
48
73
 
49
- # @private
50
- def build_config(&block)
51
- @class_configs = []
52
- self.instance_exec(&block)
53
- MasterConfig.new(@class_configs)
74
+ # @private
75
+ def build_class_config(domain_class, options, &block)
76
+ @builder = ConfigBuilder.new(domain_class, options, Driver::Postgresql.new)
77
+ instance_exec(&block) if block_given?
78
+ class_config = @builder.build
79
+ @builder = nil # make sure this ConfigBuilder is never re-used by accident.
80
+ class_config
81
+ end
82
+
83
+ # Maps the given attributes to and from the domain object and the DB. Not needed
84
+ # if a serializer and deserializer were provided.
85
+ def attributes(*attributes)
86
+ @builder.attributes(*attributes)
87
+ end
88
+
89
+ # Defines a one-to-many association to another type where the foreign key is stored on the child.
90
+ #
91
+ # In Object-Oriented programming, associations are *directed*. This means that they can only be
92
+ # traversed in one direction: from the type that defines the association (the one with the
93
+ # getter) to the type that is associated. They end that defines the association is called the
94
+ # 'Parent' and the end that is associated is called the 'Child'.
95
+ #
96
+ # @param name [String] Name of the association getter.
97
+ # @param options [Hash]
98
+ # @option options [Boolean] :owned (True) True if the child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
99
+ # @option options [String] :fk (Parent class name converted to snakecase and appended with a '_id') The name of the DB column on the child that contains the foreign key reference to the parent.
100
+ # @option options [String] :fk_type The name of the DB column on the child that contains the parent class name. Only needed when there is an association from the child side that is polymorphic.
101
+ # @option options [Class] :child_class (name converted to a Class) The child class.
102
+ def has_many(name, options={})
103
+ @builder.has_many(name, options)
104
+ end
105
+
106
+ # Defines a one-to-one association to another type where the foreign key
107
+ # is stored on the child.
108
+ #
109
+ # In Object-Oriented programming, associations are *directed*. This means that they can only be
110
+ # traversed in one direction: from the type that defines the association (the one with the
111
+ # getter) to the type that is associated. They end that defines the association is called the
112
+ # 'Parent' and the end that is associated is called the 'Child'.
113
+ #
114
+ # @param name [String] Name of the association getter.
115
+ # @param options [Hash]
116
+ # @option options [Boolean] :owned (True) True if the child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
117
+ # @option options [String] :fk (Parent class name converted to snakecase and appended with a '_id') The name of the DB column on the child that contains the foreign key reference to the parent.
118
+ # @option options [String] :fk_type The name of the DB column on the child that contains the parent class name. Only needed when there is an association from the child side that is polymorphic.
119
+ # @option options [Class] :child_class (name converted to a Class) The child class.
120
+ def has_one(name, options={})
121
+ @builder.has_one(name, options)
122
+ end
123
+
124
+ # Defines a one-to-one association with another type where the foreign key
125
+ # is stored on the parent.
126
+ #
127
+ # This association can be polymorphic. I.E. children can be of different types.
128
+ #
129
+ # In Object-Oriented programming, associations are *directed*. This means that they can only be
130
+ # traversed in one direction: from the type that defines the association (the one with the
131
+ # getter) to the type that is associated. They end that defines the association is called the
132
+ # 'Parent' and the end that is associated is called the 'Child'.
133
+ #
134
+ # @param name [String] Name of the association getter.
135
+ # @param options [Hash]
136
+ # @option options [Boolean] :owned (True) True if the child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
137
+ # @option options [String] :fk (Child class name converted to snakecase and appended with a '_id') The name of the DB column on the parent that contains the foreign key reference to the child.
138
+ # @option options [String] :fk_type The name of the DB column on the parent that contains the child class name. Only needed when the association is polymorphic.
139
+ # @option options [Class] :child_class (name converted to a Class) The child class.
140
+ # @option options [[Class]] :child_classes The list of possible classes that can be children. This is for polymorphic associations. Takes precedence over `:child_class`.
141
+ def belongs_to(name, options={})
142
+ @builder.belongs_to(name, options)
143
+ end
54
144
  end
55
145
  end
56
- end
57
146
  end
@@ -7,9 +7,9 @@ require 'vorpal/exceptions'
7
7
  module Vorpal
8
8
  class Engine
9
9
  # @private
10
- def initialize(db_driver, master_config)
10
+ def initialize(db_driver, main_config)
11
11
  @db_driver = db_driver
12
- @configs = master_config
12
+ @configs = main_config
13
13
  end
14
14
 
15
15
  # Creates a mapper for saving/updating/loading/destroying an aggregate to/from
@@ -34,6 +34,9 @@ module Vorpal
34
34
  serialize(all_owned_objects, mapping, loaded_db_objects)
35
35
  new_objects = get_unsaved_objects(mapping.keys)
36
36
  begin
37
+ # Primary keys are set eagerly (instead of waiting for them to be set by ActiveRecord upon create)
38
+ # because we want to support non-null FK constraints without needing to figure the correct
39
+ # order to save entities in.
37
40
  set_primary_keys(all_owned_objects, mapping)
38
41
  set_foreign_keys(all_owned_objects, mapping)
39
42
  remove_orphans(mapping, loaded_db_objects)
@@ -89,10 +92,16 @@ module Vorpal
89
92
  @configs.config_for(domain_class).db_class
90
93
  end
91
94
 
95
+ # Try to use {AggregateMapper#query} instead.
92
96
  def query(domain_class)
93
97
  @db_driver.query(@configs.config_for(domain_class).db_class, mapper_for(domain_class))
94
98
  end
95
99
 
100
+ # @private
101
+ def class_config(domain_class)
102
+ @configs.config_for(domain_class)
103
+ end
104
+
96
105
  private
97
106
 
98
107
  def wrap(collection_or_not)
@@ -165,7 +174,11 @@ module Vorpal
165
174
  def set_primary_keys(owned_objects, mapping)
166
175
  owned_objects.each do |config, objects|
167
176
  in_need_of_primary_keys = objects.find_all { |obj| obj.id.nil? }
168
- primary_keys = @db_driver.get_primary_keys(config.db_class, in_need_of_primary_keys.length)
177
+ if config.primary_key_type == :uuid
178
+ primary_keys = Array.new(in_need_of_primary_keys.length) { SecureRandom.uuid }
179
+ elsif config.primary_key_type == :serial
180
+ primary_keys = @db_driver.get_primary_keys(config.db_class, in_need_of_primary_keys.length)
181
+ end
169
182
  in_need_of_primary_keys.zip(primary_keys).each do |object, primary_key|
170
183
  mapping[object].id = primary_key
171
184
  object.id = primary_key
@@ -1,3 +1,3 @@
1
1
  module Vorpal
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -24,7 +24,6 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.add_development_dependency "rake", "~> 13"
26
26
  spec.add_development_dependency "rspec", "~> 3.0"
27
- spec.add_development_dependency "virtus", "~> 1.0"
28
27
  spec.add_development_dependency "appraisal", "~> 2.2"
29
28
 
30
29
  spec.required_ruby_version = ">= 2.5.7"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vorpal
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Kirby
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-03 00:00:00.000000000 Z
11
+ date: 2020-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: simple_serializer
@@ -80,20 +80,6 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '3.0'
83
- - !ruby/object:Gem::Dependency
84
- name: virtus
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '1.0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '1.0'
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: appraisal
99
85
  requirement: !ruby/object:Gem::Requirement
@@ -179,6 +165,7 @@ files:
179
165
  - lib/vorpal/aggregate_mapper.rb
180
166
  - lib/vorpal/aggregate_traversal.rb
181
167
  - lib/vorpal/aggregate_utils.rb
168
+ - lib/vorpal/config/class_config.rb
182
169
  - lib/vorpal/configs.rb
183
170
  - lib/vorpal/db_loader.rb
184
171
  - lib/vorpal/driver/postgresql.rb