sbf-dm-core 1.3.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +29 -0
  3. data/.document +5 -0
  4. data/.gitignore +44 -0
  5. data/.rspec +1 -0
  6. data/.rubocop.yml +468 -0
  7. data/.travis.yml +57 -0
  8. data/.yardopts +1 -0
  9. data/Gemfile +70 -0
  10. data/LICENSE +20 -0
  11. data/README.md +269 -0
  12. data/Rakefile +4 -0
  13. data/dm-core.gemspec +21 -0
  14. data/lib/dm-core/adapters/abstract_adapter.rb +233 -0
  15. data/lib/dm-core/adapters/in_memory_adapter.rb +110 -0
  16. data/lib/dm-core/adapters.rb +249 -0
  17. data/lib/dm-core/associations/many_to_many.rb +477 -0
  18. data/lib/dm-core/associations/many_to_one.rb +282 -0
  19. data/lib/dm-core/associations/one_to_many.rb +332 -0
  20. data/lib/dm-core/associations/one_to_one.rb +84 -0
  21. data/lib/dm-core/associations/relationship.rb +650 -0
  22. data/lib/dm-core/backwards.rb +11 -0
  23. data/lib/dm-core/collection.rb +1486 -0
  24. data/lib/dm-core/core_ext/kernel.rb +21 -0
  25. data/lib/dm-core/core_ext/pathname.rb +4 -0
  26. data/lib/dm-core/core_ext/symbol.rb +10 -0
  27. data/lib/dm-core/identity_map.rb +6 -0
  28. data/lib/dm-core/model/hook.rb +99 -0
  29. data/lib/dm-core/model/is.rb +30 -0
  30. data/lib/dm-core/model/property.rb +244 -0
  31. data/lib/dm-core/model/relationship.rb +366 -0
  32. data/lib/dm-core/model/scope.rb +87 -0
  33. data/lib/dm-core/model.rb +876 -0
  34. data/lib/dm-core/property/binary.rb +19 -0
  35. data/lib/dm-core/property/boolean.rb +35 -0
  36. data/lib/dm-core/property/class.rb +23 -0
  37. data/lib/dm-core/property/date.rb +45 -0
  38. data/lib/dm-core/property/date_time.rb +44 -0
  39. data/lib/dm-core/property/decimal.rb +47 -0
  40. data/lib/dm-core/property/discriminator.rb +40 -0
  41. data/lib/dm-core/property/float.rb +27 -0
  42. data/lib/dm-core/property/integer.rb +32 -0
  43. data/lib/dm-core/property/invalid_value_error.rb +17 -0
  44. data/lib/dm-core/property/lookup.rb +26 -0
  45. data/lib/dm-core/property/numeric.rb +35 -0
  46. data/lib/dm-core/property/object.rb +33 -0
  47. data/lib/dm-core/property/serial.rb +13 -0
  48. data/lib/dm-core/property/string.rb +47 -0
  49. data/lib/dm-core/property/text.rb +12 -0
  50. data/lib/dm-core/property/time.rb +46 -0
  51. data/lib/dm-core/property/typecast/numeric.rb +32 -0
  52. data/lib/dm-core/property/typecast/time.rb +33 -0
  53. data/lib/dm-core/property.rb +856 -0
  54. data/lib/dm-core/property_set.rb +177 -0
  55. data/lib/dm-core/query/conditions/comparison.rb +886 -0
  56. data/lib/dm-core/query/conditions/operation.rb +710 -0
  57. data/lib/dm-core/query/direction.rb +33 -0
  58. data/lib/dm-core/query/operator.rb +34 -0
  59. data/lib/dm-core/query/path.rb +113 -0
  60. data/lib/dm-core/query/sort.rb +38 -0
  61. data/lib/dm-core/query.rb +1352 -0
  62. data/lib/dm-core/relationship_set.rb +69 -0
  63. data/lib/dm-core/repository.rb +226 -0
  64. data/lib/dm-core/resource/persistence_state/clean.rb +36 -0
  65. data/lib/dm-core/resource/persistence_state/deleted.rb +26 -0
  66. data/lib/dm-core/resource/persistence_state/dirty.rb +91 -0
  67. data/lib/dm-core/resource/persistence_state/immutable.rb +32 -0
  68. data/lib/dm-core/resource/persistence_state/persisted.rb +25 -0
  69. data/lib/dm-core/resource/persistence_state/transient.rb +87 -0
  70. data/lib/dm-core/resource/persistence_state.rb +70 -0
  71. data/lib/dm-core/resource.rb +1220 -0
  72. data/lib/dm-core/spec/lib/adapter_helpers.rb +63 -0
  73. data/lib/dm-core/spec/lib/collection_helpers.rb +21 -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 +164 -0
  78. data/lib/dm-core/spec/shared/adapter_spec.rb +366 -0
  79. data/lib/dm-core/spec/shared/public/property_spec.rb +229 -0
  80. data/lib/dm-core/spec/shared/resource_spec.rb +1221 -0
  81. data/lib/dm-core/spec/shared/sel_spec.rb +111 -0
  82. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +184 -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 +388 -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 +13 -0
  102. data/lib/dm-core/support/logger.rb +201 -0
  103. data/lib/dm-core/support/mash.rb +176 -0
  104. data/lib/dm-core/support/naming_conventions.rb +109 -0
  105. data/lib/dm-core/support/ordered_set.rb +381 -0
  106. data/lib/dm-core/support/subject.rb +33 -0
  107. data/lib/dm-core/support/subject_set.rb +251 -0
  108. data/lib/dm-core/version.rb +3 -0
  109. data/lib/dm-core.rb +274 -0
  110. data/script/performance.rb +275 -0
  111. data/script/profile.rb +218 -0
  112. data/spec/lib/rspec_immediate_feedback_formatter.rb +54 -0
  113. data/spec/public/associations/many_to_many/read_multiple_join_spec.rb +69 -0
  114. data/spec/public/associations/many_to_many_spec.rb +197 -0
  115. data/spec/public/associations/many_to_one_spec.rb +83 -0
  116. data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +40 -0
  117. data/spec/public/associations/many_to_one_with_custom_fk_spec.rb +49 -0
  118. data/spec/public/associations/one_to_many_spec.rb +81 -0
  119. data/spec/public/associations/one_to_one_spec.rb +176 -0
  120. data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +46 -0
  121. data/spec/public/collection_spec.rb +69 -0
  122. data/spec/public/finalize_spec.rb +77 -0
  123. data/spec/public/model/hook_spec.rb +245 -0
  124. data/spec/public/model/property_spec.rb +91 -0
  125. data/spec/public/model/relationship_spec.rb +1040 -0
  126. data/spec/public/model_spec.rb +456 -0
  127. data/spec/public/property/binary_spec.rb +43 -0
  128. data/spec/public/property/boolean_spec.rb +21 -0
  129. data/spec/public/property/class_spec.rb +27 -0
  130. data/spec/public/property/date_spec.rb +21 -0
  131. data/spec/public/property/date_time_spec.rb +21 -0
  132. data/spec/public/property/decimal_spec.rb +23 -0
  133. data/spec/public/property/discriminator_spec.rb +134 -0
  134. data/spec/public/property/float_spec.rb +22 -0
  135. data/spec/public/property/integer_spec.rb +22 -0
  136. data/spec/public/property/object_spec.rb +117 -0
  137. data/spec/public/property/serial_spec.rb +22 -0
  138. data/spec/public/property/string_spec.rb +21 -0
  139. data/spec/public/property/text_spec.rb +62 -0
  140. data/spec/public/property/time_spec.rb +21 -0
  141. data/spec/public/property_spec.rb +333 -0
  142. data/spec/public/resource/state_spec.rb +72 -0
  143. data/spec/public/resource_spec.rb +289 -0
  144. data/spec/public/sel_spec.rb +53 -0
  145. data/spec/public/setup_spec.rb +145 -0
  146. data/spec/public/shared/association_collection_shared_spec.rb +309 -0
  147. data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
  148. data/spec/public/shared/collection_shared_spec.rb +1637 -0
  149. data/spec/public/shared/finder_shared_spec.rb +1647 -0
  150. data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
  151. data/spec/semipublic/adapters/in_memory_adapter_spec.rb +13 -0
  152. data/spec/semipublic/associations/many_to_many_spec.rb +94 -0
  153. data/spec/semipublic/associations/many_to_one_spec.rb +63 -0
  154. data/spec/semipublic/associations/one_to_many_spec.rb +55 -0
  155. data/spec/semipublic/associations/one_to_one_spec.rb +53 -0
  156. data/spec/semipublic/associations/relationship_spec.rb +200 -0
  157. data/spec/semipublic/associations_spec.rb +177 -0
  158. data/spec/semipublic/collection_spec.rb +110 -0
  159. data/spec/semipublic/model_spec.rb +96 -0
  160. data/spec/semipublic/property/binary_spec.rb +13 -0
  161. data/spec/semipublic/property/boolean_spec.rb +47 -0
  162. data/spec/semipublic/property/class_spec.rb +33 -0
  163. data/spec/semipublic/property/date_spec.rb +43 -0
  164. data/spec/semipublic/property/date_time_spec.rb +46 -0
  165. data/spec/semipublic/property/decimal_spec.rb +83 -0
  166. data/spec/semipublic/property/discriminator_spec.rb +19 -0
  167. data/spec/semipublic/property/float_spec.rb +82 -0
  168. data/spec/semipublic/property/integer_spec.rb +82 -0
  169. data/spec/semipublic/property/lookup_spec.rb +29 -0
  170. data/spec/semipublic/property/serial_spec.rb +13 -0
  171. data/spec/semipublic/property/string_spec.rb +13 -0
  172. data/spec/semipublic/property/text_spec.rb +31 -0
  173. data/spec/semipublic/property/time_spec.rb +50 -0
  174. data/spec/semipublic/property_spec.rb +114 -0
  175. data/spec/semipublic/query/conditions/comparison_spec.rb +1502 -0
  176. data/spec/semipublic/query/conditions/operation_spec.rb +1296 -0
  177. data/spec/semipublic/query/path_spec.rb +471 -0
  178. data/spec/semipublic/query_spec.rb +3665 -0
  179. data/spec/semipublic/resource/state/clean_spec.rb +89 -0
  180. data/spec/semipublic/resource/state/deleted_spec.rb +79 -0
  181. data/spec/semipublic/resource/state/dirty_spec.rb +163 -0
  182. data/spec/semipublic/resource/state/immutable_spec.rb +107 -0
  183. data/spec/semipublic/resource/state/transient_spec.rb +163 -0
  184. data/spec/semipublic/resource/state_spec.rb +230 -0
  185. data/spec/semipublic/resource_spec.rb +23 -0
  186. data/spec/semipublic/shared/condition_shared_spec.rb +9 -0
  187. data/spec/semipublic/shared/resource_shared_spec.rb +198 -0
  188. data/spec/semipublic/shared/resource_state_shared_spec.rb +91 -0
  189. data/spec/semipublic/shared/subject_shared_spec.rb +79 -0
  190. data/spec/spec_helper.rb +34 -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 +27 -0
  248. data/spec/unit/hook_spec.rb +1216 -0
  249. data/spec/unit/inflections_spec.rb +14 -0
  250. data/spec/unit/lazy_array_spec.rb +1949 -0
  251. data/spec/unit/mash_spec.rb +289 -0
  252. data/spec/unit/module_spec.rb +70 -0
  253. data/spec/unit/object_spec.rb +38 -0
  254. data/spec/unit/try_dup_spec.rb +46 -0
  255. data/tasks/ci.rake +1 -0
  256. data/tasks/spec.rake +18 -0
  257. data/tasks/yard.rake +9 -0
  258. data/tasks/yardstick.rake +19 -0
  259. metadata +323 -0
