humanoid 1.2.7

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 (210) hide show
  1. data/.gitignore +6 -0
  2. data/.watchr +29 -0
  3. data/HISTORY +342 -0
  4. data/MIT_LICENSE +20 -0
  5. data/README.rdoc +56 -0
  6. data/Rakefile +53 -0
  7. data/VERSION +1 -0
  8. data/caliper.yml +4 -0
  9. data/humanoid.gemspec +374 -0
  10. data/lib/humanoid.rb +111 -0
  11. data/lib/humanoid/associations.rb +258 -0
  12. data/lib/humanoid/associations/belongs_to.rb +64 -0
  13. data/lib/humanoid/associations/belongs_to_related.rb +62 -0
  14. data/lib/humanoid/associations/has_many.rb +180 -0
  15. data/lib/humanoid/associations/has_many_related.rb +109 -0
  16. data/lib/humanoid/associations/has_one.rb +95 -0
  17. data/lib/humanoid/associations/has_one_related.rb +81 -0
  18. data/lib/humanoid/associations/options.rb +57 -0
  19. data/lib/humanoid/associations/proxy.rb +31 -0
  20. data/lib/humanoid/attributes.rb +184 -0
  21. data/lib/humanoid/callbacks.rb +23 -0
  22. data/lib/humanoid/collection.rb +118 -0
  23. data/lib/humanoid/collections/cyclic_iterator.rb +34 -0
  24. data/lib/humanoid/collections/master.rb +28 -0
  25. data/lib/humanoid/collections/mimic.rb +46 -0
  26. data/lib/humanoid/collections/operations.rb +41 -0
  27. data/lib/humanoid/collections/slaves.rb +44 -0
  28. data/lib/humanoid/commands.rb +182 -0
  29. data/lib/humanoid/commands/create.rb +21 -0
  30. data/lib/humanoid/commands/delete.rb +16 -0
  31. data/lib/humanoid/commands/delete_all.rb +23 -0
  32. data/lib/humanoid/commands/deletion.rb +18 -0
  33. data/lib/humanoid/commands/destroy.rb +19 -0
  34. data/lib/humanoid/commands/destroy_all.rb +23 -0
  35. data/lib/humanoid/commands/save.rb +27 -0
  36. data/lib/humanoid/components.rb +24 -0
  37. data/lib/humanoid/config.rb +84 -0
  38. data/lib/humanoid/contexts.rb +25 -0
  39. data/lib/humanoid/contexts/enumerable.rb +117 -0
  40. data/lib/humanoid/contexts/ids.rb +25 -0
  41. data/lib/humanoid/contexts/mongo.rb +224 -0
  42. data/lib/humanoid/contexts/paging.rb +42 -0
  43. data/lib/humanoid/criteria.rb +259 -0
  44. data/lib/humanoid/criterion/complex.rb +21 -0
  45. data/lib/humanoid/criterion/exclusion.rb +65 -0
  46. data/lib/humanoid/criterion/inclusion.rb +91 -0
  47. data/lib/humanoid/criterion/optional.rb +128 -0
  48. data/lib/humanoid/cursor.rb +82 -0
  49. data/lib/humanoid/document.rb +300 -0
  50. data/lib/humanoid/enslavement.rb +38 -0
  51. data/lib/humanoid/errors.rb +77 -0
  52. data/lib/humanoid/extensions.rb +84 -0
  53. data/lib/humanoid/extensions/array/accessors.rb +17 -0
  54. data/lib/humanoid/extensions/array/aliasing.rb +4 -0
  55. data/lib/humanoid/extensions/array/assimilation.rb +26 -0
  56. data/lib/humanoid/extensions/array/conversions.rb +29 -0
  57. data/lib/humanoid/extensions/array/parentization.rb +13 -0
  58. data/lib/humanoid/extensions/boolean/conversions.rb +16 -0
  59. data/lib/humanoid/extensions/date/conversions.rb +15 -0
  60. data/lib/humanoid/extensions/datetime/conversions.rb +17 -0
  61. data/lib/humanoid/extensions/float/conversions.rb +16 -0
  62. data/lib/humanoid/extensions/hash/accessors.rb +38 -0
  63. data/lib/humanoid/extensions/hash/assimilation.rb +30 -0
  64. data/lib/humanoid/extensions/hash/conversions.rb +15 -0
  65. data/lib/humanoid/extensions/hash/criteria_helpers.rb +20 -0
  66. data/lib/humanoid/extensions/hash/scoping.rb +12 -0
  67. data/lib/humanoid/extensions/integer/conversions.rb +16 -0
  68. data/lib/humanoid/extensions/nil/assimilation.rb +13 -0
  69. data/lib/humanoid/extensions/object/conversions.rb +33 -0
  70. data/lib/humanoid/extensions/proc/scoping.rb +12 -0
  71. data/lib/humanoid/extensions/string/conversions.rb +15 -0
  72. data/lib/humanoid/extensions/string/inflections.rb +97 -0
  73. data/lib/humanoid/extensions/symbol/inflections.rb +36 -0
  74. data/lib/humanoid/extensions/time/conversions.rb +18 -0
  75. data/lib/humanoid/factory.rb +19 -0
  76. data/lib/humanoid/field.rb +39 -0
  77. data/lib/humanoid/fields.rb +62 -0
  78. data/lib/humanoid/finders.rb +224 -0
  79. data/lib/humanoid/identity.rb +39 -0
  80. data/lib/humanoid/indexes.rb +30 -0
  81. data/lib/humanoid/matchers.rb +36 -0
  82. data/lib/humanoid/matchers/all.rb +11 -0
  83. data/lib/humanoid/matchers/default.rb +26 -0
  84. data/lib/humanoid/matchers/exists.rb +13 -0
  85. data/lib/humanoid/matchers/gt.rb +11 -0
  86. data/lib/humanoid/matchers/gte.rb +11 -0
  87. data/lib/humanoid/matchers/in.rb +11 -0
  88. data/lib/humanoid/matchers/lt.rb +11 -0
  89. data/lib/humanoid/matchers/lte.rb +11 -0
  90. data/lib/humanoid/matchers/ne.rb +11 -0
  91. data/lib/humanoid/matchers/nin.rb +11 -0
  92. data/lib/humanoid/matchers/size.rb +11 -0
  93. data/lib/humanoid/memoization.rb +27 -0
  94. data/lib/humanoid/named_scope.rb +40 -0
  95. data/lib/humanoid/scope.rb +75 -0
  96. data/lib/humanoid/timestamps.rb +30 -0
  97. data/lib/humanoid/versioning.rb +28 -0
  98. data/perf/benchmark.rb +77 -0
  99. data/spec/integration/humanoid/associations_spec.rb +301 -0
  100. data/spec/integration/humanoid/attributes_spec.rb +22 -0
  101. data/spec/integration/humanoid/commands_spec.rb +216 -0
  102. data/spec/integration/humanoid/contexts/enumerable_spec.rb +33 -0
  103. data/spec/integration/humanoid/criteria_spec.rb +224 -0
  104. data/spec/integration/humanoid/document_spec.rb +587 -0
  105. data/spec/integration/humanoid/extensions_spec.rb +26 -0
  106. data/spec/integration/humanoid/finders_spec.rb +119 -0
  107. data/spec/integration/humanoid/inheritance_spec.rb +137 -0
  108. data/spec/integration/humanoid/named_scope_spec.rb +46 -0
  109. data/spec/models/address.rb +39 -0
  110. data/spec/models/animal.rb +6 -0
  111. data/spec/models/comment.rb +8 -0
  112. data/spec/models/country_code.rb +6 -0
  113. data/spec/models/employer.rb +5 -0
  114. data/spec/models/game.rb +6 -0
  115. data/spec/models/inheritance.rb +56 -0
  116. data/spec/models/location.rb +5 -0
  117. data/spec/models/mixed_drink.rb +4 -0
  118. data/spec/models/name.rb +13 -0
  119. data/spec/models/namespacing.rb +11 -0
  120. data/spec/models/patient.rb +4 -0
  121. data/spec/models/person.rb +98 -0
  122. data/spec/models/pet.rb +7 -0
  123. data/spec/models/pet_owner.rb +6 -0
  124. data/spec/models/phone.rb +7 -0
  125. data/spec/models/post.rb +15 -0
  126. data/spec/models/translation.rb +5 -0
  127. data/spec/models/vet_visit.rb +5 -0
  128. data/spec/spec.opts +3 -0
  129. data/spec/spec_helper.rb +31 -0
  130. data/spec/unit/mongoid/associations/belongs_to_related_spec.rb +141 -0
  131. data/spec/unit/mongoid/associations/belongs_to_spec.rb +193 -0
  132. data/spec/unit/mongoid/associations/has_many_related_spec.rb +387 -0
  133. data/spec/unit/mongoid/associations/has_many_spec.rb +471 -0
  134. data/spec/unit/mongoid/associations/has_one_related_spec.rb +179 -0
  135. data/spec/unit/mongoid/associations/has_one_spec.rb +282 -0
  136. data/spec/unit/mongoid/associations/options_spec.rb +191 -0
  137. data/spec/unit/mongoid/associations_spec.rb +545 -0
  138. data/spec/unit/mongoid/attributes_spec.rb +484 -0
  139. data/spec/unit/mongoid/callbacks_spec.rb +55 -0
  140. data/spec/unit/mongoid/collection_spec.rb +171 -0
  141. data/spec/unit/mongoid/collections/cyclic_iterator_spec.rb +75 -0
  142. data/spec/unit/mongoid/collections/master_spec.rb +41 -0
  143. data/spec/unit/mongoid/collections/mimic_spec.rb +43 -0
  144. data/spec/unit/mongoid/collections/slaves_spec.rb +81 -0
  145. data/spec/unit/mongoid/commands/create_spec.rb +30 -0
  146. data/spec/unit/mongoid/commands/delete_all_spec.rb +58 -0
  147. data/spec/unit/mongoid/commands/delete_spec.rb +35 -0
  148. data/spec/unit/mongoid/commands/destroy_all_spec.rb +23 -0
  149. data/spec/unit/mongoid/commands/destroy_spec.rb +44 -0
  150. data/spec/unit/mongoid/commands/save_spec.rb +105 -0
  151. data/spec/unit/mongoid/commands_spec.rb +282 -0
  152. data/spec/unit/mongoid/config_spec.rb +165 -0
  153. data/spec/unit/mongoid/contexts/enumerable_spec.rb +374 -0
  154. data/spec/unit/mongoid/contexts/mongo_spec.rb +505 -0
  155. data/spec/unit/mongoid/contexts_spec.rb +25 -0
  156. data/spec/unit/mongoid/criteria_spec.rb +769 -0
  157. data/spec/unit/mongoid/criterion/complex_spec.rb +19 -0
  158. data/spec/unit/mongoid/criterion/exclusion_spec.rb +91 -0
  159. data/spec/unit/mongoid/criterion/inclusion_spec.rb +211 -0
  160. data/spec/unit/mongoid/criterion/optional_spec.rb +329 -0
  161. data/spec/unit/mongoid/cursor_spec.rb +74 -0
  162. data/spec/unit/mongoid/document_spec.rb +986 -0
  163. data/spec/unit/mongoid/enslavement_spec.rb +63 -0
  164. data/spec/unit/mongoid/errors_spec.rb +103 -0
  165. data/spec/unit/mongoid/extensions/array/accessors_spec.rb +50 -0
  166. data/spec/unit/mongoid/extensions/array/assimilation_spec.rb +24 -0
  167. data/spec/unit/mongoid/extensions/array/conversions_spec.rb +35 -0
  168. data/spec/unit/mongoid/extensions/array/parentization_spec.rb +20 -0
  169. data/spec/unit/mongoid/extensions/boolean/conversions_spec.rb +49 -0
  170. data/spec/unit/mongoid/extensions/date/conversions_spec.rb +102 -0
  171. data/spec/unit/mongoid/extensions/datetime/conversions_spec.rb +70 -0
  172. data/spec/unit/mongoid/extensions/float/conversions_spec.rb +61 -0
  173. data/spec/unit/mongoid/extensions/hash/accessors_spec.rb +184 -0
  174. data/spec/unit/mongoid/extensions/hash/assimilation_spec.rb +46 -0
  175. data/spec/unit/mongoid/extensions/hash/conversions_spec.rb +21 -0
  176. data/spec/unit/mongoid/extensions/hash/criteria_helpers_spec.rb +17 -0
  177. data/spec/unit/mongoid/extensions/hash/scoping_spec.rb +14 -0
  178. data/spec/unit/mongoid/extensions/integer/conversions_spec.rb +61 -0
  179. data/spec/unit/mongoid/extensions/nil/assimilation_spec.rb +24 -0
  180. data/spec/unit/mongoid/extensions/object/conversions_spec.rb +43 -0
  181. data/spec/unit/mongoid/extensions/proc/scoping_spec.rb +34 -0
  182. data/spec/unit/mongoid/extensions/string/conversions_spec.rb +17 -0
  183. data/spec/unit/mongoid/extensions/string/inflections_spec.rb +208 -0
  184. data/spec/unit/mongoid/extensions/symbol/inflections_spec.rb +91 -0
  185. data/spec/unit/mongoid/extensions/time/conversions_spec.rb +70 -0
  186. data/spec/unit/mongoid/factory_spec.rb +31 -0
  187. data/spec/unit/mongoid/field_spec.rb +81 -0
  188. data/spec/unit/mongoid/fields_spec.rb +158 -0
  189. data/spec/unit/mongoid/finders_spec.rb +368 -0
  190. data/spec/unit/mongoid/identity_spec.rb +88 -0
  191. data/spec/unit/mongoid/indexes_spec.rb +93 -0
  192. data/spec/unit/mongoid/matchers/all_spec.rb +27 -0
  193. data/spec/unit/mongoid/matchers/default_spec.rb +27 -0
  194. data/spec/unit/mongoid/matchers/exists_spec.rb +56 -0
  195. data/spec/unit/mongoid/matchers/gt_spec.rb +39 -0
  196. data/spec/unit/mongoid/matchers/gte_spec.rb +49 -0
  197. data/spec/unit/mongoid/matchers/in_spec.rb +27 -0
  198. data/spec/unit/mongoid/matchers/lt_spec.rb +39 -0
  199. data/spec/unit/mongoid/matchers/lte_spec.rb +49 -0
  200. data/spec/unit/mongoid/matchers/ne_spec.rb +27 -0
  201. data/spec/unit/mongoid/matchers/nin_spec.rb +27 -0
  202. data/spec/unit/mongoid/matchers/size_spec.rb +27 -0
  203. data/spec/unit/mongoid/matchers_spec.rb +329 -0
  204. data/spec/unit/mongoid/memoization_spec.rb +75 -0
  205. data/spec/unit/mongoid/named_scope_spec.rb +123 -0
  206. data/spec/unit/mongoid/scope_spec.rb +240 -0
  207. data/spec/unit/mongoid/timestamps_spec.rb +25 -0
  208. data/spec/unit/mongoid/versioning_spec.rb +41 -0
  209. data/spec/unit/mongoid_spec.rb +37 -0
  210. metadata +431 -0
