tpitale-mongo_mapper 0.6.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +53 -0
  4. data/Rakefile +55 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/lib/mongo_mapper/associations/base.rb +110 -0
  8. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +26 -0
  9. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +21 -0
  10. data/lib/mongo_mapper/associations/collection.rb +19 -0
  11. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +26 -0
  12. data/lib/mongo_mapper/associations/many_documents_proxy.rb +115 -0
  13. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +31 -0
  14. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +54 -0
  15. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  16. data/lib/mongo_mapper/associations/proxy.rb +113 -0
  17. data/lib/mongo_mapper/associations.rb +70 -0
  18. data/lib/mongo_mapper/callbacks.rb +109 -0
  19. data/lib/mongo_mapper/dirty.rb +136 -0
  20. data/lib/mongo_mapper/document.rb +472 -0
  21. data/lib/mongo_mapper/dynamic_finder.rb +74 -0
  22. data/lib/mongo_mapper/embedded_document.rb +384 -0
  23. data/lib/mongo_mapper/finder_options.rb +133 -0
  24. data/lib/mongo_mapper/key.rb +36 -0
  25. data/lib/mongo_mapper/observing.rb +50 -0
  26. data/lib/mongo_mapper/pagination.rb +55 -0
  27. data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
  28. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
  29. data/lib/mongo_mapper/serialization.rb +54 -0
  30. data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
  31. data/lib/mongo_mapper/support.rb +206 -0
  32. data/lib/mongo_mapper/validations.rb +41 -0
  33. data/lib/mongo_mapper.rb +120 -0
  34. data/mongo_mapper.gemspec +173 -0
  35. data/specs.watchr +32 -0
  36. data/test/NOTE_ON_TESTING +1 -0
  37. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
  38. data/test/functional/associations/test_belongs_to_proxy.rb +48 -0
  39. data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
  40. data/test/functional/associations/test_many_documents_proxy.rb +387 -0
  41. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +156 -0
  42. data/test/functional/associations/test_many_embedded_proxy.rb +192 -0
  43. data/test/functional/associations/test_many_polymorphic_proxy.rb +339 -0
  44. data/test/functional/test_associations.rb +44 -0
  45. data/test/functional/test_binary.rb +18 -0
  46. data/test/functional/test_callbacks.rb +85 -0
  47. data/test/functional/test_dirty.rb +159 -0
  48. data/test/functional/test_document.rb +1235 -0
  49. data/test/functional/test_embedded_document.rb +135 -0
  50. data/test/functional/test_logger.rb +20 -0
  51. data/test/functional/test_pagination.rb +95 -0
  52. data/test/functional/test_rails_compatibility.rb +25 -0
  53. data/test/functional/test_string_id_compatibility.rb +72 -0
  54. data/test/functional/test_validations.rb +378 -0
  55. data/test/models.rb +271 -0
  56. data/test/support/custom_matchers.rb +55 -0
  57. data/test/support/timing.rb +16 -0
  58. data/test/test_helper.rb +27 -0
  59. data/test/unit/associations/test_base.rb +166 -0
  60. data/test/unit/associations/test_proxy.rb +91 -0
  61. data/test/unit/serializers/test_json_serializer.rb +189 -0
  62. data/test/unit/test_document.rb +204 -0
  63. data/test/unit/test_dynamic_finder.rb +125 -0
  64. data/test/unit/test_embedded_document.rb +718 -0
  65. data/test/unit/test_finder_options.rb +296 -0
  66. data/test/unit/test_key.rb +172 -0
  67. data/test/unit/test_mongo_mapper.rb +65 -0
  68. data/test/unit/test_observing.rb +101 -0
  69. data/test/unit/test_pagination.rb +113 -0
  70. data/test/unit/test_rails_compatibility.rb +49 -0
  71. data/test/unit/test_serializations.rb +52 -0
  72. data/test/unit/test_support.rb +342 -0
  73. data/test/unit/test_time_zones.rb +40 -0
  74. data/test/unit/test_validations.rb +503 -0
  75. metadata +235 -0
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *~
7
+ *.gem
8
+ tmp
9
+ .yardoc
10
+ doc/*
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 John Nunemaker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,53 @@
1
+ = MongoMapper
2
+
3
+ Awesome gem for modeling your domain and storing it in mongo.
4
+
5
+ Releases are tagged on github and released on gemcutter. Master is pushed to whenever I add a patch or a new feature, but I do not release a new gem version each time I push.
6
+
7
+ == Note on Patches/Pull Requests
8
+
9
+ * Fork the project.
10
+ * Make your feature addition or bug fix.
11
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
12
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself in another branch so I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Install
16
+
17
+ MongoMapper is only released on gemcutter. To install, you can setup gemcutter as your default gem source.
18
+
19
+ $ gem install gemcutter
20
+ $ gem tumble
21
+
22
+ Then you can install MM:
23
+
24
+ $ gem install mongo_mapper
25
+
26
+ You can also just declare the source:
27
+
28
+ $ gem install mongo_mapper -s http://gemcutter.org
29
+
30
+ == Dependencies
31
+
32
+ * ActiveSupport (typically the latest version)
33
+ * Mongo Ruby Driver (mongo)
34
+ * My fork of the validatable gem (jnunemaker-validatable)
35
+
36
+ == Documentation
37
+
38
+ Documentation is lacking right now because if you can't look through the code right now and feel comfortable, this is probably too young for you to use. Wait til it stabilizes a bit more.
39
+
40
+ http://rdoc.info/projects/jnunemaker/mongomapper
41
+ http://groups.google.com/group/mongomapper
42
+
43
+ == More Info
44
+
45
+ You can learn more about mongo here:
46
+ http://www.mongodb.org/
47
+
48
+ You can learn more about the mongo ruby driver here:
49
+ http://github.com/mongodb/mongo-ruby-driver/tree/master
50
+
51
+ == Copyright
52
+
53
+ Copyright (c) 2009 John Nunemaker. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'jeweler'
4
+ require 'yard'
5
+ require 'yard/rake/yardoc_task'
6
+
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "tpitale-mongo_mapper"
9
+ gem.summary = %Q{Awesome gem for modeling your domain and storing it in mongo}
10
+ gem.email = "nunemaker@gmail.com"
11
+ gem.homepage = "http://github.com/jnunemaker/mongomapper"
12
+ gem.authors = ["John Nunemaker"]
13
+
14
+ gem.add_dependency('activesupport', '>= 2.3')
15
+ gem.add_dependency('mongo', '0.18.1')
16
+ gem.add_dependency('jnunemaker-validatable', '1.8.1')
17
+
18
+ gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
19
+ gem.add_development_dependency('shoulda', '2.10.2')
20
+ gem.add_development_dependency('timecop', '0.3.1')
21
+ gem.add_development_dependency('mocha', '0.9.8')
22
+ end
23
+
24
+ Jeweler::GemcutterTasks.new
25
+
26
+ require 'rake/testtask'
27
+ Rake::TestTask.new(:test) do |test|
28
+ test.libs << 'test'
29
+ test.ruby_opts << '-rubygems'
30
+ test.pattern = 'test/**/test_*.rb'
31
+ test.verbose = true
32
+ end
33
+
34
+ namespace :test do
35
+ Rake::TestTask.new(:units) do |test|
36
+ test.libs << 'test'
37
+ test.ruby_opts << '-rubygems'
38
+ test.pattern = 'test/unit/**/test_*.rb'
39
+ test.verbose = true
40
+ end
41
+
42
+ Rake::TestTask.new(:functionals) do |test|
43
+ test.libs << 'test'
44
+ test.ruby_opts << '-rubygems'
45
+ test.pattern = 'test/functional/**/test_*.rb'
46
+ test.verbose = true
47
+ end
48
+ end
49
+
50
+ task :default => :test
51
+ task :test => :check_dependencies
52
+
53
+ YARD::Rake::YardocTask.new(:doc) do |t|
54
+ t.options = ["--legacy"] if RUBY_VERSION < "1.9.0"
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.6.9
data/bin/mmconsole ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
3
+
4
+ begin
5
+ require 'mongo_mapper'
6
+ require 'irb'
7
+ rescue LoadError
8
+ require 'rubygems'
9
+ retry
10
+ end
11
+
12
+ IRB.setup(nil)
13
+ irb = IRB::Irb.new
14
+
15
+ IRB.conf[:MAIN_CONTEXT] = irb.context
16
+
17
+ irb.context.evaluate("require 'irb/completion'", 0)
18
+ irb.context.evaluate(%@
19
+ include MongoMapper
20
+
21
+ MongoMapper.database = "mmtest"
22
+ $db = MongoMapper.database
23
+
24
+ @, 0)
25
+
26
+ puts %@
27
+ Welcome to the MongoMapper Console!
28
+
29
+ Example 1:
30
+ things = $db.collection("things")
31
+ things.insert("name" => "Raw Thing")
32
+ things.insert("name" => "Another Thing", "date" => Time.now)
33
+
34
+ cursor = things.find("name" => "Raw Thing")
35
+ puts cursor.next_object.inspect
36
+
37
+ Example 2:
38
+ class Thing
39
+ include MongoMapper::Document
40
+ key :name, String, :required => true
41
+ key :date, Time
42
+ end
43
+
44
+ thing = Thing.new
45
+ thing.name = "My thing"
46
+ thing.date = Time.now
47
+ thing.save
48
+
49
+ all_things = Thing.all
50
+ puts all_things.map { |object| object.name }.inspect
51
+ @
52
+
53
+ trap("SIGINT") do
54
+ irb.signal_handle
55
+ end
56
+
57
+ catch(:IRB_EXIT) do
58
+ irb.eval_input
59
+ end
60
+
@@ -0,0 +1,110 @@
1
+ module MongoMapper
2
+ module Associations
3
+ # Base class for keeping track of associations.
4
+ #
5
+ # @private
6
+ class Base
7
+ attr_reader :type, :name, :options, :finder_options
8
+
9
+ # Options that should not be considered MongoDB query options/criteria
10
+ AssociationOptions = [:as, :class, :class_name, :dependent, :extend, :foreign_key, :polymorphic]
11
+
12
+ def initialize(type, name, options={}, &extension)
13
+ @type, @name, @options, @finder_options = type, name, {}, {}
14
+ options.symbolize_keys!
15
+
16
+ options[:extend] = modulized_extensions(extension, options[:extend])
17
+
18
+ options.each_pair do |key, value|
19
+ if AssociationOptions.include?(key)
20
+ @options[key] = value
21
+ else
22
+ @finder_options[key] = value
23
+ end
24
+ end
25
+ end
26
+
27
+ def class_name
28
+ @class_name ||= begin
29
+ if cn = options[:class_name]
30
+ cn
31
+ elsif many?
32
+ name.to_s.singularize.camelize
33
+ else
34
+ name.to_s.camelize
35
+ end
36
+ end
37
+ end
38
+
39
+ def klass
40
+ @klass ||= options[:class] || class_name.constantize
41
+ end
42
+
43
+ def many?
44
+ @many_type ||= @type == :many
45
+ end
46
+
47
+ def belongs_to?
48
+ @belongs_to_type ||= @type == :belongs_to
49
+ end
50
+
51
+ def polymorphic?
52
+ !!@options[:polymorphic]
53
+ end
54
+
55
+ def as?
56
+ !!@options[:as]
57
+ end
58
+
59
+ def type_key_name
60
+ @type_key_name ||= many? ? '_type' : "#{as}_type"
61
+ end
62
+
63
+ def as
64
+ @options[:as] || self.name
65
+ end
66
+
67
+ def foreign_key
68
+ @options[:foreign_key] || "#{name}_id"
69
+ end
70
+
71
+ def ivar
72
+ @ivar ||= "@_#{name}"
73
+ end
74
+
75
+ def embeddable?
76
+ many? && klass.embeddable?
77
+ end
78
+
79
+ def proxy_class
80
+ @proxy_class ||= begin
81
+ if many?
82
+ if self.klass.embeddable?
83
+ polymorphic? ? ManyEmbeddedPolymorphicProxy : ManyEmbeddedProxy
84
+ else
85
+ if polymorphic?
86
+ ManyPolymorphicProxy
87
+ elsif as?
88
+ ManyDocumentsAsProxy
89
+ else
90
+ ManyDocumentsProxy
91
+ end
92
+ end
93
+ else
94
+ polymorphic? ? BelongsToPolymorphicProxy : BelongsToProxy
95
+ end
96
+ end # end begin
97
+ end # end proxy_class
98
+
99
+ private
100
+
101
+ # @param [Array<Module, Proc>] extensions a collection of Modules or
102
+ # Procs that extend the behaviour of this association.
103
+ def modulized_extensions(*extensions)
104
+ extensions.flatten.compact.map do |extension|
105
+ Proc === extension ? Module.new(&extension) : extension
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,26 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class BelongsToPolymorphicProxy < Proxy
4
+ def replace(doc)
5
+ if doc
6
+ doc.save if doc.new?
7
+ id, type = doc.id, doc.class.name
8
+ end
9
+
10
+ owner[reflection.foreign_key] = id
11
+ owner[reflection.type_key_name] = type
12
+ reset
13
+ end
14
+
15
+ protected
16
+ def find_target
17
+ return nil if association_class.nil? || owner[reflection.foreign_key].nil?
18
+ association_class.first(:id => owner[reflection.foreign_key])
19
+ end
20
+
21
+ def association_class
22
+ proxy_owner[reflection.type_key_name] ? proxy_owner[reflection.type_key_name].constantize : nil
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class BelongsToProxy < Proxy
4
+ def replace(doc)
5
+ if doc
6
+ doc.save if doc.new?
7
+ id = doc.id
8
+ end
9
+
10
+ owner[reflection.foreign_key] = id
11
+ reset
12
+ end
13
+
14
+ protected
15
+ def find_target
16
+ return nil if owner[reflection.foreign_key].nil?
17
+ klass.first(:id => owner[reflection.foreign_key])
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class Collection < Proxy
4
+ def to_ary
5
+ load_target
6
+ if target.is_a?(Array)
7
+ target.to_ary
8
+ else
9
+ Array(target)
10
+ end
11
+ end
12
+
13
+ def reset
14
+ super
15
+ target = []
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyDocumentsAsProxy < ManyDocumentsProxy
4
+ protected
5
+ def scoped_conditions
6
+ {type_key_name => owner.class.name, id_key_name => owner.id}
7
+ end
8
+
9
+ def apply_scope(doc)
10
+ ensure_owner_saved
11
+ doc[type_key_name] = owner.class.name
12
+ doc[id_key_name] = owner.id
13
+ doc
14
+ end
15
+
16
+ private
17
+ def type_key_name
18
+ "#{options[:as]}_type"
19
+ end
20
+
21
+ def id_key_name
22
+ "#{options[:as]}_id"
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,115 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyDocumentsProxy < Collection
4
+ include ::MongoMapper::Finders
5
+
6
+ def find(*args)
7
+ options = args.extract_options!
8
+ klass.find(*args << scoped_options(options))
9
+ end
10
+
11
+ def find!(*args)
12
+ options = args.extract_options!
13
+ klass.find!(*args << scoped_options(options))
14
+ end
15
+
16
+ def paginate(options)
17
+ klass.paginate(scoped_options(options))
18
+ end
19
+
20
+ def all(options={})
21
+ klass.all(scoped_options(options))
22
+ end
23
+
24
+ def first(options={})
25
+ klass.first(scoped_options(options))
26
+ end
27
+
28
+ def last(options={})
29
+ klass.last(scoped_options(options))
30
+ end
31
+
32
+ def count(options={})
33
+ klass.count(scoped_options(options))
34
+ end
35
+
36
+ def replace(docs)
37
+ load_target
38
+ target.map(&:destroy)
39
+ docs.each { |doc| apply_scope(doc).save }
40
+ reset
41
+ end
42
+
43
+ def <<(*docs)
44
+ ensure_owner_saved
45
+ flatten_deeper(docs).each { |doc| apply_scope(doc).save }
46
+ reset
47
+ end
48
+ alias_method :push, :<<
49
+ alias_method :concat, :<<
50
+
51
+ def build(attrs={})
52
+ doc = klass.new(attrs)
53
+ apply_scope(doc)
54
+ doc
55
+ end
56
+
57
+ def create(attrs={})
58
+ doc = klass.new(attrs)
59
+ apply_scope(doc).save
60
+ doc
61
+ end
62
+
63
+ def create!(attrs={})
64
+ doc = klass.new(attrs)
65
+ apply_scope(doc).save!
66
+ doc
67
+ end
68
+
69
+ def destroy_all(options={})
70
+ all(options).map(&:destroy)
71
+ reset
72
+ end
73
+
74
+ def delete_all(options={})
75
+ klass.delete_all(options.merge(scoped_conditions))
76
+ reset
77
+ end
78
+
79
+ def nullify
80
+ criteria = FinderOptions.new(klass, scoped_conditions).criteria
81
+ all(criteria).each do |doc|
82
+ doc.update_attributes(self.foreign_key => nil)
83
+ end
84
+ reset
85
+ end
86
+
87
+ protected
88
+ def scoped_conditions
89
+ {self.foreign_key => owner.id}
90
+ end
91
+
92
+ def scoped_options(options)
93
+ reflection.finder_options.merge(options).merge(scoped_conditions)
94
+ end
95
+
96
+ def find_target
97
+ all
98
+ end
99
+
100
+ def ensure_owner_saved
101
+ owner.save if owner.new?
102
+ end
103
+
104
+ def apply_scope(doc)
105
+ ensure_owner_saved
106
+ doc[foreign_key] = owner.id
107
+ doc
108
+ end
109
+
110
+ def foreign_key
111
+ options[:foreign_key] || owner.class.name.underscore.gsub("/", "_") + "_id"
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,31 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyEmbeddedPolymorphicProxy < Collection
4
+ def replace(values)
5
+ @_values = values.map do |v|
6
+ if v.kind_of?(EmbeddedDocument)
7
+ v.attributes.merge(reflection.type_key_name => v.class.name)
8
+ else
9
+ v
10
+ end
11
+ end
12
+ reset
13
+ end
14
+
15
+ protected
16
+ def find_target
17
+ (@_values || []).map do |hash|
18
+ polymorphic_class(hash).new(hash)
19
+ end
20
+ end
21
+
22
+ def polymorphic_class(doc)
23
+ if class_name = doc[reflection.type_key_name]
24
+ class_name.constantize
25
+ else
26
+ klass
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,54 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyEmbeddedProxy < Collection
4
+ def replace(values)
5
+ @_values = values.map do |v|
6
+ v.kind_of?(EmbeddedDocument) ? v.attributes : v
7
+ end
8
+ reset
9
+ end
10
+
11
+ def build(attributes={})
12
+ doc = klass.new(attributes)
13
+ assign_root_document(doc)
14
+ self << doc
15
+ doc
16
+ end
17
+
18
+ # TODO: test that both string and oid version work
19
+ def find(id)
20
+ load_target
21
+ target.detect { |item| item.id.to_s == id || item.id == id }
22
+ end
23
+
24
+ def <<(*docs)
25
+ load_target
26
+ docs.each do |doc|
27
+ assign_root_document(doc)
28
+ target << doc
29
+ end
30
+ end
31
+ alias_method :push, :<<
32
+ alias_method :concat, :<<
33
+
34
+ private
35
+ def find_target
36
+ (@_values || []).map do |v|
37
+ child = klass.new(v)
38
+ assign_root_document(child)
39
+ child
40
+ end
41
+ end
42
+
43
+ def root_document
44
+ owner._root_document || owner
45
+ end
46
+
47
+ def assign_root_document(*docs)
48
+ docs.each do |doc|
49
+ doc._root_document = root_document
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,11 @@
1
+ module MongoMapper
2
+ module Associations
3
+ class ManyPolymorphicProxy < ManyDocumentsProxy
4
+ private
5
+ def apply_scope(doc)
6
+ doc[reflection.type_key_name] = doc.class.name
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end