mongodoc 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/README.textile +42 -12
  2. data/Rakefile +4 -4
  3. data/TODO +26 -0
  4. data/VERSION +1 -1
  5. data/examples/simple_document.rb +1 -1
  6. data/examples/simple_object.rb +0 -2
  7. data/features/mongodb.yml +6 -5
  8. data/features/removing_documents.feature +68 -0
  9. data/features/step_definitions/collection_steps.rb +3 -3
  10. data/features/step_definitions/document_steps.rb +2 -2
  11. data/features/step_definitions/removing_documents_steps.rb +14 -0
  12. data/features/support/support.rb +2 -2
  13. data/lib/mongodoc.rb +4 -7
  14. data/lib/mongodoc/associations/collection_proxy.rb +103 -0
  15. data/lib/mongodoc/associations/document_proxy.rb +53 -0
  16. data/lib/mongodoc/associations/hash_proxy.rb +96 -0
  17. data/lib/mongodoc/associations/proxy_base.rb +51 -0
  18. data/lib/mongodoc/attributes.rb +49 -17
  19. data/lib/mongodoc/collection.rb +15 -5
  20. data/lib/mongodoc/connection.rb +83 -20
  21. data/lib/mongodoc/criteria.rb +9 -4
  22. data/lib/mongodoc/cursor.rb +9 -3
  23. data/lib/mongodoc/document.rb +37 -24
  24. data/lib/mongodoc/validations/macros.rb +11 -0
  25. data/lib/mongodoc/validations/validates_embedded.rb +13 -0
  26. data/mongodb.example.yml +13 -5
  27. data/mongodoc.gemspec +33 -23
  28. data/spec/associations/collection_proxy_spec.rb +200 -0
  29. data/spec/associations/document_proxy_spec.rb +42 -0
  30. data/spec/associations/hash_proxy_spec.rb +163 -0
  31. data/spec/attributes_spec.rb +113 -47
  32. data/spec/bson_spec.rb +24 -24
  33. data/spec/collection_spec.rb +67 -86
  34. data/spec/connection_spec.rb +98 -150
  35. data/spec/criteria_spec.rb +4 -3
  36. data/spec/cursor_spec.rb +33 -27
  37. data/spec/document_spec.rb +173 -156
  38. data/spec/embedded_save_spec.rb +8 -3
  39. data/spec/new_record_spec.rb +33 -121
  40. metadata +80 -39
  41. data/lib/mongodoc/parent_proxy.rb +0 -44
  42. data/lib/mongodoc/proxy.rb +0 -83
  43. data/spec/parent_proxy_spec.rb +0 -44
  44. data/spec/proxy_spec.rb +0 -80
