ardm-core 1.2.1

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 (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +35 -0
  5. data/.travis.yml +23 -0
  6. data/.yardopts +1 -0
  7. data/Gemfile +63 -0
  8. data/LICENSE +20 -0
  9. data/README.rdoc +237 -0
  10. data/Rakefile +4 -0
  11. data/VERSION +1 -0
  12. data/ardm-core.gemspec +25 -0
  13. data/lib/ardm-core.rb +1 -0
  14. data/lib/dm-core.rb +285 -0
  15. data/lib/dm-core/adapters.rb +222 -0
  16. data/lib/dm-core/adapters/abstract_adapter.rb +236 -0
  17. data/lib/dm-core/adapters/in_memory_adapter.rb +113 -0
  18. data/lib/dm-core/associations/many_to_many.rb +496 -0
  19. data/lib/dm-core/associations/many_to_one.rb +296 -0
  20. data/lib/dm-core/associations/one_to_many.rb +345 -0
  21. data/lib/dm-core/associations/one_to_one.rb +86 -0
  22. data/lib/dm-core/associations/relationship.rb +663 -0
  23. data/lib/dm-core/backwards.rb +13 -0
  24. data/lib/dm-core/collection.rb +1514 -0
  25. data/lib/dm-core/core_ext/kernel.rb +23 -0
  26. data/lib/dm-core/core_ext/pathname.rb +6 -0
  27. data/lib/dm-core/core_ext/symbol.rb +10 -0
  28. data/lib/dm-core/identity_map.rb +7 -0
  29. data/lib/dm-core/model.rb +869 -0
  30. data/lib/dm-core/model/hook.rb +102 -0
  31. data/lib/dm-core/model/is.rb +32 -0
  32. data/lib/dm-core/model/property.rb +253 -0
  33. data/lib/dm-core/model/relationship.rb +377 -0
  34. data/lib/dm-core/model/scope.rb +89 -0
  35. data/lib/dm-core/property.rb +839 -0
  36. data/lib/dm-core/property/binary.rb +22 -0
  37. data/lib/dm-core/property/boolean.rb +31 -0
  38. data/lib/dm-core/property/class.rb +24 -0
  39. data/lib/dm-core/property/date.rb +45 -0
  40. data/lib/dm-core/property/date_time.rb +44 -0
  41. data/lib/dm-core/property/decimal.rb +50 -0
  42. data/lib/dm-core/property/discriminator.rb +46 -0
  43. data/lib/dm-core/property/float.rb +28 -0
  44. data/lib/dm-core/property/integer.rb +32 -0
  45. data/lib/dm-core/property/lookup.rb +29 -0
  46. data/lib/dm-core/property/numeric.rb +40 -0
  47. data/lib/dm-core/property/object.rb +28 -0
  48. data/lib/dm-core/property/serial.rb +13 -0
  49. data/lib/dm-core/property/string.rb +50 -0
  50. data/lib/dm-core/property/text.rb +12 -0
  51. data/lib/dm-core/property/time.rb +46 -0
  52. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  53. data/lib/dm-core/property/typecast/time.rb +33 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query.rb +1444 -0
  56. data/lib/dm-core/query/conditions/comparison.rb +910 -0
  57. data/lib/dm-core/query/conditions/operation.rb +720 -0
  58. data/lib/dm-core/query/direction.rb +36 -0
  59. data/lib/dm-core/query/operator.rb +35 -0
  60. data/lib/dm-core/query/path.rb +114 -0
  61. data/lib/dm-core/query/sort.rb +39 -0
  62. data/lib/dm-core/relationship_set.rb +72 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource.rb +1228 -0
  65. data/lib/dm-core/resource/persistence_state.rb +75 -0
  66. data/lib/dm-core/resource/persistence_state/clean.rb +40 -0
  67. data/lib/dm-core/resource/persistence_state/deleted.rb +30 -0
  68. data/lib/dm-core/resource/persistence_state/dirty.rb +96 -0
  69. data/lib/dm-core/resource/persistence_state/immutable.rb +34 -0
  70. data/lib/dm-core/resource/persistence_state/persisted.rb +29 -0
  71. data/lib/dm-core/resource/persistence_state/transient.rb +78 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +54 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +20 -0
  74. data/lib/dm-core/spec/lib/counter_adapter.rb +38 -0
  75. data/lib/dm-core/spec/lib/pending_helpers.rb +50 -0
  76. data/lib/dm-core/spec/lib/spec_helper.rb +74 -0
  77. data/lib/dm-core/spec/setup.rb +173 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +326 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1236 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +134 -0
  83. data/lib/dm-core/spec/shared/semipublic/query/conditions/abstract_comparison_spec.rb +261 -0
  84. data/lib/dm-core/support/assertions.rb +8 -0
  85. data/lib/dm-core/support/chainable.rb +18 -0
  86. data/lib/dm-core/support/deprecate.rb +12 -0
  87. data/lib/dm-core/support/descendant_set.rb +89 -0
  88. data/lib/dm-core/support/equalizer.rb +48 -0
  89. data/lib/dm-core/support/ext/array.rb +22 -0
  90. data/lib/dm-core/support/ext/blank.rb +25 -0
  91. data/lib/dm-core/support/ext/hash.rb +67 -0
  92. data/lib/dm-core/support/ext/module.rb +47 -0
  93. data/lib/dm-core/support/ext/object.rb +57 -0
  94. data/lib/dm-core/support/ext/string.rb +24 -0
  95. data/lib/dm-core/support/ext/try_dup.rb +12 -0
  96. data/lib/dm-core/support/hook.rb +402 -0
  97. data/lib/dm-core/support/inflections.rb +60 -0
  98. data/lib/dm-core/support/inflector/inflections.rb +211 -0
  99. data/lib/dm-core/support/inflector/methods.rb +151 -0
  100. data/lib/dm-core/support/lazy_array.rb +451 -0
  101. data/lib/dm-core/support/local_object_space.rb +12 -0
  102. data/lib/dm-core/support/logger.rb +199 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +90 -0
  105. data/lib/dm-core/support/ordered_set.rb +380 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +250 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/script/performance.rb +275 -0
  110. data/script/profile.rb +218 -0
  111. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  112. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +68 -0
  113. data/spec/public/associations/many_to_many_spec.rb +197 -0
  114. data/spec/public/associations/many_to_one_spec.rb +83 -0
  115. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  116. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  117. data/spec/public/associations/one_to_many_spec.rb +81 -0
  118. data/spec/public/associations/one_to_one_spec.rb +176 -0
  119. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  120. data/spec/public/collection_spec.rb +69 -0
  121. data/spec/public/finalize_spec.rb +76 -0
  122. data/spec/public/model/hook_spec.rb +246 -0
  123. data/spec/public/model/property_spec.rb +88 -0
  124. data/spec/public/model/relationship_spec.rb +1040 -0
  125. data/spec/public/model_spec.rb +458 -0
  126. data/spec/public/property/binary_spec.rb +41 -0
  127. data/spec/public/property/boolean_spec.rb +22 -0
  128. data/spec/public/property/class_spec.rb +28 -0
  129. data/spec/public/property/date_spec.rb +22 -0
  130. data/spec/public/property/date_time_spec.rb +22 -0
  131. data/spec/public/property/decimal_spec.rb +23 -0
  132. data/spec/public/property/discriminator_spec.rb +135 -0
  133. data/spec/public/property/float_spec.rb +22 -0
  134. data/spec/public/property/integer_spec.rb +22 -0
  135. data/spec/public/property/object_spec.rb +107 -0
  136. data/spec/public/property/serial_spec.rb +22 -0
  137. data/spec/public/property/string_spec.rb +22 -0
  138. data/spec/public/property/text_spec.rb +63 -0
  139. data/spec/public/property/time_spec.rb +22 -0
  140. data/spec/public/property_spec.rb +341 -0
  141. data/spec/public/resource_spec.rb +284 -0
  142. data/spec/public/sel_spec.rb +53 -0
  143. data/spec/public/setup_spec.rb +145 -0
  144. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  145. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  146. data/spec/public/shared/collection_shared_spec.rb +1669 -0
  147. data/spec/public/shared/finder_shared_spec.rb +1629 -0
  148. data/spec/rcov.opts +6 -0
  149. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  150. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
  151. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  152. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  153. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  154. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  155. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  156. data/spec/semipublic/associations_spec.rb +177 -0
  157. data/spec/semipublic/collection_spec.rb +110 -0
  158. data/spec/semipublic/model_spec.rb +96 -0
  159. data/spec/semipublic/property/binary_spec.rb +13 -0
  160. data/spec/semipublic/property/boolean_spec.rb +47 -0
  161. data/spec/semipublic/property/class_spec.rb +33 -0
  162. data/spec/semipublic/property/date_spec.rb +43 -0
  163. data/spec/semipublic/property/date_time_spec.rb +46 -0
  164. data/spec/semipublic/property/decimal_spec.rb +83 -0
  165. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  166. data/spec/semipublic/property/float_spec.rb +82 -0
  167. data/spec/semipublic/property/integer_spec.rb +82 -0
  168. data/spec/semipublic/property/lookup_spec.rb +29 -0
  169. data/spec/semipublic/property/serial_spec.rb +13 -0
  170. data/spec/semipublic/property/string_spec.rb +13 -0
  171. data/spec/semipublic/property/text_spec.rb +31 -0
  172. data/spec/semipublic/property/time_spec.rb +50 -0
  173. data/spec/semipublic/property_spec.rb +114 -0
  174. data/spec/semipublic/query/conditions/comparison_spec.rb +1501 -0
  175. data/spec/semipublic/query/conditions/operation_spec.rb +1294 -0
  176. data/spec/semipublic/query/path_spec.rb +471 -0
  177. data/spec/semipublic/query_spec.rb +3777 -0
  178. data/spec/semipublic/resource/state/clean_spec.rb +88 -0
  179. data/spec/semipublic/resource/state/deleted_spec.rb +78 -0
  180. data/spec/semipublic/resource/state/dirty_spec.rb +156 -0
  181. data/spec/semipublic/resource/state/immutable_spec.rb +105 -0
  182. data/spec/semipublic/resource/state/transient_spec.rb +162 -0
  183. data/spec/semipublic/resource/state_spec.rb +230 -0
  184. data/spec/semipublic/resource_spec.rb +23 -0
  185. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  186. data/spec/semipublic/shared/resource_shared_spec.rb +199 -0
  187. data/spec/semipublic/shared/resource_state_shared_spec.rb +79 -0
  188. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  189. data/spec/spec.opts +5 -0
  190. data/spec/spec_helper.rb +37 -0
  191. data/spec/support/core_ext/hash.rb +10 -0
  192. data/spec/support/core_ext/inheritable_attributes.rb +46 -0
  193. data/spec/support/properties/huge_integer.rb +17 -0
  194. data/spec/unit/array_spec.rb +23 -0
  195. data/spec/unit/blank_spec.rb +73 -0
  196. data/spec/unit/data_mapper/ordered_set/append_spec.rb +26 -0
  197. data/spec/unit/data_mapper/ordered_set/clear_spec.rb +24 -0
  198. data/spec/unit/data_mapper/ordered_set/delete_spec.rb +28 -0
  199. data/spec/unit/data_mapper/ordered_set/each_spec.rb +19 -0
  200. data/spec/unit/data_mapper/ordered_set/empty_spec.rb +20 -0
  201. data/spec/unit/data_mapper/ordered_set/entries_spec.rb +22 -0
  202. data/spec/unit/data_mapper/ordered_set/eql_spec.rb +51 -0
  203. data/spec/unit/data_mapper/ordered_set/equal_value_spec.rb +84 -0
  204. data/spec/unit/data_mapper/ordered_set/hash_spec.rb +12 -0
  205. data/spec/unit/data_mapper/ordered_set/include_spec.rb +23 -0
  206. data/spec/unit/data_mapper/ordered_set/index_spec.rb +28 -0
  207. data/spec/unit/data_mapper/ordered_set/initialize_spec.rb +32 -0
  208. data/spec/unit/data_mapper/ordered_set/merge_spec.rb +36 -0
  209. data/spec/unit/data_mapper/ordered_set/shared/append_spec.rb +24 -0
  210. data/spec/unit/data_mapper/ordered_set/shared/clear_spec.rb +9 -0
  211. data/spec/unit/data_mapper/ordered_set/shared/delete_spec.rb +25 -0
  212. data/spec/unit/data_mapper/ordered_set/shared/each_spec.rb +17 -0
  213. data/spec/unit/data_mapper/ordered_set/shared/empty_spec.rb +9 -0
  214. data/spec/unit/data_mapper/ordered_set/shared/entries_spec.rb +9 -0
  215. data/spec/unit/data_mapper/ordered_set/shared/include_spec.rb +9 -0
  216. data/spec/unit/data_mapper/ordered_set/shared/index_spec.rb +13 -0
  217. data/spec/unit/data_mapper/ordered_set/shared/initialize_spec.rb +28 -0
  218. data/spec/unit/data_mapper/ordered_set/shared/merge_spec.rb +28 -0
  219. data/spec/unit/data_mapper/ordered_set/shared/size_spec.rb +13 -0
  220. data/spec/unit/data_mapper/ordered_set/shared/to_ary_spec.rb +11 -0
  221. data/spec/unit/data_mapper/ordered_set/size_spec.rb +27 -0
  222. data/spec/unit/data_mapper/ordered_set/to_ary_spec.rb +23 -0
  223. data/spec/unit/data_mapper/subject_set/append_spec.rb +47 -0
  224. data/spec/unit/data_mapper/subject_set/clear_spec.rb +34 -0
  225. data/spec/unit/data_mapper/subject_set/delete_spec.rb +40 -0
  226. data/spec/unit/data_mapper/subject_set/each_spec.rb +30 -0
  227. data/spec/unit/data_mapper/subject_set/empty_spec.rb +31 -0
  228. data/spec/unit/data_mapper/subject_set/entries_spec.rb +31 -0
  229. data/spec/unit/data_mapper/subject_set/get_spec.rb +34 -0
  230. data/spec/unit/data_mapper/subject_set/include_spec.rb +32 -0
  231. data/spec/unit/data_mapper/subject_set/named_spec.rb +33 -0
  232. data/spec/unit/data_mapper/subject_set/shared/append_spec.rb +18 -0
  233. data/spec/unit/data_mapper/subject_set/shared/clear_spec.rb +9 -0
  234. data/spec/unit/data_mapper/subject_set/shared/delete_spec.rb +9 -0
  235. data/spec/unit/data_mapper/subject_set/shared/each_spec.rb +9 -0
  236. data/spec/unit/data_mapper/subject_set/shared/empty_spec.rb +9 -0
  237. data/spec/unit/data_mapper/subject_set/shared/entries_spec.rb +9 -0
  238. data/spec/unit/data_mapper/subject_set/shared/get_spec.rb +9 -0
  239. data/spec/unit/data_mapper/subject_set/shared/include_spec.rb +9 -0
  240. data/spec/unit/data_mapper/subject_set/shared/named_spec.rb +9 -0
  241. data/spec/unit/data_mapper/subject_set/shared/size_spec.rb +13 -0
  242. data/spec/unit/data_mapper/subject_set/shared/to_ary_spec.rb +9 -0
  243. data/spec/unit/data_mapper/subject_set/shared/values_at_spec.rb +44 -0
  244. data/spec/unit/data_mapper/subject_set/size_spec.rb +42 -0
  245. data/spec/unit/data_mapper/subject_set/to_ary_spec.rb +34 -0
  246. data/spec/unit/data_mapper/subject_set/values_at_spec.rb +57 -0
  247. data/spec/unit/hash_spec.rb +28 -0
  248. data/spec/unit/hook_spec.rb +1235 -0
  249. data/spec/unit/lazy_array_spec.rb +1949 -0
  250. data/spec/unit/mash_spec.rb +312 -0
  251. data/spec/unit/module_spec.rb +71 -0
  252. data/spec/unit/object_spec.rb +38 -0
  253. data/spec/unit/try_dup_spec.rb +46 -0
  254. data/tasks/ci.rake +1 -0
  255. data/tasks/db.rake +11 -0
  256. data/tasks/spec.rake +38 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +491 -0
@@ -0,0 +1,296 @@
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
+ # @api public
76
+ def finalize
77
+ child_key
78
+ end
79
+
80
+ # Returns a hash of conditions that scopes query that fetches
81
+ # target object
82
+ #
83
+ # @return [Hash]
84
+ # Hash of conditions that scopes query
85
+ #
86
+ # @api private
87
+ def source_scope(source)
88
+ if source.kind_of?(Resource)
89
+ Query.target_conditions(source, source_key, target_key)
90
+ else
91
+ super
92
+ end
93
+ end
94
+
95
+ # Returns a Resource for this relationship with a given source
96
+ #
97
+ # @param [Resource] source
98
+ # A Resource to scope the collection with
99
+ # @param [Query] other_query (optional)
100
+ # A Query to further scope the collection with
101
+ #
102
+ # @return [Resource]
103
+ # The resource scoped to the relationship, source and query
104
+ #
105
+ # @api private
106
+ def resource_for(source, other_query = nil)
107
+ query = query_for(source, other_query)
108
+
109
+ # If the target key is equal to the model key, we can use the
110
+ # Model#get so the IdentityMap is used
111
+ if target_key == target_model.key
112
+ target = target_model.get(*source_key.get!(source))
113
+ if query.conditions.matches?(target)
114
+ target
115
+ else
116
+ nil
117
+ end
118
+ else
119
+ target_model.first(query)
120
+ end
121
+ end
122
+
123
+ # Loads and returns association target (ex.: author) for given source resource
124
+ # (ex.: article)
125
+ #
126
+ # @param source [DataMapper::Resource]
127
+ # source object (ex.: instance of article)
128
+ # @param other_query [DataMapper::Query]
129
+ # Query options
130
+ #
131
+ # @api semipublic
132
+ def get(source, query = nil)
133
+ lazy_load(source)
134
+
135
+ if query
136
+ collection = get_collection(source)
137
+ collection.first(query) if collection
138
+ else
139
+ get!(source)
140
+ end
141
+ end
142
+
143
+ def get_collection(source)
144
+ target = get!(source)
145
+ target.collection_for_self if target
146
+ end
147
+
148
+ # Sets value of association target (ex.: author) for given source resource
149
+ # (ex.: article)
150
+ #
151
+ # @param source [DataMapper::Resource]
152
+ # source object (ex.: instance of article)
153
+ #
154
+ # @param target [DataMapper::Resource]
155
+ # target object (ex.: instance of author)
156
+ #
157
+ # @api semipublic
158
+ def set(source, target)
159
+ target = typecast(target)
160
+ source_key.set(source, target_key.get(target))
161
+ set!(source, target)
162
+ end
163
+
164
+ # @api semipublic
165
+ def default_for(source)
166
+ typecast(super)
167
+ end
168
+
169
+ # Loads association target and sets resulting value on
170
+ # given source resource
171
+ #
172
+ # @param [Resource] source
173
+ # the source resource for the association
174
+ #
175
+ # @return [undefined]
176
+ #
177
+ # @api private
178
+ def lazy_load(source)
179
+ source_key_different = source_key_different?(source)
180
+
181
+ if (loaded?(source) && !source_key_different) || !valid_source?(source)
182
+ return
183
+ end
184
+
185
+ # SEL: load all related resources in the source collection
186
+ if source.saved? && (collection = source.collection).size > 1
187
+ eager_load(collection)
188
+ end
189
+
190
+ if !loaded?(source) || (source_key_dirty?(source) && source.saved?)
191
+ set!(source, resource_for(source))
192
+ elsif loaded?(source) && source_key_different
193
+ source_key.set(source, target_key.get!(get!(source)))
194
+ end
195
+ end
196
+
197
+ private
198
+
199
+ # Initializes the relationship, always using max cardinality of 1.
200
+ #
201
+ # @api semipublic
202
+ def initialize(name, source_model, target_model, options = {})
203
+ if options.key?(:nullable)
204
+ raise ":nullable is deprecated, use :required instead (#{caller[2]})"
205
+ end
206
+
207
+ @required = options.fetch(:required, true)
208
+ @key = options.fetch(:key, false)
209
+ @unique = options.fetch(:unique, false)
210
+ target_model ||= DataMapper::Inflector.camelize(name)
211
+ options = { :min => @required ? 1 : 0, :max => 1 }.update(options)
212
+ super
213
+ end
214
+
215
+ # Sets the association targets in the resource
216
+ #
217
+ # @param [Resource] source
218
+ # the source to set
219
+ # @param [Array(Resource)] targets
220
+ # the target resource for the association
221
+ # @param [Query, Hash] query
222
+ # not used
223
+ #
224
+ # @return [undefined]
225
+ #
226
+ # @api private
227
+ def eager_load_targets(source, targets, query)
228
+ set(source, targets.first)
229
+ end
230
+
231
+ # @api private
232
+ def typecast(target)
233
+ if target.kind_of?(Hash)
234
+ target_model.new(target)
235
+ else
236
+ target
237
+ end
238
+ end
239
+
240
+ # Returns the inverse relationship class
241
+ #
242
+ # @api private
243
+ def inverse_class
244
+ OneToMany::Relationship
245
+ end
246
+
247
+ # Returns the inverse relationship name
248
+ #
249
+ # @api private
250
+ def inverse_name
251
+ name = super
252
+ return name if name
253
+
254
+ name = DataMapper::Inflector.demodulize(source_model.name)
255
+ name = DataMapper::Inflector.underscore(name)
256
+ name = DataMapper::Inflector.pluralize(name)
257
+ name.to_sym
258
+ end
259
+
260
+ # @api private
261
+ def source_key_options(target_property)
262
+ options = DataMapper::Ext::Hash.only(target_property.options, :length, :precision, :scale).update(
263
+ :index => name,
264
+ :required => required?,
265
+ :key => key?,
266
+ :unique => @unique
267
+ )
268
+
269
+ if target_property.primitive == Integer
270
+ min = target_property.min
271
+ max = target_property.max
272
+
273
+ options.update(:min => min, :max => max) if min && max
274
+ end
275
+
276
+ options
277
+ end
278
+
279
+ # @api private
280
+ def child_properties
281
+ source_key.map { |property| property.name }
282
+ end
283
+
284
+ # @api private
285
+ def source_key_different?(source)
286
+ source_key.get!(source) != target_key.get!(get!(source))
287
+ end
288
+
289
+ # @api private
290
+ def source_key_dirty?(source)
291
+ source.dirty_attributes.keys.any? { |property| source_key.include?(property) }
292
+ end
293
+ end # class Relationship
294
+ end # module ManyToOne
295
+ end # module Associations
296
+ end # module DataMapper
@@ -0,0 +1,345 @@
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
+ # @api public
108
+ def finalize
109
+ child_model.relationships.each do |relationship|
110
+ # TODO: should this check #inverse?
111
+ # relationship.child_key if inverse?(relationship)
112
+ if relationship.kind_of?(Associations::ManyToOne::Relationship)
113
+ relationship.finalize
114
+ end
115
+ end
116
+ inverse.finalize
117
+ end
118
+
119
+ # @api semipublic
120
+ def default_for(source)
121
+ collection_for(source).replace(Array(super))
122
+ end
123
+
124
+ private
125
+
126
+ # @api semipublic
127
+ def initialize(name, target_model, source_model, options = {})
128
+ target_model ||= DataMapper::Inflector.camelize(DataMapper::Inflector.singularize(name.to_s))
129
+ options = { :min => 0, :max => source_model.n }.update(options)
130
+ super
131
+ end
132
+
133
+ # Sets the association targets in the resource
134
+ #
135
+ # @param [Resource] source
136
+ # the source to set
137
+ # @param [Array<Resource>] targets
138
+ # the target collection for the association
139
+ # @param [Query, Hash] query
140
+ # the query to scope the association with
141
+ #
142
+ # @return [undefined]
143
+ #
144
+ # @api private
145
+ def eager_load_targets(source, targets, query)
146
+ set!(source, collection_for(source, query).set(targets))
147
+ end
148
+
149
+ # Returns collection class used by this type of
150
+ # relationship
151
+ #
152
+ # @api private
153
+ def collection_class
154
+ OneToMany::Collection
155
+ end
156
+
157
+ # Returns the inverse relationship class
158
+ #
159
+ # @api private
160
+ def inverse_class
161
+ ManyToOne::Relationship
162
+ end
163
+
164
+ # Returns the inverse relationship name
165
+ #
166
+ # @api private
167
+ def inverse_name
168
+ super || DataMapper::Inflector.underscore(DataMapper::Inflector.demodulize(source_model.name)).to_sym
169
+ end
170
+
171
+ # @api private
172
+ def child_properties
173
+ super || parent_key.map do |parent_property|
174
+ "#{inverse_name}_#{parent_property.name}".to_sym
175
+ end
176
+ end
177
+ end # class Relationship
178
+
179
+ class Collection < DataMapper::Collection
180
+ # @api private
181
+ attr_accessor :relationship
182
+
183
+ # @api private
184
+ attr_accessor :source
185
+
186
+ # @api public
187
+ def reload(*)
188
+ assert_source_saved 'The source must be saved before reloading the collection'
189
+ super
190
+ end
191
+
192
+ # Replace the Resources within the 1:m Collection
193
+ #
194
+ # @param [Enumerable] other
195
+ # List of other Resources to replace with
196
+ #
197
+ # @return [Collection]
198
+ # self
199
+ #
200
+ # @api public
201
+ def replace(*)
202
+ lazy_load # lazy load so that targets are always orphaned
203
+ super
204
+ end
205
+
206
+ # Removes all Resources from the 1:m Collection
207
+ #
208
+ # This should remove and orphan each Resource from the 1:m Collection.
209
+ #
210
+ # @return [Collection]
211
+ # self
212
+ #
213
+ # @api public
214
+ def clear
215
+ lazy_load # lazy load so that targets are always orphaned
216
+ super
217
+ end
218
+
219
+ # Update every Resource in the 1:m Collection
220
+ #
221
+ # @param [Hash] attributes
222
+ # attributes to update with
223
+ #
224
+ # @return [Boolean]
225
+ # true if the resources were successfully updated
226
+ #
227
+ # @api public
228
+ def update(*)
229
+ assert_source_saved 'The source must be saved before mass-updating the collection'
230
+ super
231
+ end
232
+
233
+ # Update every Resource in the 1:m Collection, bypassing validation
234
+ #
235
+ # @param [Hash] attributes
236
+ # attributes to update
237
+ #
238
+ # @return [Boolean]
239
+ # true if the resources were successfully updated
240
+ #
241
+ # @api public
242
+ def update!(*)
243
+ assert_source_saved 'The source must be saved before mass-updating the collection'
244
+ super
245
+ end
246
+
247
+ # Remove every Resource in the 1:m Collection from the repository
248
+ #
249
+ # This performs a deletion of each Resource in the Collection from
250
+ # the repository and clears the Collection.
251
+ #
252
+ # @return [Boolean]
253
+ # true if the resources were successfully destroyed
254
+ #
255
+ # @api public
256
+ def destroy
257
+ assert_source_saved 'The source must be saved before mass-deleting the collection'
258
+ super
259
+ end
260
+
261
+ # Remove every Resource in the 1:m Collection from the repository, bypassing validation
262
+ #
263
+ # This performs a deletion of each Resource in the Collection from
264
+ # the repository and clears the Collection while skipping
265
+ # validation.
266
+ #
267
+ # @return [Boolean]
268
+ # true if the resources were successfully destroyed
269
+ #
270
+ # @api public
271
+ def destroy!
272
+ assert_source_saved 'The source must be saved before mass-deleting the collection'
273
+ super
274
+ end
275
+
276
+ private
277
+
278
+ # @api private
279
+ def _create(*)
280
+ assert_source_saved 'The source must be saved before creating a resource'
281
+ super
282
+ end
283
+
284
+ # @api private
285
+ def _save(execute_hooks = true)
286
+ assert_source_saved 'The source must be saved before saving the collection'
287
+
288
+ # update removed resources to not reference the source
289
+ @removed.all? { |resource| resource.destroyed? || resource.__send__(execute_hooks ? :save : :save!) } && super
290
+ end
291
+
292
+ # @api private
293
+ def lazy_load
294
+ if source.saved?
295
+ super
296
+ end
297
+ end
298
+
299
+ # @api private
300
+ def new_collection(query, resources = nil, &block)
301
+ collection = self.class.new(query, &block)
302
+
303
+ collection.relationship = relationship
304
+ collection.source = source
305
+
306
+ resources ||= filter(query) if loaded?
307
+
308
+ # set the resources after the relationship and source are set
309
+ if resources
310
+ collection.set(resources)
311
+ end
312
+
313
+ collection
314
+ end
315
+
316
+ # @api private
317
+ def resource_added(resource)
318
+ resource = initialize_resource(resource)
319
+ inverse_set(resource, source)
320
+ super
321
+ end
322
+
323
+ # @api private
324
+ def resource_removed(resource)
325
+ inverse_set(resource, nil)
326
+ super
327
+ end
328
+
329
+ # @api private
330
+ def inverse_set(source, target)
331
+ unless source.readonly?
332
+ relationship.inverse.set(source, target)
333
+ end
334
+ end
335
+
336
+ # @api private
337
+ def assert_source_saved(message)
338
+ unless source.saved?
339
+ raise UnsavedParentError, message
340
+ end
341
+ end
342
+ end # class Collection
343
+ end # module OneToMany
344
+ end # module Associations
345
+ end # module DataMapper