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,290 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Associations
|
|
3
|
+
module ManyToOne #:nodoc:
|
|
4
|
+
# Relationship class with implementation specific
|
|
5
|
+
# to n side of 1 to n association
|
|
6
|
+
class Relationship < Associations::Relationship
|
|
7
|
+
OPTIONS = superclass::OPTIONS.dup << :required << :key << :unique
|
|
8
|
+
|
|
9
|
+
# @api semipublic
|
|
10
|
+
alias_method :source_repository_name, :child_repository_name
|
|
11
|
+
|
|
12
|
+
# @api semipublic
|
|
13
|
+
alias_method :source_model, :child_model
|
|
14
|
+
|
|
15
|
+
# @api semipublic
|
|
16
|
+
alias_method :target_repository_name, :parent_repository_name
|
|
17
|
+
|
|
18
|
+
# @api semipublic
|
|
19
|
+
alias_method :target_model, :parent_model
|
|
20
|
+
|
|
21
|
+
# @api semipublic
|
|
22
|
+
alias_method :target_key, :parent_key
|
|
23
|
+
|
|
24
|
+
# @api semipublic
|
|
25
|
+
def required?
|
|
26
|
+
@required
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @api semipublic
|
|
30
|
+
def key?
|
|
31
|
+
@key
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @api semipublic
|
|
35
|
+
def unique?
|
|
36
|
+
!!@unique
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @deprecated
|
|
40
|
+
def nullable?
|
|
41
|
+
raise "#{self.class}#nullable? is deprecated, use #{self.class}#required? instead (#{caller.first})"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns a set of keys that identify source model
|
|
45
|
+
#
|
|
46
|
+
# @return [DataMapper::PropertySet] a set of properties that identify source model
|
|
47
|
+
# @api private
|
|
48
|
+
def child_key
|
|
49
|
+
return @child_key if defined?(@child_key)
|
|
50
|
+
|
|
51
|
+
model = source_model
|
|
52
|
+
repository_name = source_repository_name || target_repository_name
|
|
53
|
+
properties = model.properties(repository_name)
|
|
54
|
+
|
|
55
|
+
source_key = target_key.zip(@child_properties || []).map do |target_property, property_name|
|
|
56
|
+
property_name ||= "#{name}_#{target_property.name}".to_sym
|
|
57
|
+
|
|
58
|
+
properties[property_name] || begin
|
|
59
|
+
# create the property within the correct repository
|
|
60
|
+
DataMapper.repository(repository_name) do
|
|
61
|
+
model.property(property_name, target_property.to_child_key, source_key_options(target_property))
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@child_key = properties.class.new(source_key).freeze
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @api semipublic
|
|
70
|
+
alias_method :source_key, :child_key
|
|
71
|
+
|
|
72
|
+
# Initialize the foreign key property this "many to one"
|
|
73
|
+
# relationship uses to persist itself
|
|
74
|
+
#
|
|
75
|
+
# @return [self]
|
|
76
|
+
#
|
|
77
|
+
# @api public
|
|
78
|
+
def finalize
|
|
79
|
+
child_key
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Returns a hash of conditions that scopes query that fetches
|
|
84
|
+
# target object
|
|
85
|
+
#
|
|
86
|
+
# @return [Hash]
|
|
87
|
+
# Hash of conditions that scopes query
|
|
88
|
+
#
|
|
89
|
+
# @api private
|
|
90
|
+
def source_scope(source)
|
|
91
|
+
if source.kind_of?(Resource)
|
|
92
|
+
Query.target_conditions(source, source_key, target_key)
|
|
93
|
+
else
|
|
94
|
+
super
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Returns a Resource for this relationship with a given source
|
|
99
|
+
#
|
|
100
|
+
# @param [Resource] source
|
|
101
|
+
# A Resource to scope the collection with
|
|
102
|
+
# @param [Query] other_query (optional)
|
|
103
|
+
# A Query to further scope the collection with
|
|
104
|
+
#
|
|
105
|
+
# @return [Resource]
|
|
106
|
+
# The resource scoped to the relationship, source and query
|
|
107
|
+
#
|
|
108
|
+
# @api private
|
|
109
|
+
def resource_for(source, other_query = nil)
|
|
110
|
+
query = query_for(source, other_query)
|
|
111
|
+
|
|
112
|
+
# If the target key is equal to the model key, we can use the
|
|
113
|
+
# Model#get so the IdentityMap is used
|
|
114
|
+
if target_key == target_model.key
|
|
115
|
+
target = target_model.get(*source_key.get!(source))
|
|
116
|
+
if query.conditions.matches?(target)
|
|
117
|
+
target
|
|
118
|
+
else
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
else
|
|
122
|
+
target_model.first(query)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Loads and returns association target (ex.: author) for given source resource
|
|
127
|
+
# (ex.: article)
|
|
128
|
+
#
|
|
129
|
+
# @param source [DataMapper::Resource]
|
|
130
|
+
# source object (ex.: instance of article)
|
|
131
|
+
# @param other_query [DataMapper::Query]
|
|
132
|
+
# Query options
|
|
133
|
+
#
|
|
134
|
+
# @api semipublic
|
|
135
|
+
def get(source, query = nil)
|
|
136
|
+
lazy_load(source)
|
|
137
|
+
|
|
138
|
+
if query
|
|
139
|
+
collection = get_collection(source)
|
|
140
|
+
collection.first(query) if collection
|
|
141
|
+
else
|
|
142
|
+
get!(source)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def get_collection(source)
|
|
147
|
+
target = get!(source)
|
|
148
|
+
target.collection_for_self if target
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Sets value of association target (ex.: author) for given source resource
|
|
152
|
+
# (ex.: article)
|
|
153
|
+
#
|
|
154
|
+
# @param source [DataMapper::Resource]
|
|
155
|
+
# source object (ex.: instance of article)
|
|
156
|
+
#
|
|
157
|
+
# @param target [DataMapper::Resource]
|
|
158
|
+
# target object (ex.: instance of author)
|
|
159
|
+
#
|
|
160
|
+
# @api semipublic
|
|
161
|
+
def set(source, target)
|
|
162
|
+
target = typecast(target)
|
|
163
|
+
source_key.set(source, target_key.get(target))
|
|
164
|
+
set!(source, target)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# @api semipublic
|
|
168
|
+
def default_for(source)
|
|
169
|
+
typecast(super)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Loads association target and sets resulting value on
|
|
173
|
+
# given source resource
|
|
174
|
+
#
|
|
175
|
+
# @param [Resource] source
|
|
176
|
+
# the source resource for the association
|
|
177
|
+
#
|
|
178
|
+
# @return [undefined]
|
|
179
|
+
#
|
|
180
|
+
# @api private
|
|
181
|
+
def lazy_load(source)
|
|
182
|
+
source_key_different = source_key_different?(source)
|
|
183
|
+
|
|
184
|
+
if (loaded?(source) && !source_key_different) || !valid_source?(source)
|
|
185
|
+
return
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# SEL: load all related resources in the source collection
|
|
189
|
+
if source.saved? && (collection = source.collection).size > 1
|
|
190
|
+
eager_load(collection)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
if !loaded?(source) || (source_key_dirty?(source) && source.saved?)
|
|
194
|
+
set!(source, resource_for(source))
|
|
195
|
+
elsif loaded?(source) && source_key_different
|
|
196
|
+
source_key.set(source, target_key.get!(get!(source)))
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
# Initializes the relationship, always using max cardinality of 1.
|
|
203
|
+
#
|
|
204
|
+
# @api semipublic
|
|
205
|
+
def initialize(name, source_model, target_model, options = {})
|
|
206
|
+
if options.key?(:nullable)
|
|
207
|
+
raise ":nullable is deprecated, use :required instead (#{caller[2]})"
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
@required = options.fetch(:required, true)
|
|
211
|
+
@key = options.fetch(:key, false)
|
|
212
|
+
@unique = options.fetch(:unique, false)
|
|
213
|
+
target_model ||= DataMapper::Inflector.camelize(name)
|
|
214
|
+
options = { :min => @required ? 1 : 0, :max => 1 }.update(options)
|
|
215
|
+
super
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Sets the association targets in the resource
|
|
219
|
+
#
|
|
220
|
+
# @param [Resource] source
|
|
221
|
+
# the source to set
|
|
222
|
+
# @param [Array(Resource)] targets
|
|
223
|
+
# the target resource for the association
|
|
224
|
+
# @param [Query, Hash] query
|
|
225
|
+
# not used
|
|
226
|
+
#
|
|
227
|
+
# @return [undefined]
|
|
228
|
+
#
|
|
229
|
+
# @api private
|
|
230
|
+
def eager_load_targets(source, targets, query)
|
|
231
|
+
set(source, targets.first)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# @api private
|
|
235
|
+
def typecast(target)
|
|
236
|
+
if target.kind_of?(Hash)
|
|
237
|
+
target_model.new(target)
|
|
238
|
+
else
|
|
239
|
+
target
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Returns the inverse relationship class
|
|
244
|
+
#
|
|
245
|
+
# @api private
|
|
246
|
+
def inverse_class
|
|
247
|
+
OneToMany::Relationship
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Returns the inverse relationship name
|
|
251
|
+
#
|
|
252
|
+
# @api private
|
|
253
|
+
def inverse_name
|
|
254
|
+
name = super
|
|
255
|
+
return name if name
|
|
256
|
+
|
|
257
|
+
name = DataMapper::Inflector.demodulize(source_model.name)
|
|
258
|
+
name = DataMapper::Inflector.underscore(name)
|
|
259
|
+
name = DataMapper::Inflector.pluralize(name)
|
|
260
|
+
name.to_sym
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# @api private
|
|
264
|
+
def source_key_options(target_property)
|
|
265
|
+
DataMapper::Ext::Hash.only(target_property.options, :length, :precision, :scale, :min, :max).update(
|
|
266
|
+
:index => name,
|
|
267
|
+
:required => required?,
|
|
268
|
+
:key => key?,
|
|
269
|
+
:unique => @unique
|
|
270
|
+
)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# @api private
|
|
274
|
+
def child_properties
|
|
275
|
+
source_key.map { |property| property.name }
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# @api private
|
|
279
|
+
def source_key_different?(source)
|
|
280
|
+
source_key.get!(source) != target_key.get!(get!(source))
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# @api private
|
|
284
|
+
def source_key_dirty?(source)
|
|
285
|
+
source.dirty_attributes.keys.any? { |property| source_key.include?(property) }
|
|
286
|
+
end
|
|
287
|
+
end # class Relationship
|
|
288
|
+
end # module ManyToOne
|
|
289
|
+
end # module Associations
|
|
290
|
+
end # module DataMapper
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Associations
|
|
3
|
+
module OneToMany #:nodoc:
|
|
4
|
+
class Relationship < Associations::Relationship
|
|
5
|
+
# @api semipublic
|
|
6
|
+
alias_method :target_repository_name, :child_repository_name
|
|
7
|
+
|
|
8
|
+
# @api semipublic
|
|
9
|
+
alias_method :target_model, :child_model
|
|
10
|
+
|
|
11
|
+
# @api semipublic
|
|
12
|
+
alias_method :source_repository_name, :parent_repository_name
|
|
13
|
+
|
|
14
|
+
# @api semipublic
|
|
15
|
+
alias_method :source_model, :parent_model
|
|
16
|
+
|
|
17
|
+
# @api semipublic
|
|
18
|
+
alias_method :source_key, :parent_key
|
|
19
|
+
|
|
20
|
+
# @api semipublic
|
|
21
|
+
def child_key
|
|
22
|
+
inverse.child_key
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @api semipublic
|
|
26
|
+
alias_method :target_key, :child_key
|
|
27
|
+
|
|
28
|
+
# Returns a Collection for this relationship with a given source
|
|
29
|
+
#
|
|
30
|
+
# @param [Resource] source
|
|
31
|
+
# A Resource to scope the collection with
|
|
32
|
+
# @param [Query] other_query (optional)
|
|
33
|
+
# A Query to further scope the collection with
|
|
34
|
+
#
|
|
35
|
+
# @return [Collection]
|
|
36
|
+
# The collection scoped to the relationship, source and query
|
|
37
|
+
#
|
|
38
|
+
# @api private
|
|
39
|
+
def collection_for(source, other_query = nil)
|
|
40
|
+
query = query_for(source, other_query)
|
|
41
|
+
|
|
42
|
+
collection = collection_class.new(query)
|
|
43
|
+
collection.relationship = self
|
|
44
|
+
collection.source = source
|
|
45
|
+
|
|
46
|
+
# make the collection empty if the source is new
|
|
47
|
+
collection.replace([]) if source.new?
|
|
48
|
+
|
|
49
|
+
collection
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Loads and returns association targets (ex.: articles) for given source resource
|
|
53
|
+
# (ex.: author)
|
|
54
|
+
#
|
|
55
|
+
# @api semipublic
|
|
56
|
+
def get(source, query = nil)
|
|
57
|
+
lazy_load(source)
|
|
58
|
+
collection = get_collection(source)
|
|
59
|
+
query ? collection.all(query) : collection
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @api private
|
|
63
|
+
def get_collection(source)
|
|
64
|
+
get!(source)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Sets value of association targets (ex.: paragraphs) for given source resource
|
|
68
|
+
# (ex.: article)
|
|
69
|
+
#
|
|
70
|
+
# @api semipublic
|
|
71
|
+
def set(source, targets)
|
|
72
|
+
lazy_load(source)
|
|
73
|
+
get!(source).replace(targets)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @api private
|
|
77
|
+
def set_collection(source, target)
|
|
78
|
+
set!(source, target)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Loads association targets and sets resulting value on
|
|
82
|
+
# given source resource
|
|
83
|
+
#
|
|
84
|
+
# @param [Resource] source
|
|
85
|
+
# the source resource for the association
|
|
86
|
+
#
|
|
87
|
+
# @return [undefined]
|
|
88
|
+
#
|
|
89
|
+
# @api private
|
|
90
|
+
def lazy_load(source)
|
|
91
|
+
return if loaded?(source)
|
|
92
|
+
|
|
93
|
+
# SEL: load all related resources in the source collection
|
|
94
|
+
if source.saved? && (collection = source.collection).size > 1
|
|
95
|
+
eager_load(collection)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
unless loaded?(source)
|
|
99
|
+
set!(source, collection_for(source))
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# initialize the inverse "many to one" relationships explicitly before
|
|
104
|
+
# initializing other relationships. This makes sure that foreign key
|
|
105
|
+
# properties always appear in the order they were declared.
|
|
106
|
+
#
|
|
107
|
+
# @return [self]
|
|
108
|
+
#
|
|
109
|
+
# @api public
|
|
110
|
+
def finalize
|
|
111
|
+
child_model.relationships.each do |relationship|
|
|
112
|
+
# TODO: should this check #inverse?
|
|
113
|
+
# relationship.child_key if inverse?(relationship)
|
|
114
|
+
if relationship.kind_of?(Associations::ManyToOne::Relationship)
|
|
115
|
+
relationship.finalize
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
inverse.finalize
|
|
119
|
+
self
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# @api semipublic
|
|
123
|
+
def default_for(source)
|
|
124
|
+
collection_for(source).replace(Array(super))
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
# @api semipublic
|
|
130
|
+
def initialize(name, target_model, source_model, options = {})
|
|
131
|
+
target_model ||= DataMapper::Inflector.camelize(DataMapper::Inflector.singularize(name.to_s))
|
|
132
|
+
options = { :min => 0, :max => source_model.n }.update(options)
|
|
133
|
+
super
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Sets the association targets in the resource
|
|
137
|
+
#
|
|
138
|
+
# @param [Resource] source
|
|
139
|
+
# the source to set
|
|
140
|
+
# @param [Array<Resource>] targets
|
|
141
|
+
# the target collection for the association
|
|
142
|
+
# @param [Query, Hash] query
|
|
143
|
+
# the query to scope the association with
|
|
144
|
+
#
|
|
145
|
+
# @return [undefined]
|
|
146
|
+
#
|
|
147
|
+
# @api private
|
|
148
|
+
def eager_load_targets(source, targets, query)
|
|
149
|
+
set!(source, collection_for(source, query).set(targets))
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Returns collection class used by this type of
|
|
153
|
+
# relationship
|
|
154
|
+
#
|
|
155
|
+
# @api private
|
|
156
|
+
def collection_class
|
|
157
|
+
OneToMany::Collection
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Returns the inverse relationship class
|
|
161
|
+
#
|
|
162
|
+
# @api private
|
|
163
|
+
def inverse_class
|
|
164
|
+
ManyToOne::Relationship
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Returns the inverse relationship name
|
|
168
|
+
#
|
|
169
|
+
# @api private
|
|
170
|
+
def inverse_name
|
|
171
|
+
super || DataMapper::Inflector.underscore(DataMapper::Inflector.demodulize(source_model.name)).to_sym
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# @api private
|
|
175
|
+
def child_properties
|
|
176
|
+
super || parent_key.map do |parent_property|
|
|
177
|
+
"#{inverse_name}_#{parent_property.name}".to_sym
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end # class Relationship
|
|
181
|
+
|
|
182
|
+
class Collection < DataMapper::Collection
|
|
183
|
+
# @api private
|
|
184
|
+
attr_accessor :relationship
|
|
185
|
+
|
|
186
|
+
# @api private
|
|
187
|
+
attr_accessor :source
|
|
188
|
+
|
|
189
|
+
# @api public
|
|
190
|
+
def reload(*)
|
|
191
|
+
assert_source_saved 'The source must be saved before reloading the collection'
|
|
192
|
+
super
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Replace the Resources within the 1:m Collection
|
|
196
|
+
#
|
|
197
|
+
# @param [Enumerable] other
|
|
198
|
+
# List of other Resources to replace with
|
|
199
|
+
#
|
|
200
|
+
# @return [Collection]
|
|
201
|
+
# self
|
|
202
|
+
#
|
|
203
|
+
# @api public
|
|
204
|
+
def replace(*)
|
|
205
|
+
lazy_load # lazy load so that targets are always orphaned
|
|
206
|
+
super
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Removes all Resources from the 1:m Collection
|
|
210
|
+
#
|
|
211
|
+
# This should remove and orphan each Resource from the 1:m Collection.
|
|
212
|
+
#
|
|
213
|
+
# @return [Collection]
|
|
214
|
+
# self
|
|
215
|
+
#
|
|
216
|
+
# @api public
|
|
217
|
+
def clear
|
|
218
|
+
lazy_load # lazy load so that targets are always orphaned
|
|
219
|
+
super
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Update every Resource in the 1:m Collection
|
|
223
|
+
#
|
|
224
|
+
# @param [Hash] attributes
|
|
225
|
+
# attributes to update with
|
|
226
|
+
#
|
|
227
|
+
# @return [Boolean]
|
|
228
|
+
# true if the resources were successfully updated
|
|
229
|
+
#
|
|
230
|
+
# @api public
|
|
231
|
+
def update(*)
|
|
232
|
+
assert_source_saved 'The source must be saved before mass-updating the collection'
|
|
233
|
+
super
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Update every Resource in the 1:m Collection, bypassing validation
|
|
237
|
+
#
|
|
238
|
+
# @param [Hash] attributes
|
|
239
|
+
# attributes to update
|
|
240
|
+
#
|
|
241
|
+
# @return [Boolean]
|
|
242
|
+
# true if the resources were successfully updated
|
|
243
|
+
#
|
|
244
|
+
# @api public
|
|
245
|
+
def update!(*)
|
|
246
|
+
assert_source_saved 'The source must be saved before mass-updating the collection'
|
|
247
|
+
super
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Remove every Resource in the 1:m Collection from the repository
|
|
251
|
+
#
|
|
252
|
+
# This performs a deletion of each Resource in the Collection from
|
|
253
|
+
# the repository and clears the Collection.
|
|
254
|
+
#
|
|
255
|
+
# @return [Boolean]
|
|
256
|
+
# true if the resources were successfully destroyed
|
|
257
|
+
#
|
|
258
|
+
# @api public
|
|
259
|
+
def destroy
|
|
260
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
261
|
+
super
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Remove every Resource in the 1:m Collection from the repository, bypassing validation
|
|
265
|
+
#
|
|
266
|
+
# This performs a deletion of each Resource in the Collection from
|
|
267
|
+
# the repository and clears the Collection while skipping
|
|
268
|
+
# validation.
|
|
269
|
+
#
|
|
270
|
+
# @return [Boolean]
|
|
271
|
+
# true if the resources were successfully destroyed
|
|
272
|
+
#
|
|
273
|
+
# @api public
|
|
274
|
+
def destroy!
|
|
275
|
+
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
|
276
|
+
super
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
private
|
|
280
|
+
|
|
281
|
+
# @api private
|
|
282
|
+
def _create(*)
|
|
283
|
+
assert_source_saved 'The source must be saved before creating a resource'
|
|
284
|
+
super
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# @api private
|
|
288
|
+
def _save(execute_hooks = true)
|
|
289
|
+
assert_source_saved 'The source must be saved before saving the collection'
|
|
290
|
+
|
|
291
|
+
# update removed resources to not reference the source
|
|
292
|
+
@removed.all? { |resource| resource.destroyed? || resource.__send__(execute_hooks ? :save : :save!) } && super
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# @api private
|
|
296
|
+
def lazy_load
|
|
297
|
+
if source.saved?
|
|
298
|
+
super
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# @api private
|
|
303
|
+
def new_collection(query, resources = nil, &block)
|
|
304
|
+
collection = self.class.new(query, &block)
|
|
305
|
+
|
|
306
|
+
collection.relationship = relationship
|
|
307
|
+
collection.source = source
|
|
308
|
+
|
|
309
|
+
resources ||= filter(query) if loaded?
|
|
310
|
+
|
|
311
|
+
# set the resources after the relationship and source are set
|
|
312
|
+
if resources
|
|
313
|
+
collection.set(resources)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
collection
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# @api private
|
|
320
|
+
def resource_added(resource)
|
|
321
|
+
resource = initialize_resource(resource)
|
|
322
|
+
inverse_set(resource, source)
|
|
323
|
+
super
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# @api private
|
|
327
|
+
def resource_removed(resource)
|
|
328
|
+
inverse_set(resource, nil)
|
|
329
|
+
super
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
# @api private
|
|
333
|
+
def inverse_set(source, target)
|
|
334
|
+
unless source.readonly?
|
|
335
|
+
relationship.inverse.set(source, target)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# @api private
|
|
340
|
+
def assert_source_saved(message)
|
|
341
|
+
unless source.saved?
|
|
342
|
+
raise UnsavedParentError, message
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
end # class Collection
|
|
346
|
+
end # module OneToMany
|
|
347
|
+
end # module Associations
|
|
348
|
+
end # module DataMapper
|