@@ -0,0 +1,96 @@
1
+ module MongoDoc
2
+ class InvalidEmbeddedHashKey < RuntimeError; end
3
+
4
+ module Associations
5
+ class HashProxy < ProxyBase
6
+ HASH_METHODS = (Hash.instance_methods - Object.instance_methods).map { |n| n.to_s }
7
+
8
+ MUST_DEFINE = %w[to_a inspect to_bson ==]
9
+
10
+ DO_NOT_DEFINE = %w[merge! replace store update]
11
+
12
+ (HASH_METHODS + MUST_DEFINE - DO_NOT_DEFINE).uniq.each do |method|
13
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
14
+ def #{method}(*args, &block) # def each(*args, &block)
15
+ hash.send(:#{method}, *args, &block) # hash.send(:each, *args, &block)
16
+ end # end
17
+ RUBY
18
+ end
19
+
20
+ attr_reader :hash
21
+
22
+ def _root=(root)
23
+ @_root = root
24
+ hash.each do |key, value|
25
+ value._root = root if is_document?(value)
26
+ end
27
+ end
28
+
29
+ def initialize(options)
30
+ super
31
+ @hash = {}
32
+ end
33
+
34
+ alias put []=
35
+ def []=(key, value)
36
+ 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)
37
+ attach(value)
38
+ put(key, value)
39
+ end
40
+ alias store []=
41
+
42
+ def build(key, attrs)
43
+ item = assoc_class.new(attrs)
44
+ store(key, item)
45
+ end
46
+
47
+ # Lie about our class. Borrowed from Rake::FileList
48
+ # Note: Does not work for case equality (<tt>===</tt>)
49
+ def is_a?(klass)
50
+ klass == Hash || super(klass)
51
+ end
52
+ alias kind_of? is_a?
53
+
54
+ def merge!(other)
55
+ other.each_pair do |key, value|
56
+ self[key] = if block_given?
57
+ yield key, [key], value
58
+ else
59
+ value
60
+ end
61
+ end
62
+ end
63
+ alias update merge!
64
+
65
+ def replace(other)
66
+ clear
67
+ merge!(other)
68
+ end
69
+
70
+ def valid?
71
+ values.all? do |child|
72
+ if is_document?(child)
73
+ child.valid?
74
+ else
75
+ true
76
+ end
77
+ end
78
+ end
79
+
80
+ protected
81
+
82
+ def annotated_keys(src, attrs)
83
+ assoc_path = "#{assoc_name}.#{index(src)}"
84
+ annotated = {}
85
+ attrs.each do |(key, value)|
86
+ annotated["#{assoc_path}.#{key}"] = value
87
+ end
88
+ annotated
89
+ end
90
+
91
+ def valid_key?(key)
92
+ (String === key or Symbol === key) and key.to_s !~ /(_id|query|\$.*|.*\..*)/
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,51 @@
1
+ module MongoDoc
2
+ module Associations
3
+ class ProxyBase
4
+ undef_method :id, :to_bson
5
+
6
+ attr_reader :assoc_name, :assoc_class, :_parent, :_root
7
+
8
+ def _parent=(parent)
9
+ @_parent = parent
10
+ end
11
+
12
+ def _path_to_root(src, attrs)
13
+ _parent._path_to_root(src, annotated_keys(src, attrs))
14
+ end
15
+
16
+ def _root=(root)
17
+ @_root = root
18
+ end
19
+
20
+ def initialize(options)
21
+ @assoc_name = options[:assoc_name]
22
+ @assoc_class = options[:assoc_class]
23
+ @_root = options[:root]
24
+ @_parent = options[:parent]
25
+ end
26
+
27
+ def attach(item)
28
+ if is_document?(item)
29
+ item._parent = self
30
+ item._root = _root
31
+ _root.send(:register_save_observer, item)
32
+ end
33
+ item
34
+ end
35
+
36
+ protected
37
+
38
+ def annotated_keys(src, hash)
39
+ annotated = {}
40
+ hash.each do |(key, value)|
41
+ annotated["#{assoc_name}.#{key}"] = value
42
+ end
43
+ annotated
44
+ end
45
+
46
+ def is_document?(object)
47
+ object.respond_to?(:_parent)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,5 +1,7 @@
1
- require 'mongodoc/proxy'
2
- require 'mongodoc/parent_proxy'
1
+ require 'mongodoc/associations/proxy_base'
2
+ require 'mongodoc/associations/collection_proxy'
3
+ require 'mongodoc/associations/document_proxy'
4
+ require 'mongodoc/associations/hash_proxy'
3
5
 
4
6
  module MongoDoc
5
7
  module Attributes
@@ -34,11 +36,6 @@ module MongoDoc
34
36
  _parent._path_to_root(self, attrs)
35
37
  end
36
38
 
37
- def _selector_path_to_root(selector)
38
- return selector unless _parent
39
- _parent._selector_path_to_root(selector)
40
- end
41
-
42
39
  module ClassMethods
43
40
  def _attributes
44
41
  _keys + _associations
@@ -52,27 +49,32 @@ module MongoDoc
52
49
  end
53
50
 
54
51
  def has_one(*args)
52
+ options = args.extract_options!
53
+ assoc_class = if class_name = options.delete(:class_name)
54
+ type_name_with_module(class_name).constantize
55
+ end
56
+
55
57
  args.each do |name|
56
58
  _associations << name unless _associations.include?(name)
59
+
57
60
  attr_reader name
58
61
 
59
62
  define_method("#{name}=") do |value|
60
- if value
61
- raise NotADocumentError unless Document === value
62
- value._parent = ParentProxy.new(self, name)
63
- value._root = _root || self
64
- value._root.register_save_observer(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)
65
67
  end
