mongo_doc_rails2 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
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