mongo_doc_rails2 0.6.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 (142) hide show
  1. data/.document +5 -0
  2. data/.gitignore +8 -0
  3. data/HISTORY.md +11 -0
  4. data/LICENSE +20 -0
  5. data/README.textile +185 -0
  6. data/Rakefile +188 -0
  7. data/TODO +40 -0
  8. data/VERSION +1 -0
  9. data/data/.gitignore +2 -0
  10. data/examples/simple_document.rb +46 -0
  11. data/examples/simple_object.rb +34 -0
  12. data/features/collections.feature +9 -0
  13. data/features/embed_hash.feature +16 -0
  14. data/features/finders.feature +76 -0
  15. data/features/indexes.feature +28 -0
  16. data/features/mongodb.yml +7 -0
  17. data/features/mongodoc_base.feature +128 -0
  18. data/features/new_record.feature +36 -0
  19. data/features/partial_updates.feature +95 -0
  20. data/features/removing_documents.feature +68 -0
  21. data/features/saving_an_object.feature +15 -0
  22. data/features/scopes.feature +66 -0
  23. data/features/step_definitions/collection_steps.rb +17 -0
  24. data/features/step_definitions/document_steps.rb +149 -0
  25. data/features/step_definitions/documents.rb +40 -0
  26. data/features/step_definitions/embed_hash_steps.rb +6 -0
  27. data/features/step_definitions/finder_steps.rb +15 -0
  28. data/features/step_definitions/index_steps.rb +10 -0
  29. data/features/step_definitions/json_steps.rb +9 -0
  30. data/features/step_definitions/object_steps.rb +50 -0
  31. data/features/step_definitions/objects.rb +24 -0
  32. data/features/step_definitions/partial_update_steps.rb +31 -0
  33. data/features/step_definitions/query_steps.rb +66 -0
  34. data/features/step_definitions/removing_documents_steps.rb +14 -0
  35. data/features/step_definitions/scope_steps.rb +18 -0
  36. data/features/step_definitions/string_casting_steps.rb +29 -0
  37. data/features/step_definitions/util_steps.rb +7 -0
  38. data/features/string_casting.feature +10 -0
  39. data/features/support/support.rb +10 -0
  40. data/features/using_criteria.feature +142 -0
  41. data/lib/mongo_doc.rb +12 -0
  42. data/lib/mongo_doc/associations.rb +109 -0
  43. data/lib/mongo_doc/associations/collection_proxy.rb +121 -0
  44. data/lib/mongo_doc/associations/document_proxy.rb +65 -0
  45. data/lib/mongo_doc/associations/hash_proxy.rb +102 -0
  46. data/lib/mongo_doc/associations/proxy_base.rb +48 -0
  47. data/lib/mongo_doc/attributes.rb +84 -0
  48. data/lib/mongo_doc/bson.rb +31 -0
  49. data/lib/mongo_doc/collection.rb +82 -0
  50. data/lib/mongo_doc/connection.rb +88 -0
  51. data/lib/mongo_doc/contexts.rb +31 -0
  52. data/lib/mongo_doc/contexts/ids.rb +41 -0
  53. data/lib/mongo_doc/contexts/mongo.rb +272 -0
  54. data/lib/mongo_doc/criteria.rb +70 -0
  55. data/lib/mongo_doc/cursor.rb +32 -0
  56. data/lib/mongo_doc/document.rb +205 -0
  57. data/lib/mongo_doc/ext.rb +16 -0
  58. data/lib/mongo_doc/ext/array.rb +5 -0
  59. data/lib/mongo_doc/ext/binary.rb +7 -0
  60. data/lib/mongo_doc/ext/boolean_class.rb +17 -0
  61. data/lib/mongo_doc/ext/date.rb +19 -0
  62. data/lib/mongo_doc/ext/date_time.rb +17 -0
  63. data/lib/mongo_doc/ext/dbref.rb +7 -0
  64. data/lib/mongo_doc/ext/hash.rb +7 -0
  65. data/lib/mongo_doc/ext/min_max_keys.rb +13 -0
  66. data/lib/mongo_doc/ext/nil_class.rb +5 -0
  67. data/lib/mongo_doc/ext/numeric.rb +17 -0
  68. data/lib/mongo_doc/ext/object.rb +19 -0
  69. data/lib/mongo_doc/ext/object_id.rb +7 -0
  70. data/lib/mongo_doc/ext/regexp.rb +5 -0
  71. data/lib/mongo_doc/ext/string.rb +5 -0
  72. data/lib/mongo_doc/ext/symbol.rb +5 -0
  73. data/lib/mongo_doc/ext/time.rb +9 -0
  74. data/lib/mongo_doc/finders.rb +38 -0
  75. data/lib/mongo_doc/index.rb +46 -0
  76. data/lib/mongo_doc/matchers.rb +35 -0
  77. data/lib/mongo_doc/root.rb +26 -0
  78. data/lib/mongo_doc/scope.rb +64 -0
  79. data/lib/mongo_doc/validations.rb +12 -0
  80. data/lib/mongo_doc/validations/macros.rb +11 -0
  81. data/lib/mongo_doc/validations/validates_embedded.rb +13 -0
  82. data/lib/mongoid/contexts/enumerable.rb +151 -0
  83. data/lib/mongoid/contexts/paging.rb +42 -0
  84. data/lib/mongoid/criteria.rb +239 -0
  85. data/lib/mongoid/criterion/complex.rb +21 -0
  86. data/lib/mongoid/criterion/exclusion.rb +65 -0
  87. data/lib/mongoid/criterion/inclusion.rb +93 -0
  88. data/lib/mongoid/criterion/optional.rb +136 -0
  89. data/lib/mongoid/extensions/hash/criteria_helpers.rb +20 -0
  90. data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
  91. data/lib/mongoid/matchers/all.rb +11 -0
  92. data/lib/mongoid/matchers/default.rb +26 -0
  93. data/lib/mongoid/matchers/exists.rb +13 -0
  94. data/lib/mongoid/matchers/gt.rb +11 -0
  95. data/lib/mongoid/matchers/gte.rb +11 -0
  96. data/lib/mongoid/matchers/in.rb +11 -0
  97. data/lib/mongoid/matchers/lt.rb +11 -0
  98. data/lib/mongoid/matchers/lte.rb +11 -0
  99. data/lib/mongoid/matchers/ne.rb +11 -0
  100. data/lib/mongoid/matchers/nin.rb +11 -0
  101. data/lib/mongoid/matchers/size.rb +11 -0
  102. data/mongo_doc_rails2.gemspec +237 -0
  103. data/mongod.example.yml +2 -0
  104. data/mongodb.example.yml +14 -0
  105. data/perf/mongo_doc_object.rb +83 -0
  106. data/perf/mongo_document.rb +84 -0
  107. data/perf/ruby_driver.rb +49 -0
  108. data/script/console +8 -0
  109. data/spec/array_including_argument_matcher.rb +62 -0
  110. data/spec/associations/collection_proxy_spec.rb +233 -0
  111. data/spec/associations/document_proxy_spec.rb +45 -0
  112. data/spec/associations/hash_proxy_spec.rb +181 -0
  113. data/spec/associations/proxy_base_spec.rb +92 -0
  114. data/spec/associations_spec.rb +218 -0
  115. data/spec/attributes_accessor_spec.rb +33 -0
  116. data/spec/attributes_spec.rb +145 -0
  117. data/spec/bson_matchers.rb +54 -0
  118. data/spec/bson_spec.rb +196 -0
  119. data/spec/collection_spec.rb +169 -0
  120. data/spec/connection_spec.rb +147 -0
  121. data/spec/contexts/ids_spec.rb +49 -0
  122. data/spec/contexts/mongo_spec.rb +235 -0
  123. data/spec/contexts_spec.rb +56 -0
  124. data/spec/criteria_spec.rb +69 -0
  125. data/spec/cursor_spec.rb +91 -0
  126. data/spec/document_ext.rb +9 -0
  127. data/spec/document_spec.rb +553 -0
  128. data/spec/embedded_save_spec.rb +73 -0
  129. data/spec/ext_spec.rb +89 -0
  130. data/spec/finders_spec.rb +61 -0
  131. data/spec/hash_matchers.rb +27 -0
  132. data/spec/index_spec.rb +79 -0
  133. data/spec/matchers_spec.rb +342 -0
  134. data/spec/mongodb.yml +6 -0
  135. data/spec/mongodb_pairs.yml +8 -0
  136. data/spec/new_record_spec.rb +128 -0
  137. data/spec/root_spec.rb +41 -0
  138. data/spec/scope_spec.rb +79 -0
  139. data/spec/spec.opts +2 -0
  140. data/spec/spec_helper.rb +14 -0
  141. data/spec/validations_spec.rb +30 -0
  142. metadata +346 -0