66
- instance_variable_set("@#{name}", value)
68
+ association.document = value
67
69
  end
68
70
 
69
- validates_associated name
71
+ validates_embedded name, :if => Proc.new { !send(name).nil? }
70
72
  end
71
73
  end
72
74
 
73
75
  def has_many(*args)
74
76
  options = args.extract_options!
75
- collection_class = if class_name = options.delete(:class_name)
77
+ assoc_class = if class_name = options.delete(:class_name)
76
78
  type_name_with_module(class_name).constantize
77
79
  end
78
80
 
@@ -82,13 +84,13 @@ module MongoDoc
82
84
  define_method("#{name}") do
83
85
  association = instance_variable_get("@#{name}")
84
86
  unless association
85
- 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)
87
+ association = Associations::CollectionProxy.new(:root => _root || self, :parent => self, :assoc_name => name, :assoc_class => assoc_class || self.class.type_name_with_module(name.to_s.classify).constantize)
86
88
  instance_variable_set("@#{name}", association)
87
89
  end
88
90
  association
89
91
  end
90
92
 
91
- validates_associated name
93
+ validates_embedded name
92
94
 
93
95
  define_method("#{name}=") do |arrayish|
94
96
  proxy = send("#{name}")
@@ -100,6 +102,36 @@ module MongoDoc
100
102
  end
101
103
  end
102
104
 
105
+ def has_hash(*args)
106
+ options = args.extract_options!
107
+ assoc_class = if class_name = options.delete(:class_name)
108
+ type_name_with_module(class_name).constantize
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.type_name_with_module(name.to_s.classify).constantize)
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
+
103
135
  def type_name_with_module(type_name)
104
136
  (/^::/ =~ type_name) ? type_name : "#{parents}::#{type_name}"
105
137
  end
@@ -3,14 +3,15 @@ require 'mongodoc/cursor'
3
3
  module MongoDoc
4
4
  class Collection
5
5
  attr_accessor :_collection
6
- delegate :[], :clear, :count, :create_index, :db, :drop, :drop_index, :drop_indexes, :group, :hint, :index_information, :name, :options, :remove, :rename, :size, :to => :_collection
6
+
7
+ delegate :[], :clear, :count, :create_index, :db, :distinct, :drop, :drop_index, :drop_indexes, :group, :hint, :index_information, :map_reduce, :mapreduce, :name, :options, :pk_factory, :remove, :rename, :size, :to => :_collection
7
8
 
8
9
  def initialize(name)
9
10
  self._collection = self.class.mongo_collection(name)
10
11
  end
11
12
 
12
13
  def find(query = {}, options = {})
13
- cursor = MongoDoc::Cursor.new(_collection.find(query, options))
14
+ cursor = wrapped_cursor(query, options)
14
15
  if block_given?
15
16
  yield cursor
16
17
  cursor.close
@@ -34,12 +35,21 @@ module MongoDoc
34
35
 
35
36
  def update(spec, doc, options = {})
36
37
  _collection.update(spec, doc.to_bson, options)
37
- result = MongoDoc.database.command({'getlasterror' => 1})
38
- (result and result.has_key?('updatedExisting')) ? result['updatedExisting'] : false
38
+ (last_error || {})['updatedExisting'] || false
39
+ end
40
+
41
+ protected
42
+
43
+ def last_error
44
+ MongoDoc::Connection.database.command({'getlasterror' => 1})
45
+ end
46
+
47
+ def wrapped_cursor(query = {}, options = {})
48
+ MongoDoc::Cursor.new(self, _collection.find(query, options))
39
49
  end
40
50
 
41
51
  def self.mongo_collection(name)
42
- MongoDoc.database.collection(name)
52
+ MongoDoc::Connection.database.collection(name)
43
53
  end
44
54
  end
45
55
  end
@@ -1,25 +1,88 @@
1
1
  module MongoDoc
2
- mattr_accessor :config, :config_path, :connection, :database
3
- self.config_path = './mongodb.yml'
4
-
5
- def self.connect_to_database(name = nil, host = nil, port = nil, options = nil, force_connect = false)
6
- name ||= configuration['name']
7
- self.database = ((!force_connect && connection)|| connect(host, port, options)).db(name)
8
- raise NoDatabaseError unless database
9
- database
10
- end
2
+ class NoConnectionError < RuntimeError; end
3
+ class UnsupportedServerVersionError < RuntimeError; end
11
4
 
