sbf-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.
- checksums.yaml +7 -0
- data/.autotest +29 -0
- data/.document +5 -0
- data/.gitignore +44 -0
- data/.rspec +1 -0
- data/.rubocop.yml +468 -0
- data/.travis.yml +57 -0
- data/.yardopts +1 -0
- data/Gemfile +70 -0
- data/LICENSE +20 -0
- data/README.md +269 -0
- data/Rakefile +4 -0
- data/dm-core.gemspec +21 -0
- data/lib/dm-core/adapters/abstract_adapter.rb +233 -0
- data/lib/dm-core/adapters/in_memory_adapter.rb +110 -0
- data/lib/dm-core/adapters.rb +249 -0
- data/lib/dm-core/associations/many_to_many.rb +477 -0
- data/lib/dm-core/associations/many_to_one.rb +282 -0
- data/lib/dm-core/associations/one_to_many.rb +332 -0
- data/lib/dm-core/associations/one_to_one.rb +84 -0
- data/lib/dm-core/associations/relationship.rb +650 -0
- data/lib/dm-core/backwards.rb +11 -0
- data/lib/dm-core/collection.rb +1486 -0
- data/lib/dm-core/core_ext/kernel.rb +21 -0
- data/lib/dm-core/core_ext/pathname.rb +4 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +6 -0
- data/lib/dm-core/model/hook.rb +99 -0
- data/lib/dm-core/model/is.rb +30 -0
- data/lib/dm-core/model/property.rb +244 -0
- data/lib/dm-core/model/relationship.rb +366 -0
- data/lib/dm-core/model/scope.rb +87 -0
- data/lib/dm-core/model.rb +876 -0
- data/lib/dm-core/property/binary.rb +19 -0
- data/lib/dm-core/property/boolean.rb +35 -0
- data/lib/dm-core/property/class.rb +23 -0
- data/lib/dm-core/property/date.rb +45 -0
- data/lib/dm-core/property/date_time.rb +44 -0
- data/lib/dm-core/property/decimal.rb +47 -0
- data/lib/dm-core/property/discriminator.rb +40 -0
- data/lib/dm-core/property/float.rb +27 -0
- data/lib/dm-core/property/integer.rb +32 -0
- data/lib/dm-core/property/invalid_value_error.rb +17 -0
- data/lib/dm-core/property/lookup.rb +26 -0
- data/lib/dm-core/property/numeric.rb +35 -0
- data/lib/dm-core/property/object.rb +33 -0
- data/lib/dm-core/property/serial.rb +13 -0
- data/lib/dm-core/property/string.rb +47 -0
- data/lib/dm-core/property/text.rb +12 -0
- data/lib/dm-core/property/time.rb +46 -0
- data/lib/dm-core/property/typecast/numeric.rb +32 -0
- data/lib/dm-core/property/typecast/time.rb +33 -0
- data/lib/dm-core/property.rb +856 -0
- data/lib/dm-core/property_set.rb +177 -0
- data/lib/dm-core/query/conditions/comparison.rb +886 -0
- data/lib/dm-core/query/conditions/operation.rb +710 -0
- data/lib/dm-core/query/direction.rb +33 -0
- data/lib/dm-core/query/operator.rb +34 -0
- data/lib/dm-core/query/path.rb +113 -0
- data/lib/dm-core/query/sort.rb +38 -0
- data/lib/dm-core/query.rb +1352 -0
- data/lib/dm-core/relationship_set.rb +69 -0
- data/lib/dm-core/repository.rb +226 -0
- data/lib/dm-core/resource/persistence_state/clean.rb +36 -0
- data/lib/dm-core/resource/persistence_state/deleted.rb +26 -0
- data/lib/dm-core/resource/persistence_state/dirty.rb +91 -0
- data/lib/dm-core/resource/persistence_state/immutable.rb +32 -0
- data/lib/dm-core/resource/persistence_state/persisted.rb +25 -0
- data/lib/dm-core/resource/persistence_state/transient.rb +87 -0
- data/lib/dm-core/resource/persistence_state.rb +70 -0
- data/lib/dm-core/resource.rb +1220 -0
- data/lib/dm-core/spec/lib/adapter_helpers.rb +63 -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 +164 -0
- data/lib/dm-core/spec/shared/adapter_spec.rb +366 -0
- data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
- data/lib/dm-core/spec/shared/resource_spec.rb +1221 -0
- data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
- data/lib/dm-core/spec/shared/semipublic/property_spec.rb +184 -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 +388 -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 +109 -0
- data/lib/dm-core/support/ordered_set.rb +381 -0
- data/lib/dm-core/support/subject.rb +33 -0
- data/lib/dm-core/support/subject_set.rb +251 -0
- data/lib/dm-core/version.rb +3 -0
- data/lib/dm-core.rb +274 -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 +69 -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 +77 -0
- data/spec/public/model/hook_spec.rb +245 -0
- data/spec/public/model/property_spec.rb +91 -0
- data/spec/public/model/relationship_spec.rb +1040 -0
- data/spec/public/model_spec.rb +456 -0
- data/spec/public/property/binary_spec.rb +43 -0
- data/spec/public/property/boolean_spec.rb +21 -0
- data/spec/public/property/class_spec.rb +27 -0
- data/spec/public/property/date_spec.rb +21 -0
- data/spec/public/property/date_time_spec.rb +21 -0
- data/spec/public/property/decimal_spec.rb +23 -0
- data/spec/public/property/discriminator_spec.rb +134 -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 +117 -0
- data/spec/public/property/serial_spec.rb +22 -0
- data/spec/public/property/string_spec.rb +21 -0
- data/spec/public/property/text_spec.rb +62 -0
- data/spec/public/property/time_spec.rb +21 -0
- data/spec/public/property_spec.rb +333 -0
- data/spec/public/resource/state_spec.rb +72 -0
- data/spec/public/resource_spec.rb +289 -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 +1637 -0
- data/spec/public/shared/finder_shared_spec.rb +1647 -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 +1502 -0
- data/spec/semipublic/query/conditions/operation_spec.rb +1296 -0
- data/spec/semipublic/query/path_spec.rb +471 -0
- data/spec/semipublic/query_spec.rb +3665 -0
- data/spec/semipublic/resource/state/clean_spec.rb +89 -0
- data/spec/semipublic/resource/state/deleted_spec.rb +79 -0
- data/spec/semipublic/resource/state/dirty_spec.rb +163 -0
- data/spec/semipublic/resource/state/immutable_spec.rb +107 -0
- data/spec/semipublic/resource/state/transient_spec.rb +163 -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 +198 -0
- data/spec/semipublic/shared/resource_state_shared_spec.rb +91 -0
- data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
- data/spec/spec_helper.rb +34 -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 +27 -0
- data/spec/unit/hook_spec.rb +1216 -0
- data/spec/unit/inflections_spec.rb +14 -0
- data/spec/unit/lazy_array_spec.rb +1949 -0
- data/spec/unit/mash_spec.rb +289 -0
- data/spec/unit/module_spec.rb +70 -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 +18 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +323 -0
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Associations
|
|
3
|
+
module ManyToMany # :nodoc:
|
|
4
|
+
class Relationship < Associations::OneToMany::Relationship
|
|
5
|
+
extend Chainable
|
|
6
|
+
|
|
7
|
+
OPTIONS = superclass::OPTIONS.dup << :through << :via
|
|
8
|
+
|
|
9
|
+
# Returns a set of keys that identify the target model
|
|
10
|
+
#
|
|
11
|
+
# @return [DataMapper::PropertySet]
|
|
12
|
+
# a set of properties that identify the target model
|
|
13
|
+
#
|
|
14
|
+
# @api semipublic
|
|
15
|
+
def child_key
|
|
16
|
+
return @child_key if defined?(@child_key)
|
|
17
|
+
|
|
18
|
+
repository_name = child_repository_name || parent_repository_name
|
|
19
|
+
properties = child_model&.properties(repository_name)
|
|
20
|
+
|
|
21
|
+
@child_key = if @child_properties
|
|
22
|
+
child_key = properties&.values_at(*@child_properties)
|
|
23
|
+
properties.class.new(child_key).freeze
|
|
24
|
+
else
|
|
25
|
+
properties&.key
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @api semipublic
|
|
30
|
+
alias_method :target_key, :child_key
|
|
31
|
+
|
|
32
|
+
# Intermediate association for through model
|
|
33
|
+
# relationships
|
|
34
|
+
#
|
|
35
|
+
# Example: for :bugs association in
|
|
36
|
+
#
|
|
37
|
+
# class Software::Engineer
|
|
38
|
+
# include DataMapper::Resource
|
|
39
|
+
#
|
|
40
|
+
# has n, :missing_tests
|
|
41
|
+
# has n, :bugs, :through => :missing_tests
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# through is :missing_tests
|
|
45
|
+
#
|
|
46
|
+
# TODO: document a case when
|
|
47
|
+
# through option is a model and
|
|
48
|
+
# not an association name
|
|
49
|
+
#
|
|
50
|
+
# @api semipublic
|
|
51
|
+
def through
|
|
52
|
+
return @through if defined?(@through)
|
|
53
|
+
|
|
54
|
+
@through = options[:through]
|
|
55
|
+
|
|
56
|
+
return @through if @through.is_a?(Associations::Relationship)
|
|
57
|
+
|
|
58
|
+
model = source_model
|
|
59
|
+
repository_name = source_repository_name
|
|
60
|
+
relationships = model&.relationships(repository_name)
|
|
61
|
+
name = through_relationship_name
|
|
62
|
+
|
|
63
|
+
@through = relationships[name] ||
|
|
64
|
+
DataMapper.repository(repository_name) do
|
|
65
|
+
model&.has(min..max, name, through_model, one_to_many_options)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
@through.child_key
|
|
69
|
+
|
|
70
|
+
@through
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @api semipublic
|
|
74
|
+
def via
|
|
75
|
+
return @via if defined?(@via)
|
|
76
|
+
|
|
77
|
+
@via = options[:via]
|
|
78
|
+
|
|
79
|
+
return @via if @via.is_a?(Associations::Relationship)
|
|
80
|
+
|
|
81
|
+
name = self.name
|
|
82
|
+
through = self.through
|
|
83
|
+
repository_name = through.relative_target_repository_name
|
|
84
|
+
through_model = through.target_model
|
|
85
|
+
relationships = through_model.relationships(repository_name)
|
|
86
|
+
singular_name = DataMapper::Inflector.singularize(name.to_s).to_sym
|
|
87
|
+
|
|
88
|
+
@via = relationships[@via] ||
|
|
89
|
+
relationships[name] ||
|
|
90
|
+
relationships[singular_name]
|
|
91
|
+
|
|
92
|
+
@via ||= if anonymous_through_model?
|
|
93
|
+
DataMapper.repository(repository_name) do
|
|
94
|
+
through_model.belongs_to(singular_name, target_model, many_to_one_options)
|
|
95
|
+
end
|
|
96
|
+
else
|
|
97
|
+
raise UnknownRelationshipError, "No relationships named #{name} or #{singular_name} in #{through_model}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
@via.child_key
|
|
101
|
+
|
|
102
|
+
@via
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @api semipublic
|
|
106
|
+
def links
|
|
107
|
+
return @links if defined?(@links)
|
|
108
|
+
|
|
109
|
+
@links = []
|
|
110
|
+
links = [through, via]
|
|
111
|
+
|
|
112
|
+
while (relationship = links.shift)
|
|
113
|
+
if relationship.respond_to?(:links)
|
|
114
|
+
links.unshift(*relationship.links)
|
|
115
|
+
else
|
|
116
|
+
@links << relationship
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
@links.freeze
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Initialize the chain for "many to many" relationships
|
|
124
|
+
#
|
|
125
|
+
# @return [self]
|
|
126
|
+
#
|
|
127
|
+
# @api public
|
|
128
|
+
def finalize
|
|
129
|
+
through
|
|
130
|
+
via
|
|
131
|
+
self
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# @api private
|
|
135
|
+
def source_scope(source)
|
|
136
|
+
{through.inverse => source}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# @api private
|
|
140
|
+
def query
|
|
141
|
+
# TODO: consider making this a query_for method, so that ManyToMany::Relationship#query only
|
|
142
|
+
# returns the query supplied in the definition
|
|
143
|
+
@many_to_many_query ||= super.merge(links: links).freeze
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Eager load the collection using the source as a base
|
|
147
|
+
#
|
|
148
|
+
# @param [Resource, Collection] source
|
|
149
|
+
# the source to query with
|
|
150
|
+
# @param [Query, Hash] other_query
|
|
151
|
+
# optional query to restrict the collection
|
|
152
|
+
#
|
|
153
|
+
# @return [ManyToMany::Collection]
|
|
154
|
+
# the loaded collection for the source
|
|
155
|
+
#
|
|
156
|
+
# @api private
|
|
157
|
+
def eager_load(source, other_query = nil)
|
|
158
|
+
# FIXME: enable SEL for m:m relationships
|
|
159
|
+
source.model.all(query_for(source, other_query))
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# @api private
|
|
163
|
+
private def through_model
|
|
164
|
+
namespace, name = through_model_namespace_name
|
|
165
|
+
|
|
166
|
+
if namespace.const_defined?(name)
|
|
167
|
+
namespace.const_get(name)
|
|
168
|
+
else
|
|
169
|
+
Model.new(name, namespace) do
|
|
170
|
+
# all properties added to the anonymous through model are keys
|
|
171
|
+
def property(name, type, options = {})
|
|
172
|
+
options[:key] = true
|
|
173
|
+
options.delete(:index)
|
|
174
|
+
super
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @api private
|
|
181
|
+
private def through_model_namespace_name
|
|
182
|
+
target_parts = target_model&.base_model&.name&.split('::')
|
|
183
|
+
source_parts = source_model&.base_model&.name&.split('::')
|
|
184
|
+
|
|
185
|
+
name = [target_parts&.pop, source_parts&.pop].sort.join
|
|
186
|
+
|
|
187
|
+
namespace = Object
|
|
188
|
+
|
|
189
|
+
# find the common namespace between the target_model and source_model
|
|
190
|
+
target_parts&.zip(source_parts) do |target_part, source_part|
|
|
191
|
+
break if target_part != source_part
|
|
192
|
+
|
|
193
|
+
namespace = namespace.const_get(target_part)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
[namespace, name]
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# @api private
|
|
200
|
+
private def through_relationship_name
|
|
201
|
+
if anonymous_through_model?
|
|
202
|
+
namespace = through_model_namespace_name.first
|
|
203
|
+
relationship_name = DataMapper::Inflector.underscore(through_model.name.sub(/\A#{namespace&.name}::/, '')).tr('/', '_')
|
|
204
|
+
DataMapper::Inflector.pluralize(relationship_name).to_sym
|
|
205
|
+
else
|
|
206
|
+
options[:through]
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Check if the :through association uses an anonymous model
|
|
211
|
+
#
|
|
212
|
+
# An anonymous model means that DataMapper creates the model
|
|
213
|
+
# in-memory, and sets the relationships to join the source
|
|
214
|
+
# and the target model.
|
|
215
|
+
#
|
|
216
|
+
# @return [Boolean]
|
|
217
|
+
# true if the through model is anonymous
|
|
218
|
+
#
|
|
219
|
+
# @api private
|
|
220
|
+
private def anonymous_through_model?
|
|
221
|
+
options[:through] == Resource
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# @api private
|
|
225
|
+
private def nearest_relationship
|
|
226
|
+
return @nearest_relationship if defined?(@nearest_relationship)
|
|
227
|
+
|
|
228
|
+
nearest_relationship = self
|
|
229
|
+
|
|
230
|
+
nearest_relationship = nearest_relationship.through while nearest_relationship.respond_to?(:through)
|
|
231
|
+
|
|
232
|
+
@nearest_relationship = nearest_relationship
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# @api private
|
|
236
|
+
private def valid_target?(target)
|
|
237
|
+
relationship = via
|
|
238
|
+
source_key = relationship.source_key
|
|
239
|
+
target_key = relationship.target_key
|
|
240
|
+
|
|
241
|
+
target.is_a?(target_model) &&
|
|
242
|
+
source_key.valid?(target_key.get(target))
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# @api private
|
|
246
|
+
private def valid_source?(source)
|
|
247
|
+
relationship = nearest_relationship
|
|
248
|
+
source_key = relationship.source_key
|
|
249
|
+
target_key = relationship.target_key
|
|
250
|
+
|
|
251
|
+
source.is_a?(source_model) &&
|
|
252
|
+
target_key.valid?(source_key.get(source))
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
chainable do
|
|
256
|
+
# @api semipublic
|
|
257
|
+
def many_to_one_options
|
|
258
|
+
{parent_key: target_key.map(&:name)}
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# @api semipublic
|
|
262
|
+
def one_to_many_options
|
|
263
|
+
{parent_key: source_key.map(&:name)}
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# Returns the inverse relationship class
|
|
268
|
+
#
|
|
269
|
+
# @api private
|
|
270
|
+
private def inverse_class
|
|
271
|
+
self.class
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# @api private
|
|
275
|
+
private def invert
|
|
276
|
+
inverse_class.new(inverse_name, parent_model, child_model, inverted_options)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# @api private
|
|
280
|
+
private def inverted_options
|
|
281
|
+
links = self.links.dup
|
|
282
|
+
through = links.pop&.inverse
|
|
283
|
+
|
|
284
|
+
links.reverse_each do |relationship|
|
|
285
|
+
inverse = relationship.inverse
|
|
286
|
+
|
|
287
|
+
through = self.class.new(
|
|
288
|
+
inverse.name,
|
|
289
|
+
inverse.child_model,
|
|
290
|
+
inverse.parent_model,
|
|
291
|
+
inverse.options.merge(through: through)
|
|
292
|
+
)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
options = self.options
|
|
296
|
+
|
|
297
|
+
DataMapper::Ext::Hash.only(options, *OPTIONS - %i(min max)).update(
|
|
298
|
+
through: through,
|
|
299
|
+
child_key: options[:parent_key],
|
|
300
|
+
parent_key: options[:child_key],
|
|
301
|
+
inverse: self
|
|
302
|
+
)
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Returns collection class used by this type of
|
|
306
|
+
# relationship
|
|
307
|
+
#
|
|
308
|
+
# @api private
|
|
309
|
+
private def collection_class
|
|
310
|
+
ManyToMany::Collection
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
class Collection < Associations::OneToMany::Collection
|
|
315
|
+
# Remove every Resource in the m:m Collection from the repository
|
|
316
|
+
#
|
|
317
|
+
# This performs a deletion of each Resource in the Collection from
|
|
318
|
+
# the repository and clears the Collection.
|
|
319
|
+
#
|
|
320
|
+
# @return [Boolean]
|
|
321
|
+
# true if the resources were successfully destroyed
|
|
322
|
+
#
|
|
323
|
+
# @api public
|
|
324
|
+
def destroy
|
|
325
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
326
|
+
|
|
327
|
+
# make sure the records are loaded so they can be found when
|
|
328
|
+
# the intermediaries are removed
|
|
329
|
+
lazy_load
|
|
330
|
+
|
|
331
|
+
return false unless intermediaries.all(via => self).destroy
|
|
332
|
+
|
|
333
|
+
super
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
# Remove every Resource in the m:m Collection from the repository, bypassing validation
|
|
337
|
+
#
|
|
338
|
+
# This performs a deletion of each Resource in the Collection from
|
|
339
|
+
# the repository and clears the Collection while skipping
|
|
340
|
+
# validation.
|
|
341
|
+
#
|
|
342
|
+
# @return [Boolean]
|
|
343
|
+
# true if the resources were successfully destroyed
|
|
344
|
+
#
|
|
345
|
+
# @api public
|
|
346
|
+
def destroy!
|
|
347
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
348
|
+
|
|
349
|
+
model = self.model
|
|
350
|
+
key = model.key(repository_name)
|
|
351
|
+
conditions = Query.target_conditions(self, key, key)
|
|
352
|
+
|
|
353
|
+
return false unless intermediaries.all(via => self).destroy!
|
|
354
|
+
|
|
355
|
+
return false unless model.all(repository: repository, conditions: conditions).destroy!
|
|
356
|
+
|
|
357
|
+
each do |resource|
|
|
358
|
+
resource.persistence_state = Resource::PersistenceState::Immutable.new(resource)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
clear
|
|
362
|
+
|
|
363
|
+
true
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
# Return the intermediaries linking the source to the targets
|
|
367
|
+
#
|
|
368
|
+
# @return [Collection]
|
|
369
|
+
# the intermediary collection
|
|
370
|
+
#
|
|
371
|
+
# @api public
|
|
372
|
+
def intermediaries
|
|
373
|
+
through = self.through
|
|
374
|
+
source = self.source
|
|
375
|
+
|
|
376
|
+
@intermediaries ||= if through.loaded?(source)
|
|
377
|
+
through.get_collection(source)
|
|
378
|
+
else
|
|
379
|
+
reset_intermediaries
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Map the resources in the collection to the intermediaries
|
|
384
|
+
#
|
|
385
|
+
# @return [Hash]
|
|
386
|
+
# the map of resources to their intermediaries
|
|
387
|
+
#
|
|
388
|
+
# @api private
|
|
389
|
+
protected def intermediary_for
|
|
390
|
+
@intermediary_for ||= {}
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
# @api private
|
|
394
|
+
protected def through
|
|
395
|
+
relationship.through
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# @api private
|
|
399
|
+
protected def via
|
|
400
|
+
relationship.via
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
private def _create(attributes, execute_hooks = true)
|
|
404
|
+
via = self.via
|
|
405
|
+
if via.respond_to?(:resource_for)
|
|
406
|
+
resource = super
|
|
407
|
+
resource if create_intermediary(execute_hooks, resource)
|
|
408
|
+
elsif (intermediary = create_intermediary(execute_hooks))
|
|
409
|
+
super(attributes.merge(via.inverse => intermediary), execute_hooks)
|
|
410
|
+
end
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# @api private
|
|
414
|
+
private def _save(execute_hooks = true)
|
|
415
|
+
via = self.via
|
|
416
|
+
|
|
417
|
+
if @removed.any?
|
|
418
|
+
# delete only intermediaries linked to the removed targets
|
|
419
|
+
return false unless intermediaries.all(via => @removed).send(execute_hooks ? :destroy : :destroy!)
|
|
420
|
+
|
|
421
|
+
# reset the intermediaries so that it reflects the current state of the datastore
|
|
422
|
+
reset_intermediaries
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
loaded_entries = self.loaded_entries
|
|
426
|
+
|
|
427
|
+
if via.respond_to?(:resource_for)
|
|
428
|
+
super
|
|
429
|
+
loaded_entries.all? { |resource| create_intermediary(execute_hooks, resource) }
|
|
430
|
+
else
|
|
431
|
+
if loaded_entries.any? && (intermediary = create_intermediary(execute_hooks))
|
|
432
|
+
inverse = via.inverse
|
|
433
|
+
loaded_entries.each { |resource| inverse.set(resource, intermediary) }
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
super
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# @api private
|
|
441
|
+
private def create_intermediary(execute_hooks, resource = nil)
|
|
442
|
+
intermediary_for = self.intermediary_for
|
|
443
|
+
|
|
444
|
+
intermediary_resource = intermediary_for[resource]
|
|
445
|
+
return intermediary_resource if intermediary_resource
|
|
446
|
+
|
|
447
|
+
intermediaries = self.intermediaries
|
|
448
|
+
method = execute_hooks ? :save : :save!
|
|
449
|
+
|
|
450
|
+
return unless intermediaries.send(method)
|
|
451
|
+
|
|
452
|
+
attributes = {}
|
|
453
|
+
attributes[via] = resource if resource
|
|
454
|
+
|
|
455
|
+
intermediary = intermediaries.first_or_new(attributes)
|
|
456
|
+
return unless intermediary.__send__(method)
|
|
457
|
+
|
|
458
|
+
# map the resource, even if it is nil, to the intermediary
|
|
459
|
+
intermediary_for[resource] = intermediary
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# @api private
|
|
463
|
+
private def reset_intermediaries
|
|
464
|
+
through = self.through
|
|
465
|
+
source = self.source
|
|
466
|
+
|
|
467
|
+
through.set_collection(source, through.collection_for(source))
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
# @api private
|
|
471
|
+
private def inverse_set(*)
|
|
472
|
+
# do nothing
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
end
|