@@ -0,0 +1,366 @@
1
+ # TODO: update Model#respond_to? to return true if method_method missing
2
+ # would handle the message
3
+
4
+ module DataMapper
5
+ module Model
6
+ module Relationship
7
+ Model.append_extensions self
8
+
9
+ include DataMapper::Assertions
10
+
11
+ # Initializes relationships hash for extended model
12
+ # class.
13
+ #
14
+ # When model calls has n, has 1 or belongs_to, relationships
15
+ # are stored in that hash: keys are repository names and
16
+ # values are relationship sets.
17
+ #
18
+ # @api private
19
+ def self.extended(model)
20
+ model.instance_variable_set(:@relationships, {})
21
+ super
22
+ end
23
+
24
+ # When DataMapper model is inherited, relationships
25
+ # of parent are duplicated and copied to subclass model
26
+ #
27
+ # @api private
28
+ def inherited(model)
29
+ model.instance_variable_set(:@relationships, {})
30
+
31
+ @relationships.each do |repository_name, relationships|
32
+ model_relationships = model.relationships(repository_name)
33
+ relationships.each { |relationship| model_relationships << relationship }
34
+ end
35
+
36
+ super
37
+ end
38
+
39
+ # Returns copy of relationships set in given repository.
40
+ #
41
+ # @param [Symbol] repository_name
42
+ # Name of the repository for which relationships set is returned
43
+ # @return [RelationshipSet] relationships set for given repository
44
+ #
45
+ # @api semipublic
46
+ def relationships(repository_name = default_repository_name)
47
+ # TODO: create RelationshipSet#copy that will copy the relationships, but assign the
48
+ # new Relationship objects to a supplied repository and model. dup does not really
49
+ # do what is needed
50
+
51
+ default_repository_name = self.default_repository_name
52
+
53
+ @relationships[repository_name] ||= if repository_name == default_repository_name
54
+ RelationshipSet.new
55
+ else
56
+ relationships(default_repository_name).dup
57
+ end
58
+ end
59
+
60
+ # Used to express unlimited cardinality of association,
61
+ # see +has+
62
+ #
63
+ # @api public
64
+ def n
65
+ Infinity
66
+ end
67
+
68
+ # A shorthand, clear syntax for defining one-to-one, one-to-many and
69
+ # many-to-many resource relationships.
70
+ #
71
+ # * has 1, :friend # one friend
72
+ # * has n, :friends # many friends
73
+ # * has 1..3, :friends # many friends (at least 1, at most 3)
74
+ # * has 3, :friends # many friends (exactly 3)
75
+ # * has 1, :friend, 'User' # one friend with the class User
76
+ # * has 3, :friends, :through => :friendships # many friends through the friendships relationship
77
+ #
78
+ # @param cardinality [Integer, Range, Infinity]
79
+ # cardinality that defines the association type and constraints
80
+ # @param name [Symbol]
81
+ # the name that the association will be referenced by
82
+ # @param *args [Model, Hash] model and/or options hash
83
+ #
84
+ # @option *args :through[Symbol] A association that this join should go through to form
85
+ # a many-to-many association
86
+ # @option *args :model[Model, String] The name of the class to associate with, if omitted
87
+ # then the association name is assumed to match the class name
88
+ # @option *args :repository[Symbol] name of child model repository
89
+ #
90
+ # @return [Association::Relationship] the relationship that was
91
+ # created to reflect either a one-to-one, one-to-many or many-to-many
92
+ # relationship
93
+ # @raise [ArgumentError] if the cardinality was not understood. Should be a
94
+ # Integer, Range or Infinity(n)
95
+ #
96
+ # @api public
97
+ def has(cardinality, name, *args)
98
+ name = name.to_sym
99
+ model = extract_model(args)
100
+ options = extract_options(args)
101
+
102
+ min, max = extract_min_max(cardinality)
103
+ options.update(min: min, max: max)
104
+
105
+ assert_valid_options(options)
106
+
107
+ raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument' if options.key?(:model) && model
108
+
109
+ model ||= options.delete(:model)
110
+
111
+ repository_name = repository.name
112
+
113
+ # TODO: change to :target_respository_name and :source_repository_name
114
+ options[:child_repository_name] = options.delete(:repository)
115
+ options[:parent_repository_name] = repository_name
116
+
117
+ klass = if max > 1
118
+ options.key?(:through) ? Associations::ManyToMany::Relationship : Associations::OneToMany::Relationship
119
+ else
120
+ Associations::OneToOne::Relationship
121
+ end
122
+
123
+ relationship = klass.new(name, model, self, options)
124
+
125
+ relationships(repository_name) << relationship
126
+
127
+ descendants.each do |descendant|
128
+ descendant.relationships(repository_name) << relationship
129
+ end
130
+
131
+ create_relationship_reader(relationship)
132
+ create_relationship_writer(relationship)
133
+
134
+ relationship
135
+ end
136
+
137
+ # A shorthand, clear syntax for defining many-to-one resource relationships.
138
+ #
139
+ # * belongs_to :user # many to one user
140
+ # * belongs_to :friend, :model => 'User' # many to one friend
141
+ # * belongs_to :reference, :repository => :pubmed # association for repository other than default
142
+ #
143
+ # @param name [Symbol]
144
+ # the name that the association will be referenced by
145
+ # @param *args [Model, Hash] model and/or options hash
146
+ #
147
+ # @option *args :model[Model, String] The name of the class to associate with, if omitted
148
+ # then the association name is assumed to match the class name
149
+ # @option *args :repository[Symbol] name of child model repository
150
+ #
151
+ # @return [Association::Relationship] The association created
152
+ # should not be accessed directly
153
+ #
154
+ # @api public
155
+ def belongs_to(name, *args)
156
+ name = name.to_sym
157
+ model_name = self.name
158
+ model = extract_model(args)
159
+ options = extract_options(args)
160
+
161
+ if options.key?(:through)
162
+ raise "#{model_name}#belongs_to with :through is deprecated, use 'has 1, :#{name}, " \
163
+ "#{options.inspect}' in #{model_name} instead (#{caller.first})"
164
+ elsif options.key?(:model) && model
165
+ raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
166
+ end
167
+
168
+ assert_valid_options(options)
169
+
170
+ model ||= options.delete(:model)
171
+
172
+ repository_name = repository.name
173
+
174
+ # TODO: change to source_repository_name and target_respository_name
175
+ options[:child_repository_name] = repository_name
176
+ options[:parent_repository_name] = options.delete(:repository)
177
+
178
+ relationship = Associations::ManyToOne::Relationship.new(name, self, model, options)
179
+
180
+ relationships(repository_name) << relationship
181
+
182
+ descendants.each do |descendant|
183
+ descendant.relationships(repository_name) << relationship
184
+ end
185
+
186
+ create_relationship_reader(relationship)
187
+ create_relationship_writer(relationship)
188
+
189
+ relationship
190
+ end
191
+
192
+ # Extract the model from an Array of arguments
193
+ #
194
+ # @param [Array(Model, String, Hash)]
195
+ # The arguments passed to an relationship declaration
196
+ #
197
+ # @return [Model, #to_str]
198
+ # target model for the association
199
+ #
200
+ # @api private
201
+ private def extract_model(args)
202
+ model = args.first
203
+
204
+ if model.is_a?(Model)
205
+ model
206
+ elsif model.respond_to?(:to_str)
207
+ model.to_str
208
+ end
209
+ end
210
+
211
+ # Extract the model from an Array of arguments
212
+ #
213
+ # @param [Array(Model, String, Hash)]
214
+ # The arguments passed to an relationship declaration
215
+ #
216
+ # @return [Hash]
217
+ # options for the association
218
+ #
219
+ # @api private
220
+ private def extract_options(args)
221
+ options = args.last
222
+ options.respond_to?(:to_hash) ? options.to_hash.dup : {}
223
+ end
224
+
225
+ # A support method for converting Integer, Range or Infinity values into two
226
+ # values representing the minimum and maximum cardinality of the association
227
+ #
228
+ # @return [Array] A pair of integers, min and max
229
+ #
230
+ # @api private
231
+ private def extract_min_max(cardinality)
232
+ case cardinality
233
+ when Integer then [cardinality, cardinality]
234
+ when Range then [cardinality.first, cardinality.last]
235
+ when Infinity then [0, Infinity]
236
+ else
237
+ assert_kind_of 'options', options, Integer, Range, Infinity.class
238
+ end
239
+ end
240
+
241
+ # Validates options of association method like belongs_to or has:
242
+ # verifies types of cardinality bounds, repository, association class,
243
+ # keys and possible values of :through option.
244
+ #
245
+ # @api private
246
+ private def assert_valid_options(options)
247
+ # TODO: update to match Query#assert_valid_options
248
+ # - perform options normalization elsewhere
249
+
250
+ if options.key?(:min) && options.key?(:max)
251
+ min = options[:min]
252
+ max = options[:max]
253
+
254
+ min = min.to_int unless min == Infinity
255
+ max = max.to_int unless max == Infinity
256
+
257
+ if min == Infinity && max == Infinity
258
+ raise ArgumentError, 'Cardinality may not be n..n. The cardinality specifies the min/max number of results from the association'
259
+ elsif min > max
260
+ raise ArgumentError, "Cardinality min (#{min}) cannot be larger than the max (#{max})"
261
+ elsif min < 0
262
+ raise ArgumentError, "Cardinality min much be greater than or equal to 0, but was #{min}"
263
+ elsif max < 1
264
+ raise ArgumentError, "Cardinality max much be greater than or equal to 1, but was #{max}"
265
+ end
266
+ end
267
+
268
+ options[:repository] = options[:repository].to_sym if options.key?(:repository)
269
+
270
+ if options.key?(:class_name)
271
+ raise "+options[:class_name]+ is deprecated, use :model instead (#{caller[1]})"
272
+ elsif options.key?(:remote_name)
273
+ raise "+options[:remote_name]+ is deprecated, use :via instead (#{caller[1]})"
274
+ end
275
+
276
+ assert_kind_of 'options[:through]', options[:through], Symbol, Module if options.key?(:through)
277
+
278
+ %i(via inverse).each do |key|
279
+ assert_kind_of "options[#{key.inspect}]", options[key], Symbol, Associations::Relationship if options.key?(key)
280
+ end
281
+
282
+ # TODO: deprecate :child_key and :parent_key in favor of :source_key and
283
+ # :target_key (will mean something different for each relationship)
284
+
285
+ %i(child_key parent_key).each do |key|
286
+ options[key] = Array(options[key]) if options.key?(key)
287
+ end
288
+
289
+ raise ArgumentError, '+options[:limit]+ should not be specified on a relationship' if options.key?(:limit)
290
+ end
291
+
292
+ # Defines the anonymous module that is used to add relationships.
293
+ # Using a single module here prevents having a very large number
294
+ # of anonymous modules, where each property has their own module.
295
+ # @api private
296
+ private def relationship_module
297
+ @relationship_module ||= begin
298
+ mod = Module.new
299
+ class_eval do
300
+ include mod
301
+ end
302
+ mod
303
+ end
304
+ end
305
+
306
+ # Dynamically defines reader method
307
+ #
308
+ # @api private
309
+ private def create_relationship_reader(relationship)
310
+ name = relationship.name
311
+ reader_name = name.to_s
312
+
313
+ return if method_defined?(reader_name)
314
+
315
+ reader_visibility = relationship.reader_visibility
316
+
317
+ relationship_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
318
+ #{reader_visibility}
319
+ def #{reader_name}(query = nil)
320
+ # TODO: when no query is passed in, return the results from
321
+ # the ivar directly. This will require that the ivar
322
+ # actually hold the resource/collection, and in the case
323
+ # of 1:1, the underlying collection is hidden in a
324
+ # private ivar, and the resource is in a known ivar
325
+
326
+ persistence_state.get(relationships[#{name.inspect}], query)
327
+ end
328
+ RUBY
329
+ end
330
+
331
+ # Dynamically defines writer method
332
+ #
333
+ # @api private
334
+ private def create_relationship_writer(relationship)
335
+ name = relationship.name
336
+ writer_name = "#{name}="
337
+
338
+ return if method_defined?(writer_name)
339
+
340
+ writer_visibility = relationship.writer_visibility
341
+
342
+ relationship_module.module_eval <<-RUBY, __FILE__, __LINE__ + 1
343
+ #{writer_visibility}
344
+ def #{writer_name}(target)
345
+ relationship = relationships[#{name.inspect}]
346
+ self.persistence_state = persistence_state.set(relationship, target)
347
+ persistence_state.get(relationship)
348
+ end
349
+ RUBY
350
+ end
351
+
352
+ # @api public
353
+ private def respond_to_missing?(method, include_private)
354
+ relationships(repository_name)[method] || super
355
+ end
356
+
357
+ private def method_missing(method, *args, &block)
358
+ if (relationship = relationships(repository_name)[method])
359
+ return Query::Path.new([relationship])
360
+ end
361
+
362
+ super
363
+ end
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,87 @@
1
+ module DataMapper
2
+ module Model
3
+ # Module with query scoping functionality.
4
+ #
5
+ # Scopes are implemented using simple array based
6
+ # stack that is thread local. Default scope can be set
7
+ # on a per repository basis.
8
+ #
9
+ # Scopes are merged as new queries are nested.
10
+ # It is also possible to get exclusive scope access
11
+ # using +with_exclusive_scope+
12
+ module Scope
13
+ # @api private
14
+ def default_scope(repository_name = default_repository_name)
15
+ @default_scope ||= {}
16
+
17
+ default_repository_name = self.default_repository_name
18
+
19
+ @default_scope[repository_name] ||= if repository_name == default_repository_name
20
+ {}
21
+ else
22
+ default_scope(default_repository_name).dup
23
+ end
24
+ end
25
+
26
+ # Returns query on top of scope stack
27
+ #
28
+ # @api private
29
+ def query
30
+ repository.new_query(self, current_scope).freeze
31
+ end
32
+
33
+ # @api private
34
+ def current_scope
35
+ scope_stack.last || default_scope(repository.name)
36
+ end
37
+
38
+ # Pushes given query on top of the stack
39
+ #
40
+ # @param [Hash, Query] query to add to current scope nesting
41
+ #
42
+ # @api private
43
+ protected def with_scope(query)
44
+ options = if query.is_a?(Hash)
45
+ query
46
+ else
47
+ query.options
48
+ end
49
+
50
+ # merge the current scope with the passed in query
51
+ with_exclusive_scope(self.query.merge(options)) { |*block_args| yield(*block_args) }
52
+ end
53
+
54
+ # Pushes given query on top of scope stack and yields
55
+ # given block, then pops the stack. During block execution
56
+ # queries previously pushed onto the stack
57
+ # have no effect.
58
+ #
59
+ # @api private
60
+ protected def with_exclusive_scope(query)
61
+ query = if query.is_a?(Hash)
62
+ repository.new_query(self, query)
63
+ else
64
+ query.dup
65
+ end
66
+
67
+ scope_stack = self.scope_stack
68
+ scope_stack << query.options
69
+
70
+ begin
71
+ yield query.freeze
72
+ ensure
73
+ scope_stack.pop
74
+ end
75
+ end
76
+
77
+ # Initializes (if necessary) and returns current scope stack
78
+ # @api private
79
+ protected def scope_stack
80
+ scope_stack_for = Thread.current[:dm_scope_stack] ||= {}
81
+ scope_stack_for[object_id] ||= []
82
+ end
83
+ end
84
+
85
+ include Scope
86
+ end
87
+ end