dm-core 0.9.11 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +17 -14
- data/.gitignore +3 -1
- data/FAQ +6 -5
- data/History.txt +5 -50
- data/Manifest.txt +66 -76
- data/QUICKLINKS +1 -1
- data/README.txt +21 -15
- data/Rakefile +6 -7
- data/SPECS +2 -29
- data/TODO +1 -1
- data/deps.rip +2 -0
- data/dm-core.gemspec +11 -15
- data/lib/dm-core.rb +105 -110
- data/lib/dm-core/adapters.rb +135 -16
- data/lib/dm-core/adapters/abstract_adapter.rb +251 -181
- data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
- data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
- data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
- data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
- data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
- data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
- data/lib/dm-core/associations/many_to_many.rb +372 -90
- data/lib/dm-core/associations/many_to_one.rb +220 -73
- data/lib/dm-core/associations/one_to_many.rb +319 -255
- data/lib/dm-core/associations/one_to_one.rb +66 -53
- data/lib/dm-core/associations/relationship.rb +561 -156
- data/lib/dm-core/collection.rb +1101 -379
- data/lib/dm-core/core_ext/kernel.rb +12 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +4 -34
- data/lib/dm-core/migrations.rb +1283 -0
- data/lib/dm-core/model.rb +570 -369
- data/lib/dm-core/model/descendant_set.rb +81 -0
- data/lib/dm-core/model/hook.rb +45 -0
- data/lib/dm-core/model/is.rb +32 -0
- data/lib/dm-core/model/property.rb +247 -0
- data/lib/dm-core/model/relationship.rb +335 -0
- data/lib/dm-core/model/scope.rb +90 -0
- data/lib/dm-core/property.rb +808 -273
- data/lib/dm-core/property_set.rb +141 -98
- data/lib/dm-core/query.rb +1037 -483
- data/lib/dm-core/query/conditions/comparison.rb +872 -0
- data/lib/dm-core/query/conditions/operation.rb +221 -0
- data/lib/dm-core/query/direction.rb +43 -0
- data/lib/dm-core/query/operator.rb +84 -0
- data/lib/dm-core/query/path.rb +138 -0
- data/lib/dm-core/query/sort.rb +45 -0
- data/lib/dm-core/repository.rb +210 -94
- data/lib/dm-core/resource.rb +641 -421
- data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
- data/lib/dm-core/support/chainable.rb +22 -0
- data/lib/dm-core/support/deprecate.rb +12 -0
- data/lib/dm-core/support/logger.rb +13 -0
- data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
- data/lib/dm-core/transaction.rb +333 -92
- data/lib/dm-core/type.rb +98 -60
- data/lib/dm-core/types/boolean.rb +1 -1
- data/lib/dm-core/types/discriminator.rb +34 -20
- data/lib/dm-core/types/object.rb +7 -4
- data/lib/dm-core/types/paranoid_boolean.rb +11 -9
- data/lib/dm-core/types/paranoid_datetime.rb +11 -9
- data/lib/dm-core/types/serial.rb +3 -3
- data/lib/dm-core/types/text.rb +3 -4
- data/lib/dm-core/version.rb +1 -1
- data/script/performance.rb +102 -109
- data/script/profile.rb +169 -38
- data/spec/lib/adapter_helpers.rb +105 -0
- data/spec/lib/collection_helpers.rb +18 -0
- data/spec/lib/counter_adapter.rb +34 -0
- data/spec/lib/pending_helpers.rb +27 -0
- data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
- data/spec/public/associations/many_to_many_spec.rb +193 -0
- data/spec/public/associations/many_to_one_spec.rb +73 -0
- data/spec/public/associations/one_to_many_spec.rb +77 -0
- data/spec/public/associations/one_to_one_spec.rb +156 -0
- data/spec/public/collection_spec.rb +65 -0
- data/spec/public/migrations_spec.rb +359 -0
- data/spec/public/model/relationship_spec.rb +924 -0
- data/spec/public/model_spec.rb +159 -0
- data/spec/public/property_spec.rb +829 -0
- data/spec/public/resource_spec.rb +71 -0
- data/spec/public/sel_spec.rb +44 -0
- data/spec/public/setup_spec.rb +145 -0
- data/spec/public/shared/association_collection_shared_spec.rb +317 -0
- data/spec/public/shared/collection_shared_spec.rb +1670 -0
- data/spec/public/shared/finder_shared_spec.rb +1619 -0
- data/spec/public/shared/resource_shared_spec.rb +924 -0
- data/spec/public/shared/sel_shared_spec.rb +112 -0
- data/spec/public/transaction_spec.rb +129 -0
- data/spec/public/types/discriminator_spec.rb +130 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
- data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
- data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
- data/spec/semipublic/associations/relationship_spec.rb +194 -0
- data/spec/semipublic/associations_spec.rb +177 -0
- data/spec/semipublic/collection_spec.rb +142 -0
- data/spec/semipublic/property_spec.rb +61 -0
- data/spec/semipublic/query/conditions_spec.rb +528 -0
- data/spec/semipublic/query/path_spec.rb +443 -0
- data/spec/semipublic/query_spec.rb +2626 -0
- data/spec/semipublic/resource_spec.rb +47 -0
- data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
- data/spec/spec.opts +3 -1
- data/spec/spec_helper.rb +80 -57
- data/tasks/ci.rb +19 -31
- data/tasks/dm.rb +43 -48
- data/tasks/doc.rb +8 -11
- data/tasks/gemspec.rb +5 -5
- data/tasks/hoe.rb +15 -16
- data/tasks/install.rb +8 -10
- metadata +74 -111
- data/lib/dm-core/associations.rb +0 -207
- data/lib/dm-core/associations/relationship_chain.rb +0 -81
- data/lib/dm-core/auto_migrations.rb +0 -105
- data/lib/dm-core/dependency_queue.rb +0 -32
- data/lib/dm-core/hook.rb +0 -11
- data/lib/dm-core/is.rb +0 -16
- data/lib/dm-core/logger.rb +0 -232
- data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
- data/lib/dm-core/migrator.rb +0 -29
- data/lib/dm-core/scope.rb +0 -58
- data/lib/dm-core/support.rb +0 -7
- data/lib/dm-core/support/array.rb +0 -13
- data/lib/dm-core/support/assertions.rb +0 -8
- data/lib/dm-core/support/errors.rb +0 -23
- data/lib/dm-core/support/kernel.rb +0 -11
- data/lib/dm-core/support/symbol.rb +0 -41
- data/lib/dm-core/type_map.rb +0 -80
- data/lib/dm-core/types.rb +0 -19
- data/script/all +0 -4
- data/spec/integration/association_spec.rb +0 -1382
- data/spec/integration/association_through_spec.rb +0 -203
- data/spec/integration/associations/many_to_many_spec.rb +0 -449
- data/spec/integration/associations/many_to_one_spec.rb +0 -163
- data/spec/integration/associations/one_to_many_spec.rb +0 -188
- data/spec/integration/auto_migrations_spec.rb +0 -413
- data/spec/integration/collection_spec.rb +0 -1073
- data/spec/integration/data_objects_adapter_spec.rb +0 -32
- data/spec/integration/dependency_queue_spec.rb +0 -46
- data/spec/integration/model_spec.rb +0 -197
- data/spec/integration/mysql_adapter_spec.rb +0 -85
- data/spec/integration/postgres_adapter_spec.rb +0 -731
- data/spec/integration/property_spec.rb +0 -253
- data/spec/integration/query_spec.rb +0 -514
- data/spec/integration/repository_spec.rb +0 -61
- data/spec/integration/resource_spec.rb +0 -513
- data/spec/integration/sqlite3_adapter_spec.rb +0 -352
- data/spec/integration/sti_spec.rb +0 -273
- data/spec/integration/strategic_eager_loading_spec.rb +0 -156
- data/spec/integration/transaction_spec.rb +0 -75
- data/spec/integration/type_spec.rb +0 -275
- data/spec/lib/logging_helper.rb +0 -18
- data/spec/lib/mock_adapter.rb +0 -27
- data/spec/lib/model_loader.rb +0 -100
- data/spec/lib/publicize_methods.rb +0 -28
- data/spec/models/content.rb +0 -16
- data/spec/models/vehicles.rb +0 -34
- data/spec/models/zoo.rb +0 -48
- data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
- data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
- data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
- data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
- data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
- data/spec/unit/associations/many_to_many_spec.rb +0 -32
- data/spec/unit/associations/many_to_one_spec.rb +0 -159
- data/spec/unit/associations/one_to_many_spec.rb +0 -393
- data/spec/unit/associations/one_to_one_spec.rb +0 -7
- data/spec/unit/associations/relationship_spec.rb +0 -71
- data/spec/unit/associations_spec.rb +0 -242
- data/spec/unit/auto_migrations_spec.rb +0 -111
- data/spec/unit/collection_spec.rb +0 -182
- data/spec/unit/data_mapper_spec.rb +0 -35
- data/spec/unit/identity_map_spec.rb +0 -126
- data/spec/unit/is_spec.rb +0 -80
- data/spec/unit/migrator_spec.rb +0 -33
- data/spec/unit/model_spec.rb +0 -321
- data/spec/unit/naming_conventions_spec.rb +0 -36
- data/spec/unit/property_set_spec.rb +0 -90
- data/spec/unit/property_spec.rb +0 -753
- data/spec/unit/query_spec.rb +0 -571
- data/spec/unit/repository_spec.rb +0 -93
- data/spec/unit/resource_spec.rb +0 -649
- data/spec/unit/scope_spec.rb +0 -142
- data/spec/unit/transaction_spec.rb +0 -493
- data/spec/unit/type_map_spec.rb +0 -114
- data/spec/unit/type_spec.rb +0 -119
|
@@ -1,61 +1,74 @@
|
|
|
1
1
|
module DataMapper
|
|
2
2
|
module Associations
|
|
3
|
-
module OneToOne
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# @api private
|
|
9
|
-
def self.setup(name, model, options = {})
|
|
10
|
-
assert_kind_of 'name', name, Symbol
|
|
11
|
-
assert_kind_of 'model', model, Model
|
|
12
|
-
assert_kind_of 'options', options, Hash
|
|
13
|
-
|
|
14
|
-
repository_name = model.repository.name
|
|
15
|
-
|
|
16
|
-
model.class_eval <<-EOS, __FILE__, __LINE__
|
|
17
|
-
def #{name}
|
|
18
|
-
#{name}_association.first
|
|
3
|
+
module OneToOne #:nodoc:
|
|
4
|
+
class Relationship < Associations::Relationship
|
|
5
|
+
%w[ public protected private ].map do |visibility|
|
|
6
|
+
superclass.send("#{visibility}_instance_methods", false).each do |method|
|
|
7
|
+
undef_method method unless method.to_s == 'initialize'
|
|
19
8
|
end
|
|
9
|
+
end
|
|
20
10
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
# Loads (if necessary) and returns association target
|
|
12
|
+
# for given source
|
|
13
|
+
#
|
|
14
|
+
# @api semipublic
|
|
15
|
+
def get(source, other_query = nil)
|
|
16
|
+
assert_kind_of 'source', source, source_model
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)
|
|
57
|
-
end
|
|
58
|
-
|
|
18
|
+
return unless loaded?(source) || source_key.get(source).all?
|
|
19
|
+
|
|
20
|
+
relationship.get(source, other_query).first
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Sets and returns association target
|
|
24
|
+
# for given source
|
|
25
|
+
#
|
|
26
|
+
# @api semipublic
|
|
27
|
+
def set(source, target)
|
|
28
|
+
assert_kind_of 'source', source, source_model
|
|
29
|
+
assert_kind_of 'target', target, target_model, Hash, NilClass
|
|
30
|
+
|
|
31
|
+
relationship.set(source, [ target ].compact).first
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# TODO: document
|
|
35
|
+
# @api public
|
|
36
|
+
def kind_of?(klass)
|
|
37
|
+
super || relationship.kind_of?(klass)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# TODO: document
|
|
41
|
+
# @api public
|
|
42
|
+
def instance_of?(klass)
|
|
43
|
+
super || relationship.instance_of?(klass)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# TODO: document
|
|
47
|
+
# @api public
|
|
48
|
+
def respond_to?(method, include_private = false)
|
|
49
|
+
super || relationship.respond_to?(method, include_private)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
attr_reader :relationship
|
|
55
|
+
|
|
56
|
+
# Initializes the relationship. Always assumes target model class is
|
|
57
|
+
# a camel cased association name.
|
|
58
|
+
#
|
|
59
|
+
# @api semipublic
|
|
60
|
+
def initialize(name, target_model, source_model, options = {})
|
|
61
|
+
klass = options.key?(:through) ? ManyToMany::Relationship : OneToMany::Relationship
|
|
62
|
+
target_model ||= Extlib::Inflection.camelize(name).freeze
|
|
63
|
+
@relationship = klass.new(name, target_model, source_model, options)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# TODO: document
|
|
67
|
+
# @api private
|
|
68
|
+
def method_missing(method, *args, &block)
|
|
69
|
+
relationship.send(method, *args, &block)
|
|
70
|
+
end
|
|
71
|
+
end # class Relationship
|
|
59
72
|
end # module HasOne
|
|
60
73
|
end # module Associations
|
|
61
74
|
end # module DataMapper
|
|
@@ -1,226 +1,631 @@
|
|
|
1
1
|
module DataMapper
|
|
2
2
|
module Associations
|
|
3
|
+
# Base class for relationships. Each type of relationship
|
|
4
|
+
# (1 to 1, 1 to n, n to m) implements a subclass of this class
|
|
5
|
+
# with methods like get and set overridden.
|
|
3
6
|
class Relationship
|
|
4
|
-
include Assertions
|
|
7
|
+
include Extlib::Assertions
|
|
8
|
+
|
|
9
|
+
OPTIONS = [ :child_repository_name, :parent_repository_name, :child_key, :parent_key, :min, :max, :inverse ].to_set
|
|
10
|
+
|
|
11
|
+
# Relationship name
|
|
12
|
+
#
|
|
13
|
+
# @example for :parent association in
|
|
14
|
+
#
|
|
15
|
+
# class VersionControl::Commit
|
|
16
|
+
# # ...
|
|
17
|
+
#
|
|
18
|
+
# belongs_to :parent
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# name is :parent
|
|
22
|
+
#
|
|
23
|
+
# @api semipublic
|
|
24
|
+
attr_reader :name
|
|
25
|
+
|
|
26
|
+
# Options used to set up association of this relationship
|
|
27
|
+
#
|
|
28
|
+
# @example for :author association in
|
|
29
|
+
#
|
|
30
|
+
# class VersionControl::Commit
|
|
31
|
+
# # ...
|
|
32
|
+
#
|
|
33
|
+
# belongs_to :author, :model => 'Person'
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# options is a hash with a single key, :model
|
|
37
|
+
#
|
|
38
|
+
# @api semipublic
|
|
39
|
+
attr_reader :options
|
|
40
|
+
|
|
41
|
+
# ivar used to store collection of child options in source
|
|
42
|
+
#
|
|
43
|
+
# @example for :commits association in
|
|
44
|
+
#
|
|
45
|
+
# class VersionControl::Branch
|
|
46
|
+
# # ...
|
|
47
|
+
#
|
|
48
|
+
# has n, :commits
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# instance variable name for source will be @commits
|
|
52
|
+
#
|
|
53
|
+
# @api semipublic
|
|
54
|
+
attr_reader :instance_variable_name
|
|
55
|
+
|
|
56
|
+
# Repository from where child objects are loaded
|
|
57
|
+
#
|
|
58
|
+
# @api semipublic
|
|
59
|
+
attr_reader :child_repository_name
|
|
60
|
+
|
|
61
|
+
# Repository from where parent objects are loaded
|
|
62
|
+
#
|
|
63
|
+
# @api semipublic
|
|
64
|
+
attr_reader :parent_repository_name
|
|
65
|
+
|
|
66
|
+
# Minimum number of child objects for relationship
|
|
67
|
+
#
|
|
68
|
+
# @example for :cores association in
|
|
69
|
+
#
|
|
70
|
+
# class CPU::Multicore
|
|
71
|
+
# # ...
|
|
72
|
+
#
|
|
73
|
+
# has 2..n, :cores
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
# minimum is 2
|
|
77
|
+
#
|
|
78
|
+
# @api semipublic
|
|
79
|
+
attr_reader :min
|
|
80
|
+
|
|
81
|
+
# Maximum number of child objects for
|
|
82
|
+
# relationship
|
|
83
|
+
#
|
|
84
|
+
# @example for :fouls association in
|
|
85
|
+
#
|
|
86
|
+
# class Basketball::Player
|
|
87
|
+
# # ...
|
|
88
|
+
#
|
|
89
|
+
# has 0..5, :fouls
|
|
90
|
+
# end
|
|
91
|
+
#
|
|
92
|
+
# maximum is 5
|
|
93
|
+
#
|
|
94
|
+
# @api semipublic
|
|
95
|
+
attr_reader :max
|
|
96
|
+
|
|
97
|
+
# Returns query options for relationship.
|
|
98
|
+
#
|
|
99
|
+
# For this base class, always returns query options
|
|
100
|
+
# has been initialized with.
|
|
101
|
+
# Overriden in subclasses.
|
|
102
|
+
#
|
|
103
|
+
# @api private
|
|
104
|
+
attr_reader :query
|
|
105
|
+
|
|
106
|
+
# Returns a hash of conditions that scopes query that fetches
|
|
107
|
+
# target object
|
|
108
|
+
#
|
|
109
|
+
# @return [Hash]
|
|
110
|
+
# Hash of conditions that scopes query
|
|
111
|
+
#
|
|
112
|
+
# @api private
|
|
113
|
+
def source_scope(source)
|
|
114
|
+
{ inverse => source }
|
|
115
|
+
end
|
|
5
116
|
|
|
6
|
-
|
|
117
|
+
# Creates and returns Query instance that fetches
|
|
118
|
+
# target resource(s) (ex.: articles) for given target resource (ex.: author)
|
|
119
|
+
#
|
|
120
|
+
# @api semipublic
|
|
121
|
+
def query_for(source, other_query = nil)
|
|
122
|
+
repository_name = relative_target_repository_name_for(source)
|
|
123
|
+
|
|
124
|
+
DataMapper.repository(repository_name).scope do
|
|
125
|
+
query = target_model.query.dup
|
|
126
|
+
query.update(self.query)
|
|
127
|
+
query.update(source_scope(source))
|
|
128
|
+
query.update(other_query) if other_query
|
|
129
|
+
query.update(:fields => query.fields | target_key)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
7
132
|
|
|
133
|
+
# Returns model class used by child side of the relationship
|
|
134
|
+
#
|
|
135
|
+
# @return [Resource]
|
|
136
|
+
# Model for association child
|
|
137
|
+
#
|
|
8
138
|
# @api private
|
|
9
|
-
|
|
139
|
+
def child_model
|
|
140
|
+
@child_model ||= (@parent_model || Object).find_const(child_model_name)
|
|
141
|
+
rescue NameError
|
|
142
|
+
raise NameError, "Cannot find the child_model #{child_model_name} for #{parent_model_name} in #{name}"
|
|
143
|
+
end
|
|
10
144
|
|
|
145
|
+
# TODO: document
|
|
11
146
|
# @api private
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
child_key = parent_key.zip(@child_properties || []).map do |parent_property,property_name|
|
|
19
|
-
# TODO: use something similar to DM::NamingConventions to determine the property name
|
|
20
|
-
parent_name = Extlib::Inflection.underscore(Extlib::Inflection.demodulize(parent_model.base_model.name))
|
|
21
|
-
property_name ||= "#{parent_name}_#{parent_property.name}".to_sym
|
|
22
|
-
|
|
23
|
-
if model_properties.has_property?(property_name)
|
|
24
|
-
model_properties[property_name]
|
|
25
|
-
else
|
|
26
|
-
options = {}
|
|
27
|
-
|
|
28
|
-
[ :length, :precision, :scale ].each do |option|
|
|
29
|
-
options[option] = parent_property.send(option)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# NOTE: hack to make each many to many child_key a true key,
|
|
33
|
-
# until I can figure out a better place for this check
|
|
34
|
-
if child_model.respond_to?(:many_to_many)
|
|
35
|
-
options[:key] = true
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
child_model.property(property_name, parent_property.primitive, options)
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
PropertySet.new(child_key)
|
|
43
|
-
end
|
|
147
|
+
def child_model?
|
|
148
|
+
child_model
|
|
149
|
+
true
|
|
150
|
+
rescue NameError
|
|
151
|
+
false
|
|
44
152
|
end
|
|
45
153
|
|
|
154
|
+
# TODO: document
|
|
46
155
|
# @api private
|
|
47
|
-
def
|
|
48
|
-
@
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
156
|
+
def child_model_name
|
|
157
|
+
@child_model ? child_model.name : @child_model_name
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Returns a set of keys that identify the target model
|
|
161
|
+
#
|
|
162
|
+
# @return [PropertySet]
|
|
163
|
+
# a set of properties that identify the target model
|
|
164
|
+
#
|
|
165
|
+
# @api semipublic
|
|
166
|
+
def child_key
|
|
167
|
+
return @child_key if defined?(@child_key)
|
|
168
|
+
|
|
169
|
+
repository_name = child_repository_name || parent_repository_name
|
|
170
|
+
properties = child_model.properties(repository_name)
|
|
171
|
+
|
|
172
|
+
@child_key = if @child_properties
|
|
173
|
+
child_key = properties.values_at(*@child_properties)
|
|
174
|
+
properties.class.new(child_key).freeze
|
|
175
|
+
else
|
|
176
|
+
properties.key
|
|
58
177
|
end
|
|
59
178
|
end
|
|
60
179
|
|
|
180
|
+
# Access Relationship#child_key directly
|
|
181
|
+
#
|
|
182
|
+
# @api private
|
|
183
|
+
alias relationship_child_key child_key
|
|
184
|
+
private :relationship_child_key
|
|
185
|
+
|
|
186
|
+
# Returns model class used by parent side of the relationship
|
|
187
|
+
#
|
|
188
|
+
# @return [Resource]
|
|
189
|
+
# Class of association parent
|
|
190
|
+
#
|
|
61
191
|
# @api private
|
|
62
192
|
def parent_model
|
|
63
|
-
|
|
64
|
-
@parent_model = @child_model.find_const(@parent_model)
|
|
193
|
+
@parent_model ||= (@child_model || Object).find_const(parent_model_name)
|
|
65
194
|
rescue NameError
|
|
66
|
-
raise NameError, "Cannot find the parent_model #{
|
|
195
|
+
raise NameError, "Cannot find the parent_model #{parent_model_name} for #{child_model_name} in #{name}"
|
|
67
196
|
end
|
|
68
197
|
|
|
198
|
+
# TODO: document
|
|
69
199
|
# @api private
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
200
|
+
def parent_model?
|
|
201
|
+
parent_model
|
|
202
|
+
true
|
|
73
203
|
rescue NameError
|
|
74
|
-
|
|
204
|
+
false
|
|
75
205
|
end
|
|
76
206
|
|
|
207
|
+
# TODO: document
|
|
77
208
|
# @api private
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
209
|
+
def parent_model_name
|
|
210
|
+
@parent_model ? parent_model.name : @parent_model_name
|
|
211
|
+
end
|
|
81
212
|
|
|
82
|
-
|
|
83
|
-
|
|
213
|
+
# Returns a set of keys that identify parent model
|
|
214
|
+
#
|
|
215
|
+
# @return [PropertySet]
|
|
216
|
+
# a set of properties that identify parent model
|
|
217
|
+
#
|
|
218
|
+
# @api private
|
|
219
|
+
def parent_key
|
|
220
|
+
return @parent_key if defined?(@parent_key)
|
|
84
221
|
|
|
85
|
-
|
|
222
|
+
repository_name = parent_repository_name || child_repository_name
|
|
223
|
+
properties = parent_model.properties(repository_name)
|
|
86
224
|
|
|
87
|
-
|
|
88
|
-
|
|
225
|
+
@parent_key = if @parent_properties
|
|
226
|
+
parent_key = properties.values_at(*@parent_properties)
|
|
227
|
+
properties.class.new(parent_key).freeze
|
|
228
|
+
else
|
|
229
|
+
properties.key
|
|
230
|
+
end
|
|
231
|
+
end
|
|
89
232
|
|
|
90
|
-
|
|
233
|
+
# Loads and returns "other end" of the association.
|
|
234
|
+
# Must be implemented in subclasses.
|
|
235
|
+
#
|
|
236
|
+
# @api semipublic
|
|
237
|
+
def get(resource, other_query = nil)
|
|
238
|
+
raise NotImplementedError, "#{self.class}#get not implemented"
|
|
239
|
+
end
|
|
91
240
|
|
|
92
|
-
|
|
241
|
+
# Gets "other end" of the association directly
|
|
242
|
+
# as @ivar on given resource. Subclasses usually
|
|
243
|
+
# use implementation of this class.
|
|
244
|
+
#
|
|
245
|
+
# @api semipublic
|
|
246
|
+
def get!(resource)
|
|
247
|
+
resource.instance_variable_get(instance_variable_name)
|
|
248
|
+
end
|
|
93
249
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
250
|
+
# Sets value of the "other end" of association
|
|
251
|
+
# on given resource. Must be implemented in subclasses.
|
|
252
|
+
#
|
|
253
|
+
# @api semipublic
|
|
254
|
+
def set(resource, association)
|
|
255
|
+
raise NotImplementedError, "#{self.class}#set not implemented"
|
|
256
|
+
end
|
|
101
257
|
|
|
102
|
-
|
|
258
|
+
# Sets "other end" of the association directly
|
|
259
|
+
# as @ivar on given resource. Subclasses usually
|
|
260
|
+
# use implementation of this class.
|
|
261
|
+
#
|
|
262
|
+
# @api semipublic
|
|
263
|
+
def set!(resource, association)
|
|
264
|
+
resource.instance_variable_set(instance_variable_name, association)
|
|
265
|
+
end
|
|
103
266
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
267
|
+
# Eager load the collection using the source as a base
|
|
268
|
+
#
|
|
269
|
+
# @param [Resource, Collection] source
|
|
270
|
+
# the source to query with
|
|
271
|
+
# @param [Query, Hash] query
|
|
272
|
+
# optional query to restrict the collection
|
|
273
|
+
#
|
|
274
|
+
# @return [Collection]
|
|
275
|
+
# the loaded collection for the source
|
|
276
|
+
#
|
|
277
|
+
# @api private
|
|
278
|
+
def eager_load(source, query = nil)
|
|
279
|
+
target_maps = Hash.new { |h,k| h[k] = [] }
|
|
107
280
|
|
|
108
|
-
|
|
109
|
-
query.conditions.map! do |operator, property, bind_value|
|
|
110
|
-
if operator != :raw && child_key.has_property?(property.name)
|
|
111
|
-
bind_value = *children.map { |child| property.get(child) }.uniq
|
|
112
|
-
end
|
|
113
|
-
[ operator, property, bind_value ]
|
|
114
|
-
end
|
|
281
|
+
collection_query = query_for(source, query)
|
|
115
282
|
|
|
116
|
-
|
|
117
|
-
|
|
283
|
+
# TODO: create an object that wraps this logic, and when the first
|
|
284
|
+
# kicker is fired, then it'll load up the collection, and then
|
|
285
|
+
# populate all the other methods
|
|
118
286
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
association.instance_variable_set(:@children, parents_children)
|
|
123
|
-
end
|
|
124
|
-
end
|
|
287
|
+
collection = source.model.all(collection_query).each do |target|
|
|
288
|
+
target_maps[target_key.get(target)] << target
|
|
289
|
+
end
|
|
125
290
|
|
|
126
|
-
|
|
291
|
+
Array(source).each do |source|
|
|
292
|
+
key = target_key.typecast(source_key.get(source))
|
|
293
|
+
eager_load_targets(source, target_maps[key], query)
|
|
127
294
|
end
|
|
295
|
+
|
|
296
|
+
collection
|
|
128
297
|
end
|
|
129
298
|
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
299
|
+
# Checks if "other end" of association is loaded on given
|
|
300
|
+
# resource.
|
|
301
|
+
#
|
|
302
|
+
# @api semipublic
|
|
303
|
+
def loaded?(resource)
|
|
304
|
+
assert_kind_of 'resource', resource, source_model
|
|
134
305
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
child_identity_map = child.repository.identity_map(child_model.base_model)
|
|
306
|
+
resource.instance_variable_defined?(instance_variable_name)
|
|
307
|
+
end
|
|
138
308
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
309
|
+
# Test the source to see if it is a valid target
|
|
310
|
+
#
|
|
311
|
+
# @param [Object] source
|
|
312
|
+
# the resource or collection to be tested
|
|
313
|
+
#
|
|
314
|
+
# @return [Boolean]
|
|
315
|
+
# true if the resource is valid
|
|
316
|
+
#
|
|
317
|
+
# @api semipulic
|
|
318
|
+
def valid?(source)
|
|
319
|
+
return true if source.nil?
|
|
320
|
+
|
|
321
|
+
case source
|
|
322
|
+
when Array, Collection then valid_collection?(source)
|
|
323
|
+
when Resource then valid_resource?(source)
|
|
324
|
+
else
|
|
325
|
+
raise ArgumentError, "+source+ should be an Array or Resource, but was a #{source.class.name}"
|
|
326
|
+
end
|
|
327
|
+
end
|
|
142
328
|
|
|
143
|
-
|
|
144
|
-
|
|
329
|
+
# Compares another Relationship for equality
|
|
330
|
+
#
|
|
331
|
+
# @param [Relationship] other
|
|
332
|
+
# the other Relationship to compare with
|
|
333
|
+
#
|
|
334
|
+
# @return [Boolean]
|
|
335
|
+
# true if they are equal, false if not
|
|
336
|
+
#
|
|
337
|
+
# @api public
|
|
338
|
+
def eql?(other)
|
|
339
|
+
return true if equal?(other)
|
|
340
|
+
instance_of?(other.class) && cmp?(other, :eql?)
|
|
341
|
+
end
|
|
145
342
|
|
|
146
|
-
|
|
147
|
-
|
|
343
|
+
# Compares another Relationship for equivalency
|
|
344
|
+
#
|
|
345
|
+
# @param [Relationship] other
|
|
346
|
+
# the other Relationship to compare with
|
|
347
|
+
#
|
|
348
|
+
# @return [Boolean]
|
|
349
|
+
# true if they are equal, false if not
|
|
350
|
+
#
|
|
351
|
+
# @api public
|
|
352
|
+
def ==(other)
|
|
353
|
+
return true if equal?(other)
|
|
354
|
+
return false if kind_of_inverse?(other)
|
|
355
|
+
|
|
356
|
+
other.respond_to?(:cmp_repository?, true) &&
|
|
357
|
+
other.respond_to?(:cmp_model?, true) &&
|
|
358
|
+
other.respond_to?(:cmp_key?, true) &&
|
|
359
|
+
other.respond_to?(:query) &&
|
|
360
|
+
cmp?(other, :==)
|
|
361
|
+
end
|
|
148
362
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
363
|
+
# Get the inverse relationship from the target model
|
|
364
|
+
#
|
|
365
|
+
# @api semipublic
|
|
366
|
+
def inverse
|
|
367
|
+
return @inverse if defined?(@inverse)
|
|
152
368
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
collection.send(:lazy_load)
|
|
156
|
-
children.each do |c|
|
|
157
|
-
c.send(association_accessor).instance_variable_set(:@parent, collection.get(*child_key.get(c)))
|
|
158
|
-
end
|
|
159
|
-
child.send(association_accessor).instance_variable_get(:@parent)
|
|
160
|
-
end
|
|
369
|
+
if kind_of_inverse?(options[:inverse])
|
|
370
|
+
return @inverse = options[:inverse]
|
|
161
371
|
end
|
|
372
|
+
|
|
373
|
+
relationships = target_model.relationships(relative_target_repository_name).values
|
|
374
|
+
|
|
375
|
+
@inverse = relationships.detect { |relationship| inverse?(relationship) } ||
|
|
376
|
+
invert
|
|
377
|
+
|
|
378
|
+
@inverse.child_key
|
|
379
|
+
|
|
380
|
+
@inverse
|
|
162
381
|
end
|
|
163
382
|
|
|
383
|
+
# TODO: document
|
|
164
384
|
# @api private
|
|
165
|
-
def
|
|
166
|
-
|
|
167
|
-
|
|
385
|
+
def relative_target_repository_name
|
|
386
|
+
target_repository_name || source_repository_name
|
|
387
|
+
end
|
|
168
388
|
|
|
169
|
-
|
|
170
|
-
|
|
389
|
+
# TODO: document
|
|
390
|
+
# @api private
|
|
391
|
+
def relative_target_repository_name_for(source)
|
|
392
|
+
target_repository_name || if source.respond_to?(:repository)
|
|
393
|
+
source.repository.name
|
|
171
394
|
else
|
|
172
|
-
|
|
395
|
+
source_repository_name
|
|
173
396
|
end
|
|
174
397
|
end
|
|
175
398
|
|
|
399
|
+
private
|
|
400
|
+
|
|
401
|
+
# TODO: document
|
|
176
402
|
# @api private
|
|
177
|
-
|
|
178
|
-
child_key.set(child, parent && parent_key.get(parent))
|
|
179
|
-
end
|
|
403
|
+
attr_reader :child_properties
|
|
180
404
|
|
|
181
|
-
|
|
405
|
+
# TODO: document
|
|
406
|
+
# @api private
|
|
407
|
+
attr_reader :parent_properties
|
|
408
|
+
|
|
409
|
+
# Initializes new Relationship: sets attributes of relationship
|
|
410
|
+
# from options as well as conventions: for instance, @ivar name
|
|
411
|
+
# for association is constructed by prefixing @ to association name.
|
|
412
|
+
#
|
|
413
|
+
# Once attributes are set, reader and writer are created for
|
|
414
|
+
# the resource association belongs to
|
|
415
|
+
#
|
|
416
|
+
# @api semipublic
|
|
417
|
+
def initialize(name, child_model, parent_model, options = {})
|
|
418
|
+
initialize_object_ivar('child_model', child_model)
|
|
419
|
+
initialize_object_ivar('parent_model', parent_model)
|
|
420
|
+
|
|
421
|
+
@name = name
|
|
422
|
+
@instance_variable_name = "@#{@name}".freeze
|
|
423
|
+
@options = options.dup.freeze
|
|
424
|
+
@child_repository_name = @options[:child_repository_name]
|
|
425
|
+
@parent_repository_name = @options[:parent_repository_name]
|
|
426
|
+
@child_properties = @options[:child_key].try_dup.freeze
|
|
427
|
+
@parent_properties = @options[:parent_key].try_dup.freeze
|
|
428
|
+
@min = @options[:min]
|
|
429
|
+
@max = @options[:max]
|
|
430
|
+
|
|
431
|
+
# TODO: normalize the @query to become :conditions => AndOperation
|
|
432
|
+
# - Property/Relationship/Path should be left alone
|
|
433
|
+
# - Symbol/String keys should become a Property, scoped to the target_repository and target_model
|
|
434
|
+
# - Extract subject (target) from Operator
|
|
435
|
+
# - subject should be processed same as above
|
|
436
|
+
# - each subject should be transformed into AbstractComparison
|
|
437
|
+
# object with the subject, operator and value
|
|
438
|
+
# - transform into an AndOperation object, and return the
|
|
439
|
+
# query as :condition => and_object from self.query
|
|
440
|
+
# - this should provide the best performance
|
|
441
|
+
|
|
442
|
+
@query = @options.except(*self.class::OPTIONS).freeze
|
|
443
|
+
|
|
444
|
+
create_reader
|
|
445
|
+
create_writer
|
|
446
|
+
end
|
|
182
447
|
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
448
|
+
# Set the correct ivars for the named object
|
|
449
|
+
#
|
|
450
|
+
# This method should set the object in an ivar with the same name
|
|
451
|
+
# provided, plus it should set a String form of the object in
|
|
452
|
+
# a second ivar.
|
|
453
|
+
#
|
|
454
|
+
# @param [String]
|
|
455
|
+
# the name of the ivar to set
|
|
456
|
+
# @param [#name, #to_str, #to_sym] object
|
|
457
|
+
# the object to set in the ivar
|
|
458
|
+
#
|
|
459
|
+
# @return [String]
|
|
460
|
+
# the String value
|
|
461
|
+
#
|
|
462
|
+
# @raise [ArgumentError]
|
|
463
|
+
# raise when object does not respond to expected methods
|
|
464
|
+
#
|
|
465
|
+
# @api private
|
|
466
|
+
def initialize_object_ivar(name, object)
|
|
467
|
+
if object.respond_to?(:name)
|
|
468
|
+
instance_variable_set("@#{name}", object)
|
|
469
|
+
initialize_object_ivar(name, object.name)
|
|
470
|
+
elsif object.respond_to?(:to_str)
|
|
471
|
+
instance_variable_set("@#{name}_name", object.to_str.dup.freeze)
|
|
472
|
+
elsif object.respond_to?(:to_sym)
|
|
473
|
+
instance_variable_set("@#{name}_name", object.to_sym)
|
|
474
|
+
else
|
|
475
|
+
raise ArgumentError, "#{name} does not respond to #to_str or #name"
|
|
195
476
|
end
|
|
196
477
|
|
|
197
|
-
|
|
198
|
-
|
|
478
|
+
object
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# Creates reader method for association.
|
|
482
|
+
#
|
|
483
|
+
# Must be implemented by subclasses.
|
|
484
|
+
#
|
|
485
|
+
# @api semipublic
|
|
486
|
+
def create_reader
|
|
487
|
+
raise NotImplementedError, "#{self.class}#create_reader not implemented"
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# Creates both writer method for association.
|
|
491
|
+
#
|
|
492
|
+
# Must be implemented by subclasses.
|
|
493
|
+
#
|
|
494
|
+
# @api semipublic
|
|
495
|
+
def create_writer
|
|
496
|
+
raise NotImplementedError, "#{self.class}#create_writer not implemented"
|
|
497
|
+
end
|
|
498
|
+
|
|
499
|
+
# Sets the association targets in the resource
|
|
500
|
+
#
|
|
501
|
+
# @param [Resource] source
|
|
502
|
+
# the source to set
|
|
503
|
+
# @param [Array<Resource>] targets
|
|
504
|
+
# the targets for the association
|
|
505
|
+
# @param [Query, Hash] query
|
|
506
|
+
# the query to scope the association with
|
|
507
|
+
#
|
|
508
|
+
# @return [undefined]
|
|
509
|
+
#
|
|
510
|
+
# @api private
|
|
511
|
+
def eager_load_targets(source, targets, query)
|
|
512
|
+
raise NotImplementedError, "#{self.class}#eager_load_targets not implemented"
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# TODO: document
|
|
516
|
+
# @api private
|
|
517
|
+
def valid_collection?(collection)
|
|
518
|
+
if collection.instance_of?(Array) || collection.loaded?
|
|
519
|
+
collection.all? { |resource| valid_resource?(resource) }
|
|
520
|
+
else
|
|
521
|
+
collection.model <= target_model && (collection.query.fields & target_key) == target_key
|
|
199
522
|
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# TODO: document
|
|
526
|
+
# @api private
|
|
527
|
+
def valid_resource?(resource)
|
|
528
|
+
resource.kind_of?(target_model) &&
|
|
529
|
+
target_key.zip(target_key.get!(resource)).all? { |property, value| property.valid?(value) }
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# TODO: document
|
|
533
|
+
# @api private
|
|
534
|
+
def inverse?(other)
|
|
535
|
+
return true if @inverse.equal?(other)
|
|
536
|
+
|
|
537
|
+
other != self &&
|
|
538
|
+
kind_of_inverse?(other) &&
|
|
539
|
+
cmp_repository?(other, :==, :child) &&
|
|
540
|
+
cmp_repository?(other, :==, :parent) &&
|
|
541
|
+
cmp_model?(other, :==, :child) &&
|
|
542
|
+
cmp_model?(other, :==, :parent) &&
|
|
543
|
+
cmp_key?(other, :==, :child) &&
|
|
544
|
+
cmp_key?(other, :==, :parent)
|
|
545
|
+
|
|
546
|
+
# TODO: match only when the Query is empty, or is the same as the
|
|
547
|
+
# default scope for the target model
|
|
548
|
+
end
|
|
200
549
|
|
|
201
|
-
|
|
202
|
-
|
|
550
|
+
# TODO: document
|
|
551
|
+
# @api private
|
|
552
|
+
def inverse_name
|
|
553
|
+
if options[:inverse].kind_of?(Relationship)
|
|
554
|
+
options[:inverse].name
|
|
555
|
+
else
|
|
556
|
+
options[:inverse]
|
|
203
557
|
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
# TODO: document
|
|
561
|
+
# @api private
|
|
562
|
+
def invert
|
|
563
|
+
inverse_class.new(inverse_name, child_model, parent_model, inverted_options)
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
# TODO: document
|
|
567
|
+
# @api private
|
|
568
|
+
def inverted_options
|
|
569
|
+
options.only(*OPTIONS - [ :min, :max ]).update(:inverse => self)
|
|
570
|
+
end
|
|
204
571
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
@options = options
|
|
213
|
-
|
|
214
|
-
# attempt to load the child_key if the parent and child model constants are defined
|
|
215
|
-
if model_defined?(@child_model) && model_defined?(@parent_model)
|
|
216
|
-
child_key
|
|
572
|
+
# TODO: document
|
|
573
|
+
# @api private
|
|
574
|
+
def options_with_inverse
|
|
575
|
+
if child_model? && parent_model?
|
|
576
|
+
options.merge(:inverse => inverse)
|
|
577
|
+
else
|
|
578
|
+
options.merge(:inverse => inverse_name)
|
|
217
579
|
end
|
|
218
580
|
end
|
|
219
581
|
|
|
582
|
+
# TODO: document
|
|
583
|
+
# @api private
|
|
584
|
+
def kind_of_inverse?(other)
|
|
585
|
+
other.kind_of?(inverse_class)
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# TODO: document
|
|
220
589
|
# @api private
|
|
221
|
-
def
|
|
222
|
-
|
|
223
|
-
|
|
590
|
+
def cmp?(other, operator)
|
|
591
|
+
name.send(operator, other.name) &&
|
|
592
|
+
cmp_repository?(other, operator, :child) &&
|
|
593
|
+
cmp_repository?(other, operator, :parent) &&
|
|
594
|
+
cmp_model?(other, operator, :child) &&
|
|
595
|
+
cmp_model?(other, operator, :parent) &&
|
|
596
|
+
cmp_key?(other, operator, :child) &&
|
|
597
|
+
cmp_key?(other, operator, :parent) &&
|
|
598
|
+
query.send(operator, other.query)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
# TODO: document
|
|
602
|
+
# @api private
|
|
603
|
+
def cmp_repository?(other, operator, type)
|
|
604
|
+
# if either repository is nil, then the relationship is relative,
|
|
605
|
+
# and the repositories are considered equivalent
|
|
606
|
+
return true unless repository_name = send("#{type}_repository_name")
|
|
607
|
+
return true unless other_repository_name = other.send("#{type}_repository_name")
|
|
608
|
+
|
|
609
|
+
repository_name.send(operator, other_repository_name)
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# TODO: document
|
|
613
|
+
# @api private
|
|
614
|
+
def cmp_model?(other, operator, type)
|
|
615
|
+
send("#{type}_model?") &&
|
|
616
|
+
other.send("#{type}_model?") &&
|
|
617
|
+
send("#{type}_model").base_model.send(operator, other.send("#{type}_model").base_model)
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
# TODO: document
|
|
621
|
+
# @api private
|
|
622
|
+
def cmp_key?(other, operator, type)
|
|
623
|
+
property_method = "#{type}_properties"
|
|
624
|
+
|
|
625
|
+
self_key = send(property_method)
|
|
626
|
+
other_key = other.send(property_method)
|
|
627
|
+
|
|
628
|
+
self_key.send(operator, other_key)
|
|
224
629
|
end
|
|
225
630
|
end # class Relationship
|
|
226
631
|
end # module Associations
|