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.
Files changed (254) hide show
  1. data/.autotest +29 -0
  2. data/.document +5 -0
  3. data/.gitignore +35 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +65 -0
  6. data/LICENSE +20 -0
  7. data/README.md +269 -0
  8. data/Rakefile +4 -0
  9. data/dm-core.gemspec +24 -0
  10. data/lib/dm-core.rb +292 -0
  11. data/lib/dm-core/adapters.rb +222 -0
  12. data/lib/dm-core/adapters/abstract_adapter.rb +237 -0
  13. data/lib/dm-core/adapters/in_memory_adapter.rb +113 -0
  14. data/lib/dm-core/associations/many_to_many.rb +499 -0
  15. data/lib/dm-core/associations/many_to_one.rb +290 -0
  16. data/lib/dm-core/associations/one_to_many.rb +348 -0
  17. data/lib/dm-core/associations/one_to_one.rb +86 -0
  18. data/lib/dm-core/associations/relationship.rb +663 -0
  19. data/lib/dm-core/backwards.rb +13 -0
  20. data/lib/dm-core/collection.rb +1515 -0
  21. data/lib/dm-core/core_ext/kernel.rb +23 -0
  22. data/lib/dm-core/core_ext/pathname.rb +6 -0
  23. data/lib/dm-core/core_ext/symbol.rb +10 -0
  24. data/lib/dm-core/identity_map.rb +7 -0
  25. data/lib/dm-core/model.rb +874 -0
  26. data/lib/dm-core/model/hook.rb +103 -0
  27. data/lib/dm-core/model/is.rb +32 -0
  28. data/lib/dm-core/model/property.rb +249 -0
  29. data/lib/dm-core/model/relationship.rb +378 -0
  30. data/lib/dm-core/model/scope.rb +89 -0
  31. data/lib/dm-core/property.rb +866 -0
  32. data/lib/dm-core/property/binary.rb +21 -0
  33. data/lib/dm-core/property/boolean.rb +20 -0
  34. data/lib/dm-core/property/class.rb +17 -0
  35. data/lib/dm-core/property/date.rb +10 -0
  36. data/lib/dm-core/property/date_time.rb +10 -0
  37. data/lib/dm-core/property/decimal.rb +36 -0
  38. data/lib/dm-core/property/discriminator.rb +44 -0
  39. data/lib/dm-core/property/float.rb +16 -0
  40. data/lib/dm-core/property/integer.rb +22 -0
  41. data/lib/dm-core/property/invalid_value_error.rb +22 -0
  42. data/lib/dm-core/property/lookup.rb +27 -0
  43. data/lib/dm-core/property/numeric.rb +38 -0
  44. data/lib/dm-core/property/object.rb +34 -0
  45. data/lib/dm-core/property/serial.rb +14 -0
  46. data/lib/dm-core/property/string.rb +38 -0
  47. data/lib/dm-core/property/text.rb +9 -0
  48. data/lib/dm-core/property/time.rb +10 -0
  49. data/lib/dm-core/property_set.rb +177 -0
  50. data/lib/dm-core/query.rb +1366 -0
  51. data/lib/dm-core/query/conditions/comparison.rb +911 -0
  52. data/lib/dm-core/query/conditions/operation.rb +721 -0
  53. data/lib/dm-core/query/direction.rb +36 -0
  54. data/lib/dm-core/query/operator.rb +35 -0
  55. data/lib/dm-core/query/path.rb +114 -0
  56. data/lib/dm-core/query/sort.rb +39 -0
  57. data/lib/dm-core/relationship_set.rb +72 -0
  58. data/lib/dm-core/repository.rb +226 -0
  59. data/lib/dm-core/resource.rb +1214 -0
  60. data/lib/dm-core/resource/persistence_state.rb +75 -0
  61. data/lib/dm-core/resource/persistence_state/clean.rb +40 -0
  62. data/lib/dm-core/resource/persistence_state/deleted.rb +30 -0
  63. data/lib/dm-core/resource/persistence_state/dirty.rb +96 -0
  64. data/lib/dm-core/resource/persistence_state/immutable.rb +34 -0
  65. data/lib/dm-core/resource/persistence_state/persisted.rb +29 -0
  66. data/lib/dm-core/resource/persistence_state/transient.rb +80 -0
  67. data/lib/dm-core/spec/lib/adapter_helpers.rb +64 -0
  68. data/lib/dm-core/spec/lib/collection_helpers.rb +21 -0
  69. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  70. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  71. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  72. data/lib/dm-core/spec/setup.rb +174 -0
  73. data/lib/dm-core/spec/shared/adapter_spec.rb +341 -0
  74. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  75. data/lib/dm-core/spec/shared/resource_spec.rb +1232 -0
  76. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  77. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +176 -0
  78. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  79. data/lib/dm-core/support/assertions.rb +8 -0
  80. data/lib/dm-core/support/chainable.rb +18 -0
  81. data/lib/dm-core/support/deprecate.rb +12 -0
  82. data/lib/dm-core/support/descendant_set.rb +89 -0
  83. data/lib/dm-core/support/equalizer.rb +48 -0
  84. data/lib/dm-core/support/ext/array.rb +22 -0
  85. data/lib/dm-core/support/ext/blank.rb +25 -0
  86. data/lib/dm-core/support/ext/hash.rb +67 -0
  87. data/lib/dm-core/support/ext/module.rb +47 -0
  88. data/lib/dm-core/support/ext/object.rb +57 -0
  89. data/lib/dm-core/support/ext/string.rb +24 -0
  90. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  91. data/lib/dm-core/support/hook.rb +405 -0
  92. data/lib/dm-core/support/inflections.rb +60 -0
  93. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  94. data/lib/dm-core/support/inflector/methods.rb +151 -0
  95. data/lib/dm-core/support/lazy_array.rb +451 -0
  96. data/lib/dm-core/support/local_object_space.rb +13 -0
  97. data/lib/dm-core/support/logger.rb +201 -0
  98. data/lib/dm-core/support/mash.rb +176 -0
  99. data/lib/dm-core/support/naming_conventions.rb +90 -0
  100. data/lib/dm-core/support/ordered_set.rb +380 -0
  101. data/lib/dm-core/support/subject.rb +33 -0
  102. data/lib/dm-core/support/subject_set.rb +250 -0
  103. data/lib/dm-core/version.rb +3 -0
  104. data/script/performance.rb +275 -0
  105. data/script/profile.rb +218 -0
  106. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  107. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +68 -0
  108. data/spec/public/associations/many_to_many_spec.rb +197 -0
  109. data/spec/public/associations/many_to_one_spec.rb +83 -0
  110. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  111. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  112. data/spec/public/associations/one_to_many_spec.rb +81 -0
  113. data/spec/public/associations/one_to_one_spec.rb +176 -0
  114. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  115. data/spec/public/collection_spec.rb +69 -0
  116. data/spec/public/finalize_spec.rb +76 -0
  117. data/spec/public/model/hook_spec.rb +246 -0
  118. data/spec/public/model/property_spec.rb +88 -0
  119. data/spec/public/model/relationship_spec.rb +1040 -0
  120. data/spec/public/model_spec.rb +462 -0
  121. data/spec/public/property/binary_spec.rb +41 -0
  122. data/spec/public/property/boolean_spec.rb +22 -0
  123. data/spec/public/property/class_spec.rb +28 -0
  124. data/spec/public/property/date_spec.rb +22 -0
  125. data/spec/public/property/date_time_spec.rb +22 -0
  126. data/spec/public/property/decimal_spec.rb +23 -0
  127. data/spec/public/property/discriminator_spec.rb +135 -0
  128. data/spec/public/property/float_spec.rb +22 -0
  129. data/spec/public/property/integer_spec.rb +22 -0
  130. data/spec/public/property/object_spec.rb +107 -0
  131. data/spec/public/property/serial_spec.rb +22 -0
  132. data/spec/public/property/string_spec.rb +22 -0
  133. data/spec/public/property/text_spec.rb +63 -0
  134. data/spec/public/property/time_spec.rb +22 -0
  135. data/spec/public/property_spec.rb +341 -0
  136. data/spec/public/resource_spec.rb +288 -0
  137. data/spec/public/sel_spec.rb +53 -0
  138. data/spec/public/setup_spec.rb +145 -0
  139. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  140. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  141. data/spec/public/shared/collection_shared_spec.rb +1667 -0
  142. data/spec/public/shared/finder_shared_spec.rb +1629 -0
  143. data/spec/rcov.opts +6 -0
  144. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  145. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
  146. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  147. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  148. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  149. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  150. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  151. data/spec/semipublic/associations_spec.rb +177 -0
  152. data/spec/semipublic/collection_spec.rb +110 -0
  153. data/spec/semipublic/model_spec.rb +96 -0
  154. data/spec/semipublic/property/binary_spec.rb +13 -0
  155. data/spec/semipublic/property/boolean_spec.rb +47 -0
  156. data/spec/semipublic/property/class_spec.rb +33 -0
  157. data/spec/semipublic/property/date_spec.rb +43 -0
  158. data/spec/semipublic/property/date_time_spec.rb +46 -0
  159. data/spec/semipublic/property/decimal_spec.rb +83 -0
  160. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  161. data/spec/semipublic/property/float_spec.rb +82 -0
  162. data/spec/semipublic/property/integer_spec.rb +82 -0
  163. data/spec/semipublic/property/lookup_spec.rb +29 -0
  164. data/spec/semipublic/property/serial_spec.rb +13 -0
  165. data/spec/semipublic/property/string_spec.rb +13 -0
  166. data/spec/semipublic/property/text_spec.rb +31 -0
  167. data/spec/semipublic/property/time_spec.rb +50 -0
  168. data/spec/semipublic/property_spec.rb +114 -0
  169. data/spec/semipublic/query/conditions/comparison_spec.rb +1501 -0
  170. data/spec/semipublic/query/conditions/operation_spec.rb +1294 -0
  171. data/spec/semipublic/query/path_spec.rb +471 -0
  172. data/spec/semipublic/query_spec.rb +3682 -0
  173. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  174. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  175. data/spec/semipublic/resource/state/dirty_spec.rb +162 -0
  176. data/spec/semipublic/resource/state/immutable_spec.rb +105 -0
  177. data/spec/semipublic/resource/state/transient_spec.rb +162 -0
  178. data/spec/semipublic/resource/state_spec.rb +230 -0
  179. data/spec/semipublic/resource_spec.rb +23 -0
  180. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  181. data/spec/semipublic/shared/resource_shared_spec.rb +199 -0
  182. data/spec/semipublic/shared/resource_state_shared_spec.rb +79 -0
  183. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  184. data/spec/spec.opts +5 -0
  185. data/spec/spec_helper.rb +38 -0
  186. data/spec/support/core_ext/hash.rb +10 -0
  187. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  188. data/spec/support/properties/huge_integer.rb +17 -0
  189. data/spec/unit/array_spec.rb +23 -0
  190. data/spec/unit/blank_spec.rb +73 -0
  191. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  192. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  193. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  194. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  195. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  196. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  197. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  198. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  199. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  200. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  201. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  202. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  203. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  204. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  205. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  206. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  207. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  208. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  216. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  217. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  218. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  219. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  220. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  221. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  222. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  223. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  224. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  226. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  227. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  228. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  229. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  230. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  231. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  232. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  233. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  237. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  239. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  240. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  241. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  242. data/spec/unit/hash_spec.rb +28 -0
  243. data/spec/unit/hook_spec.rb +1235 -0
  244. data/spec/unit/inflections_spec.rb +16 -0
  245. data/spec/unit/lazy_array_spec.rb +1949 -0
  246. data/spec/unit/mash_spec.rb +312 -0
  247. data/spec/unit/module_spec.rb +71 -0
  248. data/spec/unit/object_spec.rb +38 -0
  249. data/spec/unit/try_dup_spec.rb +46 -0
  250. data/tasks/ci.rake +1 -0
  251. data/tasks/spec.rake +38 -0
  252. data/tasks/yard.rake +9 -0
  253. data/tasks/yardstick.rake +19 -0
  254. 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