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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83737ac0a684cae35b5e849cba0c7851234dd83644ec04458e2c3c49ae0213f1
4
- data.tar.gz: 8ac93c1267e080bc874dc2526d7b9f2c5abc1635c579d0f4be7cbd9af4fd7fc9
3
+ metadata.gz: c236d5dee0ca27224473d24e59680317a5b30fae7e400a13738298d27e6f1e74
4
+ data.tar.gz: ffe4922245341adc4ef2e4ed25751dee28a03232c2bb5b19699120f07d7fd3d6
5
5
  SHA512:
6
- metadata.gz: 959ef178a6479c47053c2d100c323a48134fdcf7c63479c21afff0cc7cb91f2a198962013e5b324c7a8a4b1946e19332b15198c957c3e1b097e7734036b2ab09
7
- data.tar.gz: 2783c22ba9362a9285e4e80369565a8be0695feb910325f77233b43060826dfffafd03e74c2c4cdcb554e6246eba1e42441b7b2f94d7ba0748cda140effee044
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/main/frames
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 clients to set UUID-based ids.
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
- child = belongs_to_config.get_child(object)
23
- accept(child, visitor, already_visited) if visitor.continue_traversal?(belongs_to_config)
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
- child = has_one_config.get_child(object)
28
- accept(child, visitor, already_visited) if visitor.continue_traversal?(has_one_config)
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
- children = has_many_config.get_children(object)
33
- children.each do |child|
34
- accept(child, visitor, already_visited) if visitor.continue_traversal?(has_many_config)
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
@@ -12,7 +12,7 @@ module Vorpal
12
12
  end
13
13
 
14
14
  def load_from_db(ids, config)
15
- db_roots = @db_driver.load_by_id(config.db_class, ids)
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
- new_objects = lookup.load_all(@db_driver)
28
- @loaded_objects.add(lookup.config, new_objects)
29
- explore_objects(lookup.config, new_objects)
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
- child_config = belongs_to_config.child_config(db_object)
59
- id = belongs_to_config.fk_value(db_object)
60
- return if id.nil? || @loaded_objects.already_loaded?(child_config, id)
61
- @lookup_instructions.lookup_by_id(child_config, id)
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
- child_config = has_some_config.child_config
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.id
68
- @lookup_instructions.lookup_by_fk(child_config, fk_info, fk_value)
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 lookup_by_id(config, ids)
81
- add_to_hash(@lookup_by_id, config, Array(ids))
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
- add_to_hash(@lookup_by_fk, [config, fk_info], fk_value)
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
- config, ids = pop(@lookup_by_id)
104
- LookupById.new(config, ids)
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 = pop(@lookup_by_fk)
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.load_by_id(@config.db_class, @ids)
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 primary key.
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 load_by_id(db_class, ids)
48
- db_class.where(id: ids).to_a
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/configs'
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[:child_class] ||= @defaults_generator.child_class(options[:name])
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[:child_class] ||= @defaults_generator.child_class(options[:name])
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
- child_class = options[:child_classes] || options[:child_class] || @defaults_generator.child_class(options[:name])
82
- options[:child_classes] = Array(child_class)
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 child.
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. They end that defines the association is called the
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 child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
99
- # @option options [String] :fk (Parent class name converted to snakecase and appended with a '_id') The name of the DB column on the child that contains the foreign key reference to the parent.
100
- # @option options [String] :fk_type The name of the DB column on the child that contains the parent class name. Only needed when there is an association from the child side that is polymorphic.
101
- # @option options [Class] :child_class (name converted to a Class) The child class.
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 child.
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. They end that defines the association is called the
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 child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
117
- # @option options [String] :fk (Parent class name converted to snakecase and appended with a '_id') The name of the DB column on the child that contains the foreign key reference to the parent.
118
- # @option options [String] :fk_type The name of the DB column on the child that contains the parent class name. Only needed when there is an association from the child side that is polymorphic.
119
- # @option options [Class] :child_class (name converted to a Class) The child class.
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 parent.
130
+ # is stored on the table of the entity declaring the association.
126
131
  #
