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.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.rdoc +18 -0
- data/Rakefile +80 -0
- data/VERSION +1 -0
- data/data/.gitignore +2 -0
- data/features/mongodoc_base.feature +117 -0
- data/features/saving_an_object.feature +17 -0
- data/features/step_definitions/collection_steps.rb +14 -0
- data/features/step_definitions/connect_steps.rb +4 -0
- data/features/step_definitions/document_steps.rb +88 -0
- data/features/step_definitions/json_steps.rb +9 -0
- data/features/step_definitions/object_steps.rb +43 -0
- data/features/step_definitions/util_steps.rb +7 -0
- data/features/support/support.rb +9 -0
- data/lib/mongodoc.rb +17 -0
- data/lib/mongodoc/attributes.rb +97 -0
- data/lib/mongodoc/base.rb +163 -0
- data/lib/mongodoc/bson.rb +45 -0
- data/lib/mongodoc/connection.rb +20 -0
- data/lib/mongodoc/ext/array.rb +5 -0
- data/lib/mongodoc/ext/binary.rb +7 -0
- data/lib/mongodoc/ext/boolean_class.rb +11 -0
- data/lib/mongodoc/ext/date.rb +16 -0
- data/lib/mongodoc/ext/date_time.rb +13 -0
- data/lib/mongodoc/ext/dbref.rb +7 -0
- data/lib/mongodoc/ext/hash.rb +7 -0
- data/lib/mongodoc/ext/nil_class.rb +5 -0
- data/lib/mongodoc/ext/numeric.rb +17 -0
- data/lib/mongodoc/ext/object.rb +17 -0
- data/lib/mongodoc/ext/object_id.rb +7 -0
- data/lib/mongodoc/ext/regexp.rb +5 -0
- data/lib/mongodoc/ext/string.rb +5 -0
- data/lib/mongodoc/ext/symbol.rb +5 -0
- data/lib/mongodoc/ext/time.rb +5 -0
- data/lib/mongodoc/parent_proxy.rb +37 -0
- data/lib/mongodoc/proxy.rb +76 -0
- data/lib/mongodoc/query.rb +7 -0
- data/lib/mongodoc/value_equals.rb +8 -0
- data/mongod.example.yml +2 -0
- data/mongodoc.gemspec +117 -0
- data/script/console +8 -0
- data/spec/attributes_spec.rb +159 -0
- data/spec/base_ext.rb +9 -0
- data/spec/base_spec.rb +273 -0
- data/spec/bson_matchers.rb +54 -0
- data/spec/bson_spec.rb +316 -0
- data/spec/connection_spec.rb +81 -0
- data/spec/parent_proxy_spec.rb +42 -0
- data/spec/query_spec.rb +12 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/test_classes.rb +19 -0
- data/spec/test_documents.rb +35 -0
- 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)
|
data/lib/mongodoc.rb
ADDED
@@ -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,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
|