@@ -0,0 +1,258 @@
1
+ # encoding: utf-8
2
+ require "humanoid/associations/proxy"
3
+ require "humanoid/associations/belongs_to"
4
+ require "humanoid/associations/belongs_to_related"
5
+ require "humanoid/associations/has_many"
6
+ require "humanoid/associations/has_many_related"
7
+ require "humanoid/associations/has_one"
8
+ require "humanoid/associations/has_one_related"
9
+
10
+ module Humanoid # :nodoc:
11
+ module Associations #:nodoc:
12
+ def self.included(base)
13
+ base.class_eval do
14
+ # Associations need to inherit down the chain.
15
+ class_inheritable_accessor :associations
16
+ self.associations = {}
17
+
18
+ include InstanceMethods
19
+ extend ClassMethods
20
+ end
21
+ end
22
+
23
+ module InstanceMethods
24
+ # Returns the associations for the +Document+.
25
+ def associations
26
+ self.class.associations
27
+ end
28
+
29
+ # Updates all the one-to-many relational associations for the name.
30
+ def update_associations(name)
31
+ send(name).each { |doc| doc.save }
32
+ end
33
+
34
+ # Update the one-to-one relational association for the name.
35
+ def update_association(name)
36
+ association = send(name)
37
+ association.save unless association.nil?
38
+ end
39
+ end
40
+
41
+ module ClassMethods
42
+ # Adds the association back to the parent document. This macro is
43
+ # necessary to set the references from the child back to the parent
44
+ # document. If a child does not define this association calling
45
+ # persistence methods on the child object will cause a save to fail.
46
+ #
47
+ # Options:
48
+ #
49
+ # name: A +Symbol+ that matches the name of the parent class.
50
+ #
51
+ # Example:
52
+ #
53
+ # class Person
54
+ # include Humanoid::Document
55
+ # has_many :addresses
56
+ # end
57
+ #
58
+ # class Address
59
+ # include Humanoid::Document
60
+ # belongs_to :person, :inverse_of => :addresses
61
+ # end
62
+ def belongs_to(name, options = {}, &block)
63
+ unless options.has_key?(:inverse_of)
64
+ raise Errors::InvalidOptions.new("Options for belongs_to association must include :inverse_of")
65
+ end
66
+ self.embedded = true
67
+ add_association(
68
+ Associations::BelongsTo,
69
+ Associations::Options.new(
70
+ options.merge(:name => name, :extend => block)
71
+ )
72
+ )
73
+ end
74
+
75
+ # Adds a relational association from the child Document to a Document in
76
+ # another database or collection.
77
+ #
78
+ # Options:
79
+ #
80
+ # name: A +Symbol+ that is the related class name.
81
+ #
82
+ # Example:
83
+ #
84
+ # class Game
85
+ # include Humanoid::Document
86
+ # belongs_to_related :person
87
+ # end
88
+ #
89
+ def belongs_to_related(name, options = {}, &block)
90
+ field "#{name.to_s}_id"
91
+ index "#{name.to_s}_id" unless self.embedded
92
+ add_association(
93
+ Associations::BelongsToRelated,
94
+ Associations::Options.new(
95
+ options.merge(:name => name, :extend => block)
96
+ )
97
+ )
98
+ end
99
+
100
+ # Adds the association from a parent document to its children. The name
101
+ # of the association needs to be a pluralized form of the child class
102
+ # name.
103
+ #
104
+ # Options:
105
+ #
106
+ # name: A +Symbol+ that is the plural child class name.
107
+ #
108
+ # Example:
109
+ #
110
+ # class Person
111
+ # include Humanoid::Document
112
+ # has_many :addresses
113
+ # end
114
+ #
115
+ # class Address
116
+ # include Humanoid::Document
117
+ # belongs_to :person, :inverse_of => :addresses
118
+ # end
119
+ def has_many(name, options = {}, &block)
120
+ add_association(
121
+ Associations::HasMany,
122
+ Associations::Options.new(
123
+ options.merge(:name => name, :extend => block)
124
+ )
125
+ )
126
+ end
127
+
128
+ # Adds a relational association from the Document to many Documents in
129
+ # another database or collection.
130
+ #
131
+ # Options:
132
+ #
133
+ # name: A +Symbol+ that is the related class name pluralized.
134
+ #
135
+ # Example:
136
+ #
137
+ # class Person
138
+ # include Humanoid::Document
139
+ # has_many_related :posts
140
+ # end
141
+ #
142
+ def has_many_related(name, options = {}, &block)
143
+ add_association(
144
+ Associations::HasManyRelated,
145
+ Associations::Options.new(
146
+ options.merge(:name => name, :parent_key => self.name.foreign_key, :extend => block)
147
+ )
148
+ )
149
+ before_save do |document|
150
+ document.update_associations(name)
151
+ end
152
+ end
153
+
154
+ # Adds the association from a parent document to its child. The name
155
+ # of the association needs to be a singular form of the child class
156
+ # name.
157
+ #
158
+ # Options:
159
+ #
160
+ # name: A +Symbol+ that is the plural child class name.
161
+ #
162
+ # Example:
163
+ #
164
+ # class Person
165
+ # include Humanoid::Document
166
+ # has_many :addresses
167
+ # end
168
+ #
169
+ # class Address
170
+ # include Humanoid::Document
171
+ # belongs_to :person
172
+ # end
173
+ def has_one(name, options = {}, &block)
174
+ opts = Associations::Options.new(
175
+ options.merge(:name => name, :extend => block)
176
+ )
177
+ type = Associations::HasOne
178
+ add_association(type, opts)
179
+ add_builder(type, opts)
180
+ add_creator(type, opts)
181
+ end
182
+
183
+ # Adds a relational association from the Document to one Document in
184
+ # another database or collection.
185
+ #
186
+ # Options:
187
+ #
188
+ # name: A +Symbol+ that is the related class name pluralized.
189
+ #
190
+ # Example:
191
+ #
192
+ # class Person
193
+ # include Humanoid::Document
194
+ # has_one_related :game
195
+ # end
196
+ def has_one_related(name, options = {}, &block)
197
+ add_association(
198
+ Associations::HasOneRelated,
199
+ Associations::Options.new(
200
+ options.merge(:name => name, :parent_key => self.name.foreign_key, :extend => block)
201
+ )
202
+ )
203
+ before_save do |document|
204
+ document.update_association(name)
205
+ end
206
+ end
207
+
208
+ # Returns the macro associated with the supplied association name. This
209
+ # will return has_one, has_many, belongs_to or nil.
210
+ #
211
+ # Options:
212
+ #
213
+ # name: The association name.
214
+ #
215
+ # Example:
216
+ #
217
+ # <tt>Person.reflect_on_association(:addresses)</tt>
218
+ def reflect_on_association(name)
219
+ association = associations[name.to_s]
220
+ association ? association.macro : nil
221
+ end
222
+
223
+ protected
224
+ # Adds the association to the associations hash with the type as the key,
225
+ # then adds the accessors for the association. The defined setters and
226
+ # getters for the associations will perform the necessary memoization.
227
+ def add_association(type, options)
228
+ name = options.name.to_s
229
+ associations[name] = type
230
+ define_method(name) do
231
+ memoized(name) { type.instantiate(self, options) }
232
+ end
233
+ define_method("#{name}=") do |object|
234
+ reset(name) { type.update(object, self, options) }
235
+ end
236
+ end
237
+
238
+ # Adds a builder for a has_one association. This comes in the form of
239
+ # build_name(attributes)
240
+ def add_builder(type, options)
241
+ name = options.name.to_s
242
+ define_method("build_#{name}") do |attrs|
243
+ reset(name) { type.new(self, (attrs || {}).stringify_keys, options) }
244
+ end
245
+ end
246
+
247
+ # Adds a creator for a has_one association. This comes in the form of
248
+ # create_name(attributes)
249
+ def add_creator(type, options)
250
+ name = options.name.to_s
251
+ define_method("create_#{name}") do |attrs|
252
+ document = send("build_#{name}", attrs)
253
+ document.save; document
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Associations #:nodoc:
4
+ class BelongsTo #:nodoc:
5
+ include Proxy
6
+
7
+ # Creates the new association by setting the internal
8
+ # target as the passed in Document. This should be the
9
+ # parent.
10
+ #
11
+ # All method calls on this object will then be delegated
12
+ # to the internal document itself.
13
+ #
14
+ # Options:
15
+ #
16
+ # target: The parent +Document+
17
+ # options: The association options
18
+ def initialize(target, options)
19
+ @target, @options = target, options
20
+ extends(options)
21
+ end
22
+
23
+ # Returns the parent document. The id param is present for
24
+ # compatibility with rails, however this could be overwritten
25
+ # in the future.
26
+ def find(id)
27
+ @target
28
+ end
29
+
30
+ class << self
31
+ # Creates the new association by setting the internal
32
+ # document as the passed in Document. This should be the
33
+ # parent.
34
+ #
35
+ # Options:
36
+ #
37
+ # document: The parent +Document+
38
+ # options: The association options
39
+ def instantiate(document, options)
40
+ target = document._parent
41
+ target.nil? ? nil : new(target, options)
42
+ end
43
+
44
+ # Returns the macro used to create the association.
45
+ def macro
46
+ :belongs_to
47
+ end
48
+
49
+ # Perform an update of the relationship of the parent and child. This
50
+ # is initialized by setting a parent object as the association on the
51
+ # +Document+. Will properly set a has_one or a has_many.
52
+ #
53
+ # Returns:
54
+ #
55
+ # A new +BelongsTo+ association proxy.
56
+ def update(target, child, options)
57
+ child.parentize(target, options.inverse_of)
58
+ child.notify
59
+ instantiate(child, options)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Associations #:nodoc:
4
+ class BelongsToRelated #:nodoc:
5
+ include Proxy
6
+
7
+ # Initializing a related association only requires looking up the object
8
+ # by its id.
9
+ #
10
+ # Options:
11
+ #
12
+ # document: The +Document+ that contains the relationship.
13
+ # options: The association +Options+.
14
+ def initialize(document, foreign_key, options, target = nil)
15
+ @options = options
16
+ @target = target || options.klass.find(foreign_key)
17
+ extends(options)
18
+ end
19
+
20
+ class << self
21
+ # Instantiate a new +BelongsToRelated+ or return nil if the foreign key is
22
+ # nil. It is preferrable to use this method over the traditional call
23
+ # to new.
24
+ #
25
+ # Options:
26
+ #
27
+ # document: The +Document+ that contains the relationship.
28
+ # options: The association +Options+.
29
+ def instantiate(document, options, target = nil)
30
+ foreign_key = document.send(options.foreign_key)
31
+ foreign_key.blank? ? nil : new(document, foreign_key, options, target)
32
+ end
33
+
34
+ # Returns the macro used to create the association.
35
+ def macro
36
+ :belongs_to_related
37
+ end
38
+
39
+ # Perform an update of the relationship of the parent and child. This
40
+ # will assimilate the child +Document+ into the parent's object graph.
41
+ #
42
+ # Options:
43
+ #
44
+ # related: The related object
45
+ # parent: The parent +Document+ to update.
46
+ # options: The association +Options+
47
+ #
48
+ # Example:
49
+ #
50
+ # <tt>BelongsToRelated.update(game, person, options)</tt>
51
+ def update(target, parent, options)
52
+ if target
53
+ parent.send("#{options.foreign_key}=", target.id)
54
+ return instantiate(parent, options, target)
55
+ end
56
+ target
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,180 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Associations #:nodoc:
4
+ class HasMany
5
+ include Proxy
6
+
7
+ attr_accessor :association_name, :klass
8
+
9
+ # Appends the object to the +Array+, setting its parent in
10
+ # the process.
11
+ def <<(*objects)
12
+ objects.flatten.each do |object|
13
+ object.parentize(@parent, @association_name)
14
+ @target << object
15
+ object.notify
16
+ end
17
+ end
18
+
19
+ alias :concat :<<
20
+ alias :push :<<
21
+
22
+ # Clears the association, and notifies the parents of the removal.
23
+ def clear
24
+ unless @target.empty?
25
+ object = @target.first
26
+ object.changed(true)
27
+ object.notify_observers(object, true)
28
+ @target.clear
29
+ end
30
+ end
31
+
32
+ # Builds a new Document and adds it to the association collection. The
33
+ # document created will be of the same class as the others in the
34
+ # association, and the attributes will be passed into the constructor.
35
+ #
36
+ # Returns:
37
+ #
38
+ # The newly created Document.
39
+ def build(attrs = {}, type = nil)
40
+ object = type ? type.instantiate : @klass.instantiate
41
+ object.parentize(@parent, @association_name)
42
+ object.write_attributes(attrs)
43
+ @target << object
44
+ object
45
+ end
46
+
47
+ # Creates a new Document and adds it to the association collection. The
48
+ # document created will be of the same class as the others in the
49
+ # association, and the attributes will be passed into the constructor and
50
+ # the new object will then be saved.
51
+ #
52
+ # Returns:
53
+ #
54
+ # Rhe newly created Document.
55
+ def create(attrs = {}, type = nil)
56
+ object = build(attrs, type)
57
+ object.save
58
+ object
59
+ end
60
+
61
+ # Finds a document in this association.
62
+ #
63
+ # If :all is passed, returns all the documents
64
+ #
65
+ # If an id is passed, will return the document for that id.
66
+ #
67
+ # Returns:
68
+ #
69
+ # Array or single Document.
70
+ def find(param)
71
+ return @target if param == :all
72
+ return detect { |document| document.id == param }
73
+ end
74
+
75
+ # Creates the new association by finding the attributes in
76
+ # the parent document with its name, and instantiating a
77
+ # new document for each one found. These will then be put in an
78
+ # internal array.
79
+ #
80
+ # This then delegated all methods to the array class since this is
81
+ # essentially a proxy to an array itself.
82
+ #
83
+ # Options:
84
+ #
85
+ # parent: The parent document to the association.
86
+ # options: The association options.
87
+ def initialize(parent, options)
88
+ @parent, @association_name = parent, options.name
89
+ @klass, @options = options.klass, options
90
+ initialize_each(parent.raw_attributes[@association_name])
91
+ extends(options)
92
+ end
93
+
94
+ # If the target array does not respond to the supplied method then try to
95
+ # find a named scope or criteria on the class and send the call there.
96
+ #
97
+ # If the method exists on the array, use the default proxy behavior.
98
+ def method_missing(name, *args, &block)
99
+ unless @target.respond_to?(name)
100
+ object = @klass.send(name, *args)
101
+ object.documents = @target
102
+ return object
103
+ end
104
+ super
105
+ end
106
+
107
+ # Used for setting associations via a nested attributes setter from the
108
+ # parent +Document+.
109
+ #
110
+ # Options:
111
+ #
112
+ # attributes: A +Hash+ of integer keys and +Hash+ values.
113
+ #
114
+ # Returns:
115
+ #
116
+ # The newly build target Document.
117
+ def nested_build(attributes)
118
+ attributes.values.each do |attrs|
119
+ build(attrs)
120
+ end
121
+ end
122
+
123
+ # Paginate the association. Will create a new criteria, set the documents
124
+ # on it and execute in an enumerable context.
125
+ #
126
+ # Options:
127
+ #
128
+ # options: A +Hash+ of pagination options.
129
+ #
130
+ # Returns:
131
+ #
132
+ # A +WillPaginate::Collection+.
133
+ def paginate(options)
134
+ criteria = Humanoid::Criteria.translate(@klass, options)
135
+ criteria.documents = @target
136
+ criteria.paginate
137
+ end
138
+
139
+ protected
140
+ # Initializes each of the attributes in the hash.
141
+ def initialize_each(attributes)
142
+ @target = attributes ? attributes.collect do |attrs|
143
+ klass = attrs.klass
144
+ child = klass ? klass.instantiate(attrs) : @klass.instantiate(attrs)
145
+ child.parentize(@parent, @association_name)
146
+ child
147
+ end : []
148
+ end
149
+
150
+ class << self
151
+
152
+ # Preferred method of creating a new +HasMany+ association. It will
153
+ # delegate to new.
154
+ #
155
+ # Options:
156
+ #
157
+ # document: The parent +Document+
158
+ # options: The association options
159
+ def instantiate(document, options)
160
+ new(document, options)
161
+ end
162
+
163
+ # Returns the macro used to create the association.
164
+ def macro
165
+ :has_many
166
+ end
167
+
168
+ # Perform an update of the relationship of the parent and child. This
169
+ # is initialized by setting the has_many to the supplied +Enumerable+
170
+ # and setting up the parentization.
171
+ def update(children, parent, options)
172
+ parent.remove_attribute(options.name)
173
+ children.assimilate(parent, options)
174
+ instantiate(parent, options)
175
+ end
176
+ end
177
+
178
+ end
179
+ end
180
+ end