127
- # This association can be polymorphic. I.E. children can be of different types.
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. They end that defines the association is called the
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 child type belongs to the aggregate. Changes to any object belonging to the aggregate will be persisted when the aggregate is persisted.
137
- # @option options [String] :fk (Child class name converted to snakecase and appended with a '_id') The name of the DB column on the parent that contains the foreign key reference to the child.
138
- # @option options [String] :fk_type The name of the DB column on the parent that contains the child class name. Only needed when the association is polymorphic.
139
- # @option options [Class] :child_class (name converted to a Class) The child class.
140
- # @option options [[Class]] :child_classes The list of possible classes that can be children. This is for polymorphic associations. Takes precedence over `:child_class`.
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
@@ -35,7 +35,7 @@ module Vorpal
35
35
  ActiveSupport::Inflector.foreign_key(name.to_s)
36
36
  end
37
37
 
38
- def child_class(association_name)
38
+ def associated_class(association_name)
39
39
  module_parent.const_get(ActiveSupport::Inflector.classify(association_name.to_s))
40
40
  end
41
41
 
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.find_by_id(
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.find_by_id(config, object.id)
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
- children = has_many_config.get_children(object)
196
- children.each do |child|
197
- has_many_config.set_foreign_key(mapping[child], object)
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
- child = has_one_config.get_child(object)
205
- has_one_config.set_foreign_key(mapping[child], object)
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
- child = belongs_to_config.get_child(object)
211
- belongs_to_config.set_foreign_key(mapping[object], child)
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
@@ -4,4 +4,8 @@ module Vorpal
4
4
  class InvalidAggregateRoot < StandardError; end
5
5
 
6
6
  class ConfigurationNotFound < StandardError; end
7
+
8
+ class ConfigurationError < StandardError; end
9
+
10
+ class InvariantViolated < StandardError; end
7
11
  end
@@ -28,9 +28,14 @@ module Vorpal
28
28
 
29
29
  def key(db_row)
30
30
  return nil unless db_row
31
- raise "Cannot map a DB row without an id '#{db_row.inspect}' to an entity." if db_row.id.nil?
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
- [db_row.id, db_row.class.name]
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
- attr_reader :objects
13
- def_delegators :objects, :each
11
+ def_delegators :@objects, :each
14
12
 
15
13
  def initialize
16
- @objects = Hash.new([])
17
- @objects_by_id = Hash.new
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.id)
23
- @objects_by_id[[config.domain_class.name, object.id]] = object
20
+ if !already_loaded?(config, object)
21
+ add_to_cache(config, object)
24
22
  end
25
- end
26
- add_to_hash(@objects, config, objects_to_add.compact)
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 find_by_id(config, id)
30
- @objects_by_id[[config.domain_class.name, id]]
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
- @objects_by_id.values
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
- def already_loaded?(config, id)
38
- !find_by_id(config, id).nil?
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
- module ArrayHash
5
- def add_to_hash(array_hash, key, values)
6
- if array_hash[key].nil? || array_hash[key].empty?
7
- array_hash[key] = []
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
- array_hash[key].concat(Array(values))
19
+ @hash[key].concat(Array(values))
10
20
  end
11
21
 
12
- def pop(array_hash)
13
- key = array_hash.first.first
14
- values = array_hash.delete(key)
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
@@ -2,7 +2,7 @@ module Vorpal
2
2
  module Util
3
3
  # @private
4
4
  module HashInitialization
5
- def initialize(attrs)
5
+ def initialize(attrs={})
6
6
  attrs.each do |k,v|
7
7
  instance_variable_set("@#{k}", v)
8
8
  end
@@ -1,3 +1,3 @@
1
1
  module Vorpal
2
- VERSION = "1.2.1"
2
+ VERSION = "1.3.0"
3
3
  end
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.2.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: 2020-11-26 00:00:00.000000000 Z
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
@@ -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