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,109 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Associations #:nodoc:
4
+ class HasManyRelated #:nodoc:
5
+ include Proxy
6
+
7
+ # Appends the object to the +Array+, setting its parent in
8
+ # the process.
9
+ def <<(*objects)
10
+ objects.flatten.each do |object|
11
+ object.send("#{@foreign_key}=", @parent.id)
12
+ @target << object
13
+ object.save unless @parent.new_record?
14
+ end
15
+ end
16
+
17
+ # Builds a new Document and adds it to the association collection. The
18
+ # document created will be of the same class as the others in the
19
+ # association, and the attributes will be passed into the constructor.
20
+ #
21
+ # Returns the newly created object.
22
+ def build(attributes = {})
23
+ name = @parent.class.to_s.underscore
24
+ object = @klass.instantiate(attributes.merge(name => @parent))
25
+ @target << object
26
+ object
27
+ end
28
+
29
+ # Delegates to <<
30
+ def concat(*objects)
31
+ self << objects
32
+ end
33
+
34
+ # Creates a new Document and adds it to the association collection. The
35
+ # document created will be of the same class as the others in the
36
+ # association, and the attributes will be passed into the constructor and
37
+ # the new object will then be saved.
38
+ #
39
+ # Returns the newly created object.
40
+ def create(attributes)
41
+ object = build(attributes)
42
+ object.save
43
+ object
44
+ end
45
+
46
+ # Finds a document in this association.
47
+ # If an id is passed, will return the document for that id.
48
+ def find(*args)
49
+ args[1][:conditions].merge!(@foreign_key.to_sym => @parent.id) if args.size > 1
50
+ @klass.find(*args)
51
+ end
52
+
53
+ # Initializing a related association only requires looking up the objects
54
+ # by their ids.
55
+ #
56
+ # Options:
57
+ #
58
+ # document: The +Document+ that contains the relationship.
59
+ # options: The association +Options+.
60
+ def initialize(document, options, target = nil)
61
+ @parent, @klass = document, options.klass
62
+ @foreign_key = document.class.to_s.foreign_key
63
+ @target = target || @klass.all(:conditions => { @foreign_key => document.id })
64
+ extends(options)
65
+ end
66
+
67
+ # Delegates to <<
68
+ def push(*objects)
69
+ self << objects
70
+ end
71
+
72
+ class << self
73
+ # Preferred method for creating the new +HasManyRelated+ association.
74
+ #
75
+ # Options:
76
+ #
77
+ # document: The +Document+ that contains the relationship.
78
+ # options: The association +Options+.
79
+ def instantiate(document, options, target = nil)
80
+ new(document, options, target)
81
+ end
82
+
83
+ # Returns the macro used to create the association.
84
+ def macro
85
+ :has_many_related
86
+ end
87
+
88
+ # Perform an update of the relationship of the parent and child. This
89
+ # will assimilate the child +Document+ into the parent's object graph.
90
+ #
91
+ # Options:
92
+ #
93
+ # related: The related object
94
+ # parent: The parent +Document+ to update.
95
+ # options: The association +Options+
96
+ #
97
+ # Example:
98
+ #
99
+ # <tt>RelatesToOne.update(game, person, options)</tt>
100
+ def update(target, document, options)
101
+ name = document.class.to_s.underscore
102
+ target.each { |child| child.send("#{name}=", document) }
103
+ instantiate(document, options, target)
104
+ end
105
+ end
106
+
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,95 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Associations #:nodoc:
4
+ class HasOne #:nodoc:
5
+ include Proxy
6
+
7
+ # Build a new object for the association.
8
+ def build(attrs = {}, type = nil)
9
+ @target = attrs.assimilate(@parent, @options, type); self
10
+ end
11
+
12
+ # Creates the new association by finding the attributes in
13
+ # the parent document with its name, and instantiating a
14
+ # new document for it.
15
+ #
16
+ # All method calls on this object will then be delegated
17
+ # to the internal document itself.
18
+ #
19
+ # Options:
20
+ #
21
+ # document: The parent +Document+
22
+ # attributes: The attributes of the target object.
23
+ # options: The association options.
24
+ #
25
+ # Returns:
26
+ #
27
+ # A new +HashOne+ association proxy.
28
+ def initialize(document, attrs, options)
29
+ @parent, @options = document, options
30
+ @target = attrs.assimilate(@parent, @options, attrs.klass)
31
+ extends(options)
32
+ end
33
+
34
+ # Used for setting the association via a nested attributes setter on the
35
+ # parent +Document+. Called when using accepts_nested_attributes_for.
36
+ #
37
+ # Options:
38
+ #
39
+ # attributes: The attributes for the new association
40
+ #
41
+ # Returns:
42
+ #
43
+ # A new target document.
44
+ def nested_build(attributes)
45
+ build(attributes)
46
+ end
47
+
48
+ class << self
49
+ # Preferred method of instantiating a new +HasOne+, since nil values
50
+ # will be handled properly.
51
+ #
52
+ # Options:
53
+ #
54
+ # document: The parent +Document+
55
+ # options: The association options.
56
+ #
57
+ # Returns:
58
+ #
59
+ # A new +HasOne+ association proxy.
60
+ def instantiate(document, options)
61
+ attributes = document.raw_attributes[options.name]
62
+ return nil if attributes.blank?
63
+ new(document, attributes, options)
64
+ end
65
+
66
+ # Returns the macro used to create the association.
67
+ def macro
68
+ :has_one
69
+ end
70
+
71
+ # Perform an update of the relationship of the parent and child. This
72
+ # will assimilate the child +Document+ into the parent's object graph.
73
+ #
74
+ # Options:
75
+ #
76
+ # child: The child +Document+ or +Hash+.
77
+ # parent: The parent +Document+ to update.
78
+ # options: The association +Options+
79
+ #
80
+ # Example:
81
+ #
82
+ # <tt>HasOne.update({:first_name => "Hank"}, person, options)</tt>
83
+ #
84
+ # Returns:
85
+ #
86
+ # A new +HasOne+ association proxy.
87
+ def update(child, parent, options)
88
+ child.assimilate(parent, options)
89
+ instantiate(parent, options)
90
+ end
91
+ end
92
+
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,81 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Associations #:nodoc:
4
+ class HasOneRelated #:nodoc:
5
+ include Proxy
6
+
7
+ delegate :nil?, :to => :target
8
+
9
+ # Builds a new Document and sets it as the association.
10
+ #
11
+ # Returns the newly created object.
12
+ def build(attributes = {})
13
+ @target = @klass.instantiate(attributes)
14
+ name = @parent.class.to_s.underscore
15
+ @target.send("#{name}=", @parent)
16
+ @target
17
+ end
18
+
19
+ # Builds a new Document and sets it as the association, then saves the
20
+ # newly created document.
21
+ #
22
+ # Returns the newly created object.
23
+ def create(attributes)
24
+ build(attributes); @target.save; @target
25
+ end
26
+
27
+ # Initializing a related association only requires looking up the objects
28
+ # by their ids.
29
+ #
30
+ # Options:
31
+ #
32
+ # document: The +Document+ that contains the relationship.
33
+ # options: The association +Options+.
34
+ def initialize(document, options, target = nil)
35
+ @parent, @klass = document, options.klass
36
+ @foreign_key = document.class.to_s.foreign_key
37
+ @target = target || @klass.first(:conditions => { @foreign_key => @parent.id })
38
+ extends(options)
39
+ end
40
+
41
+ class << self
42
+ # Preferred method for creating the new +RelatesToMany+ association.
43
+ #
44
+ # Options:
45
+ #
46
+ # document: The +Document+ that contains the relationship.
47
+ # options: The association +Options+.
48
+ def instantiate(document, options, target = nil)
49
+ new(document, options, target)
50
+ end
51
+
52
+ # Returns the macro used to create the association.
53
+ def macro
54
+ :has_one_related
55
+ end
56
+
57
+ # Perform an update of the relationship of the parent and child. This
58
+ # will assimilate the child +Document+ into the parent's object graph.
59
+ #
60
+ # Options:
61
+ #
62
+ # related: The related object to update.
63
+ # document: The parent +Document+.
64
+ # options: The association +Options+
65
+ #
66
+ # Example:
67
+ #
68
+ # <tt>HasOneToRelated.update(game, person, options)</tt>
69
+ def update(target, document, options)
70
+ if target
71
+ name = document.class.to_s.underscore
72
+ target.send("#{name}=", document)
73
+ return instantiate(document, options, target)
74
+ end
75
+ target
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,57 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Associations #:nodoc:
4
+ class Options #:nodoc:
5
+
6
+ # Create the new +Options+ object, which provides convenience methods for
7
+ # accessing values out of an options +Hash+.
8
+ def initialize(attributes = {})
9
+ @attributes = attributes
10
+ end
11
+
12
+ # Returns the extension if it exists, nil if not.
13
+ def extension
14
+ @attributes[:extend]
15
+ end
16
+
17
+ # Returns true is the options have extensions.
18
+ def extension?
19
+ !extension.nil?
20
+ end
21
+
22
+ # Return the foreign key based off the association name.
23
+ def foreign_key
24
+ name.to_s.foreign_key
25
+ end
26
+
27
+ # Returns the name of the inverse_of association
28
+ def inverse_of
29
+ @attributes[:inverse_of]
30
+ end
31
+
32
+ # Return a +Class+ for the options. If a class_name was provided, then the
33
+ # constantized class_name will be returned. If not, a constant based on the
34
+ # association name will be returned.
35
+ def klass
36
+ class_name = @attributes[:class_name]
37
+ class_name ? class_name.constantize : name.to_s.classify.constantize
38
+ end
39
+
40
+ # Returns the association name of the options.
41
+ def name
42
+ @attributes[:name].to_s
43
+ end
44
+
45
+ # Returns the parent foreign key association name.
46
+ def parent_key
47
+ @attributes[:parent_key]
48
+ end
49
+
50
+ # Returns whether or not this association is polymorphic.
51
+ def polymorphic
52
+ @attributes[:polymorphic] == true
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc
3
+ module Associations #:nodoc
4
+ module Proxy #:nodoc
5
+ def self.included(base)
6
+ base.class_eval do
7
+ instance_methods.each do |method|
8
+ undef_method(method) unless method =~ /(^__|^nil\?$|^send$|^object_id$|^extend$)/
9
+ end
10
+ include InstanceMethods
11
+ end
12
+ end
13
+ module InstanceMethods #:nodoc:
14
+ attr_reader \
15
+ :options,
16
+ :target
17
+
18
+ # Default behavior of method missing should be to delegate all calls
19
+ # to the target of the proxy. This can be overridden in special cases.
20
+ def method_missing(name, *args, &block)
21
+ @target.send(name, *args, &block)
22
+ end
23
+
24
+ # If anonymous extensions are added this will take care of them.
25
+ def extends(options)
26
+ extend Module.new(&options.extension) if options.extension?
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,184 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Attributes
4
+ def self.included(base)
5
+ base.class_eval do
6
+ include InstanceMethods
7
+ extend ClassMethods
8
+ end
9
+ end
10
+ module InstanceMethods
11
+ # Get the id associated with this object. This will pull the _id value out
12
+ # of the attributes +Hash+.
13
+ def id
14
+ @attributes["_id"]
15
+ end
16
+
17
+ # Set the id of the +Document+ to a new one.
18
+ def id=(new_id)
19
+ @attributes["_id"] = new_id
20
+ end
21
+
22
+ alias :_id :id
23
+ alias :_id= :id=
24
+
25
+ # Used for allowing accessor methods for dynamic attributes.
26
+ def method_missing(name, *args)
27
+ attr = name.to_s
28
+ return super unless @attributes.has_key?(attr.reader)
29
+ if attr.writer?
30
+ # "args.size > 1" allows to simulate 1.8 behavior of "*args"
31
+ @attributes[attr.reader] = (args.size > 1) ? args : args.first
32
+ else
33
+ @attributes[attr.reader]
34
+ end
35
+ end
36
+
37
+ # Process the provided attributes casting them to their proper values if a
38
+ # field exists for them on the +Document+. This will be limited to only the
39
+ # attributes provided in the suppied +Hash+ so that no extra nil values get
40
+ # put into the document's attributes.
41
+ def process(attrs = nil)
42
+ (attrs || {}).each_pair do |key, value|
43
+ if set_allowed?(key)
44
+ @attributes[key.to_s] = value
45
+ else
46
+ send("#{key}=", value) if value
47
+ end
48
+ end
49
+ end
50
+
51
+ # Read a value from the +Document+ attributes. If the value does not exist
52
+ # it will return nil.
53
+ #
54
+ # Options:
55
+ #
56
+ # name: The name of the attribute to get.
57
+ #
58
+ # Example:
59
+ #
60
+ # <tt>person.read_attribute(:title)</tt>
61
+ def read_attribute(name)
62
+ access = name.to_s
63
+ fields[access].get(@attributes[access])
64
+ end
65
+
66
+ # Remove a value from the +Document+ attributes. If the value does not exist
67
+ # it will fail gracefully.
68
+ #
69
+ # Options:
70
+ #
71
+ # name: The name of the attribute to remove.
72
+ #
73
+ # Example:
74
+ #
75
+ # <tt>person.remove_attribute(:title)</tt>
76
+ def remove_attribute(name)
77
+ @attributes.delete(name.to_s)
78
+ end
79
+
80
+ # Returns the object type. This corresponds to the name of the class that
81
+ # this +Document+ is, which is used in determining the class to
82
+ # instantiate in various cases.
83
+ def _type
84
+ @attributes["_type"]
85
+ end
86
+
87
+ # Set the type of the +Document+. This should be the name of the class.
88
+ def _type=(new_type)
89
+ @attributes["_type"] = new_type
90
+ end
91
+
92
+ # Write a single attribute to the +Document+ attribute +Hash+. This will
93
+ # also fire the before and after update callbacks, and perform any
94
+ # necessary typecasting.
95
+ #
96
+ # Options:
97
+ #
98
+ # name: The name of the attribute to update.
99
+ # value: The value to set for the attribute.
100
+ #
101
+ # Example:
102
+ #
103
+ # <tt>person.write_attribute(:title, "Mr.")</tt>
104
+ #
105
+ # This will also cause the observing +Document+ to notify it's parent if
106
+ # there is any.
107
+ def write_attribute(name, value)
108
+ access = name.to_s
109
+ @attributes[access] = fields[access].set(value)
110
+ notify unless id.blank?
111
+ end
112
+
113
+ # Writes the supplied attributes +Hash+ to the +Document+. This will only
114
+ # overwrite existing attributes if they are present in the new +Hash+, all
115
+ # others will be preserved.
116
+ #
117
+ # Options:
118
+ #
119
+ # attrs: The +Hash+ of new attributes to set on the +Document+
120
+ #
121
+ # Example:
122
+ #
123
+ # <tt>person.write_attributes(:title => "Mr.")</tt>
124
+ #
125
+ # This will also cause the observing +Document+ to notify it's parent if
126
+ # there is any.
127
+ def write_attributes(attrs = nil)
128
+ process(attrs || {})
129
+ identify if id.blank?
130
+ notify
131
+ end
132
+ alias :attributes= write_attributes
133
+
134
+ protected
135
+ # Return true is dynamic field setting is enabled.
136
+ def set_allowed?(key)
137
+ Humanoid.allow_dynamic_fields && !respond_to?("#{key}=")
138
+ end
139
+
140
+ # Used when supplying a :reject_if block as an option to
141
+ # accepts_nested_attributes_for
142
+ def reject(attributes, options)
143
+ rejector = options[:reject_if]
144
+ if rejector
145
+ attributes.delete_if do |key, value|
146
+ rejector.call(value)
147
+ end
148
+ end
149
+ end
150
+
151
+ end
152
+
153
+ module ClassMethods
154
+ # Defines attribute setters for the associations specified by the names.
155
+ # This will work for a has one or has many association.
156
+ #
157
+ # Example:
158
+ #
159
+ # class Person
160
+ # include Humanoid::Document
161
+ # has_one :name
162
+ # has_many :addresses
163
+ #
164
+ # accepts_nested_attributes_for :name, :addresses
165
+ # end
166
+ def accepts_nested_attributes_for(*args)
167
+ associations = args.flatten
168
+ options = associations.last.is_a?(Hash) ? associations.pop : {}
169
+ associations.each do |name|
170
+ define_method("#{name}_attributes=") do |attrs|
171
+ reject(attrs, options)
172
+ association = send(name)
173
+ if association
174
+ update(association, true)
175
+ association.nested_build(attrs)
176
+ else
177
+ send("build_#{name}", attrs)
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end