mongodoc 0.0.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 (56) hide show
  1. data/.document +5 -0
  2. data/.gitignore +7 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +18 -0
  5. data/Rakefile +80 -0
  6. data/VERSION +1 -0
  7. data/data/.gitignore +2 -0
  8. data/features/mongodoc_base.feature +117 -0
  9. data/features/saving_an_object.feature +17 -0
  10. data/features/step_definitions/collection_steps.rb +14 -0
  11. data/features/step_definitions/connect_steps.rb +4 -0
  12. data/features/step_definitions/document_steps.rb +88 -0
  13. data/features/step_definitions/json_steps.rb +9 -0
  14. data/features/step_definitions/object_steps.rb +43 -0
  15. data/features/step_definitions/util_steps.rb +7 -0
  16. data/features/support/support.rb +9 -0
  17. data/lib/mongodoc.rb +17 -0
  18. data/lib/mongodoc/attributes.rb +97 -0
  19. data/lib/mongodoc/base.rb +163 -0
  20. data/lib/mongodoc/bson.rb +45 -0
  21. data/lib/mongodoc/connection.rb +20 -0
  22. data/lib/mongodoc/ext/array.rb +5 -0
  23. data/lib/mongodoc/ext/binary.rb +7 -0
  24. data/lib/mongodoc/ext/boolean_class.rb +11 -0
  25. data/lib/mongodoc/ext/date.rb +16 -0
  26. data/lib/mongodoc/ext/date_time.rb +13 -0
  27. data/lib/mongodoc/ext/dbref.rb +7 -0
  28. data/lib/mongodoc/ext/hash.rb +7 -0
  29. data/lib/mongodoc/ext/nil_class.rb +5 -0
  30. data/lib/mongodoc/ext/numeric.rb +17 -0
  31. data/lib/mongodoc/ext/object.rb +17 -0
  32. data/lib/mongodoc/ext/object_id.rb +7 -0
  33. data/lib/mongodoc/ext/regexp.rb +5 -0
  34. data/lib/mongodoc/ext/string.rb +5 -0
  35. data/lib/mongodoc/ext/symbol.rb +5 -0
  36. data/lib/mongodoc/ext/time.rb +5 -0
  37. data/lib/mongodoc/parent_proxy.rb +37 -0
  38. data/lib/mongodoc/proxy.rb +76 -0
  39. data/lib/mongodoc/query.rb +7 -0
  40. data/lib/mongodoc/value_equals.rb +8 -0
  41. data/mongod.example.yml +2 -0
  42. data/mongodoc.gemspec +117 -0
  43. data/script/console +8 -0
  44. data/spec/attributes_spec.rb +159 -0
  45. data/spec/base_ext.rb +9 -0
  46. data/spec/base_spec.rb +273 -0
  47. data/spec/bson_matchers.rb +54 -0
  48. data/spec/bson_spec.rb +316 -0
  49. data/spec/connection_spec.rb +81 -0
  50. data/spec/parent_proxy_spec.rb +42 -0
  51. data/spec/query_spec.rb +12 -0
  52. data/spec/spec.opts +2 -0
  53. data/spec/spec_helper.rb +13 -0
  54. data/spec/test_classes.rb +19 -0
  55. data/spec/test_documents.rb +35 -0
  56. metadata +159 -0
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
2
+ require 'cucumber'
3
+ require 'spec/expectations'
4
+ require 'spec/bson_matchers'
5
+ require 'mongodoc'
6
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec', 'test_classes')
7
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec', 'test_documents')
8
+
9
+ World(BsonMatchers)
@@ -0,0 +1,17 @@
1
+ require 'rubygems'
2
+
3
+ gem 'mongo', '0.16'
4
+ gem 'durran-validatable', '1.8.2'
5
+
6
+ require 'mongo'
7
+ require 'activesupport'
8
+ require 'validatable'
9
+
10
+ module MongoDoc
11
+ VERSION = '0.1'
12
+
13
+ class NoConnectionError < RuntimeError; end
14
+ class NoDatabaseError < RuntimeError; end
15
+ end
16
+
17
+ require 'mongodoc/base'
@@ -0,0 +1,97 @@
1
+ require 'mongodoc/proxy'
2
+ require 'mongodoc/parent_proxy'
3
+
4
+ module MongoDoc
5
+ module Document
6
+ module Attributes
7
+ def self.extended(klass)
8
+ klass.class_inheritable_array :_keys
9
+ klass._keys = []
10
+ klass.class_inheritable_array :_associations
11
+ klass._associations = []
12
+
13
+ klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
14
+ attr_accessor :_parent
15
+
16
+ def _root
17
+ @_root
18
+ end
19
+
20
+ def _root=(root)
21
+ @_root = root
22
+ _associations.each do|a|
23
+ association = send(a)
24
+ association._root = root if association
25
+ end
26
+ end
27
+
28
+ def path_to_root(prev)
29
+ return prev unless _parent
30
+ _parent.path_to_root(prev)
31
+ end
32
+ RUBY
33
+ end
34
+
35
+ def _attributes
36
+ _keys + _associations
37
+ end
38
+
39
+ def key(*args)
40
+ args.each do |name|
41
+ _keys << name unless _keys.include?(name)
42
+ attr_accessor name
43
+ end
44
+ end
45
+
46
+ def has_one(*args)
47
+ args.each do |name|
48
+ _associations << name unless _associations.include?(name)
49
+ attr_reader name
50
+
51
+ define_method("#{name}=") do |value|
52
+ if value
53
+ raise NotADocumentError unless Document === value
54
+ value._parent = ParentProxy.new(self, name)
55
+ value._root = _root || self
56
+ end
57
+ instance_variable_set("@#{name}", value)
58
+ end
59
+
60
+ validates_associated name
61
+ end
62
+ end
63
+
64
+ def has_many(*args)
65
+ options = args.extract_options!
66
+ collection_class = if class_name = options.delete(:class_name)
67
+ type_name_with_module(class_name).constantize
68
+ end
69
+
70
+ args.each do |name|
71
+ _associations << name unless _associations.include?(name)
72
+
73
+ define_method("#{name}") do
74
+ association = instance_variable_get("@#{name}")
75
+ unless association
76
+ association = Proxy.new(:root => _root || self, :parent => self, :assoc_name => name, :collection_class => collection_class || self.class.type_name_with_module(name.to_s.classify).constantize)
77
+ instance_variable_set("@#{name}", association)
78
+ end
79
+ association
80
+ end
81
+
82
+ validates_associated name
83
+
84
+ define_method("#{name}=") do |array|
85
+ proxy = send("#{name}")
86
+ proxy.clear
87
+ proxy << array
88
+ end
89
+ end
90
+ end
91
+
92
+ def type_name_with_module(type_name)
93
+ (/^::/ =~ type_name) ? type_name : "#{parents}::#{type_name}"
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,163 @@
1
+ require 'mongodoc/bson'
2
+ require 'mongodoc/connection'
3
+ require 'mongodoc/value_equals'
4
+ require 'mongodoc/query'
5
+ require 'mongodoc/attributes'
6
+
7
+ module MongoDoc
8
+ module Document
9
+ class DocumentInvalidError < RuntimeError; end
10
+ class NotADocumentError < RuntimeError; end
11
+
12
+ def self.included(klass)
13
+ klass.instance_eval do
14
+ extend MongoDoc::Document::Attributes
15
+ extend MongoDoc::Document::BSONCreate
16
+ include MongoDoc::Document::ToBSON
17
+ include MongoDoc::Document::ValueEquals
18
+ include MongoDoc::Document::Identity
19
+ include Validatable
20
+ end
21
+ end
22
+
23
+ module ValueEquals
24
+ def ==(other)
25
+ return false unless self.class === other
26
+ self.class._attributes.all? {|var| self.send(var) == other.send(var)}
27
+ end
28
+ end
29
+
30
+ module Identity
31
+ attr_accessor :_id
32
+ alias :id :_id
33
+ alias :to_param :_id
34
+
35
+ def new_record?
36
+ _id.nil?
37
+ end
38
+ end
39
+
40
+ module ToBSON
41
+ def to_bson(*args)
42
+ {MongoDoc::BSON::CLASS_KEY => self.class.name}.tap do |bson_hash|
43
+ bson_hash['_id'] = _id unless _id.nil?
44
+ self.class._attributes.each do |name|
45
+ bson_hash[name.to_s] = send(name).to_bson(args)
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ module BSONCreate
52
+ def bson_create(bson_hash, options = {})
53
+ new.tap do |obj|
54
+ bson_hash.each do |name, value|
55
+ obj.send("#{name}=", MongoDoc::BSON.decode(value, options))
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ class Base
63
+ include MongoDoc::Document
64
+
65
+ def attributes=(attrs)
66
+ attrs.each do |key, value|
67
+ send("#{key}=", value)
68
+ end
69
+ end
70
+
71
+ def initialize(attrs = {})
72
+ self.attributes = attrs
73
+ end
74
+
75
+ def save(validate = true)
76
+ return _root.save(validate) if _root
77
+ unless validate and not valid?
78
+ _save(false)
79
+ else
80
+ false
81
+ end
82
+ end
83
+
84
+ def save!
85
+ return _root.save! if _root
86
+ if valid?
87
+ _save(true)
88
+ else
89
+ raise DocumentInvalidError
90
+ end
91
+ end
92
+
93
+ def update_attributes(attrs)
94
+ self.attributes = attrs
95
+ return false unless valid?
96
+ _propose_update_attributes(self, path_to_root(attrs), false)
97
+ end
98
+
99
+ def update_attributes!(attrs)
100
+ self.attributes = attrs
101
+ if valid?
102
+ _propose_update_attributes(self, path_to_root(attrs), true)
103
+ else
104
+ raise DocumentInvalidError
105
+ end
106
+ end
107
+
108
+ def self.collection_name
109
+ self.to_s.tableize.gsub('/', '.')
110
+ end
111
+
112
+ def self.collection
113
+ MongoDoc.database.collection(collection_name)
114
+ end
115
+
116
+ def self.count
117
+ collection.count
118
+ end
119
+
120
+ def self.create(attrs = {}, safe = false)
121
+ instance = new(attrs)
122
+ instance.valid? and _create(instance, false)
123
+ end
124
+
125
+ def self.create!(attrs = {})
126
+ instance = new(attrs)
127
+ if instance.valid?
128
+ _create(instance, true)
129
+ else
130
+ raise DocumentInvalidError
131
+ end
132
+ end
133
+
134
+ def self.find_one(id)
135
+ MongoDoc::BSON.decode(collection.find_one(id))
136
+ end
137
+
138
+ protected
139
+
140
+ def _propose_update_attributes(src, attrs, safe)
141
+ if _parent
142
+ _parent.send(:_propose_update_attributes, src, attrs, safe)
143
+ else
144
+ _update_attributes(attrs, safe)
145
+ end
146
+ end
147
+
148
+ def _save(safe)
149
+ self._id = self.class.collection.save(self.to_bson, :safe => safe)
150
+ end
151
+
152
+ def _update_attributes(attrs, safe)
153
+ self.class.collection.update({'_id' => self._id}, MongoDoc::Query.set_modifier(attrs.to_bson), :safe => safe)
154
+ result = MongoDoc.database.db_command({'getlasterror' => 1})
155
+ return (result and result.has_key?('updatedExisting') and result['updatedExisting'])
156
+ end
157
+
158
+ def self._create(instance, safe)
159
+ instance._id = collection.insert(instance.to_bson, :safe => safe)
160
+ instance
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,45 @@
1
+ require 'mongodoc/ext/array'
2
+ require 'mongodoc/ext/binary'
3
+ require 'mongodoc/ext/boolean_class'
4
+ require 'mongodoc/ext/date'
5
+ require 'mongodoc/ext/date_time'
6
+ require 'mongodoc/ext/dbref'
7
+ require 'mongodoc/ext/hash'
8
+ require 'mongodoc/ext/nil_class'
9
+ require 'mongodoc/ext/numeric'
10
+ require 'mongodoc/ext/object'
11
+ require 'mongodoc/ext/object_id'
12
+ require 'mongodoc/ext/regexp'
13
+ require 'mongodoc/ext/string'
14
+ require 'mongodoc/ext/symbol'
15
+ require 'mongodoc/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
@@ -0,0 +1,20 @@
1
+ module MongoDoc
2
+ def self.database(name = nil)
3
+ if name
4
+ @@database = connection.db(name)
5
+ else
6
+ raise NoDatabaseError unless defined? @@database
7
+ @@database
8
+ end
9
+ end
10
+
11
+ def self.connection
12
+ raise NoConnectionError unless defined? @@connection
13
+ @@connection
14
+ end
15
+
16
+ def self.connect(*args)
17
+ opts = args.extract_options!
18
+ @@connection = Mongo::Connection.new(args[0], args[1], opts)
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def to_bson(*args)
3
+ map {|item| item.to_bson(args)}
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ module Mongo
2
+ class Binary
3
+ def to_bson(*args)
4
+ self
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ class FalseClass
2
+ def to_bson(*args)
3
+ self
4
+ end
5
+ end
6
+
7
+ class TrueClass
8
+ def to_bson(*args)
9
+ self
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ class Date
2
+ def to_bson(*args)
3
+ {
4
+ MongoDoc::BSON::CLASS_KEY => self.class.name,
5
+ 'dt' => strftime,
6
+ 'sg' => start
7
+ }
8
+ end
9
+
10
+ alias start sg unless method_defined?(:start)
11
+
12
+ def self.bson_create(bson_hash, options = nil)
13
+ Date.parse(*bson_hash.values_at('dt', 'sg'))
14
+ end
15
+
16
+ end
@@ -0,0 +1,13 @@
1
+ class DateTime
2
+ def to_bson(*args)
3
+ {
4
+ MongoDoc::BSON::CLASS_KEY => self.class.name,
5
+ 'dt' => strftime,
6
+ 'sg' => start
7
+ }
8
+ end
9
+
10
+ def self.bson_create(bson_hash, options = nil)
11
+ DateTime.parse(*bson_hash.values_at('dt', 'sg'))
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module Mongo
2
+ class DBRef
3
+ def to_bson(*args)
4
+ self
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ class Hash
2
+ def to_bson(*args)
3
+ {}.tap do |hash|
4
+ each {|key, value| hash[key.to_s] = value.to_bson}
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class NilClass
2
+ def to_bson(*args)
3
+ self
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ class Numeric
2
+ def to_bson(*args)
3
+ self
4
+ end
5
+ end
6
+
7
+ class Float
8
+ def to_bson(*args)
9
+ self
10
+ end
11
+ end
12
+
13
+ class Integer
14
+ def to_bson(*args)
15
+ self
16
+ end
17
+ end