mongo_doc 0.3.0

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 (122) hide show
  1. data/.document +5 -0
  2. data/.gitignore +7 -0
  3. data/LICENSE +20 -0
  4. data/README.textile +174 -0
  5. data/Rakefile +135 -0
  6. data/TODO +31 -0
  7. data/VERSION +1 -0
  8. data/data/.gitignore +2 -0
  9. data/examples/simple_document.rb +35 -0
  10. data/examples/simple_object.rb +30 -0
  11. data/features/finders.feature +76 -0
  12. data/features/mongodb.yml +7 -0
  13. data/features/mongodoc_base.feature +128 -0
  14. data/features/new_record.feature +36 -0
  15. data/features/partial_updates.feature +105 -0
  16. data/features/removing_documents.feature +68 -0
  17. data/features/saving_an_object.feature +15 -0
  18. data/features/scopes.feature +66 -0
  19. data/features/step_definitions/collection_steps.rb +14 -0
  20. data/features/step_definitions/document_steps.rb +149 -0
  21. data/features/step_definitions/documents.rb +30 -0
  22. data/features/step_definitions/finder_steps.rb +15 -0
  23. data/features/step_definitions/json_steps.rb +9 -0
  24. data/features/step_definitions/object_steps.rb +50 -0
  25. data/features/step_definitions/objects.rb +24 -0
  26. data/features/step_definitions/partial_update_steps.rb +32 -0
  27. data/features/step_definitions/query_steps.rb +54 -0
  28. data/features/step_definitions/removing_documents_steps.rb +14 -0
  29. data/features/step_definitions/scope_steps.rb +18 -0
  30. data/features/step_definitions/util_steps.rb +7 -0
  31. data/features/support/support.rb +10 -0
  32. data/features/using_criteria.feature +128 -0
  33. data/lib/mongo_doc/associations/collection_proxy.rb +105 -0
  34. data/lib/mongo_doc/associations/document_proxy.rb +56 -0
  35. data/lib/mongo_doc/associations/hash_proxy.rb +98 -0
  36. data/lib/mongo_doc/associations/proxy_base.rb +53 -0
  37. data/lib/mongo_doc/attributes.rb +140 -0
  38. data/lib/mongo_doc/bson.rb +45 -0
  39. data/lib/mongo_doc/collection.rb +55 -0
  40. data/lib/mongo_doc/connection.rb +88 -0
  41. data/lib/mongo_doc/contexts/enumerable.rb +128 -0
  42. data/lib/mongo_doc/contexts/ids.rb +41 -0
  43. data/lib/mongo_doc/contexts/mongo.rb +232 -0
  44. data/lib/mongo_doc/contexts.rb +25 -0
  45. data/lib/mongo_doc/criteria.rb +38 -0
  46. data/lib/mongo_doc/cursor.rb +32 -0
  47. data/lib/mongo_doc/document.rb +216 -0
  48. data/lib/mongo_doc/ext/array.rb +5 -0
  49. data/lib/mongo_doc/ext/binary.rb +7 -0
  50. data/lib/mongo_doc/ext/boolean_class.rb +11 -0
  51. data/lib/mongo_doc/ext/date.rb +16 -0
  52. data/lib/mongo_doc/ext/date_time.rb +13 -0
  53. data/lib/mongo_doc/ext/dbref.rb +7 -0
  54. data/lib/mongo_doc/ext/hash.rb +7 -0
  55. data/lib/mongo_doc/ext/nil_class.rb +5 -0
  56. data/lib/mongo_doc/ext/numeric.rb +17 -0
  57. data/lib/mongo_doc/ext/object.rb +17 -0
  58. data/lib/mongo_doc/ext/object_id.rb +7 -0
  59. data/lib/mongo_doc/ext/regexp.rb +5 -0
  60. data/lib/mongo_doc/ext/string.rb +5 -0
  61. data/lib/mongo_doc/ext/symbol.rb +5 -0
  62. data/lib/mongo_doc/ext/time.rb +5 -0
  63. data/lib/mongo_doc/finders.rb +49 -0
  64. data/lib/mongo_doc/matchers.rb +35 -0
  65. data/lib/mongo_doc/query.rb +7 -0
  66. data/lib/mongo_doc/scope.rb +64 -0
  67. data/lib/mongo_doc/validations/macros.rb +11 -0
  68. data/lib/mongo_doc/validations/validates_embedded.rb +13 -0
  69. data/lib/mongo_doc.rb +19 -0
  70. data/lib/mongoid/contexts/paging.rb +42 -0
  71. data/lib/mongoid/criteria.rb +247 -0
  72. data/lib/mongoid/criterion/complex.rb +21 -0
  73. data/lib/mongoid/criterion/exclusion.rb +65 -0
  74. data/lib/mongoid/criterion/inclusion.rb +92 -0
  75. data/lib/mongoid/criterion/optional.rb +136 -0
  76. data/lib/mongoid/extensions/hash/criteria_helpers.rb +20 -0
  77. data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
  78. data/lib/mongoid/matchers/all.rb +11 -0
  79. data/lib/mongoid/matchers/default.rb +26 -0
  80. data/lib/mongoid/matchers/exists.rb +13 -0
  81. data/lib/mongoid/matchers/gt.rb +11 -0
  82. data/lib/mongoid/matchers/gte.rb +11 -0
  83. data/lib/mongoid/matchers/in.rb +11 -0
  84. data/lib/mongoid/matchers/lt.rb +11 -0
  85. data/lib/mongoid/matchers/lte.rb +11 -0
  86. data/lib/mongoid/matchers/ne.rb +11 -0
  87. data/lib/mongoid/matchers/nin.rb +11 -0
  88. data/lib/mongoid/matchers/size.rb +11 -0
  89. data/mongo_doc.gemspec +205 -0
  90. data/mongod.example.yml +2 -0
  91. data/mongodb.example.yml +14 -0
  92. data/perf/mongo_doc_runner.rb +90 -0
  93. data/perf/ruby_driver_runner.rb +64 -0
  94. data/script/console +8 -0
  95. data/spec/associations/collection_proxy_spec.rb +200 -0
  96. data/spec/associations/document_proxy_spec.rb +42 -0
  97. data/spec/associations/hash_proxy_spec.rb +163 -0
  98. data/spec/attributes_spec.rb +273 -0
  99. data/spec/bson_matchers.rb +54 -0
  100. data/spec/bson_spec.rb +196 -0
  101. data/spec/collection_spec.rb +161 -0
  102. data/spec/connection_spec.rb +147 -0
  103. data/spec/contexts/enumerable_spec.rb +274 -0
  104. data/spec/contexts/ids_spec.rb +49 -0
  105. data/spec/contexts/mongo_spec.rb +198 -0
  106. data/spec/contexts_spec.rb +28 -0
  107. data/spec/criteria_spec.rb +33 -0
  108. data/spec/cursor_spec.rb +91 -0
  109. data/spec/document_ext.rb +9 -0
  110. data/spec/document_spec.rb +664 -0
  111. data/spec/embedded_save_spec.rb +109 -0
  112. data/spec/finders_spec.rb +73 -0
  113. data/spec/hash_matchers.rb +27 -0
  114. data/spec/matchers_spec.rb +342 -0
  115. data/spec/mongodb.yml +6 -0
  116. data/spec/mongodb_pairs.yml +8 -0
  117. data/spec/new_record_spec.rb +128 -0
  118. data/spec/query_spec.rb +12 -0
  119. data/spec/scope_spec.rb +79 -0
  120. data/spec/spec.opts +2 -0
  121. data/spec/spec_helper.rb +13 -0
  122. metadata +290 -0
