vorpal 1.1.0 → 1.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 +4 -4
- data/README.md +39 -20
- data/lib/vorpal/config/class_config.rb +71 -0
- data/lib/vorpal/configs.rb +9 -66
- data/lib/vorpal/dsl/config_builder.rb +12 -65
- data/lib/vorpal/dsl/configuration.rb +131 -42
- data/lib/vorpal/engine.rb +16 -3
- data/lib/vorpal/version.rb +1 -1
- data/vorpal.gemspec +0 -1
- metadata +3 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf920ae279d48b7b9db84a79c77eeb521602a885c8fe88e8f2121e47a5a46e41
|
4
|
+
data.tar.gz: 9e97596434cf65e598279005a0bb325cadde5d7a6d3f2180ea5a828432f89760
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7c07c613b24c7d342be0361e33bda6db6cff0270222db1b7ca8018cf141fb623b2583e49a0d39db51d53389225e527d6a5fd74f133edc6f7653309e86644be4
|
7
|
+
data.tar.gz: 492d62d23506de0e26ffdc8e525169dcb78d6c1060dc9309c9828e9865f2341e8f01cc84fe95a059d01d89462c62292a67ebcfc4148ce4ace770884a56e5e04c
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Vorpal [](https://travis-ci.com/nulogy/vorpal) [](https://codeclimate.com/github/nulogy/vorpal) [](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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
55
|
+
class Gardener
|
60
56
|
end
|
61
57
|
|
62
58
|
class Tree
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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/
|
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
|
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/
|
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
|
data/lib/vorpal/configs.rb
CHANGED
@@ -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
|
8
|
-
def initialize
|
9
|
-
@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
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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 =
|
85
|
-
class_config.has_ones =
|
86
|
-
class_config.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
|
-
|
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
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
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
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
22
|
+
# map Branch do
|
23
|
+
# attributes :length
|
24
|
+
# belongs_to :tree
|
25
|
+
# end
|
26
|
+
# end
|
36
27
|
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
data/lib/vorpal/engine.rb
CHANGED
@@ -7,9 +7,9 @@ require 'vorpal/exceptions'
|
|
7
7
|
module Vorpal
|
8
8
|
class Engine
|
9
9
|
# @private
|
10
|
-
def initialize(db_driver,
|
10
|
+
def initialize(db_driver, main_config)
|
11
11
|
@db_driver = db_driver
|
12
|
-
@configs =
|
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
|
-
|
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
|
data/lib/vorpal/version.rb
CHANGED
data/vorpal.gemspec
CHANGED
@@ -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.
|
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-
|
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
|