vorpal 1.0.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +73 -26
  3. data/lib/vorpal/aggregate_mapper.rb +13 -2
  4. data/lib/vorpal/aggregate_traversal.rb +9 -8
  5. data/lib/vorpal/config/association_config.rb +84 -0
  6. data/lib/vorpal/config/belongs_to_config.rb +35 -0
  7. data/lib/vorpal/config/class_config.rb +71 -0
  8. data/lib/vorpal/config/configs.rb +54 -0
  9. data/lib/vorpal/config/foreign_key_info.rb +23 -0
  10. data/lib/vorpal/config/has_many_config.rb +38 -0
  11. data/lib/vorpal/config/has_one_config.rb +35 -0
  12. data/lib/vorpal/config/main_config.rb +68 -0
  13. data/lib/vorpal/db_loader.rb +25 -22
  14. data/lib/vorpal/driver/postgresql.rb +42 -6
  15. data/lib/vorpal/dsl/config_builder.rb +26 -73
  16. data/lib/vorpal/dsl/configuration.rb +139 -42
  17. data/lib/vorpal/dsl/defaults_generator.rb +1 -1
  18. data/lib/vorpal/engine.rb +27 -13
  19. data/lib/vorpal/exceptions.rb +4 -0
  20. data/lib/vorpal/identity_map.rb +7 -2
  21. data/lib/vorpal/loaded_objects.rb +57 -14
  22. data/lib/vorpal/util/array_hash.rb +22 -8
  23. data/lib/vorpal/util/hash_initialization.rb +1 -1
  24. data/lib/vorpal/version.rb +1 -1
  25. data/vorpal.gemspec +4 -5
  26. metadata +18 -78
  27. data/.editorconfig +0 -13
  28. data/.envrc +0 -4
  29. data/.gitignore +0 -16
  30. data/.rspec +0 -1
  31. data/.ruby-version +0 -1
  32. data/.travis.yml +0 -18
  33. data/.yardopts +0 -1
  34. data/Appraisals +0 -18
  35. data/Gemfile +0 -4
  36. data/Rakefile +0 -39
  37. data/bin/appraisal +0 -29
  38. data/bin/rake +0 -29
  39. data/bin/rspec +0 -29
  40. data/docker-compose.yml +0 -19
  41. data/gemfiles/rails_5_1.gemfile +0 -11
  42. data/gemfiles/rails_5_1.gemfile.lock +0 -101
  43. data/gemfiles/rails_5_2.gemfile +0 -11
  44. data/gemfiles/rails_5_2.gemfile.lock +0 -101
  45. data/gemfiles/rails_6_0.gemfile +0 -9
  46. data/gemfiles/rails_6_0.gemfile.lock +0 -101
  47. data/lib/vorpal/configs.rb +0 -296
  48. data/spec/acceptance/vorpal/aggregate_mapper_spec.rb +0 -910
  49. data/spec/helpers/codecov_helper.rb +0 -7
  50. data/spec/helpers/db_helpers.rb +0 -69
  51. data/spec/helpers/profile_helpers.rb +0 -26
  52. data/spec/integration/vorpal/driver/postgresql_spec.rb +0 -42
  53. data/spec/integration_spec_helper.rb +0 -29
  54. data/spec/performance/vorpal/performance_spec.rb +0 -305
  55. data/spec/unit/vorpal/configs_spec.rb +0 -117
  56. data/spec/unit/vorpal/db_loader_spec.rb +0 -103
  57. data/spec/unit/vorpal/dsl/config_builder_spec.rb +0 -18
  58. data/spec/unit/vorpal/dsl/defaults_generator_spec.rb +0 -75
  59. data/spec/unit/vorpal/identity_map_spec.rb +0 -62
  60. data/spec/unit/vorpal/loaded_objects_spec.rb +0 -22
  61. data/spec/unit/vorpal/util/string_utils_spec.rb +0 -25
  62. data/spec/unit_spec_helper.rb +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6567d89919ba2836790c9d13fa729c43c21723fbcfa12ef8f77be2552e3f5df5