@@ -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,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
+ $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,128 @@
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
@@ -0,0 +1,105 @@
1
+ # Thanks Sandro!
2
+ # http://github.com/sandro
3
+ module MongoDoc
4
+ module Associations
5
+ class CollectionProxy
6
+ include ProxyBase
7
+
8
+ # List of array methods (that are not in +Object+) that need to be
9
+ # delegated to +collection+.
10
+ ARRAY_METHODS = (Array.instance_methods - Object.instance_methods).map { |n| n.to_s }
11
+
12
+ # List of additional methods that must be delegated to +collection+.
13
+ MUST_DEFINE = %w[to_a to_ary inspect to_bson ==]
14
+
15
+ DO_NOT_DEFINE = %w[concat insert replace]
16
+
17
+ (ARRAY_METHODS + MUST_DEFINE - DO_NOT_DEFINE).uniq.each do |method|
18
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
19
+ def #{method}(*args, &block) # def each(*args, &block)
20
+ collection.send(:#{method}, *args, &block) # collection.send(:each, *args, &block)
21
+ end # end
22
+ RUBY
23
+ end
24
+
25
+ attr_reader :collection
26
+
27
+ def _root=(root)
28
+ @_root = root
29
+ collection.each do |item|
30
+ item._root = root if is_document?(item)
31
+ end
32
+ end
33
+
34
+ def initialize(options)
35
+ super
36
+ @collection = []
37
+ end
38
+
39
+ alias _append <<
40
+ def <<(item)
41
+ attach(item)
42
+ _append item
43
+ self
44
+ end
45
+ alias push <<
46
+
47
+ alias add []=
48
+ def []=(index, item)
49
+ attach(item)
50
+ add(index, item)
51
+ end
52
+ alias insert []=
53
+
54
+ def build(attrs)
55
+ item = assoc_class.new(attrs)
56
+ push(item)
57
+ end
58
+
59
+ def concat(array)
60
+ array.each do |item|
61
+ push(item)
62
+ end
63
+ end
64
+
65
+ # Lie about our class. Borrowed from Rake::FileList
66
+ # Note: Does not work for case equality (<tt>===</tt>)
67
+ def is_a?(klass)
68
+ klass == Array || super(klass)
69
+ end
70
+ alias kind_of? is_a?
71
+
72
+ def replace(other)
73
+ clear
74
+ concat(other)
75
+ end
76
+
77
+ alias _unshift unshift
78
+ def unshift(item)
79
+ attach(item)
80
+ _unshift(item)
81
+ end
82
+
83
+ def valid?
84
+ all? do |child|
85
+ if is_document?(child)
86
+ child.valid?
87
+ else
88
+ true
89
+ end
90
+ end
91
+ end
92
+
93
+ protected
94
+
95
+ def annotated_keys(src, attrs)
96
+ assoc_path = "#{assoc_name}.#{index(src)}"
97
+ annotated = {}
98
+ attrs.each do |(key, value)|
99
+ annotated["#{assoc_path}.#{key}"] = value
100
+ end
101
+ annotated
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,56 @@
1
+ module MongoDoc
2
+ module Associations
3
+ class DocumentProxy
4
+ include ProxyBase
5
+
6
+ attr_reader :document
7
+
8
+ delegate :to_bson, :id, :to => :document
9
+
10
+ def _root=(root)
11
+ @_root = root
12
+ document._root = root if is_document?(document)
13
+ end
14
+
15
+ def ==(other)
16
+ if self.class === other
17
+ document == other.document
18
+ else
19
+ document == other
20
+ end
21
+ end
22
+
23
+ def build(attrs)
24
+ item = assoc_class.new(attrs)
25
+ self.document = item
26
+ end
27
+
28
+ def document=(doc)
29
+ attach(doc)
30
+ @document = doc
31
+ end
32
+
33
+ def valid?
34
+ if is_document?(document)
35
+ document.valid?
36
+ else
37
+ true
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def method_missing(method, *args)
44
+ unless document.respond_to?(method)
45
+ raise NoMethodError, "undefined method `#{method.to_s}' for proxied \"#{document}\":#{document.class.to_s}"
46
+ end
47
+
48
+ if block_given?
49
+ document.send(method, *args) { |*block_args| yield(*block_args) }
50
+ else
51
+ document.send(method, *args)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,98 @@
1
+ module MongoDoc
2
+ class InvalidEmbeddedHashKey < RuntimeError; end
3
+
4
+ module Associations
5
+ class HashProxy
6
+ include ProxyBase
7
+
8
+ HASH_METHODS = (Hash.instance_methods - Object.instance_methods).map { |n| n.to_s }
9
+
10
+ MUST_DEFINE = %w[to_a inspect to_bson ==]
11
+
12
+ DO_NOT_DEFINE = %w[merge! replace store update]
13
+
14
+ (HASH_METHODS + MUST_DEFINE - DO_NOT_DEFINE).uniq.each do |method|
15
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
16
+ def #{method}(*args, &block) # def each(*args, &block)
17
+ hash.send(:#{method}, *args, &block) # hash.send(:each, *args, &block)
18
+ end # end
19
+ RUBY
20
+ end
21
+
22
+ attr_reader :hash
23
+
24
+ def _root=(root)
25
+ @_root = root
26
+ hash.each do |key, value|
27
+ value._root = root if is_document?(value)
28
+ end
29
+ end
30
+
31
+ def initialize(options)
32
+ super
33
+ @hash = {}
34
+ end
35
+
36
+ alias put []=
37
+ def []=(key, value)
38
+ raise InvalidEmbeddedHashKey.new("Key name [#{key}] must be a valid element name, see http://www.mongodb.org/display/DOCS/BSON#BSON-noteonelementname") unless valid_key?(key)
39
+ attach(value)
40
+ put(key, value)
41
+ end
42
+ alias store []=
43
+
44
+ def build(key, attrs)
45
+ item = assoc_class.new(attrs)
46
+ store(key, item)
47
+ end
48
+
49
+ # Lie about our class. Borrowed from Rake::FileList
50
+ # Note: Does not work for case equality (<tt>===</tt>)
51
+ def is_a?(klass)
52
+ klass == Hash || super(klass)
53
+ end
54
+ alias kind_of? is_a?
55
+
56
+ def merge!(other)
57
+ other.each_pair do |key, value|
58
+ self[key] = if block_given?
59
+ yield key, [key], value
60
+ else
61
+ value
62
+ end
63
+ end
64
+ end
65
+ alias update merge!
66
+
67
+ def replace(other)
68
+ clear
69
+ merge!(other)
70
+ end
71
+
72
+ def valid?
73
+ values.all? do |child|
74
+ if is_document?(child)
75
+ child.valid?
76
+ else
77
+ true
78
+ end
79
+ end
80
+ end
81
+
82
+ protected
83
+
84
+ def annotated_keys(src, attrs)
85
+ assoc_path = "#{assoc_name}.#{index(src)}"
86
+ annotated = {}
87
+ attrs.each do |(key, value)|
88
+ annotated["#{assoc_path}.#{key}"] = value
89
+ end
90
+ annotated
91
+ end
92
+
93
+ def valid_key?(key)
94
+ (String === key or Symbol === key) and key.to_s !~ /(_id|query|\$.*|.*\..*)/
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,53 @@
1
+ module MongoDoc
2
+ module Associations
3
+ module ProxyBase
4
+ def self.included(klass)
5
+ klass.class_eval do
6
+ attr_reader :assoc_name, :assoc_class, :_parent, :_root
7
+ end
8
+ end
9
+
10
+ def _parent=(parent)
11
+ @_parent = parent
12
+ end
13
+
14
+ def _path_to_root(src, attrs)
15
+ _parent._path_to_root(src, annotated_keys(src, attrs))
16
+ end
17
+
18
+ def _root=(root)
19
+ @_root = root
20
+ end
21
+
22
+ def initialize(options)
23
+ @assoc_name = options[:assoc_name]
24
+ @assoc_class = options[:assoc_class]
25
+ @_root = options[:root]
26
+ @_parent = options[:parent]
27
+ end
28
+
29
+ def attach(item)
30
+ if is_document?(item)
31
+ item._parent = self
32
+ item._root = _root
33
+ _root.send(:register_save_observer, item)
34
+ end
35
+ item
36
+ end
37
+
38
+ protected
39
+
40
+ def annotated_keys(src, hash)
41
+ annotated = {}
42
+ hash.each do |(key, value)|
43
+ annotated["#{assoc_name}.#{key}"] = value
44
+ end
45
+ annotated
46
+ end
47
+
48
+ def is_document?(object)
49
+ object.respond_to?(:_parent)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,140 @@
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 Attributes
8
+ def self.included(klass)
9
+ klass.class_eval do
10
+ class_inheritable_array :_keys
11
+ self._keys = []
12
+ class_inheritable_array :_associations
13
+ self._associations = []
14
+
15
+ attr_accessor :_parent
16
+ attr_accessor :_id
17
+
18
+ extend ClassMethods
19
+ end
20
+ end
21
+
22
+ def _root
23
+ @_root
24
+ end
25
+
26
+ def _root=(root)
27
+ @_root = root
28
+ _associations.each do|a|
29
+ association = send(a)
30
+ association._root = root if association
31
+ end
32
+ end
33
+
34
+ def _path_to_root(src, attrs)
35
+ return attrs unless _parent
36
+ _parent._path_to_root(self, attrs)
37
+ end
38
+
39
+ module ClassMethods
40
+ def _attributes
41
+ _keys + _associations
42
+ end
43
+
44
+ def key(*args)
45
+ args.each do |name|
46
+ _keys << name unless _keys.include?(name)
47
+ attr_accessor name
48
+ end
49
+ end
50
+
51
+ def has_one(*args)
52
+ options = args.extract_options!
53
+ assoc_class = if class_name = options.delete(:class_name)
54
+ self.class_from_name(class_name)
55
+ end
56
+
57
+ args.each do |name|
58
+ _associations << name unless _associations.include?(name)
59
+
60
+ attr_reader name
61
+
62
+ define_method("#{name}=") do |value|
63
+ association = instance_variable_get("@#{name}")
64
+ unless association
65
+ association = Associations::DocumentProxy.new(:root => _root || self, :parent => self, :assoc_name => name, :assoc_class => assoc_class || self.class.class_from_name(name))
66
+ instance_variable_set("@#{name}", association)
67
+ end
68
+ association.document = value
69
+ end
70
+
71
+ validates_embedded name, :if => Proc.new { !send(name).nil? }
72
+ end
73
+ end
74
+
75
+ def has_many(*args)
76
+ options = args.extract_options!
77
+ assoc_class = if class_name = options.delete(:class_name)
78
+ self.class_from_name(class_name)
79
+ end
80
+
81
+ args.each do |name|
82
+ _associations << name unless _associations.include?(name)
83
+
84
+ define_method("#{name}") do
85
+ association = instance_variable_get("@#{name}")
86
+ unless association
87
+ association = Associations::CollectionProxy.new(:root => _root || self, :parent => self, :assoc_name => name, :assoc_class => assoc_class || self.class.class_from_name(name))
88
+ instance_variable_set("@#{name}", association)
89
+ end
90
+ association
91
+ end
92
+
93
+ validates_embedded name
94
+
95
+ define_method("#{name}=") do |arrayish|
96
+ proxy = send("#{name}")
97
+ proxy.clear
98
+ Array.wrap(arrayish).each do|item|
99
+ proxy << item
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ def has_hash(*args)
106
+ options = args.extract_options!
107
+ assoc_class = if class_name = options.delete(:class_name)
108
+ self.class_from_name(class_name)
109
+ end
110
+
111
+ args.each do |name|
112
+ _associations << name unless _associations.include?(name)
113
+
114
+ define_method("#{name}") do
115
+ association = instance_variable_get("@#{name}")
116
+ unless association
117
+ association = Associations::HashProxy.new(:root => _root || self, :parent => self, :assoc_name => name, :assoc_class => assoc_class || self.class.class_from_name(name))
118
+ instance_variable_set("@#{name}", association)
119
+ end
120
+ association
121
+ end
122
+
123
+ validates_embedded name
124
+
125
+ define_method("#{name}=") do |hash|
126
+ send("#{name}").replace(hash)
127
+ end
128
+ end
129
+ end
130
+
131
+ def class_from_name(name)
132
+ type_name_with_module(name.to_s.classify).constantize rescue nil
133
+ end
134
+
135
+ def type_name_with_module(type_name)
136
+ (/^::/ =~ type_name) ? type_name : "#{parent}::#{type_name}"
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,45 @@
1
+ require 'mongo_doc/ext/array'
2
+ require 'mongo_doc/ext/binary'
3
+ require 'mongo_doc/ext/boolean_class'
4
+ require 'mongo_doc/ext/date'
5
+ require 'mongo_doc/ext/date_time'
6
+ require 'mongo_doc/ext/dbref'
7
+ require 'mongo_doc/ext/hash'
8
+ require 'mongo_doc/ext/nil_class'
9
+ require 'mongo_doc/ext/numeric'
10
+ require 'mongo_doc/ext/object'
11
+ require 'mongo_doc/ext/object_id'
12
+ require 'mongo_doc/ext/regexp'
13
+ require 'mongo_doc/ext/string'
14
+ require 'mongo_doc/ext/symbol'
15
+ require 'mongo_doc/ext/time'
16
+
17
+ module MongoDoc
18
+ module BSON
19
+ CLASS_KEY = "json_class"
20
+
21
+ def self.decode(bson, options = {})
22
+ return bson if options[:raw_json]
23
+ case bson
24
+ when Hash
25
+ bson_create(bson, options)
26
+ when Array
27
+ array_create(bson, options)
28
+ else
29
+ bson
30
+ end
31
+ end
32
+
33
+ def self.bson_create(bson_hash, options = {})
34
+ return bson_hash if options[:raw_json]
35
+ klass = bson_hash.delete(CLASS_KEY)
36
+ return bson_hash.each_pair {|key, value| bson_hash[key] = decode(value, options)} unless klass
37
+ klass.constantize.bson_create(bson_hash, options)
38
+ end
39
+
40
+ def self.array_create(bson_array, options = {})
41
+ return bson_array if options[:raw_json]
42
+ bson_array.map {|item| decode(item, options)}
43
+ end
44
+ end
45
+ end