mongodoc 0.0.0

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