vorpal 1.2.0 → 1.4.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 +28 -7
- data/lib/vorpal/aggregate_mapper.rb +2 -2
- data/lib/vorpal/aggregate_traversal.rb +9 -8
- data/lib/vorpal/config/association_config.rb +84 -0
- data/lib/vorpal/config/belongs_to_config.rb +35 -0
- data/lib/vorpal/config/configs.rb +54 -0
- data/lib/vorpal/config/foreign_key_info.rb +23 -0
- data/lib/vorpal/config/has_many_config.rb +38 -0
- data/lib/vorpal/config/has_one_config.rb +35 -0
- data/lib/vorpal/config/main_config.rb +68 -0
- data/lib/vorpal/db_loader.rb +25 -22
- data/lib/vorpal/driver/postgresql.rb +4 -3
- data/lib/vorpal/dsl/config_builder.rb +14 -8
- data/lib/vorpal/dsl/configuration.rb +32 -24
- data/lib/vorpal/dsl/defaults_generator.rb +1 -1
- data/lib/vorpal/engine.rb +11 -10
- data/lib/vorpal/exceptions.rb +4 -0
- data/lib/vorpal/loaded_objects.rb +57 -14
- data/lib/vorpal/util/array_hash.rb +22 -8
- data/lib/vorpal/util/hash_initialization.rb +1 -1
- data/lib/vorpal/version.rb +1 -1
- metadata +9 -3
- data/lib/vorpal/configs.rb +0 -239
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f49a200d7a039a41a37ec89fb7d34264132d8946e9ca1d6d48d4b1eff63bf84
|
4
|
+
data.tar.gz: bd124780b9af0181fcf410cfefad8ba36bf8f65b57580341e23b9b46f4e6325d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ceee079b196c3152cc146446330d6ee747436c73f960439326c8fa6b51018b2c5aea167e4d2ffb5da6a5a4b0fa237941c1074886eec1e79502fd36c76847e3a0
|
7
|
+
data.tar.gz: 4355d199f235bc789591adcb5371e5c28e697da34ca33106e5aa82353e039dc53268924bca24fbd8e6f07c1ea43474eeaccbde7d9d1c4d79ffaebedabbe8d5fc
|
data/README.md
CHANGED
@@ -180,11 +180,9 @@ Vorpal.define do
|
|
180
180
|
end
|
181
181
|
```
|
182
182
|
|
183
|
-
CAVEAT: Vorpal currently does NOT SUPPORT anyone but Vorpal setting the id of an entity!
|
184
|
-
|
185
183
|
## API Documentation
|
186
184
|
|
187
|
-
http://rubydoc.info/github/nulogy/vorpal
|
185
|
+
http://rubydoc.info/github/nulogy/vorpal
|
188
186
|
|
189
187
|
## Caveats
|
190
188
|
It also does not do some things that you might expect from other ORMs:
|
@@ -201,10 +199,7 @@ It also does not do some things that you might expect from other ORMs:
|
|
201
199
|
1. Only supports PostgreSQL.
|
202
200
|
|
203
201
|
## Future Enhancements
|
204
|
-
* Support for
|
205
|
-
* Nicer DSL for specifying attributes that have different names in the domain model than in the DB.
|
206
|
-
* Aggregate updated_at.
|
207
|
-
* Better support for value objects.
|
202
|
+
* Support for storing entity ids in a column called something other than "id".
|
208
203
|
|
209
204
|
## FAQ
|
210
205
|
|
@@ -251,6 +246,24 @@ For example, use the [#query](https://rubydoc.info/github/nulogy/vorpal/main/Vor
|
|
251
246
|
|
252
247
|
**A.** Yes. If they exist on your database tables, they will behave exactly as if you were using vanilla ActiveRecord.
|
253
248
|
|
249
|
+
**Q.** How do I tell ActiveRecord to ignore certain columns so that I can rename/remove them using zero-downtime deploys?
|
250
|
+
|
251
|
+
**A.** You will want to use ActiveRecord's [`ignored_columns=`](https://api.rubyonrails.org/classes/ActiveRecord/ModelSchema/ClassMethods.html#method-i-ignored_columns) method like this:
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
engine = Vorpal.define do
|
255
|
+
map Product do
|
256
|
+
attributes(
|
257
|
+
:name,
|
258
|
+
:description,
|
259
|
+
# :column_to_remove
|
260
|
+
)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
product_ar_class = engine.mapper_for(Product).db_class
|
264
|
+
product_ar_class.ignored_columns = [:column_to_remove]
|
265
|
+
```
|
266
|
+
|
254
267
|
## Contributing
|
255
268
|
|
256
269
|
1. Fork it ( https://github.com/nulogy/vorpal/fork )
|
@@ -287,6 +300,14 @@ For example, use the [#query](https://rubydoc.info/github/nulogy/vorpal/main/Vor
|
|
287
300
|
|
288
301
|
Please see the [Appraisal gem docs](https://github.com/thoughtbot/appraisal) for more information.
|
289
302
|
|
303
|
+
### Releasing
|
304
|
+
|
305
|
+
1. Update the version number in `lib/vorpal/version.rb`
|
306
|
+
2. Update the version of Vorpal in the Appraisal gemfiles (otherwise Travis CI will fail): `appraisal install`
|
307
|
+
3. Commit the above changes with the message: `Bump version to <X.Y.Z>`
|
308
|
+
4. Release the new version to Rubygems: `rake release`
|
309
|
+
5. Profit!
|
310
|
+
|
290
311
|
## Contributors
|
291
312
|
|
292
313
|
See who's [contributed](https://github.com/nulogy/vorpal/graphs/contributors)!
|
@@ -27,12 +27,12 @@ module Vorpal
|
|
27
27
|
# Loads an aggregate from the DB. Will eagerly load all objects in the
|
28
28
|
# aggregate and on the boundary (owned: false).
|
29
29
|
#
|
30
|
-
# @param db_root [Object] DB representation of the root of the aggregate to be
|
30
|
+
# @param db_root [Object, nil] DB representation of the root of the aggregate to be
|
31
31
|
# loaded. This can be nil.
|
32
32
|
# @param identity_map [IdentityMap] Provide your own IdentityMap instance
|
33
33
|
# if you want entity id -> unique object mapping for a greater scope than one
|
34
34
|
# operation.
|
35
|
-
# @return [Object] Aggregate root corresponding to the given DB representation.
|
35
|
+
# @return [Object, nil] Aggregate root corresponding to the given DB representation.
|
36
36
|
def load_one(db_root, identity_map=IdentityMap.new)
|
37
37
|
@engine.load_one(db_root, @domain_class, identity_map)
|
38
38
|
end
|
@@ -19,19 +19,20 @@ module Vorpal
|
|
19
19
|
visitor.visit_object(object, config)
|
20
20
|
|
21
21
|
config.belongs_tos.each do |belongs_to_config|
|
22
|
-
|
23
|
-
accept(
|
22
|
+
associate = belongs_to_config.get_associated(object)
|
23
|
+
accept(associate, visitor, already_visited) if visitor.continue_traversal?(belongs_to_config)
|
24
24
|
end
|
25
25
|
|
26
26
|
config.has_ones.each do |has_one_config|
|
27
|
-
|
28
|
-
accept(
|
27
|
+
associate = has_one_config.get_associated(object)
|
28
|
+
accept(associate, visitor, already_visited) if visitor.continue_traversal?(has_one_config)
|
29
29
|
end
|
30
30
|
|
31
31
|
config.has_manys.each do |has_many_config|
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
associates = has_many_config.get_associated(object)
|
33
|
+
raise InvariantViolated.new("#{has_many_config.pretty_name} was set to nil. Use an empty array instead.") if associates.nil?
|
34
|
+
associates.each do |associate|
|
35
|
+
accept(associate, visitor, already_visited) if visitor.continue_traversal?(has_many_config)
|
35
36
|
end
|
36
37
|
end
|
37
38
|
end
|
@@ -47,4 +48,4 @@ module Vorpal
|
|
47
48
|
true
|
48
49
|
end
|
49
50
|
end
|
50
|
-
end
|
51
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'vorpal/config/foreign_key_info'
|
2
|
+
require 'equalizer'
|
3
|
+
|
4
|
+
module Vorpal
|
5
|
+
module Config
|
6
|
+
# @private
|
7
|
+
# Object association terminology:
|
8
|
+
# - All object associations are uni-directional
|
9
|
+
# - The end that holds the association is the 'Owner' and the end that
|
10
|
+
# is referred to is the 'Associate' or 'Associates'
|
11
|
+
#
|
12
|
+
# Relational association terminology:
|
13
|
+
# - Local end: has FK
|
14
|
+
# - Remote end: has no FK
|
15
|
+
#
|
16
|
+
class AssociationConfig
|
17
|
+
include Equalizer.new(:local_class_config, :fk)
|
18
|
+
|
19
|
+
attr_reader :local_class_config, :remote_class_configs, :fk
|
20
|
+
|
21
|
+
# Only one of these two attributes needs to be specified
|
22
|
+
# If one is specified, then the association is uni-directional.
|
23
|
+
# If both are specified, then the association is bi-directional.
|
24
|
+
attr_accessor :local_end_config, :remote_end_config
|
25
|
+
|
26
|
+
def initialize(local_class_config, fk, fk_type)
|
27
|
+
@local_class_config = local_class_config
|
28
|
+
@remote_class_configs = {}
|
29
|
+
@fk = fk
|
30
|
+
@fk_type = fk_type
|
31
|
+
end
|
32
|
+
|
33
|
+
def fk_value(local_db_object)
|
34
|
+
local_db_object.send(fk)
|
35
|
+
end
|
36
|
+
|
37
|
+
def associate(local_object, remote_object)
|
38
|
+
local_end_config.associate(local_object, remote_object) if local_end_config && local_object
|
39
|
+
remote_end_config.associate(remote_object, local_object) if remote_end_config && remote_object
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_remote_class_config(remote_class_configs)
|
43
|
+
Array(remote_class_configs).each do |remote_class_config|
|
44
|
+
@remote_class_configs[remote_class_config.domain_class.name] = remote_class_config
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def remote_class_config(local_db_object)
|
49
|
+
if polymorphic?
|
50
|
+
fk_type_value = local_db_object.send(@fk_type)
|
51
|
+
@remote_class_configs[fk_type_value]
|
52
|
+
else
|
53
|
+
@remote_class_configs.values.first
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def polymorphic?
|
58
|
+
!@fk_type.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_foreign_key(local_db_object, remote_object)
|
62
|
+
local_class_config.set_attribute(local_db_object, @fk, remote_object&.send(unique_key_name))
|
63
|
+
local_class_config.set_attribute(local_db_object, @fk_type, remote_object.class.name) if polymorphic?
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return ForeignKeyInfo
|
67
|
+
def foreign_key_info(remote_class_config)
|
68
|
+
ForeignKeyInfo.new(@fk, @fk_type, remote_class_config.domain_class.name, polymorphic?)
|
69
|
+
end
|
70
|
+
|
71
|
+
def unique_key_name
|
72
|
+
(@local_end_config || @remote_end_config).unique_key_name
|
73
|
+
end
|
74
|
+
|
75
|
+
def validate
|
76
|
+
if @local_end_config && @remote_end_config
|
77
|
+
if @local_end_config.unique_key_name != @remote_end_config.unique_key_name
|
78
|
+
raise ConfigurationError.new("#{@local_end_config.pretty_name} and #{@remote_end_config.pretty_name} must have the same unique_key_name/primary_key")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'vorpal/config/configs'
|
2
|
+
|
3
|
+
module Vorpal
|
4
|
+
module Config
|
5
|
+
# @private
|
6
|
+
# Object association terminology:
|
7
|
+
# - All object associations are uni-directional
|
8
|
+
# - The end that holds the association is the 'Owner' and the end that
|
9
|
+
# is referred to is the 'Associate' or 'Associates'
|
10
|
+
#
|
11
|
+
# Relational association terminology:
|
12
|
+
# - Local end: has FK
|
13
|
+
# - Remote end: has no FK
|
14
|
+
#
|
15
|
+
class BelongsToConfig
|
16
|
+
include Util::HashInitialization
|
17
|
+
include LocalEndConfig
|
18
|
+
|
19
|
+
attr_reader :name, :owned, :fk, :fk_type, :associated_classes, :unique_key_name
|
20
|
+
attr_accessor :association_config
|
21
|
+
|
22
|
+
def get_associated(owner)
|
23
|
+
owner.send(name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def associate(owner, associate)
|
27
|
+
owner.send("#{name}=", associate)
|
28
|
+
end
|
29
|
+
|
30
|
+
def pretty_name
|
31
|
+
"#{association_config.local_class_config.domain_class.name} belongs_to :#{name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'vorpal/util/hash_initialization'
|
2
|
+
require 'vorpal/exceptions'
|
3
|
+
require 'equalizer'
|
4
|
+
|
5
|
+
module Vorpal
|
6
|
+
module Config
|
7
|
+
# @private
|
8
|
+
# Object association terminology:
|
9
|
+
# - All object associations are uni-directional
|
10
|
+
# - The end that holds the association is the 'Owner' and the end that
|
11
|
+
# is referred to is the 'Associate' or 'Associates'
|
12
|
+
#
|
13
|
+
# Relational association terminology:
|
14
|
+
# - Local end: has FK
|
15
|
+
# - Remote end: has no FK
|
16
|
+
#
|
17
|
+
module RemoteEndConfig
|
18
|
+
def associated_class_config
|
19
|
+
association_config.local_class_config
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_foreign_key(db_associate, owner)
|
23
|
+
association_config.set_foreign_key(db_associate, owner)
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_class_config(class_config)
|
27
|
+
@class_config = class_config
|
28
|
+
end
|
29
|
+
|
30
|
+
def foreign_key_info
|
31
|
+
association_config.foreign_key_info(@class_config)
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_unique_key_value(db_owner)
|
35
|
+
db_owner.send(unique_key_name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# @private
|
40
|
+
module LocalEndConfig
|
41
|
+
def associated_class_config(db_owner)
|
42
|
+
association_config.remote_class_config(db_owner)
|
43
|
+
end
|
44
|
+
|
45
|
+
def set_foreign_key(db_owner, associate)
|
46
|
+
association_config.set_foreign_key(db_owner, associate)
|
47
|
+
end
|
48
|
+
|
49
|
+
def fk_value(db_owner)
|
50
|
+
db_owner.send(fk)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'equalizer'
|
2
|
+
|
3
|
+
module Vorpal
|
4
|
+
module Config
|
5
|
+
# @private
|
6
|
+
class ForeignKeyInfo
|
7
|
+
include Equalizer.new(:fk_column, :fk_type_column, :fk_type)
|
8
|
+
|
9
|
+
attr_reader :fk_column, :fk_type_column, :fk_type, :polymorphic
|
10
|
+
|
11
|
+
def initialize(fk_column, fk_type_column, fk_type, polymorphic)
|
12
|
+
@fk_column = fk_column
|
13
|
+
@fk_type_column = fk_type_column
|
14
|
+
@fk_type = fk_type
|
15
|
+
@polymorphic = polymorphic
|
16
|
+
end
|
17
|
+
|
18
|
+
def polymorphic?
|
19
|
+
@polymorphic
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'vorpal/config/configs'
|
2
|
+
|
3
|
+
module Vorpal
|
4
|
+
module Config
|
5
|
+
# @private
|
6
|
+
# Object association terminology:
|
7
|
+
# - All object associations are uni-directional
|
8
|
+
# - The end that holds the association is the 'Owner' and the end that
|
9
|
+
# is referred to is the 'Associate' or 'Associates'
|
10
|
+
#
|
11
|
+
# Relational association terminology:
|
12
|
+
# - Local end: has FK
|
13
|
+
# - Remote end: has no FK
|
14
|
+
#
|
15
|
+
class HasManyConfig
|
16
|
+
include Util::HashInitialization
|
17
|
+
include RemoteEndConfig
|
18
|
+
|
19
|
+
attr_reader :name, :owned, :fk, :fk_type, :associated_class, :unique_key_name
|
20
|
+
attr_accessor :association_config
|
21
|
+
|
22
|
+
def get_associated(owner)
|
23
|
+
owner.send(name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def associate(owner, associates)
|
27
|
+
if get_associated(owner).nil?
|
28
|
+
owner.send("#{name}=", [])
|
29
|
+
end
|
30
|
+
get_associated(owner) << associates
|
31
|
+
end
|
32
|
+
|
33
|
+
def pretty_name
|
34
|
+
"#{@class_config.domain_class.name} has_many :#{name}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'vorpal/config/configs'
|
2
|
+
|
3
|
+
module Vorpal
|
4
|
+
module Config
|
5
|
+
# @private
|
6
|
+
# Object association terminology:
|
7
|
+
# - All object associations are uni-directional
|
8
|
+
# - The end that holds the association is the 'Owner' and the end that
|
9
|
+
# is referred to is the 'Associate' or 'Associates'
|
10
|
+
#
|
11
|
+
# Relational association terminology:
|
12
|
+
# - Local end: has FK
|
13
|
+
# - Remote end: has no FK
|
14
|
+
#
|
15
|
+
class HasOneConfig
|
16
|
+
include Util::HashInitialization
|
17
|
+
include RemoteEndConfig
|
18
|
+
|
19
|
+
attr_reader :name, :owned, :fk, :fk_type, :associated_class, :unique_key_name
|
20
|
+
attr_accessor :association_config
|
21
|
+
|
22
|
+
def get_associated(owner)
|
23
|
+
owner.send(name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def associate(owner, associate)
|
27
|
+
owner.send("#{name}=", associate)
|
28
|
+
end
|
29
|
+
|
30
|
+
def pretty_name
|
31
|
+
"#{@class_config.domain_class.name} has_one :#{name}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'vorpal/exceptions'
|
2
|
+
require 'vorpal/config/association_config'
|
3
|
+
|
4
|
+
module Vorpal
|
5
|
+
module Config
|
6
|
+
# @private
|
7
|
+
class MainConfig
|
8
|
+
def initialize
|
9
|
+
@class_configs = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def config_for(clazz)
|
13
|
+
config = @class_configs.detect { |conf| conf.domain_class == clazz }
|
14
|
+
raise Vorpal::ConfigurationNotFound.new("No configuration found for #{clazz}") unless config
|
15
|
+
config
|
16
|
+
end
|
17
|
+
|
18
|
+
def config_for_db_object(db_object)
|
19
|
+
@class_configs.detect { |conf| conf.db_class == db_object.class }
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_class_config(class_config)
|
23
|
+
@class_configs << class_config
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize_association_configs
|
27
|
+
association_configs = {}
|
28
|
+
@class_configs.each do |config|
|
29
|
+
(config.has_ones + config.has_manys).each do |association_end_config|
|
30
|
+
associated_class_config = config_for(association_end_config.associated_class)
|
31
|
+
association_end_config.set_class_config(config)
|
32
|
+
|
33
|
+
association_config = build_association_config(association_configs, associated_class_config, association_end_config.fk, association_end_config.fk_type)
|
34
|
+
association_config.remote_end_config = association_end_config
|
35
|
+
association_config.add_remote_class_config(config)
|
36
|
+
association_end_config.association_config = association_config
|
37
|
+
end
|
38
|
+
|
39
|
+
config.belongs_tos.each do |association_end_config|
|
40
|
+
associated_class_configs = association_end_config.associated_classes.map(&method(:config_for))
|
41
|
+
|
42
|
+
association_config = build_association_config(association_configs, config, association_end_config.fk, association_end_config.fk_type)
|
43
|
+
association_config.local_end_config = association_end_config
|
44
|
+
association_config.add_remote_class_config(associated_class_configs)
|
45
|
+
association_end_config.association_config = association_config
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
association_configs.values.each do |association_config|
|
50
|
+
association_config.local_class_config.local_association_configs << association_config
|
51
|
+
association_config.validate
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def build_association_config(association_configs, local_config, fk, fk_type)
|
58
|
+
association_config = AssociationConfig.new(local_config, fk, fk_type)
|
59
|
+
if association_configs[association_config]
|
60
|
+
association_config = association_configs[association_config]
|
61
|
+
else
|
62
|
+
association_configs[association_config] = association_config
|
63
|
+
end
|
64
|
+
association_config
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/vorpal/db_loader.rb
CHANGED
@@ -12,7 +12,7 @@ module Vorpal
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def load_from_db(ids, config)
|
15
|
-
db_roots = @db_driver.
|
15
|
+
db_roots = @db_driver.load_by_unique_key(config.db_class, ids, "id")
|
16
16
|
load_from_db_objects(db_roots, config)
|
17
17
|
end
|
18
18
|
|
@@ -24,9 +24,9 @@ module Vorpal
|
|
24
24
|
|
25
25
|
until @lookup_instructions.empty?
|
26
26
|
lookup = @lookup_instructions.next_lookup
|
27
|
-
|
28
|
-
@loaded_objects.add(lookup.config,
|
29
|
-
explore_objects(lookup.config,
|
27
|
+
newly_loaded_objects = lookup.load_all(@db_driver)
|
28
|
+
unexplored_objects = @loaded_objects.add(lookup.config, newly_loaded_objects)
|
29
|
+
explore_objects(lookup.config, unexplored_objects)
|
30
30
|
end
|
31
31
|
|
32
32
|
@loaded_objects
|
@@ -55,34 +55,34 @@ module Vorpal
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def lookup_by_id(db_object, belongs_to_config)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@
|
58
|
+
associated_class_config = belongs_to_config.associated_class_config(db_object)
|
59
|
+
unique_key_value = belongs_to_config.fk_value(db_object)
|
60
|
+
unique_key_name = belongs_to_config.unique_key_name
|
61
|
+
return if unique_key_value.nil? || @loaded_objects.already_loaded_by_unique_key?(associated_class_config, unique_key_name, unique_key_value)
|
62
|
+
@lookup_instructions.lookup_by_unique_key(associated_class_config, unique_key_name, unique_key_value)
|
62
63
|
end
|
63
64
|
|
64
65
|
def lookup_by_fk(db_object, has_some_config)
|
65
|
-
|
66
|
+
associated_class_config = has_some_config.associated_class_config
|
66
67
|
fk_info = has_some_config.foreign_key_info
|
67
|
-
fk_value = db_object
|
68
|
-
@lookup_instructions.lookup_by_fk(
|
68
|
+
fk_value = has_some_config.get_unique_key_value(db_object)
|
69
|
+
@lookup_instructions.lookup_by_fk(associated_class_config, fk_info, fk_value)
|
69
70
|
end
|
70
71
|
end
|
71
72
|
|
72
73
|
# @private
|
73
74
|
class LookupInstructions
|
74
|
-
include Util::ArrayHash
|
75
75
|
def initialize
|
76
|
-
@lookup_by_id =
|
77
|
-
@lookup_by_fk =
|
76
|
+
@lookup_by_id = Util::ArrayHash.new
|
77
|
+
@lookup_by_fk = Util::ArrayHash.new
|
78
78
|
end
|
79
79
|
|
80
|
-
def
|
81
|
-
|
80
|
+
def lookup_by_unique_key(config, column_name, values)
|
81
|
+
@lookup_by_id.append([config, column_name], values)
|
82
82
|
end
|
83
83
|
|
84
84
|
def lookup_by_fk(config, fk_info, fk_value)
|
85
|
-
|
85
|
+
@lookup_by_fk.append([config, fk_info], fk_value)
|
86
86
|
end
|
87
87
|
|
88
88
|
def next_lookup
|
@@ -100,12 +100,14 @@ module Vorpal
|
|
100
100
|
private
|
101
101
|
|
102
102
|
def pop_id_lookup
|
103
|
-
|
104
|
-
|
103
|
+
key, ids = @lookup_by_id.pop
|
104
|
+
config = key.first
|
105
|
+
column_name = key.last
|
106
|
+
LookupById.new(config, column_name, ids)
|
105
107
|
end
|
106
108
|
|
107
109
|
def pop_fk_lookup
|
108
|
-
key, fk_values =
|
110
|
+
key, fk_values = @lookup_by_fk.pop
|
109
111
|
config = key.first
|
110
112
|
fk_info = key.last
|
111
113
|
LookupByFk.new(config, fk_info, fk_values)
|
@@ -115,14 +117,15 @@ module Vorpal
|
|
115
117
|
# @private
|
116
118
|
class LookupById
|
117
119
|
attr_reader :config
|
118
|
-
def initialize(config, ids)
|
120
|
+
def initialize(config, column_name, ids)
|
119
121
|
@config = config
|
122
|
+
@column_name = column_name
|
120
123
|
@ids = ids
|
121
124
|
end
|
122
125
|
|
123
126
|
def load_all(db_driver)
|
124
127
|
return [] if @ids.empty?
|
125
|
-
db_driver.
|
128
|
+
db_driver.load_by_unique_key(@config.db_class, @ids, @column_name)
|
126
129
|
end
|
127
130
|
end
|
128
131
|
|
@@ -40,12 +40,12 @@ module Vorpal
|
|
40
40
|
db_class.where(id: ids).delete_all
|
41
41
|
end
|
42
42
|
|
43
|
-
# Loads instances of the given class by
|
43
|
+
# Loads instances of the given class by a unique key.
|
44
44
|
#
|
45
45
|
# @param db_class [Class] A subclass of ActiveRecord::Base
|
46
46
|
# @return [[Object]] An array of entities.
|
47
|
-
def
|
48
|
-
db_class.where(
|
47
|
+
def load_by_unique_key(db_class, ids, column_name)
|
48
|
+
db_class.where(column_name => ids).to_a
|
49
49
|
end
|
50
50
|
|
51
51
|
# Loads instances of the given class whose foreign key has the given value.
|
@@ -99,6 +99,7 @@ module Vorpal
|
|
99
99
|
|
100
100
|
db_class.vorpal_model_class_name = Util::StringUtils.escape_class_name(model_class.name)
|
101
101
|
db_class.table_name = table_name
|
102
|
+
db_class.primary_key = 'id'
|
102
103
|
db_class
|
103
104
|
end
|
104
105
|
|
@@ -1,4 +1,7 @@
|
|
1
|
-
require 'vorpal/
|
1
|
+
require 'vorpal/config/class_config'
|
2
|
+
require 'vorpal/config/has_many_config'
|
3
|
+
require 'vorpal/config/has_one_config'
|
4
|
+
require 'vorpal/config/belongs_to_config'
|
2
5
|
require 'vorpal/dsl/defaults_generator'
|
3
6
|
|
4
7
|
module Vorpal
|
@@ -64,25 +67,28 @@ module Vorpal
|
|
64
67
|
end
|
65
68
|
|
66
69
|
def build_has_many(options)
|
67
|
-
options[:
|
70
|
+
options[:associated_class] ||= options[:child_class] || @defaults_generator.associated_class(options[:name])
|
68
71
|
options[:fk] ||= @defaults_generator.foreign_key(@domain_class.name)
|
72
|
+
options[:unique_key_name] ||= (options[:primary_key] || "id")
|
69
73
|
options[:owned] = options.fetch(:owned, true)
|
70
|
-
Vorpal::HasManyConfig.new(options)
|
74
|
+
Vorpal::Config::HasManyConfig.new(options)
|
71
75
|
end
|
72
76
|
|
73
77
|
def build_has_one(options)
|
74
|
-
options[:
|
78
|
+
options[:associated_class] ||= options[:child_class] || @defaults_generator.associated_class(options[:name])
|
75
79
|
options[:fk] ||= @defaults_generator.foreign_key(@domain_class.name)
|
80
|
+
options[:unique_key_name] ||= (options[:primary_key] || "id")
|
76
81
|
options[:owned] = options.fetch(:owned, true)
|
77
|
-
Vorpal::HasOneConfig.new(options)
|
82
|
+
Vorpal::Config::HasOneConfig.new(options)
|
78
83
|
end
|
79
84
|
|
80
85
|
def build_belongs_to(options)
|
81
|
-
|
82
|
-
options[:
|
86
|
+
associated_classes = options[:associated_classes] || options[:child_classes] || options[:associated_class] || options[:child_class] || @defaults_generator.associated_class(options[:name])
|
87
|
+
options[:associated_classes] = Array(associated_classes)
|
83
88
|
options[:fk] ||= @defaults_generator.foreign_key(options[:name])
|
89
|
+
options[:unique_key_name] ||= (options[:primary_key] || "id")
|
84
90
|
options[:owned] = options.fetch(:owned, true)
|
85
|
-
Vorpal::BelongsToConfig.new(options)
|
91
|
+
Vorpal::Config::BelongsToConfig.new(options)
|
86
92
|
end
|
87
93
|
end
|
88
94
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'vorpal/engine'
|
2
2
|
require 'vorpal/dsl/config_builder'
|
3
3
|
require 'vorpal/driver/postgresql'
|
4
|
+
require 'vorpal/config/main_config'
|
4
5
|
|
5
6
|
module Vorpal
|
6
7
|
module Dsl
|
@@ -36,7 +37,7 @@ module Vorpal
|
|
36
37
|
#
|
37
38
|
# @return [Engine] Instance of the mapping engine.
|
38
39
|
def define(options={}, &block)
|
39
|
-
@main_config = MainConfig.new
|
40
|
+
@main_config = Config::MainConfig.new
|
40
41
|
instance_exec(&block)
|
41
42
|
@main_config.initialize_association_configs
|
42
43
|
db_driver = options.fetch(:db_driver, Driver::Postgresql.new)
|
@@ -86,58 +87,65 @@ module Vorpal
|
|
86
87
|
@builder.attributes(*attributes)
|
87
88
|
end
|
88
89
|
|
89
|
-
# Defines a one-to-many association to another type where the foreign key is stored on the
|
90
|
+
# Defines a one-to-many association to another type where the foreign key is stored on the associated table.
|
90
91
|
#
|
91
92
|
# In Object-Oriented programming, associations are *directed*. This means that they can only be
|
92
93
|
# traversed in one direction: from the type that defines the association (the one with the
|
93
|
-
# getter) to the type that is associated.
|
94
|
-
# 'Parent' and the end that is associated is called the 'Child'.
|
94
|
+
# getter) to the type that is associated.
|
95
95
|
#
|
96
96
|
# @param name [String] Name of the association getter.
|
97
97
|
# @param options [Hash]
|
98
|
-
# @option options [Boolean] :owned (True) True if the
|
99
|
-
# @option options [String] :fk (
|
100
|
-
# @option options [String] :fk_type The name of the DB column on the
|
101
|
-
# @option options [
|
98
|
+
# @option options [Boolean] :owned (True) True if the associated 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 (Association-owning class name converted to snakecase and appended with a '_id') The name of the DB column on the associated table that contains the foreign key reference to the association owner.
|
100
|
+
# @option options [String] :fk_type The name of the DB column on the associated table that contains the association-owning class name. Only needed when the associated end is polymorphic.
|
101
|
+
# @option options [String] :unique_key_name ("id") The name of the column on the owning table that the foreign key points to. Normally the primary key column.
|
102
|
+
# @option options [String] :primary_key Same as :unique_key_name. Exists for compatibility with Rails API.
|
103
|
+
# @option options [Class] :child_class DEPRECATED. Use `associated_class` instead. The associated class.
|
104
|
+
# @option options [Class] :associated_class (Name of the association converted to a Class) The associated class.
|
102
105
|
def has_many(name, options={})
|
103
106
|
@builder.has_many(name, options)
|
104
107
|
end
|
105
108
|
|
106
109
|
# Defines a one-to-one association to another type where the foreign key
|
107
|
-
# is stored on the
|
110
|
+
# is stored on the associated table.
|
108
111
|
#
|
109
112
|
# In Object-Oriented programming, associations are *directed*. This means that they can only be
|
110
113
|
# traversed in one direction: from the type that defines the association (the one with the
|
111
|
-
# getter) to the type that is associated.
|
112
|
-
# 'Parent' and the end that is associated is called the 'Child'.
|
114
|
+
# getter) to the type that is associated.
|
113
115
|
#
|
114
116
|
# @param name [String] Name of the association getter.
|
115
117
|
# @param options [Hash]
|
116
|
-
# @option options [Boolean] :owned (True) True if the
|
117
|
-
# @option options [String] :fk (
|
118
|
-
# @option options [String] :fk_type The name of the DB column on the
|
119
|
-
# @option options [
|
118
|
+
# @option options [Boolean] :owned (True) True if the associated type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
|
119
|
+
# @option options [String] :fk (Association-owning class name converted to snakecase and appended with a '_id') The name of the DB column on the associated table that contains the foreign key reference to the association owner.
|
120
|
+
# @option options [String] :fk_type The name of the DB column on the associated table that contains the association-owning class name. Only needed when the associated end is polymorphic.
|
121
|
+
# @option options [String] :unique_key_name ("id") The name of the column on the owning table that the foreign key points to. Normally the primary key column.
|
122
|
+
# @option options [String] :primary_key Same as :unique_key_name. Exists for compatibility with Rails API.
|
123
|
+
# @option options [Class] :child_class DEPRECATED. Use `associated_class` instead. The associated class.
|
124
|
+
# @option options [Class] :associated_class (Name of the association converted to a Class) The associated class.
|
120
125
|
def has_one(name, options={})
|
121
126
|
@builder.has_one(name, options)
|
122
127
|
end
|
123
128
|
|
124
129
|
# Defines a one-to-one association with another type where the foreign key
|
125
|
-
# is stored on the
|
130
|
+
# is stored on the table of the entity declaring the association.
|
126
131
|
#
|
127
|
-
# This association can be polymorphic. I.E.
|
132
|
+
# This association can be polymorphic. I.E. associates can be of different types.
|
128
133
|
#
|
129
134
|
# In Object-Oriented programming, associations are *directed*. This means that they can only be
|
130
135
|
# traversed in one direction: from the type that defines the association (the one with the
|
131
|
-
# getter) to the type that is associated.
|
132
|
-
# 'Parent' and the end that is associated is called the 'Child'.
|
136
|
+
# getter) to the type that is associated.
|
133
137
|
#
|
134
138
|
# @param name [String] Name of the association getter.
|
135
139
|
# @param options [Hash]
|
136
|
-
# @option options [Boolean] :owned (True) True if the
|
137
|
-
# @option options [String] :fk (
|
138
|
-
# @option options [String] :fk_type The name of the DB column on the
|
139
|
-
# @option options [
|
140
|
-
# @option options [
|
140
|
+
# @option options [Boolean] :owned (True) True if the associated type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
|
141
|
+
# @option options [String] :fk (Associated class name converted to snakecase and appended with a '_id') The name of the DB column on the association-owning table that contains the foreign key reference to the associated table.
|
142
|
+
# @option options [String] :fk_type The name of the DB column on the association-owning table that contains the associated class name. Only needed when the association is polymorphic.
|
143
|
+
# @option options [String] :unique_key_name ("id") The name of the column on the associated table that the foreign key points to. Normally the primary key column.
|
144
|
+
# @option options [String] :primary_key Same as :unique_key_name. Exists for compatibility with Rails API.
|
145
|
+
# @option options [Class] :child_class DEPRECATED. Use `associated_class` instead. The associated class.
|
146
|
+
# @option options [Class] :associated_class (Name of the association converted to a Class) The associated class.
|
147
|
+
# @option options [[Class]] :child_classes DEPRECATED. Use `associated_classes` instead. The list of possible classes that can be associated. This is for polymorphic associations. Takes precedence over `:associated_class`.
|
148
|
+
# @option options [[Class]] :associated_classes (Name of the association converted to a Class) The list of possible classes that can be associated. This is for polymorphic associations. Takes precedence over `:associated_class`.
|
141
149
|
def belongs_to(name, options={})
|
142
150
|
@builder.belongs_to(name, options)
|
143
151
|
end
|
data/lib/vorpal/engine.rb
CHANGED
@@ -137,8 +137,9 @@ module Vorpal
|
|
137
137
|
loaded_db_objects.each do |config, db_objects|
|
138
138
|
db_objects.each do |db_object|
|
139
139
|
config.local_association_configs.each do |association_config|
|
140
|
-
db_remote = loaded_db_objects.
|
140
|
+
db_remote = loaded_db_objects.find_by_unique_key(
|
141
141
|
association_config.remote_class_config(db_object),
|
142
|
+
association_config.unique_key_name,
|
142
143
|
association_config.fk_value(db_object)
|
143
144
|
)
|
144
145
|
association_config.associate(identity_map.get(db_object), identity_map.get(db_remote))
|
@@ -159,10 +160,10 @@ module Vorpal
|
|
159
160
|
def serialize_object(object, config, loaded_db_objects)
|
160
161
|
if config.serialization_required?
|
161
162
|
attributes = config.serialize(object)
|
162
|
-
|
163
|
+
db_object = loaded_db_objects.find_by_primary_key(config, object)
|
164
|
+
if object.id.nil? || db_object.nil? # object doesn't exist in the DB
|
163
165
|
config.build_db_object(attributes)
|
164
166
|
else
|
165
|
-
db_object = loaded_db_objects.find_by_id(config, object.id)
|
166
167
|
config.set_db_object_attributes(db_object, attributes)
|
167
168
|
db_object
|
168
169
|
end
|
@@ -192,23 +193,23 @@ module Vorpal
|
|
192
193
|
objects.each do |object|
|
193
194
|
config.has_manys.each do |has_many_config|
|
194
195
|
if has_many_config.owned
|
195
|
-
|
196
|
-
|
197
|
-
has_many_config.set_foreign_key(mapping[
|
196
|
+
associates = has_many_config.get_associated(object)
|
197
|
+
associates.each do |associate|
|
198
|
+
has_many_config.set_foreign_key(mapping[associate], object)
|
198
199
|
end
|
199
200
|
end
|
200
201
|
end
|
201
202
|
|
202
203
|
config.has_ones.each do |has_one_config|
|
203
204
|
if has_one_config.owned
|
204
|
-
|
205
|
-
has_one_config.set_foreign_key(mapping[
|
205
|
+
associate = has_one_config.get_associated(object)
|
206
|
+
has_one_config.set_foreign_key(mapping[associate], object) if associate
|
206
207
|
end
|
207
208
|
end
|
208
209
|
|
209
210
|
config.belongs_tos.each do |belongs_to_config|
|
210
|
-
|
211
|
-
belongs_to_config.set_foreign_key(mapping[object],
|
211
|
+
associate = belongs_to_config.get_associated(object)
|
212
|
+
belongs_to_config.set_foreign_key(mapping[object], associate)
|
212
213
|
end
|
213
214
|
end
|
214
215
|
end
|
data/lib/vorpal/exceptions.rb
CHANGED
@@ -5,37 +5,80 @@ module Vorpal
|
|
5
5
|
|
6
6
|
# @private
|
7
7
|
class LoadedObjects
|
8
|
-
include Util::ArrayHash
|
9
8
|
extend Forwardable
|
10
9
|
include Enumerable
|
11
10
|
|
12
|
-
|
13
|
-
def_delegators :objects, :each
|
11
|
+
def_delegators :@objects, :each
|
14
12
|
|
15
13
|
def initialize
|
16
|
-
@objects =
|
17
|
-
@
|
14
|
+
@objects = Util::ArrayHash.new
|
15
|
+
@cache = {}
|
18
16
|
end
|
19
17
|
|
20
18
|
def add(config, objects)
|
21
19
|
objects_to_add = objects.map do |object|
|
22
|
-
if !already_loaded?(config, object
|
23
|
-
|
20
|
+
if !already_loaded?(config, object)
|
21
|
+
add_to_cache(config, object)
|
24
22
|
end
|
25
|
-
end
|
26
|
-
|
23
|
+
end.compact
|
24
|
+
@objects.append(config, objects_to_add)
|
25
|
+
objects_to_add
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_by_primary_key(config, object)
|
29
|
+
find_by_unique_key(config, "id", object.id)
|
27
30
|
end
|
28
31
|
|
29
|
-
def
|
30
|
-
|
32
|
+
def find_by_unique_key(config, column_name, value)
|
33
|
+
get_from_cache(config, column_name, value)
|
31
34
|
end
|
32
35
|
|
33
36
|
def all_objects
|
34
|
-
@
|
37
|
+
@objects.values
|
38
|
+
end
|
39
|
+
|
40
|
+
def already_loaded_by_unique_key?(config, column_name, id)
|
41
|
+
!find_by_unique_key(config, column_name, id).nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def already_loaded?(config, object)
|
47
|
+
!find_by_primary_key(config, object).nil?
|
35
48
|
end
|
36
49
|
|
37
|
-
|
38
|
-
|
50
|
+
# TODO: Do we have to worry about symbols vs strings for the column_name?
|
51
|
+
def add_to_cache(config, object)
|
52
|
+
# we take a shortcut here assuming that the cache has already been primed with the primary key column
|
53
|
+
# because this method should always be guarded by #already_loaded?
|
54
|
+
column_cache = @cache[config]
|
55
|
+
column_cache.each do |column_name, unique_key_cache|
|
56
|
+
unique_key_cache[object.send(column_name)] = object
|
57
|
+
end
|
58
|
+
object
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_from_cache(config, column_name, value)
|
62
|
+
lookup_hash(config, column_name)[value]
|
63
|
+
end
|
64
|
+
|
65
|
+
# lazily primes the cache
|
66
|
+
# TODO: Do we have to worry about symbols vs strings for the column_name?
|
67
|
+
def lookup_hash(config, column_name)
|
68
|
+
column_cache = @cache[config]
|
69
|
+
if column_cache.nil?
|
70
|
+
column_cache = {}
|
71
|
+
@cache[config] = column_cache
|
72
|
+
end
|
73
|
+
unique_key_cache = column_cache[column_name]
|
74
|
+
if unique_key_cache.nil?
|
75
|
+
unique_key_cache = {}
|
76
|
+
column_cache[column_name] = unique_key_cache
|
77
|
+
@objects[config].each do |object|
|
78
|
+
unique_key_cache[object.send(column_name)] = object
|
79
|
+
end
|
80
|
+
end
|
81
|
+
unique_key_cache
|
39
82
|
end
|
40
83
|
end
|
41
84
|
end
|
@@ -1,19 +1,33 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
module Vorpal
|
2
4
|
module Util
|
3
5
|
# @private
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
class ArrayHash
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :@hash, :each, :empty?, :[]
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@hash = Hash.new([])
|
13
|
+
end
|
14
|
+
|
15
|
+
def append(key, values)
|
16
|
+
if @hash[key].nil? || @hash[key].empty?
|
17
|
+
@hash[key] = []
|
8
18
|
end
|
9
|
-
|
19
|
+
@hash[key].concat(Array(values))
|
10
20
|
end
|
11
21
|
|
12
|
-
def pop
|
13
|
-
key =
|
14
|
-
values =
|
22
|
+
def pop
|
23
|
+
key = @hash.first.first
|
24
|
+
values = @hash.delete(key)
|
15
25
|
[key, values]
|
16
26
|
end
|
27
|
+
|
28
|
+
def values
|
29
|
+
@hash.values.flatten
|
30
|
+
end
|
17
31
|
end
|
18
32
|
end
|
19
33
|
end
|
data/lib/vorpal/version.rb
CHANGED
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.4.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:
|
11
|
+
date: 2021-07-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: simple_serializer
|
@@ -165,8 +165,14 @@ files:
|
|
165
165
|
- lib/vorpal/aggregate_mapper.rb
|
166
166
|
- lib/vorpal/aggregate_traversal.rb
|
167
167
|
- lib/vorpal/aggregate_utils.rb
|
168
|
+
- lib/vorpal/config/association_config.rb
|
169
|
+
- lib/vorpal/config/belongs_to_config.rb
|
168
170
|
- lib/vorpal/config/class_config.rb
|
169
|
-
- lib/vorpal/configs.rb
|
171
|
+
- lib/vorpal/config/configs.rb
|
172
|
+
- lib/vorpal/config/foreign_key_info.rb
|
173
|
+
- lib/vorpal/config/has_many_config.rb
|
174
|
+
- lib/vorpal/config/has_one_config.rb
|
175
|
+
- lib/vorpal/config/main_config.rb
|
170
176
|
- lib/vorpal/db_loader.rb
|
171
177
|
- lib/vorpal/driver/postgresql.rb
|
172
178
|
- lib/vorpal/dsl/config_builder.rb
|
data/lib/vorpal/configs.rb
DELETED
@@ -1,239 +0,0 @@
|
|
1
|
-
require 'vorpal/util/hash_initialization'
|
2
|
-
require 'vorpal/exceptions'
|
3
|
-
require 'vorpal/config/class_config'
|
4
|
-
require 'equalizer'
|
5
|
-
|
6
|
-
module Vorpal
|
7
|
-
# @private
|
8
|
-
class MainConfig
|
9
|
-
def initialize
|
10
|
-
@class_configs = []
|
11
|
-
end
|
12
|
-
|
13
|
-
def config_for(clazz)
|
14
|
-
config = @class_configs.detect { |conf| conf.domain_class == clazz }
|
15
|
-
raise Vorpal::ConfigurationNotFound.new("No configuration found for #{clazz}") unless config
|
16
|
-
config
|
17
|
-
end
|
18
|
-
|
19
|
-
def config_for_db_object(db_object)
|
20
|
-
@class_configs.detect { |conf| conf.db_class == db_object.class }
|
21
|
-
end
|
22
|
-
|
23
|
-
def add_class_config(class_config)
|
24
|
-
@class_configs << class_config
|
25
|
-
end
|
26
|
-
|
27
|
-
def initialize_association_configs
|
28
|
-
association_configs = {}
|
29
|
-
@class_configs.each do |config|
|
30
|
-
(config.has_ones + config.has_manys).each do |association_end_config|
|
31
|
-
child_config = config_for(association_end_config.child_class)
|
32
|
-
association_end_config.set_parent_class_config(config)
|
33
|
-
|
34
|
-
association_config = build_association_config(association_configs, child_config, association_end_config.fk, association_end_config.fk_type)
|
35
|
-
association_config.remote_end_config = association_end_config
|
36
|
-
association_config.add_remote_class_config(config)
|
37
|
-
association_end_config.association_config = association_config
|
38
|
-
end
|
39
|
-
|
40
|
-
config.belongs_tos.each do |association_end_config|
|
41
|
-
child_configs = association_end_config.child_classes.map(&method(:config_for))
|
42
|
-
|
43
|
-
association_config = build_association_config(association_configs, config, association_end_config.fk, association_end_config.fk_type)
|
44
|
-
association_config.local_end_config = association_end_config
|
45
|
-
association_config.add_remote_class_config(child_configs)
|
46
|
-
association_end_config.association_config = association_config
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
association_configs.values.each do |association_config|
|
51
|
-
association_config.local_class_config.local_association_configs << association_config
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
def build_association_config(association_configs, local_config, fk, fk_type)
|
58
|
-
association_config = AssociationConfig.new(local_config, fk, fk_type)
|
59
|
-
if association_configs[association_config]
|
60
|
-
association_config = association_configs[association_config]
|
61
|
-
else
|
62
|
-
association_configs[association_config] = association_config
|
63
|
-
end
|
64
|
-
association_config
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# @private
|
69
|
-
# Object associations:
|
70
|
-
# - All object associations are uni-directional
|
71
|
-
# - The end that holds the association is the 'Parent' and the end that
|
72
|
-
# is referred to is the 'Child' or 'Children'
|
73
|
-
#
|
74
|
-
# Relational associations:
|
75
|
-
# - Local end: has FK
|
76
|
-
# - Remote end: has no FK
|
77
|
-
#
|
78
|
-
class AssociationConfig
|
79
|
-
include Equalizer.new(:local_class_config, :fk)
|
80
|
-
|
81
|
-
attr_reader :local_class_config, :remote_class_configs, :fk
|
82
|
-
|
83
|
-
# Only one of these two attributes needs to be specified
|
84
|
-
# If one is specified, then the association is uni-directional.
|
85
|
-
# If both are specified, then the association is bi-directional.
|
86
|
-
attr_accessor :local_end_config, :remote_end_config
|
87
|
-
|
88
|
-
def initialize(local_class_config, fk, fk_type)
|
89
|
-
@local_class_config = local_class_config
|
90
|
-
@remote_class_configs = {}
|
91
|
-
@fk = fk
|
92
|
-
@fk_type = fk_type
|
93
|
-
end
|
94
|
-
|
95
|
-
def fk_value(local_db_object)
|
96
|
-
local_db_object.send(fk)
|
97
|
-
end
|
98
|
-
|
99
|
-
def associate(local_object, remote_object)
|
100
|
-
local_end_config.associate(local_object, remote_object) if local_end_config && local_object
|
101
|
-
remote_end_config.associate(remote_object, local_object) if remote_end_config && remote_object
|
102
|
-
end
|
103
|
-
|
104
|
-
def add_remote_class_config(remote_class_configs)
|
105
|
-
Array(remote_class_configs).each do |remote_class_config|
|
106
|
-
@remote_class_configs[remote_class_config.domain_class.name] = remote_class_config
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def remote_class_config(local_db_object)
|
111
|
-
if polymorphic?
|
112
|
-
fk_type_value = local_db_object.send(@fk_type)
|
113
|
-
@remote_class_configs[fk_type_value]
|
114
|
-
else
|
115
|
-
@remote_class_configs.values.first
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def polymorphic?
|
120
|
-
!@fk_type.nil?
|
121
|
-
end
|
122
|
-
|
123
|
-
def set_foreign_key(local_db_object, remote_object)
|
124
|
-
local_class_config.set_attribute(local_db_object, @fk, remote_object.try(:id))
|
125
|
-
local_class_config.set_attribute(local_db_object, @fk_type, remote_object.class.name) if polymorphic?
|
126
|
-
end
|
127
|
-
|
128
|
-
def foreign_key_info(remote_class_config)
|
129
|
-
ForeignKeyInfo.new(@fk, @fk_type, remote_class_config.domain_class.name, polymorphic?)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
# @private
|
134
|
-
class ForeignKeyInfo
|
135
|
-
include Equalizer.new(:fk_column, :fk_type_column, :fk_type)
|
136
|
-
|
137
|
-
attr_reader :fk_column, :fk_type_column, :fk_type, :polymorphic
|
138
|
-
|
139
|
-
def initialize(fk_column, fk_type_column, fk_type, polymorphic)
|
140
|
-
@fk_column = fk_column
|
141
|
-
@fk_type_column = fk_type_column
|
142
|
-
@fk_type = fk_type
|
143
|
-
@polymorphic = polymorphic
|
144
|
-
end
|
145
|
-
|
146
|
-
def polymorphic?
|
147
|
-
@polymorphic
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
# @private
|
152
|
-
module RemoteEndConfig
|
153
|
-
def child_config
|
154
|
-
association_config.local_class_config
|
155
|
-
end
|
156
|
-
|
157
|
-
def set_foreign_key(db_child, parent)
|
158
|
-
association_config.set_foreign_key(db_child, parent)
|
159
|
-
end
|
160
|
-
|
161
|
-
def set_parent_class_config(parent_config)
|
162
|
-
@parent_config = parent_config
|
163
|
-
end
|
164
|
-
|
165
|
-
def foreign_key_info
|
166
|
-
association_config.foreign_key_info(@parent_config)
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
# @private
|
171
|
-
module LocalEndConfig
|
172
|
-
def child_config(db_parent)
|
173
|
-
association_config.remote_class_config(db_parent)
|
174
|
-
end
|
175
|
-
|
176
|
-
def set_foreign_key(db_parent, child)
|
177
|
-
association_config.set_foreign_key(db_parent, child)
|
178
|
-
end
|
179
|
-
|
180
|
-
def fk_value(db_parent)
|
181
|
-
db_parent.send(fk)
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# @private
|
186
|
-
module ToOneConfig
|
187
|
-
def get_child(parent)
|
188
|
-
parent.send(name)
|
189
|
-
end
|
190
|
-
|
191
|
-
def associate(parent, child)
|
192
|
-
parent.send("#{name}=", child)
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
# @private
|
197
|
-
module ToManyConfig
|
198
|
-
def get_children(parent)
|
199
|
-
parent.send(name)
|
200
|
-
end
|
201
|
-
|
202
|
-
def associate(parent, child)
|
203
|
-
if get_children(parent).nil?
|
204
|
-
parent.send("#{name}=", [])
|
205
|
-
end
|
206
|
-
get_children(parent) << child
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
# @private
|
211
|
-
class HasManyConfig
|
212
|
-
include Util::HashInitialization
|
213
|
-
include RemoteEndConfig
|
214
|
-
include ToManyConfig
|
215
|
-
|
216
|
-
attr_reader :name, :owned, :fk, :fk_type, :child_class
|
217
|
-
attr_accessor :association_config
|
218
|
-
end
|
219
|
-
|
220
|
-
# @private
|
221
|
-
class HasOneConfig
|
222
|
-
include Util::HashInitialization
|
223
|
-
include RemoteEndConfig
|
224
|
-
include ToOneConfig
|
225
|
-
|
226
|
-
attr_reader :name, :owned, :fk, :fk_type, :child_class
|
227
|
-
attr_accessor :association_config
|
228
|
-
end
|
229
|
-
|
230
|
-
# @private
|
231
|
-
class BelongsToConfig
|
232
|
-
include Util::HashInitialization
|
233
|
-
include LocalEndConfig
|
234
|
-
include ToOneConfig
|
235
|
-
|
236
|
-
attr_reader :name, :owned, :fk, :fk_type, :child_classes
|
237
|
-
attr_accessor :association_config
|
238
|
-
end
|
239
|
-
end
|