vorpal 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +29 -2
- 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 +3 -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 +10 -9
- data/lib/vorpal/exceptions.rb +4 -0
- data/lib/vorpal/identity_map.rb +7 -2
- 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: c236d5dee0ca27224473d24e59680317a5b30fae7e400a13738298d27e6f1e74
|
4
|
+
data.tar.gz: ffe4922245341adc4ef2e4ed25751dee28a03232c2bb5b19699120f07d7fd3d6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6ba4c4ab43cc66a776be399be185130665ea4951a18a34e664b45d851801b37260ce4eee343f81511910e739f7451b3b30ead059c24712e9ee577c579fdeffa
|
7
|
+
data.tar.gz: e134e15a04d0e7037fbbedf83f5e381e281e72f8bae51e43b0f9b488877477d7d6dfb7350c037e9bfb06279ac78d74024b1e592725cd8673348bf6ad4d6f61c7
|
data/README.md
CHANGED
@@ -182,7 +182,7 @@ end
|
|
182
182
|
|
183
183
|
## API Documentation
|
184
184
|
|
185
|
-
http://rubydoc.info/github/nulogy/vorpal
|
185
|
+
http://rubydoc.info/github/nulogy/vorpal
|
186
186
|
|
187
187
|
## Caveats
|
188
188
|
It also does not do some things that you might expect from other ORMs:
|
@@ -199,7 +199,8 @@ It also does not do some things that you might expect from other ORMs:
|
|
199
199
|
1. Only supports PostgreSQL.
|
200
200
|
|
201
201
|
## Future Enhancements
|
202
|
-
* Support for
|
202
|
+
* Support for having foreign keys point to columns other than primary keys.
|
203
|
+
* Support for storing entity ids in a column called something other than "id".
|
203
204
|
* Nicer DSL for specifying attributes that have different names in the domain model than in the DB.
|
204
205
|
* Aggregate updated_at.
|
205
206
|
* Better support for value objects.
|
@@ -249,6 +250,24 @@ For example, use the [#query](https://rubydoc.info/github/nulogy/vorpal/main/Vor
|
|
249
250
|
|
250
251
|
**A.** Yes. If they exist on your database tables, they will behave exactly as if you were using vanilla ActiveRecord.
|
251
252
|
|
253
|
+
**Q.** How do I tell ActiveRecord to ignore certain columns so that I can rename/remove them using zero-downtime deploys?
|
254
|
+
|
255
|
+
**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:
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
engine = Vorpal.define do
|
259
|
+
map Product do
|
260
|
+
attributes(
|
261
|
+
:name,
|
262
|
+
:description,
|
263
|
+
# :column_to_remove
|
264
|
+
)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
product_ar_class = engine.mapper_for(Product).db_class
|
268
|
+
product_ar_class.ignored_columns = [:column_to_remove]
|
269
|
+
```
|
270
|
+
|
252
271
|
## Contributing
|
253
272
|
|
254
273
|
1. Fork it ( https://github.com/nulogy/vorpal/fork )
|
@@ -285,6 +304,14 @@ For example, use the [#query](https://rubydoc.info/github/nulogy/vorpal/main/Vor
|
|
285
304
|
|
286
305
|
Please see the [Appraisal gem docs](https://github.com/thoughtbot/appraisal) for more information.
|
287
306
|
|
307
|
+
### Releasing
|
308
|
+
|
309
|
+
1. Update the version number in `lib/vorpal/version.rb`
|
310
|
+
2. Update the version of Vorpal in the Appraisal gemfiles (otherwise Travis CI will fail): `appraisal install`
|
311
|
+
3. Commit the above changes with the message: `Bump version to <X.Y.Z>`
|
312
|
+
4. Release the new version to Rubygems: `rake release`
|
313
|
+
5. Profit!
|
314
|
+
|
288
315
|
## Contributors
|
289
316
|
|
290
317
|
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.
|
@@ -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,7 +160,7 @@ 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
|
-
db_object = loaded_db_objects.
|
163
|
+
db_object = loaded_db_objects.find_by_primary_key(config, object)
|
163
164
|
if object.id.nil? || db_object.nil? # object doesn't exist in the DB
|
164
165
|
config.build_db_object(attributes)
|
165
166
|
else
|
@@ -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
data/lib/vorpal/identity_map.rb
CHANGED
@@ -28,9 +28,14 @@ module Vorpal
|
|
28
28
|
|
29
29
|
def key(db_row)
|
30
30
|
return nil unless db_row
|
31
|
-
|
31
|
+
primary_key_value = get_primary_key_value(db_row)
|
32
|
+
raise "Cannot map a DB row without an id '#{db_row.inspect}' to an entity." if primary_key_value.nil?
|
32
33
|
raise "Cannot map a DB row without a Class with a name '#{db_row.inspect}' to an entity." if db_row.class.name.nil?
|
33
|
-
[
|
34
|
+
[primary_key_value, db_row.class.name]
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_primary_key_value(db_row)
|
38
|
+
db_row.send(db_row.class.primary_key)
|
34
39
|
end
|
35
40
|
end
|
36
41
|
end
|
@@ -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.3.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-07 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
|