ghost_dm-core 1.3.0.beta
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 +29 -0
- data/.document +5 -0
- data/.gitignore +35 -0
- data/.yardopts +1 -0
- data/Gemfile +65 -0
- data/LICENSE +20 -0
- data/README.md +269 -0
- data/Rakefile +4 -0
- data/dm-core.gemspec +24 -0
- data/lib/dm-core.rb +292 -0
- data/lib/dm-core/adapters.rb +222 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +237 -0
- data/lib/dm-core/adapters/in_memory_adapter.rb +113 -0
- data/lib/dm-core/associations/many_to_many.rb +499 -0
- data/lib/dm-core/associations/many_to_one.rb +290 -0
- data/lib/dm-core/associations/one_to_many.rb +348 -0
- data/lib/dm-core/associations/one_to_one.rb +86 -0
- data/lib/dm-core/associations/relationship.rb +663 -0
- data/lib/dm-core/backwards.rb +13 -0
- data/lib/dm-core/collection.rb +1515 -0
- data/lib/dm-core/core_ext/kernel.rb +23 -0
- data/lib/dm-core/core_ext/pathname.rb +6 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +7 -0
- data/lib/dm-core/model.rb +874 -0
- data/lib/dm-core/model/hook.rb +103 -0
- data/lib/dm-core/model/is.rb +32 -0
- data/lib/dm-core/model/property.rb +249 -0
- data/lib/dm-core/model/relationship.rb +378 -0
- data/lib/dm-core/model/scope.rb +89 -0
- data/lib/dm-core/property.rb +866 -0
- data/lib/dm-core/property/binary.rb +21 -0
- data/lib/dm-core/property/boolean.rb +20 -0
- data/lib/dm-core/property/class.rb +17 -0
- data/lib/dm-core/property/date.rb +10 -0
- data/lib/dm-core/property/date_time.rb +10 -0
- data/lib/dm-core/property/decimal.rb +36 -0
- data/lib/dm-core/property/discriminator.rb +44 -0
- data/lib/dm-core/property/float.rb +16 -0
- data/lib/dm-core/property/integer.rb +22 -0
- data/lib/dm-core/property/invalid_value_error.rb +22 -0
- data/lib/dm-core/property/lookup.rb +27 -0
- data/lib/dm-core/property/numeric.rb +38 -0
- data/lib/dm-core/property/object.rb +34 -0
- data/lib/dm-core/property/serial.rb +14 -0
- data/lib/dm-core/property/string.rb +38 -0
- data/lib/dm-core/property/text.rb +9 -0
- data/lib/dm-core/property/time.rb +10 -0
- data/lib/dm-core/property_set.rb +177 -0
- data/lib/dm-core/query.rb +1366 -0
- data/lib/dm-core/query/conditions/comparison.rb +911 -0
- data/lib/dm-core/query/conditions/operation.rb +721 -0
- data/lib/dm-core/query/direction.rb +36 -0
- data/lib/dm-core/query/operator.rb +35 -0
- data/lib/dm-core/query/path.rb +114 -0
- data/lib/dm-core/query/sort.rb +39 -0
- data/lib/dm-core/relationship_set.rb +72 -0
- data/lib/dm-core/repository.rb +226 -0
- data/lib/dm-core/resource.rb +1214 -0
- data/lib/dm-core/resource/persistence_state.rb +75 -0
- data/lib/dm-core/resource/persistence_state/clean.rb +40 -0
- data/lib/dm-core/resource/persistence_state/deleted.rb +30 -0
- data/lib/dm-core/resource/persistence_state/dirty.rb +96 -0
- data/lib/dm-core/resource/persistence_state/immutable.rb +34 -0
- data/lib/dm-core/resource/persistence_state/persisted.rb +29 -0
- data/lib/dm-core/resource/persistence_state/transient.rb +80 -0
- data/lib/dm-core/spec/lib/adapter_helpers.rb +64 -0
- data/lib/dm-core/spec/lib/collection_helpers.rb +21 -0
- data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
- data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
- data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
- data/lib/dm-core/spec/setup.rb +174 -0
- data/lib/dm-core/spec/shared/adapter_spec.rb +341 -0
- data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
- data/lib/dm-core/spec/shared/resource_spec.rb +1232 -0
- data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
- data/lib/dm-core/spec/shared/semipublic/property_spec.rb +176 -0
- data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
- data/lib/dm-core/support/assertions.rb +8 -0
- data/lib/dm-core/support/chainable.rb +18 -0
- data/lib/dm-core/support/deprecate.rb +12 -0
- data/lib/dm-core/support/descendant_set.rb +89 -0
- data/lib/dm-core/support/equalizer.rb +48 -0
- data/lib/dm-core/support/ext/array.rb +22 -0
- data/lib/dm-core/support/ext/blank.rb +25 -0
- data/lib/dm-core/support/ext/hash.rb +67 -0
- data/lib/dm-core/support/ext/module.rb +47 -0
- data/lib/dm-core/support/ext/object.rb +57 -0
- data/lib/dm-core/support/ext/string.rb +24 -0
- data/lib/dm-core/support/ext/try_dup.rb +12 -0
- data/lib/dm-core/support/hook.rb +405 -0
- data/lib/dm-core/support/inflections.rb +60 -0
- data/lib/dm-core/support/inflector/inflections.rb +211 -0
- data/lib/dm-core/support/inflector/methods.rb +151 -0
- data/lib/dm-core/support/lazy_array.rb +451 -0
- data/lib/dm-core/support/local_object_space.rb +13 -0
- data/lib/dm-core/support/logger.rb +201 -0
- data/lib/dm-core/support/mash.rb +176 -0
- data/lib/dm-core/support/naming_conventions.rb +90 -0
- data/lib/dm-core/support/ordered_set.rb +380 -0
- data/lib/dm-core/support/subject.rb +33 -0
- data/lib/dm-core/support/subject_set.rb +250 -0
- data/lib/dm-core/version.rb +3 -0
- data/script/performance.rb +275 -0
- data/script/profile.rb +218 -0
- data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
- data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +68 -0
- data/spec/public/associations/many_to_many_spec.rb +197 -0
- data/spec/public/associations/many_to_one_spec.rb +83 -0
- data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
- data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
- data/spec/public/associations/one_to_many_spec.rb +81 -0
- data/spec/public/associations/one_to_one_spec.rb +176 -0
- data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
- data/spec/public/collection_spec.rb +69 -0
- data/spec/public/finalize_spec.rb +76 -0
- data/spec/public/model/hook_spec.rb +246 -0
- data/spec/public/model/property_spec.rb +88 -0
- data/spec/public/model/relationship_spec.rb +1040 -0
- data/spec/public/model_spec.rb +462 -0
- data/spec/public/property/binary_spec.rb +41 -0
- data/spec/public/property/boolean_spec.rb +22 -0
- data/spec/public/property/class_spec.rb +28 -0
- data/spec/public/property/date_spec.rb +22 -0
- data/spec/public/property/date_time_spec.rb +22 -0
- data/spec/public/property/decimal_spec.rb +23 -0
- data/spec/public/property/discriminator_spec.rb +135 -0
- data/spec/public/property/float_spec.rb +22 -0
- data/spec/public/property/integer_spec.rb +22 -0
- data/spec/public/property/object_spec.rb +107 -0
- data/spec/public/property/serial_spec.rb +22 -0
- data/spec/public/property/string_spec.rb +22 -0
- data/spec/public/property/text_spec.rb +63 -0
- data/spec/public/property/time_spec.rb +22 -0
- data/spec/public/property_spec.rb +341 -0
- data/spec/public/resource_spec.rb +288 -0
- data/spec/public/sel_spec.rb +53 -0
- data/spec/public/setup_spec.rb +145 -0
- data/spec/public/shared/association_collection_shared_spec.rb +309 -0
- data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
- data/spec/public/shared/collection_shared_spec.rb +1667 -0
- data/spec/public/shared/finder_shared_spec.rb +1629 -0
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
- data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
- data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
- data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
- data/spec/semipublic/associations/relationship_spec.rb +200 -0
- data/spec/semipublic/associations_spec.rb +177 -0
- data/spec/semipublic/collection_spec.rb +110 -0
- data/spec/semipublic/model_spec.rb +96 -0
- data/spec/semipublic/property/binary_spec.rb +13 -0
- data/spec/semipublic/property/boolean_spec.rb +47 -0
- data/spec/semipublic/property/class_spec.rb +33 -0
- data/spec/semipublic/property/date_spec.rb +43 -0
- data/spec/semipublic/property/date_time_spec.rb +46 -0
- data/spec/semipublic/property/decimal_spec.rb +83 -0
- data/spec/semipublic/property/discriminator_spec.rb +19 -0
- data/spec/semipublic/property/float_spec.rb +82 -0
- data/spec/semipublic/property/integer_spec.rb +82 -0
- data/spec/semipublic/property/lookup_spec.rb +29 -0
- data/spec/semipublic/property/serial_spec.rb +13 -0
- data/spec/semipublic/property/string_spec.rb +13 -0
- data/spec/semipublic/property/text_spec.rb +31 -0
- data/spec/semipublic/property/time_spec.rb +50 -0
- data/spec/semipublic/property_spec.rb +114 -0
- data/spec/semipublic/query/conditions/comparison_spec.rb +1501 -0
- data/spec/semipublic/query/conditions/operation_spec.rb +1294 -0
- data/spec/semipublic/query/path_spec.rb +471 -0
- data/spec/semipublic/query_spec.rb +3682 -0
- data/spec/semipublic/resource/state/clean_spec.rb +88 -0
- data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
- data/spec/semipublic/resource/state/dirty_spec.rb +162 -0
- data/spec/semipublic/resource/state/immutable_spec.rb +105 -0
- data/spec/semipublic/resource/state/transient_spec.rb +162 -0
- data/spec/semipublic/resource/state_spec.rb +230 -0
- data/spec/semipublic/resource_spec.rb +23 -0
- data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +199 -0
- data/spec/semipublic/shared/resource_state_shared_spec.rb +79 -0
- data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/core_ext/hash.rb +10 -0
- data/spec/support/core_ext/inheritable_attributes.rb +46 -0
- data/spec/support/properties/huge_integer.rb +17 -0
- data/spec/unit/array_spec.rb +23 -0
- data/spec/unit/blank_spec.rb +73 -0
- data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
- data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
- data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
- data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
- data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
- data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
- data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
- data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
- data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
- data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
- data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
- data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
- data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
- data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
- data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
- data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
- data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
- data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
- data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
- data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
- data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
- data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
- data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
- data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
- data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
- data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
- data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
- data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
- data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
- data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
- data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
- data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
- data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
- data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
- data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
- data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
- data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
- data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
- data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
- data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
- data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
- data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
- data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
- data/spec/unit/hash_spec.rb +28 -0
- data/spec/unit/hook_spec.rb +1235 -0
- data/spec/unit/inflections_spec.rb +16 -0
- data/spec/unit/lazy_array_spec.rb +1949 -0
- data/spec/unit/mash_spec.rb +312 -0
- data/spec/unit/module_spec.rb +71 -0
- data/spec/unit/object_spec.rb +38 -0
- data/spec/unit/try_dup_spec.rb +46 -0
- data/tasks/ci.rake +1 -0
- data/tasks/spec.rake +38 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +365 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Associations
|
|
3
|
+
module OneToOne #:nodoc:
|
|
4
|
+
class Relationship < Associations::Relationship
|
|
5
|
+
%w[ public protected private ].map do |visibility|
|
|
6
|
+
methods = superclass.send("#{visibility}_instance_methods", false) |
|
|
7
|
+
DataMapper::Subject.send("#{visibility}_instance_methods", false)
|
|
8
|
+
|
|
9
|
+
methods.each do |method|
|
|
10
|
+
undef_method method.to_sym unless method.to_s == 'initialize'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Loads (if necessary) and returns association target
|
|
15
|
+
# for given source
|
|
16
|
+
#
|
|
17
|
+
# @api semipublic
|
|
18
|
+
def get(source, query = nil)
|
|
19
|
+
relationship.get(source, query).first
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Get the resource directly
|
|
23
|
+
#
|
|
24
|
+
# @api semipublic
|
|
25
|
+
def get!(source)
|
|
26
|
+
collection = relationship.get!(source)
|
|
27
|
+
collection.first if collection
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Sets and returns association target
|
|
31
|
+
# for given source
|
|
32
|
+
#
|
|
33
|
+
# @api semipublic
|
|
34
|
+
def set(source, target)
|
|
35
|
+
relationship.set(source, [ target ].compact).first
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Sets the resource directly
|
|
39
|
+
#
|
|
40
|
+
# @api semipublic
|
|
41
|
+
def set!(source, target)
|
|
42
|
+
set(source, target)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @api semipublic
|
|
46
|
+
def default_for(source)
|
|
47
|
+
relationship.default_for(source).first
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @api public
|
|
51
|
+
def kind_of?(klass)
|
|
52
|
+
super || relationship.kind_of?(klass)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @api public
|
|
56
|
+
def instance_of?(klass)
|
|
57
|
+
super || relationship.instance_of?(klass)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @api public
|
|
61
|
+
def respond_to?(method, include_private = false)
|
|
62
|
+
super || relationship.respond_to?(method, include_private)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
attr_reader :relationship
|
|
68
|
+
|
|
69
|
+
# Initializes the relationship. Always assumes target model class is
|
|
70
|
+
# a camel cased association name.
|
|
71
|
+
#
|
|
72
|
+
# @api semipublic
|
|
73
|
+
def initialize(name, target_model, source_model, options = {})
|
|
74
|
+
klass = options.key?(:through) ? ManyToMany::Relationship : OneToMany::Relationship
|
|
75
|
+
target_model ||= DataMapper::Inflector.camelize(name).freeze
|
|
76
|
+
@relationship = klass.new(name, target_model, source_model, options)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @api private
|
|
80
|
+
def method_missing(method, *args, &block)
|
|
81
|
+
relationship.send(method, *args, &block)
|
|
82
|
+
end
|
|
83
|
+
end # class Relationship
|
|
84
|
+
end # module HasOne
|
|
85
|
+
end # module Associations
|
|
86
|
+
end # module DataMapper
|
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
# TODO: move argument and option validation into the class
|
|
2
|
+
|
|
3
|
+
module DataMapper
|
|
4
|
+
module Associations
|
|
5
|
+
# Base class for relationships. Each type of relationship
|
|
6
|
+
# (1 to 1, 1 to n, n to m) implements a subclass of this class
|
|
7
|
+
# with methods like get and set overridden.
|
|
8
|
+
class Relationship
|
|
9
|
+
include DataMapper::Assertions
|
|
10
|
+
include Subject
|
|
11
|
+
|
|
12
|
+
OPTIONS = [ :child_repository_name, :parent_repository_name, :child_key, :parent_key, :min, :max, :inverse, :reader_visibility, :writer_visibility, :default ].to_set
|
|
13
|
+
|
|
14
|
+
# Relationship name
|
|
15
|
+
#
|
|
16
|
+
# @example for :parent association in
|
|
17
|
+
#
|
|
18
|
+
# class VersionControl::Commit
|
|
19
|
+
# # ...
|
|
20
|
+
#
|
|
21
|
+
# belongs_to :parent
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# name is :parent
|
|
25
|
+
#
|
|
26
|
+
# @api semipublic
|
|
27
|
+
attr_reader :name
|
|
28
|
+
|
|
29
|
+
# Options used to set up association of this relationship
|
|
30
|
+
#
|
|
31
|
+
# @example for :author association in
|
|
32
|
+
#
|
|
33
|
+
# class VersionControl::Commit
|
|
34
|
+
# # ...
|
|
35
|
+
#
|
|
36
|
+
# belongs_to :author, :model => 'Person'
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# options is a hash with a single key, :model
|
|
40
|
+
#
|
|
41
|
+
# @api semipublic
|
|
42
|
+
attr_reader :options
|
|
43
|
+
|
|
44
|
+
# ivar used to store collection of child options in source
|
|
45
|
+
#
|
|
46
|
+
# @example for :commits association in
|
|
47
|
+
#
|
|
48
|
+
# class VersionControl::Branch
|
|
49
|
+
# # ...
|
|
50
|
+
#
|
|
51
|
+
# has n, :commits
|
|
52
|
+
# end
|
|
53
|
+
#
|
|
54
|
+
# instance variable name for source will be @commits
|
|
55
|
+
#
|
|
56
|
+
# @api semipublic
|
|
57
|
+
attr_reader :instance_variable_name
|
|
58
|
+
|
|
59
|
+
# Repository from where child objects are loaded
|
|
60
|
+
#
|
|
61
|
+
# @api semipublic
|
|
62
|
+
attr_reader :child_repository_name
|
|
63
|
+
|
|
64
|
+
# Repository from where parent objects are loaded
|
|
65
|
+
#
|
|
66
|
+
# @api semipublic
|
|
67
|
+
attr_reader :parent_repository_name
|
|
68
|
+
|
|
69
|
+
# Minimum number of child objects for relationship
|
|
70
|
+
#
|
|
71
|
+
# @example for :cores association in
|
|
72
|
+
#
|
|
73
|
+
# class CPU::Multicore
|
|
74
|
+
# # ...
|
|
75
|
+
#
|
|
76
|
+
# has 2..n, :cores
|
|
77
|
+
# end
|
|
78
|
+
#
|
|
79
|
+
# minimum is 2
|
|
80
|
+
#
|
|
81
|
+
# @api semipublic
|
|
82
|
+
attr_reader :min
|
|
83
|
+
|
|
84
|
+
# Maximum number of child objects for
|
|
85
|
+
# relationship
|
|
86
|
+
#
|
|
87
|
+
# @example for :fouls association in
|
|
88
|
+
#
|
|
89
|
+
# class Basketball::Player
|
|
90
|
+
# # ...
|
|
91
|
+
#
|
|
92
|
+
# has 0..5, :fouls
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# maximum is 5
|
|
96
|
+
#
|
|
97
|
+
# @api semipublic
|
|
98
|
+
attr_reader :max
|
|
99
|
+
|
|
100
|
+
# Returns the visibility for the source accessor
|
|
101
|
+
#
|
|
102
|
+
# @return [Symbol]
|
|
103
|
+
# the visibility for the accessor added to the source
|
|
104
|
+
#
|
|
105
|
+
# @api semipublic
|
|
106
|
+
attr_reader :reader_visibility
|
|
107
|
+
|
|
108
|
+
# Returns the visibility for the source mutator
|
|
109
|
+
#
|
|
110
|
+
# @return [Symbol]
|
|
111
|
+
# the visibility for the mutator added to the source
|
|
112
|
+
#
|
|
113
|
+
# @api semipublic
|
|
114
|
+
attr_reader :writer_visibility
|
|
115
|
+
|
|
116
|
+
# Returns query options for relationship.
|
|
117
|
+
#
|
|
118
|
+
# For this base class, always returns query options
|
|
119
|
+
# has been initialized with.
|
|
120
|
+
# Overriden in subclasses.
|
|
121
|
+
#
|
|
122
|
+
# @api private
|
|
123
|
+
attr_reader :query
|
|
124
|
+
|
|
125
|
+
# Returns the String the Relationship would use in a Hash
|
|
126
|
+
#
|
|
127
|
+
# @return [String]
|
|
128
|
+
# String name for the Relationship
|
|
129
|
+
#
|
|
130
|
+
# @api private
|
|
131
|
+
def field
|
|
132
|
+
name.to_s
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Returns a hash of conditions that scopes query that fetches
|
|
136
|
+
# target object
|
|
137
|
+
#
|
|
138
|
+
# @return [Hash]
|
|
139
|
+
# Hash of conditions that scopes query
|
|
140
|
+
#
|
|
141
|
+
# @api private
|
|
142
|
+
def source_scope(source)
|
|
143
|
+
{ inverse => source }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Creates and returns Query instance that fetches
|
|
147
|
+
# target resource(s) (ex.: articles) for given target resource (ex.: author)
|
|
148
|
+
#
|
|
149
|
+
# @api semipublic
|
|
150
|
+
def query_for(source, other_query = nil)
|
|
151
|
+
repository_name = relative_target_repository_name_for(source)
|
|
152
|
+
|
|
153
|
+
DataMapper.repository(repository_name).scope do
|
|
154
|
+
query = target_model.query.dup
|
|
155
|
+
query.update(self.query)
|
|
156
|
+
query.update(:conditions => source_scope(source))
|
|
157
|
+
query.update(other_query) if other_query
|
|
158
|
+
query.update(:fields => query.fields | target_key)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Returns model class used by child side of the relationship
|
|
163
|
+
#
|
|
164
|
+
# @return [Resource]
|
|
165
|
+
# Model for association child
|
|
166
|
+
#
|
|
167
|
+
# @api private
|
|
168
|
+
def child_model
|
|
169
|
+
return @child_model if defined?(@child_model)
|
|
170
|
+
child_model_name = self.child_model_name
|
|
171
|
+
@child_model = DataMapper::Ext::Module.find_const(@parent_model || Object, child_model_name)
|
|
172
|
+
rescue NameError
|
|
173
|
+
raise NameError, "Cannot find the child_model #{child_model_name} for #{parent_model_name} in #{name}"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# @api private
|
|
177
|
+
def child_model?
|
|
178
|
+
child_model
|
|
179
|
+
true
|
|
180
|
+
rescue NameError
|
|
181
|
+
false
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# @api private
|
|
185
|
+
def child_model_name
|
|
186
|
+
@child_model ? child_model.name : @child_model_name
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Returns a set of keys that identify the target model
|
|
190
|
+
#
|
|
191
|
+
# @return [PropertySet]
|
|
192
|
+
# a set of properties that identify the target model
|
|
193
|
+
#
|
|
194
|
+
# @api semipublic
|
|
195
|
+
def child_key
|
|
196
|
+
return @child_key if defined?(@child_key)
|
|
197
|
+
|
|
198
|
+
repository_name = child_repository_name || parent_repository_name
|
|
199
|
+
properties = child_model.properties(repository_name)
|
|
200
|
+
|
|
201
|
+
@child_key = if @child_properties
|
|
202
|
+
child_key = properties.values_at(*@child_properties)
|
|
203
|
+
properties.class.new(child_key).freeze
|
|
204
|
+
else
|
|
205
|
+
properties.key
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Access Relationship#child_key directly
|
|
210
|
+
#
|
|
211
|
+
# @api private
|
|
212
|
+
alias_method :relationship_child_key, :child_key
|
|
213
|
+
private :relationship_child_key
|
|
214
|
+
|
|
215
|
+
# Returns model class used by parent side of the relationship
|
|
216
|
+
#
|
|
217
|
+
# @return [Resource]
|
|
218
|
+
# Class of association parent
|
|
219
|
+
#
|
|
220
|
+
# @api private
|
|
221
|
+
def parent_model
|
|
222
|
+
return @parent_model if defined?(@parent_model)
|
|
223
|
+
parent_model_name = self.parent_model_name
|
|
224
|
+
@parent_model = DataMapper::Ext::Module.find_const(@child_model || Object, parent_model_name)
|
|
225
|
+
rescue NameError
|
|
226
|
+
raise NameError, "Cannot find the parent_model #{parent_model_name} for #{child_model_name} in #{name}"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# @api private
|
|
230
|
+
def parent_model?
|
|
231
|
+
parent_model
|
|
232
|
+
true
|
|
233
|
+
rescue NameError
|
|
234
|
+
false
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# @api private
|
|
238
|
+
def parent_model_name
|
|
239
|
+
@parent_model ? parent_model.name : @parent_model_name
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Returns a set of keys that identify parent model
|
|
243
|
+
#
|
|
244
|
+
# @return [PropertySet]
|
|
245
|
+
# a set of properties that identify parent model
|
|
246
|
+
#
|
|
247
|
+
# @api private
|
|
248
|
+
def parent_key
|
|
249
|
+
return @parent_key if defined?(@parent_key)
|
|
250
|
+
|
|
251
|
+
repository_name = parent_repository_name || child_repository_name
|
|
252
|
+
properties = parent_model.properties(repository_name)
|
|
253
|
+
|
|
254
|
+
@parent_key = if @parent_properties
|
|
255
|
+
parent_key = properties.values_at(*@parent_properties)
|
|
256
|
+
properties.class.new(parent_key).freeze
|
|
257
|
+
else
|
|
258
|
+
properties.key
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Loads and returns "other end" of the association.
|
|
263
|
+
# Must be implemented in subclasses.
|
|
264
|
+
#
|
|
265
|
+
# @api semipublic
|
|
266
|
+
def get(resource, other_query = nil)
|
|
267
|
+
raise NotImplementedError, "#{self.class}#get not implemented"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Gets "other end" of the association directly
|
|
271
|
+
# as @ivar on given resource. Subclasses usually
|
|
272
|
+
# use implementation of this class.
|
|
273
|
+
#
|
|
274
|
+
# @api semipublic
|
|
275
|
+
def get!(resource)
|
|
276
|
+
resource.instance_variable_get(instance_variable_name)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Sets value of the "other end" of association
|
|
280
|
+
# on given resource. Must be implemented in subclasses.
|
|
281
|
+
#
|
|
282
|
+
# @api semipublic
|
|
283
|
+
def set(resource, association)
|
|
284
|
+
raise NotImplementedError, "#{self.class}#set not implemented"
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Sets "other end" of the association directly
|
|
288
|
+
# as @ivar on given resource. Subclasses usually
|
|
289
|
+
# use implementation of this class.
|
|
290
|
+
#
|
|
291
|
+
# @api semipublic
|
|
292
|
+
def set!(resource, association)
|
|
293
|
+
resource.instance_variable_set(instance_variable_name, association)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Eager load the collection using the source as a base
|
|
297
|
+
#
|
|
298
|
+
# @param [Collection] source
|
|
299
|
+
# the source collection to query with
|
|
300
|
+
# @param [Query, Hash] query
|
|
301
|
+
# optional query to restrict the collection
|
|
302
|
+
#
|
|
303
|
+
# @return [Collection]
|
|
304
|
+
# the loaded collection for the source
|
|
305
|
+
#
|
|
306
|
+
# @api private
|
|
307
|
+
def eager_load(source, query = nil)
|
|
308
|
+
targets = source.model.all(query_for(source, query))
|
|
309
|
+
|
|
310
|
+
# FIXME: cannot associate targets to m:m collection yet
|
|
311
|
+
if source.loaded? && !source.kind_of?(ManyToMany::Collection)
|
|
312
|
+
associate_targets(source, targets)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
targets
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Checks if "other end" of association is loaded on given
|
|
319
|
+
# resource.
|
|
320
|
+
#
|
|
321
|
+
# @api semipublic
|
|
322
|
+
def loaded?(resource)
|
|
323
|
+
resource.instance_variable_defined?(instance_variable_name)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Test the resource to see if it is a valid target
|
|
327
|
+
#
|
|
328
|
+
# @param [Object] source
|
|
329
|
+
# the resource or collection to be tested
|
|
330
|
+
#
|
|
331
|
+
# @return [Boolean]
|
|
332
|
+
# true if the resource is valid
|
|
333
|
+
#
|
|
334
|
+
# @api semipulic
|
|
335
|
+
def valid?(value, negated = false)
|
|
336
|
+
case value
|
|
337
|
+
when Enumerable then valid_target_collection?(value, negated)
|
|
338
|
+
when Resource then valid_target?(value)
|
|
339
|
+
when nil then true
|
|
340
|
+
else
|
|
341
|
+
raise ArgumentError, "+value+ should be an Enumerable, Resource or nil, but was a #{value.class.name}"
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# Compares another Relationship for equality
|
|
346
|
+
#
|
|
347
|
+
# @param [Relationship] other
|
|
348
|
+
# the other Relationship to compare with
|
|
349
|
+
#
|
|
350
|
+
# @return [Boolean]
|
|
351
|
+
# true if they are equal, false if not
|
|
352
|
+
#
|
|
353
|
+
# @api public
|
|
354
|
+
def eql?(other)
|
|
355
|
+
return true if equal?(other)
|
|
356
|
+
instance_of?(other.class) && cmp?(other, :eql?)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Compares another Relationship for equivalency
|
|
360
|
+
#
|
|
361
|
+
# @param [Relationship] other
|
|
362
|
+
# the other Relationship to compare with
|
|
363
|
+
#
|
|
364
|
+
# @return [Boolean]
|
|
365
|
+
# true if they are equal, false if not
|
|
366
|
+
#
|
|
367
|
+
# @api public
|
|
368
|
+
def ==(other)
|
|
369
|
+
return true if equal?(other)
|
|
370
|
+
other.respond_to?(:cmp_repository?, true) &&
|
|
371
|
+
other.respond_to?(:cmp_model?, true) &&
|
|
372
|
+
other.respond_to?(:cmp_key?, true) &&
|
|
373
|
+
other.respond_to?(:min) &&
|
|
374
|
+
other.respond_to?(:max) &&
|
|
375
|
+
other.respond_to?(:query) &&
|
|
376
|
+
cmp?(other, :==)
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Get the inverse relationship from the target model
|
|
380
|
+
#
|
|
381
|
+
# @api semipublic
|
|
382
|
+
def inverse
|
|
383
|
+
return @inverse if defined?(@inverse)
|
|
384
|
+
|
|
385
|
+
@inverse = options[:inverse]
|
|
386
|
+
|
|
387
|
+
if kind_of_inverse?(@inverse)
|
|
388
|
+
return @inverse
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
relationships = target_model.relationships(relative_target_repository_name)
|
|
392
|
+
|
|
393
|
+
@inverse = relationships.detect { |relationship| inverse?(relationship) } ||
|
|
394
|
+
invert
|
|
395
|
+
|
|
396
|
+
@inverse.child_key
|
|
397
|
+
|
|
398
|
+
@inverse
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# @api private
|
|
402
|
+
def relative_target_repository_name
|
|
403
|
+
target_repository_name || source_repository_name
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# @api private
|
|
407
|
+
def relative_target_repository_name_for(source)
|
|
408
|
+
target_repository_name || if source.respond_to?(:repository)
|
|
409
|
+
source.repository.name
|
|
410
|
+
else
|
|
411
|
+
source_repository_name
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# @api private
|
|
416
|
+
def hash
|
|
417
|
+
self.class.hash ^
|
|
418
|
+
name.hash ^
|
|
419
|
+
child_repository_name.hash ^
|
|
420
|
+
parent_repository_name.hash ^
|
|
421
|
+
child_model.hash ^
|
|
422
|
+
parent_model.hash ^
|
|
423
|
+
child_properties.hash ^
|
|
424
|
+
parent_properties.hash ^
|
|
425
|
+
min.hash ^
|
|
426
|
+
max.hash ^
|
|
427
|
+
query.hash
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
private
|
|
431
|
+
|
|
432
|
+
# @api private
|
|
433
|
+
attr_reader :child_properties
|
|
434
|
+
|
|
435
|
+
# @api private
|
|
436
|
+
attr_reader :parent_properties
|
|
437
|
+
|
|
438
|
+
# Initializes new Relationship: sets attributes of relationship
|
|
439
|
+
# from options as well as conventions: for instance, @ivar name
|
|
440
|
+
# for association is constructed by prefixing @ to association name.
|
|
441
|
+
#
|
|
442
|
+
# Once attributes are set, reader and writer are created for
|
|
443
|
+
# the resource association belongs to
|
|
444
|
+
#
|
|
445
|
+
# @api semipublic
|
|
446
|
+
def initialize(name, child_model, parent_model, options = {})
|
|
447
|
+
initialize_object_ivar('child_model', child_model)
|
|
448
|
+
initialize_object_ivar('parent_model', parent_model)
|
|
449
|
+
|
|
450
|
+
@name = name
|
|
451
|
+
@instance_variable_name = "@#{@name}".freeze
|
|
452
|
+
@options = options.dup.freeze
|
|
453
|
+
@child_repository_name = @options[:child_repository_name]
|
|
454
|
+
@parent_repository_name = @options[:parent_repository_name]
|
|
455
|
+
|
|
456
|
+
unless @options[:child_key].nil?
|
|
457
|
+
@child_properties = DataMapper::Ext.try_dup(@options[:child_key]).freeze
|
|
458
|
+
end
|
|
459
|
+
unless @options[:parent_key].nil?
|
|
460
|
+
@parent_properties = DataMapper::Ext.try_dup(@options[:parent_key]).freeze
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
@min = @options[:min]
|
|
464
|
+
@max = @options[:max]
|
|
465
|
+
@reader_visibility = @options.fetch(:reader_visibility, :public)
|
|
466
|
+
@writer_visibility = @options.fetch(:writer_visibility, :public)
|
|
467
|
+
@default = @options.fetch(:default, nil)
|
|
468
|
+
|
|
469
|
+
# TODO: normalize the @query to become :conditions => AndOperation
|
|
470
|
+
# - Property/Relationship/Path should be left alone
|
|
471
|
+
# - Symbol/String keys should become a Property, scoped to the target_repository and target_model
|
|
472
|
+
# - Extract subject (target) from Operator
|
|
473
|
+
# - subject should be processed same as above
|
|
474
|
+
# - each subject should be transformed into AbstractComparison
|
|
475
|
+
# object with the subject, operator and value
|
|
476
|
+
# - transform into an AndOperation object, and return the
|
|
477
|
+
# query as :condition => and_object from self.query
|
|
478
|
+
# - this should provide the best performance
|
|
479
|
+
|
|
480
|
+
@query = DataMapper::Ext::Hash.except(@options, *self.class::OPTIONS).freeze
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Set the correct ivars for the named object
|
|
484
|
+
#
|
|
485
|
+
# This method should set the object in an ivar with the same name
|
|
486
|
+
# provided, plus it should set a String form of the object in
|
|
487
|
+
# a second ivar.
|
|
488
|
+
#
|
|
489
|
+
# @param [String]
|
|
490
|
+
# the name of the ivar to set
|
|
491
|
+
# @param [#name, #to_str, #to_sym] object
|
|
492
|
+
# the object to set in the ivar
|
|
493
|
+
#
|
|
494
|
+
# @return [String]
|
|
495
|
+
# the String value
|
|
496
|
+
#
|
|
497
|
+
# @raise [ArgumentError]
|
|
498
|
+
# raise when object does not respond to expected methods
|
|
499
|
+
#
|
|
500
|
+
# @api private
|
|
501
|
+
def initialize_object_ivar(name, object)
|
|
502
|
+
if object.respond_to?(:name)
|
|
503
|
+
instance_variable_set("@#{name}", object)
|
|
504
|
+
initialize_object_ivar(name, object.name)
|
|
505
|
+
elsif object.respond_to?(:to_str)
|
|
506
|
+
instance_variable_set("@#{name}_name", object.to_str.dup.freeze)
|
|
507
|
+
elsif object.respond_to?(:to_sym)
|
|
508
|
+
instance_variable_set("@#{name}_name", object.to_sym)
|
|
509
|
+
else
|
|
510
|
+
raise ArgumentError, "#{name} does not respond to #to_str or #name"
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
object
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Sets the association targets in the resource
|
|
517
|
+
#
|
|
518
|
+
# @param [Resource] source
|
|
519
|
+
# the source to set
|
|
520
|
+
# @param [Array<Resource>] targets
|
|
521
|
+
# the targets for the association
|
|
522
|
+
# @param [Query, Hash] query
|
|
523
|
+
# the query to scope the association with
|
|
524
|
+
#
|
|
525
|
+
# @return [undefined]
|
|
526
|
+
#
|
|
527
|
+
# @api private
|
|
528
|
+
def eager_load_targets(source, targets, query)
|
|
529
|
+
raise NotImplementedError, "#{self.class}#eager_load_targets not implemented"
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# @api private
|
|
533
|
+
def valid_target_collection?(collection, negated)
|
|
534
|
+
if collection.kind_of?(Collection)
|
|
535
|
+
# TODO: move the check for model_key into Collection#reloadable?
|
|
536
|
+
# since what we're really checking is a Collection's ability
|
|
537
|
+
# to reload itself, which is (currently) only possible if the
|
|
538
|
+
# key was loaded.
|
|
539
|
+
model = target_model
|
|
540
|
+
model_key = model.key(repository.name)
|
|
541
|
+
|
|
542
|
+
collection.model <= model &&
|
|
543
|
+
(collection.query.fields & model_key) == model_key &&
|
|
544
|
+
(collection.loaded? ? (collection.any? || negated) : true)
|
|
545
|
+
else
|
|
546
|
+
collection.all? { |resource| valid_target?(resource) }
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# @api private
|
|
551
|
+
def valid_target?(target)
|
|
552
|
+
target.kind_of?(target_model) &&
|
|
553
|
+
source_key.valid?(target_key.get(target))
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
# @api private
|
|
557
|
+
def valid_source?(source)
|
|
558
|
+
source.kind_of?(source_model) &&
|
|
559
|
+
target_key.valid?(source_key.get(source))
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
# @api private
|
|
563
|
+
def inverse?(other)
|
|
564
|
+
return true if @inverse.equal?(other)
|
|
565
|
+
|
|
566
|
+
other != self &&
|
|
567
|
+
kind_of_inverse?(other) &&
|
|
568
|
+
cmp_repository?(other, :==, :child) &&
|
|
569
|
+
cmp_repository?(other, :==, :parent) &&
|
|
570
|
+
cmp_model?(other, :==, :child) &&
|
|
571
|
+
cmp_model?(other, :==, :parent) &&
|
|
572
|
+
cmp_key?(other, :==, :child) &&
|
|
573
|
+
cmp_key?(other, :==, :parent)
|
|
574
|
+
|
|
575
|
+
# TODO: match only when the Query is empty, or is the same as the
|
|
576
|
+
# default scope for the target model
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
# @api private
|
|
580
|
+
def inverse_name
|
|
581
|
+
inverse = options[:inverse]
|
|
582
|
+
if inverse.kind_of?(Relationship)
|
|
583
|
+
inverse.name
|
|
584
|
+
else
|
|
585
|
+
inverse
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# @api private
|
|
590
|
+
def invert
|
|
591
|
+
inverse_class.new(inverse_name, child_model, parent_model, inverted_options)
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
# @api private
|
|
595
|
+
def inverted_options
|
|
596
|
+
DataMapper::Ext::Hash.only(options, *OPTIONS - [ :min, :max ]).update(:inverse => self)
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
# @api private
|
|
600
|
+
def kind_of_inverse?(other)
|
|
601
|
+
other.kind_of?(inverse_class)
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
# @api private
|
|
605
|
+
def cmp?(other, operator)
|
|
606
|
+
name.send(operator, other.name) &&
|
|
607
|
+
cmp_repository?(other, operator, :child) &&
|
|
608
|
+
cmp_repository?(other, operator, :parent) &&
|
|
609
|
+
cmp_model?(other, operator, :child) &&
|
|
610
|
+
cmp_model?(other, operator, :parent) &&
|
|
611
|
+
cmp_key?(other, operator, :child) &&
|
|
612
|
+
cmp_key?(other, operator, :parent) &&
|
|
613
|
+
min.send(operator, other.min) &&
|
|
614
|
+
max.send(operator, other.max) &&
|
|
615
|
+
query.send(operator, other.query)
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# @api private
|
|
619
|
+
def cmp_repository?(other, operator, type)
|
|
620
|
+
# if either repository is nil, then the relationship is relative,
|
|
621
|
+
# and the repositories are considered equivalent
|
|
622
|
+
return true unless repository_name = send("#{type}_repository_name")
|
|
623
|
+
return true unless other_repository_name = other.send("#{type}_repository_name")
|
|
624
|
+
|
|
625
|
+
repository_name.send(operator, other_repository_name)
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# @api private
|
|
629
|
+
def cmp_model?(other, operator, type)
|
|
630
|
+
send("#{type}_model?") &&
|
|
631
|
+
other.send("#{type}_model?") &&
|
|
632
|
+
send("#{type}_model").base_model.send(operator, other.send("#{type}_model").base_model)
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
# @api private
|
|
636
|
+
def cmp_key?(other, operator, type)
|
|
637
|
+
property_method = "#{type}_properties"
|
|
638
|
+
|
|
639
|
+
self_key = send(property_method)
|
|
640
|
+
other_key = other.send(property_method)
|
|
641
|
+
|
|
642
|
+
self_key.send(operator, other_key)
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
def associate_targets(source, targets)
|
|
646
|
+
# TODO: create an object that wraps this logic, and when the first
|
|
647
|
+
# kicker is fired, then it'll load up the collection, and then
|
|
648
|
+
# populate all the other methods
|
|
649
|
+
|
|
650
|
+
target_maps = Hash.new { |hash, key| hash[key] = [] }
|
|
651
|
+
|
|
652
|
+
targets.each do |target|
|
|
653
|
+
target_maps[target_key.get(target)] << target
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
Array(source).each do |source|
|
|
657
|
+
key = source_key.get(source)
|
|
658
|
+
eager_load_targets(source, target_maps[key], query)
|
|
659
|
+
end
|
|
660
|
+
end
|
|
661
|
+
end # class Relationship
|
|
662
|
+
end # module Associations
|
|
663
|
+
end # module DataMapper
|