12
- def self.connect(*args)
13
- opts = args.extract_options!
14
- host = args[0] || configuration['host'] || configuration['host_pairs']
15
- port = args[1] || configuration['port']
16
- options = opts.empty? ? configuration['options'] || {} : opts
17
- self.connection = Mongo::Connection.new(host, port, options)
18
- raise NoConnectionError unless connection
19
- connection
20
- end
5
+ module Connection
6
+
7
+ extend self
8
+
9
+ attr_writer :config_path, :env, :host, :name, :options, :port, :strict
10
+
11
+ def config_path
12
+ @config_path || default_path
13
+ end
14
+
15
+ def configuration
16
+ @configuration ||= File.exists?(config_path) ? YAML.load_file(config_path)[env] : {}
17
+ end
18
+
19
+ def connection
20
+ @connection ||= connect
21
+ end
22
+
23
+ def database
24
+ @database ||= connection.db(name, :strict => strict)
25
+ end
26
+
27
+ def env
28
+ if rails?
29
+ Rails.env
30
+ else
31
+ @env ||= 'development'
32
+ end
33
+ end
34
+
35
+ def host
36
+ @host ||= configuration['host']
37
+ end
38
+
39
+ def name
40
+ @name ||= configuration['name'] || default_name
41
+ end
42
+
43
+ def options
44
+ @options ||= configuration['options'] || {}
45
+ end
46
+
47
+ def port
48
+ @port ||= configuration['port']
49
+ end
50
+
51
+ def strict
52
+ @strict ||= configuration['strict'] || false
53
+ end
54
+
55
+ private
56
+
57
+ def connect
58
+ connection = Mongo::Connection.new(host, port, options)
59
+ raise NoConnectionError unless connection
60
+ verify_server_version(connection)
61
+ connection
62
+ end
63
+
64
+ def default_name
65
+ if rails?
66
+ "#{Rails.root.basename}_#{Rails.env}"
67
+ else
68
+ "mongodoc"
69
+ end
70
+ end
71
+
72
+ def default_path
73
+ if rails?
74
+ Rails.root + 'config/mongodb.yml'
75
+ else
76
+ './mongodb.yml'
77
+ end
78
+ end
79
+
80
+ def rails?
81
+ Object.const_defined?("Rails")
82
+ end
21
83
 
22
- def self.configuration
23
- self.config ||= File.exists?(config_path || '') ? YAML.load_file(config_path) : {}
84
+ def verify_server_version(connection)
85
+ raise UnsupportedServerVersionError.new('MongoDoc requires at least mongoDB version 1.3.2') unless connection.server_version >= "1.3.2"
86
+ end
24
87
  end
25
88
  end
@@ -101,11 +101,12 @@ module MongoDoc #:nodoc:
101
101
  def each(&block)
102
102
  @collection ||= execute
103
103
  if block_given?
104
- @collection = collection.inject([]) do |container, item|
104
+ container = []
105
+ collection.each do |item|
105
106
  container << item
106
107
  yield item
107
- container
108
108
  end
109
+ @collection = container
109
110
  end
110
111
  self
111
112
  end
@@ -166,9 +167,10 @@ module MongoDoc #:nodoc:
166
167
  #
167
168
  # Returns <tt>self</tt>
168
169
  def criteria(criteria_conditions = {})
169
- criteria_conditions.inject(self) do |criteria, (key, value)|
170
- criteria.send(key, value)
170
+ criteria_conditions.each do |(key, value)|
171
+ send(key, value)
171
172
  end
173
+ self
172
174
  end
173
175
 
174
176
  # Adds a criterion to the +Criteria+ that specifies values that must all
@@ -242,6 +244,9 @@ module MongoDoc #:nodoc:
242
244
  #
243
245
  # Returns: <tt>self</tt>
244
246
  def id(id_or_object_id)
247
+ if id_or_object_id.kind_of?(String)
248
+ id_or_object_id = Mongo::ObjectID.from_string(id_or_object_id)
249
+ end
245
250
  selector[:_id] = id_or_object_id; self
246
251
  end
247
252