@@ -0,0 +1,24 @@
1
+ module ValueEquals
2
+ def ==(other)
3
+ return false unless instance_variables.size == other.instance_variables.size
4
+ instance_variables.all? {|var| self.instance_variable_get(var) == other.instance_variable_get(var)}
5
+ end
6
+ end
7
+
8
+ class Movie
9
+ include ValueEquals
10
+
11
+ attr_accessor :title, :director, :writers
12
+ end
13
+
14
+ class Director
15
+ include ValueEquals
16
+
17
+ attr_accessor :name, :awards
18
+ end
19
+
20
+ class AcademyAward
21
+ include ValueEquals
22
+
23
+ attr_accessor :year, :category
24
+ end
@@ -0,0 +1,31 @@
1
+ When /^I update the '(.+)' for '(.+)' to '(.+)'$/ do |attr, doc_name, value|
2
+ doc = instance_variable_get("@#{doc_name}")
3
+ attrs = {attr => value}
4
+ @last_return = doc.update_attributes(attrs)
5
+ end
6
+
7
+ When /^someone else changes the (.+?) '(.+)' of '(.+)' to$/ do |assoc_klass, assoc_name, name, table|
8
+ orig = instance_variable_get("@#{name}")
9
+ doc = orig.class.find_one(orig._id)
10
+ obj = assoc_klass.constantize.new
11
+ table.hashes.each do |hash|
12
+ hash.each do |key, value|
13
+ obj.send("#{key.underscore.gsub(' ', '_')}=", value)
14
+ end
15
+ end
16
+ doc.send("#{assoc_name.underscore.gsub(' ', '_')}=", obj)
17
+ doc.save
18
+ end
19
+
20
+ When /^someone else changes the (.+) of '(.+)':$/ do |assoc_name, name, table|
21
+ orig = instance_variable_get("@#{name}")
22
+ doc = orig.class.find_one(orig._id)
23
+ doc.send(assoc_name).clear
24
+ table.hashes.each do |hash|
25
+ doc.send(assoc_name) << hash.inject({}) do |attrs, (attr, value)|
26
+ attrs["#{attr.underscore.gsub(' ', '_')}"] = value
27
+ attrs
28
+ end
29
+ end
30
+ doc.save
31
+ end
@@ -0,0 +1,66 @@
1
+ def klass(klass_name = nil)
2
+ @klass ||= klass_name.singularize.camelize.constantize
3
+ end
4
+
5
+ def query(klass_name = nil)
6
+ @query ||= klass(klass_name).criteria
7
+ end
8
+
9
+ When "I also want a $number query with criteria $criteria" do |number, criteria|
10
+ instance_variable_set("@#{number}", eval("query.#{criteria}"))
11
+ end
12
+
13
+ Then /^the query result is equal to the document '(.*)'$/ do |name|
14
+ doc = instance_variable_get("@#{name}")
15
+ query.should == doc
16
+ end
17
+
18
+ Then /^one of the query results is the document '(.*)'$/ do |name|
19
+ doc = instance_variable_get("@#{name}")
20
+ query.any? {|d| d == doc}.should be_true
21
+ end
22
+
23
+ Then /^the query result with "(.*)" == "(.*)" has a count of (.*)$/ do |key, value, count|
24
+ query.find {|r| r.has_key?(key) and r[key] == value }['count'].should == count.to_i
25
+ end
26
+
27
+ Then /^the query result with "([^\"]*)" == "([^\"]*)" has the document '(.*)'$/ do |key, value, name|
28
+ doc = instance_variable_get("@#{name}")
29
+ query.find {|r| r.has_key?(key) and r[key] == value }['group'].should include(doc)
30
+ end
31
+
32
+ Then /^the query result has (.*) documents*$/ do |count|
33
+ if query.respond_to?(:size)
34
+ query.size.should == count.to_i
35
+ else
36
+ query.count.should == count.to_i
37
+ end
38
+ end
39
+
40
+ Then /^the (first|last) query result is the document '(.*)'$/ do |position, name|
41
+ doc = instance_variable_get("@#{name}")
42
+ query.entries.send(position).should == doc
43
+ end
44
+
45
+ Then /^the size of the query result is (.*)$/ do |count|
46
+ query.to_a.size.should == count.to_i
47
+ end
48
+
49
+ Then /^the query result is the document '(.*)'$/ do |name|
50
+ object = instance_variable_get("@#{name}")
51
+ if query.kind_of?(Array)
52
+ query.size.should == 1
53
+ query.first.should == object
54
+ else
55
+ query.should == object
56
+ end
57
+ end
58
+
59
+ Then /^the query (is|is not) (empty|blank)$/ do |is, empty|
60
+ query.send("#{empty}?").should == (is == 'is')
61
+ end
62
+
63
+ Then /^the (.+) query (is|is not) (empty|blank)$/ do |number, is, empty|
64
+ instance_variable_get("@#{number}").send("#{empty}?").should == (is == 'is')
65
+ end
66
+
@@ -0,0 +1,14 @@
1
+ When /^I remove '(.+)'$/ do |doc_name|
2
+ doc = instance_variable_get("@#{doc_name}")
3
+ doc.remove
4
+ end
5
+
6
+ Then /^the document '(.+)' is not found$/ do |doc_name|
7
+ doc = instance_variable_get("@#{doc_name}")
8
+ doc.class.find_one(doc.id)
9
+ end
10
+
11
+ Then /^an exception is raised if I remove '(.+)'$/ do |doc_name|
12
+ doc = instance_variable_get("@#{doc_name}")
13
+ lambda { doc.remove }.should raise_error
14
+ end
@@ -0,0 +1,18 @@
1
+ def scope_query=(scope)
2
+ @query = scope
3
+ end
4
+
5
+ When /^I query (.*) with scope '(.*)'$/ do |doc, scope|
6
+ self.scope_query = klass(doc).send(scope)
7
+ end
8
+
9
+ When /^I query (.*) with scopes '(.*)'$/ do |doc, scopes|
10
+ self.scope_query = scopes.split(',').inject(klass(doc)) do |result, scope|
11
+ result.send(scope.strip)
12
+ end
13
+ end
14
+
15
+ When /^I query (.*) with lambda scope '(.*)' with parameters '(.*)'$/ do |doc, scope, params_text|
16
+ params = params_text.split(',').map(&:strip)
17
+ self.scope_query = klass(doc).send(scope, *params)
18
+ end
@@ -0,0 +1,29 @@
1
+ def last
2
+ @last
3
+ end
4
+
5
+ def last=(value)
6
+ @last = value
7
+ end
8
+
9
+ def all
10
+ @all ||= []
11
+ end
12
+
13
+ def all=(value)
14
+ @all = value
15
+ end
16
+
17
+ Given /^a class (.+)$/ do |type_name|
18
+ type_name.constantize.should be_kind_of(Class)
19
+ end
20
+
21
+ Given /^I create an (.+) '(.+)' with:$/ do |klass_name, object_name, table|
22
+ Given "an #{klass_name} document named '#{object_name}' :", table
23
+ end
24
+
25
+ Then /^the object '(.+)' has an attribute '(.+)' of type (.*)$/ do |object_name, attr_name, type_name|
26
+ object = instance_variable_get("@#{object_name}")
27
+ type_name.constantize.should === object.send(attr_name)
28
+ end
29
+
@@ -0,0 +1,7 @@
1
+ Given /^that @last is named '(.*)'$/ do |name|
2
+ instance_variable_set("@#{name}", @last)
3
+ end
4
+
5
+ Then /^I invoke the debugger$/ do
6
+ require 'ruby-debug'; Debugger.start; Debugger.settings[:autoeval] = 1; Debugger.settings[:autolist] = 1; debugger
7
+ end
@@ -0,0 +1,10 @@
1
+ Feature: String casting
2
+
3
+ Background:
4
+ Given a class Event
5
+
6
+ Scenario: Creating a new document
7
+ When I create an Event 'event' with:
8
+ | Name | Venue | Date |
9
+ | NoSQL Live | John Hancock Conference Center | 2010-03-11 |
10
+ Then the object 'event' has an attribute 'date' of type Date
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
2
+ require 'cucumber'
3
+ require 'spec/expectations'
4
+ require 'spec/bson_matchers'
5
+ require 'mongo_doc'
6
+
7
+ MongoDoc::Connection.env = 'cucumber'
8
+ MongoDoc::Connection.config_path = './features/mongodb.yml'
9
+
10
+ World(BsonMatchers)
@@ -0,0 +1,142 @@
1
+ Feature: MongoDoc::Base
2
+
3
+ Background:
4
+ Given an empty Contact document collection
5
+ And a Contact document named 'hashrocket' :
6
+ | Name | Type |
7
+ | Hashrocket | company |
8
+ And 'hashrocket' has interests, an array of:
9
+ | Interest |
10
+ | ruby |
11
+ | rails |
12
+ | employment |
13
+ | contract work |
14
+ | restaurants |
15
+ | hotels |
16
+ | flights |
17
+ | car rentals |
18
+ And 'hashrocket' has many addresses :
19
+ | Street | City | State | Zip Code |
20
+ | 320 First Street North | Jacksonville Beach | FL | 32250 |
21
+ | 1 Lake Michigan Street | Chicago | IL | 60611 |
22
+ | 1 Main Street | Santiago | Chile | |
23
+ And I save the document 'hashrocket'
24
+ And a Contact document named 'rocketeer' :
25
+ | Name |
26
+ | Rocketeer Mike |
27
+ And 'rocketeer' has interests, an array of:
28
+ | Interest |
29
+ | ruby |
30
+ | rails |
31
+ | restaurants |
32
+ | employment |
33
+ And 'rocketeer' has many addresses :
34
+ | Street | City | State | Zip Code |
35
+ | 1 Main Street | Atlantic Beach | FL | 32233 |
36
+ And I save the document 'rocketeer'
37
+ And a Contact document named 'contractor' :
38
+ | Name |
39
+ | Contractor Joe |
40
+ And 'contractor' has interests, an array of:
41
+ | Interest |
42
+ | ruby |
43
+ | rails |
44
+ | contract work |
45
+ | flights |
46
+ | car rentals |
47
+ | hotels |
48
+ | restaurants |
49
+ And 'contractor' has many addresses :
50
+ | Street | City | State | Zip Code |
51
+ | 1 Main St. | Jacksonville | FL | 32218 |
52
+ And I save the document 'contractor'
53
+ And an empty Place document collection
54
+ And a Place document named 'one_ocean' :
55
+ | Name | Type |
56
+ | One Ocean | hotel |
57
+ And 'one_ocean' has one Address as address :
58
+ | Street | City | State | Zip Code |
59
+ | 1 Ocean Street | Atlantic Beach | FL | 32233 |
60
+ And I save the document 'one_ocean'
61
+ And a Place document named 'sea_horse' :
62
+ | Name | Type |
63
+ | Sea Horse | hotel |
64
+ And 'sea_horse' has one Address as address :
65
+ | Street | City | State | Zip Code |
66
+ | 1401 Atlantic Blvd | Neptune Beach | FL | 32266 |
67
+ And I save the document 'sea_horse'
68
+ And a Place document named 'jax' :
69
+ | Name | Type |
70
+ | Jacksonville International Airport | airport |
71
+ And 'jax' has one Address as address :
72
+ | Street | City | State | Zip Code |
73
+ | | Jacksonville | FL | 32218 |
74
+ And I save the document 'jax'
75
+
76
+ Scenario: Counting results
77
+ When I query contacts with criteria all('interests' => ['ruby', 'rails', 'employment'])
78
+ Then the query result has 2 documents
79
+
80
+ Scenario: Finding contacts with interests in ruby and rails
81
+ When I query contacts with criteria all('interests' => ['ruby', 'rails', 'employment'])
82
+ Then one of the query results is the document 'rocketeer'
83
+
84
+ Scenario: Finding contacts with interests in restaurants or hotels
85
+ When I query contacts with criteria in('interests' => ['restaurants', 'hotels'])
86
+ Then one of the query results is the document 'contractor'
87
+
88
+ Scenario: Aggregating Places
89
+ When I query places with criteria only('type').where('address.state' => 'FL').aggregate
90
+ Then the query result with "type" == "hotel" has a count of 2
91
+
92
+ Scenario: Excluding places in Neptune Beach
93
+ When I query places with criteria only('type').where('address.city' => 'Neptune Beach').aggregate
94
+ Then the query result with "type" == "hotel" has a count of 1
95
+
96
+ Scenario: Using extras to limit results
97
+ When I query contacts with criteria all('interests' => ['ruby', 'rails', 'employment']).limit(1)
98
+ Then the size of the query result is 1
99
+
100
+ Scenario: Finding the first result
101
+ When I query contacts with criteria all('interests' => ['ruby', 'rails', 'employment']).first
102
+ Then the query result is equal to the document 'hashrocket'
103
+
104
+ Scenario: Grouping places by type
105
+ When I query places with criteria only('type').where('type' => 'hotel').group
106
+ Then the query result with "type" == "hotel" has the document 'one_ocean'
107
+
108
+ Scenario: Selecting contacts with in operator
109
+ When I query contacts with criteria in('interests' => ['ruby', 'rails', 'employment'])
110
+ Then the query result has 3 documents
111
+
112
+ Scenario: Selecting a contact with the id operator
113
+ When I query contacts with the 'hashrocket' id
114
+ Then the query result has 1 documents
115
+ And the query result is the document 'hashrocket'
116
+
117
+ Scenario: Selecting contacts with not in operator
118
+ When I query contacts with criteria not_in('interests' => ['contract work', 'employment'])
119
+ Then the query result has 0 documents
120
+
121
+ Scenario: Ordering contacts
122
+ When I query contacts with criteria in('interests' => ['ruby', 'rails']).order_by([[:name, :asc]]).entries
123
+ Then the first query result is the document 'contractor'
124
+ And the last query result is the document 'rocketeer'
125
+
126
+ Scenario: Using skip on results
127
+ When I query contacts with criteria all('interests' => ['ruby', 'rails']).skip(1)
128
+ Then the size of the query result is 2
129
+
130
+ Scenario: Is a criteria blank?
131
+ When I query contacts with criteria all('interests' => ['wii'])
132
+ Then the query is blank
133
+
134
+ Scenario: Is a criteria empty?
135
+ When I query contacts with criteria all('interests' => ['ruby'])
136
+ Then the query is not empty
137
+
138
+ Scenario: Criteria return a new criteria
139
+ When I query contacts with criteria all('interests' => ['ruby'])
140
+ And I also want a second query with criteria where('addresses.state' => 'HI')
141
+ Then the query is not empty
142
+ And the second query is empty
@@ -0,0 +1,12 @@
1
+ require 'mongo'
2
+ require 'active_support'
3
+ require 'validatable'
4
+ require 'will_paginate/collection'
5
+
6
+ module MongoDoc
7
+ VERSION = '0.6.1'
8
+ end
9
+
10
+ require 'mongo_doc/connection'
11
+ require 'mongo_doc/collection'
12
+ require 'mongo_doc/document'
@@ -0,0 +1,109 @@
1
+ require 'mongo_doc/associations/proxy_base'
2
+ require 'mongo_doc/associations/collection_proxy'
3
+ require 'mongo_doc/associations/document_proxy'
4
+ require 'mongo_doc/associations/hash_proxy'
5
+
6
+ module MongoDoc
7
+ module Associations
8
+
9
+ def embed(*args)
10
+ options = args.extract_options!
11
+ assoc_class = if class_name = options.delete(:class_name)
12
+ self.class_from_name(class_name)
13
+ end
14
+
15
+ args.each do |name|
16
+ _associations << name unless _associations.include?(name)
17
+
18
+ attr_reader name
19
+
20
+ define_method("#{name}=") do |value|
21
+ association = instance_variable_get("@#{name}")
22
+ unless association
23
+ association = Associations::DocumentProxy.new(:path => _selector_path,
24
+ :root => _root || self,
25
+ :assoc_name => name,
26
+ :assoc_class => assoc_class || self.class.class_from_name(name))
27
+ instance_variable_set("@#{name}", association)
28
+ end
29
+ association.document = value
30
+ end
31
+
32
+ validates_embedded name, :if => Proc.new { !send(name).nil? }
33
+ end
34
+ end
35
+ alias has_one embed
36
+
37
+ def embed_many(*args)
38
+ options = args.extract_options!
39
+ assoc_class = if class_name = options.delete(:class_name)
40
+ self.class_from_name(class_name)
41
+ end
42
+
43
+ args.each do |name|
44
+ _associations << name unless _associations.include?(name)
45
+
46
+ define_method("#{name}") do
47
+ association = instance_variable_get("@#{name}")
48
+ unless association
49
+ association = Associations::CollectionProxy.new(:path => _selector_path,
50
+ :root => _root || self,
51
+ :assoc_name => name,
52
+ :assoc_class => assoc_class || self.class.class_from_name(name))
53
+ instance_variable_set("@#{name}", association)
54
+ end
55
+ association
56
+ end
57
+
58
+ validates_embedded name
59
+
60
+ define_method("#{name}=") do |arrayish|
61
+ proxy = send("#{name}")
62
+ proxy.clear
63
+ Array.wrap(arrayish).each do|item|
64
+ proxy << item
65
+ end
66
+ end
67
+ end
68
+ end
69
+ alias has_many embed_many
70
+
71
+ def embed_hash(*args)
72
+ options = args.extract_options!
73
+ assoc_class = if class_name = options.delete(:class_name)
74
+ self.class_from_name(class_name)
75
+ end
76
+
77
+ args.each do |name|
78
+ _associations << name unless _associations.include?(name)
79
+
80
+ define_method("#{name}") do
81
+ association = instance_variable_get("@#{name}")
82
+ unless association
83
+ association = Associations::HashProxy.new(:path => _selector_path,
84
+ :root => _root || self,
85
+ :assoc_name => name,
86
+ :assoc_class => assoc_class || self.class.class_from_name(name))
87
+ instance_variable_set("@#{name}", association)
88
+ end
89
+ association
90
+ end
91
+
92
+ validates_embedded name
93
+
94
+ define_method("#{name}=") do |hash|
95
+ send("#{name}").replace(hash)
96
+ end
97
+ end
98
+ end
99
+ alias has_hash embed_hash
100
+
101
+ def class_from_name(name)
102
+ type_name_with_module(name.to_s.classify).constantize rescue nil
103
+ end
104
+
105
+ def type_name_with_module(type_name)
106
+ (/^::/ =~ type_name) ? type_name : "#{parent}::#{type_name}"
107
+ end
108
+ end
109
+ end