4
- data.tar.gz: 3b5ebeea70138f7cf6e4e68c0e21d4024e3732c1cf6cbe1f04054a03fab815d0
3
+ metadata.gz: c236d5dee0ca27224473d24e59680317a5b30fae7e400a13738298d27e6f1e74
4
+ data.tar.gz: ffe4922245341adc4ef2e4ed25751dee28a03232c2bb5b19699120f07d7fd3d6
5
5
  SHA512:
6
- metadata.gz: 7f991b2d7a25ebf9d85bc7a2af59c02e7105ff981724372167e13e6a30ffcd4a9bfdcd7db5a886b22bbbd9c86e52065a69a9e8c072e805420d16b1cdb4ed0151
7
- data.tar.gz: 5bdcb19c91b9ce1de724caa2231d86903e472d65ebd6a47a7fe39c705b31437b6b797e9a0df70c841251581ea882a5d0a33c1fdc236030fe951fc9bb1cf3c723
6
+ metadata.gz: d6ba4c4ab43cc66a776be399be185130665ea4951a18a34e664b45d851801b37260ce4eee343f81511910e739f7451b3b30ead059c24712e9ee577c579fdeffa
7
+ data.tar.gz: e134e15a04d0e7037fbbedf83f5e381e281e72f8bae51e43b0f9b488877477d7d6dfb7350c037e9bfb06279ac78d74024b1e592725cd8673348bf6ad4d6f61c7
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Vorpal [![Build Status](https://travis-ci.org/nulogy/vorpal.svg?branch=master)](https://travis-ci.org/nulogy/vorpal) [![Code Climate](https://codeclimate.com/github/nulogy/vorpal/badges/gpa.svg)](https://codeclimate.com/github/nulogy/vorpal) [![Code Coverage](https://codecov.io/gh/nulogy/vorpal/branch/master/graph/badge.svg)](https://codecov.io/gh/nulogy/vorpal/branch/master/graph/badge.svg)
1
+ # Vorpal [![Build Status](https://travis-ci.com/nulogy/vorpal.svg?branch=main)](https://travis-ci.com/nulogy/vorpal) [![Code Climate](https://codeclimate.com/github/nulogy/vorpal/badges/gpa.svg)](https://codeclimate.com/github/nulogy/vorpal) [![Code Coverage](https://codecov.io/gh/nulogy/vorpal/branch/main/graph/badge.svg)](https://codecov.io/gh/nulogy/vorpal/branch/main)
2
2
 
3
3
  Separate your domain model from your persistence mechanism. Some problems call for a really sharp tool.
4
4
 
@@ -45,27 +45,21 @@ Or install it yourself as:
45
45
  Start with a domain model of POROs and AR::Base objects that form an aggregate:
46
46
 
47
47
  ```ruby
48
- class Tree; end
49
-
50
48
  class Branch
51
- include Virtus.model
52
-
53
- attribute :id, Integer
54
- attribute :length, Decimal
55
- attribute :diameter, Decimal
56
- attribute :tree, Tree
49
+ attr_accessor :id
50
+ attr_accessor :length
51
+ attr_accessor :diameter
52
+ attr_accessor :tree
57
53
  end
58
54
 
59
- class Gardener < ActiveRecord::Base
55
+ class Gardener
60
56
  end
61
57
 
62
58
  class Tree
63
- include Virtus.model
64
-
65
- attribute :id, Integer
66
- attribute :name, String
67
- attribute :gardener, Gardener
68
- attribute :branches, Array[Branch]
59
+ attr_accessor :id
60
+ attr_accessor :name
61
+ attr_accessor :gardener
62
+ attr_accessor :branches
69
63
  end
70
64
  ```
71
65
 
@@ -162,9 +156,33 @@ TreeRepository.destroy(dead_tree)
162
156
  TreeRepository.destroy_by_id(dead_tree_id)
163
157
  ```
164
158
 
159
+ ### Ids
160
+
161
+ Vorpal by default will use auto-incrementing Integers from a DB sequence for ids. However, UUID v4 ids are also
162
+ supported:
163
+
164
+ ```ruby
165
+ Vorpal.define do
166
+ # UUID v4 id!
167
+ map Tree, primary_key_type: :uuid do
168
+ # ..
169
+ end
170
+
171
+ # Also a UUID v4 id, the Rails Way!
172
+ map Trunk, id: :uuid do
173
+ # ..
174
+ end
175
+
176
+ # If you feel the need to specify an auto-incrementing integer id.
177
+ map Branch, primary_key_type: :serial do
178
+ # ..
179
+ end
180
+ end
181
+ ```
182
+
165
183
  ## API Documentation
166
184
 
167
- http://rubydoc.info/github/nulogy/vorpal/master/frames
185
+ http://rubydoc.info/github/nulogy/vorpal
168
186
 
169
187
  ## Caveats
170
188
  It also does not do some things that you might expect from other ORMs:
@@ -181,13 +199,11 @@ It also does not do some things that you might expect from other ORMs:
181
199
  1. Only supports PostgreSQL.
182
200
 
183
201
  ## Future Enhancements
184
- * Aggregate updated_at.
185
- * Support for other DBMSs (no MySQL support until ids can be generated without inserting into a table!)
186
- * Support for other ORMs.
187
- * Value objects.
188
- * Remove dependency on ActiveRecord (optimistic locking? updated_at, created_at support? Data type conversions? TimeZone support?)
189
- * More efficient updates (use fewer queries.)
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".
190
204
  * Nicer DSL for specifying attributes that have different names in the domain model than in the DB.
205
+ * Aggregate updated_at.
206
+ * Better support for value objects.
191
207
 
192
208
  ## FAQ
193
209
 
@@ -203,11 +219,12 @@ It also does not do some things that you might expect from other ORMs:
203
219
 
204
220
  **A.** Create a method on a [Repository](http://martinfowler.com/eaaCatalog/repository.html)! They have full access to the DB/ORM so you can use [Arel](https://github.com/rails/arel) and go [crazy](http://asciicasts.com/episodes/239-activerecord-relation-walkthrough) or use direct SQL if you want.
205
221
 
206
- For example:
222
+ For example, use the [#query](https://rubydoc.info/github/nulogy/vorpal/main/Vorpal/AggregateMapper#query-instance_method) method on the [AggregateMapper](https://rubydoc.info/github/nulogy/vorpal/main/Vorpal/AggregateMapper) to access the underyling [ActiveRecordRelation](https://api.rubyonrails.org/classes/ActiveRecord/Relation.html):
207
223
 
208
224
  ```ruby
209
- def find_all
210
- @mapper.query.load_all # use the mapper to load all the aggregates
225
+ def find_special_ones
226
+ # use `load_all` or `load_one` to convert from ActiveRecord objects to domain POROs.
227
+ @mapper.query.where(special: true).load_all
211
228
  end
212
229
  ```
213
230
 
@@ -229,6 +246,28 @@ For example:
229
246
 
230
247
  **A.** You can use [ActiveModel::Serialization](http://api.rubyonrails.org/classes/ActiveModel/Serialization.html) or [ActiveModel::Serializers](https://github.com/rails-api/active_model_serializers) but they are not heartily recommended. The former is too coupled to the model and the latter is too coupled to Rails controllers. Vorpal uses [SimpleSerializer](https://github.com/nulogy/simple_serializer) for this purpose.
231
248
 
249
+ **Q.** Are `updated_at` and `created_at` supported?
250
+
251
+ **A.** Yes. If they exist on your database tables, they will behave exactly as if you were using vanilla ActiveRecord.
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
+
232
271
  ## Contributing
233
272
 
234
273
  1. Fork it ( https://github.com/nulogy/vorpal/fork )
@@ -265,6 +304,14 @@ For example:
265
304
 
266
305
  Please see the [Appraisal gem docs](https://github.com/thoughtbot/appraisal) for more information.
267
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
+
268
315
  ## Contributors
269
316
 
270
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
@@ -84,6 +84,17 @@ module Vorpal
84
84
  @engine
85
85
  end
86
86
 
87
+ # Returns a 'Vorpal-aware' [ActiveRecord::Relation](https://api.rubyonrails.org/classes/ActiveRecord/Relation.html)
88
+ # for the ActiveRecord object underlying the domain entity mapped by this mapper.
89
+ #
90
+ # This method allows you to easily access the power of ActiveRecord::Relation to do more complex
91
+ # queries in your repositories.
92
+ #
93
+ # The ActiveRecord::Relation is 'Vorpal-aware' because it has the {#load_one} and {#load_many} methods
94
+ # mixed in so that you can get the POROs from your domain model instead of the ActiveRecord
95
+ # objects normally returned by ActiveRecord::Relation.
96
+ #
97
+ # @return [ActiveRecord::Relation]
87
98
  def query
88
99
  @engine.query(@domain_class)
89
100
  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,71 @@
1
+ require 'equalizer'
2
+
3
+ module Vorpal
4
+ module Config
5
+ # @private
6
+ class ClassConfig
7
+ include Equalizer.new(:domain_class, :db_class)
8
+ attr_reader :serializer, :deserializer, :domain_class, :db_class, :primary_key_type, :local_association_configs
9
+ attr_accessor :has_manys, :belongs_tos, :has_ones
10
+
11
+ ALLOWED_PRIMARY_KEY_TYPE_OPTIONS = [:serial, :uuid]
12
+
13
+ def initialize(attrs)
14
+ @has_manys = []
15
+ @belongs_tos = []
16
+ @has_ones = []
17
+ @local_association_configs = []
18
+
19
+ @serializer = attrs[:serializer]
20
+ @deserializer = attrs[:deserializer]
21
+ @domain_class = attrs[:domain_class]
22
+ @db_class = attrs[:db_class]
23
+ @primary_key_type = attrs[:primary_key_type]
24
+ raise "Invalid primary_key_type: '#{@primary_key_type}'" unless ALLOWED_PRIMARY_KEY_TYPE_OPTIONS.include?(@primary_key_type)
25
+ end
26
+
27
+ def build_db_object(attributes)
28
+ db_class.new(attributes)
29
+ end
30
+
31
+ def set_db_object_attributes(db_object, attributes)
32
+ db_object.attributes = attributes
33
+ end
34
+
35
+ def get_db_object_attributes(db_object)
36
+ symbolize_keys(db_object.attributes)
37
+ end
38
+
39
+ def serialization_required?
40
+ domain_class.superclass.name != 'ActiveRecord::Base'
41
+ end
42
+
43
+ def serialize(object)
44
+ serializer.serialize(object)
45
+ end
46
+
47
+ def deserialize(db_object)
48
+ attributes = get_db_object_attributes(db_object)
49
+ serialization_required? ? deserializer.deserialize(domain_class.new, attributes) : db_object
50
+ end
51
+
52
+ def set_attribute(db_object, attribute, value)
53
+ db_object.send("#{attribute}=", value)
54
+ end
55
+
56
+ def get_attribute(db_object, attribute)
57
+ db_object.send(attribute)
58
+ end
59
+
60
+ private
61
+
62
+ def symbolize_keys(hash)
63
+ result = {}
64
+ hash.each_key do |key|
65
+ result[key.to_sym] = hash[key]
66
+ end
67
+ result
68
+ end
69
+ end
70
+ end
71
+ end
@@ -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