jnunemaker-mongomapper 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/History +13 -0
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/mongomapper.rb +20 -6
- data/lib/mongomapper/associations.rb +69 -0
- data/lib/mongomapper/associations/array_proxy.rb +6 -0
- data/lib/mongomapper/associations/base.rb +50 -0
- data/lib/mongomapper/associations/belongs_to_proxy.rb +26 -0
- data/lib/mongomapper/associations/has_many_embedded_proxy.rb +19 -0
- data/lib/mongomapper/associations/has_many_proxy.rb +28 -0
- data/lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb +31 -0
- data/lib/mongomapper/associations/proxy.rb +60 -0
- data/lib/mongomapper/callbacks.rb +106 -0
- data/lib/mongomapper/document.rb +83 -54
- data/lib/mongomapper/embedded_document.rb +61 -91
- data/lib/mongomapper/finder_options.rb +37 -35
- data/lib/mongomapper/key.rb +11 -10
- data/lib/mongomapper/observing.rb +90 -0
- data/lib/mongomapper/rails_compatibility.rb +5 -2
- data/lib/mongomapper/save_with_validation.rb +6 -36
- data/lib/mongomapper/validations.rb +47 -0
- data/mongomapper.gemspec +18 -5
- data/test/test_associations.rb +121 -24
- data/test/test_callbacks.rb +3 -6
- data/test/test_document.rb +20 -14
- data/test/test_embedded_document.rb +2 -3
- data/test/test_finder_options.rb +37 -22
- data/test/test_key.rb +30 -30
- data/test/test_observing.rb +101 -0
- data/test/test_rails_compatibility.rb +8 -3
- data/test/test_validations.rb +193 -22
- metadata +16 -3
data/.gitignore
CHANGED
data/History
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
0.2.0 7/7/2009
|
2
|
+
* 2 major additions (observers, associations), several minor additions, and a few bug fixes
|
3
|
+
* Added observers
|
4
|
+
* many now supports embedded docs or docs in another collection (dcu on github)
|
5
|
+
* added belongs_to association (dcu)
|
6
|
+
* added validates_uniqueness_of (dcu)
|
7
|
+
* added :unique key shortcut to add validates_uniqueness_of automatically
|
8
|
+
* now tracking descendants of document (dcu)
|
9
|
+
* added validates_exclusion_of and validates_inclusion_of
|
10
|
+
* Bumped required version of validatable for callback fixes
|
11
|
+
* More thorough use of converting find conditions and options to mongo speak
|
12
|
+
* #attributes= no longer bombs when given nil
|
13
|
+
|
1
14
|
0.1.2 7/3/2009
|
2
15
|
* 2 minor changes
|
3
16
|
* Straightened out callbacks and added validate, validate_on_create and validate_on_update.
|
data/Rakefile
CHANGED
@@ -13,7 +13,7 @@ begin
|
|
13
13
|
|
14
14
|
gem.add_dependency('activesupport')
|
15
15
|
gem.add_dependency('mongodb-mongo', '0.9')
|
16
|
-
gem.add_dependency('jnunemaker-validatable', '1.7.
|
16
|
+
gem.add_dependency('jnunemaker-validatable', '1.7.1')
|
17
17
|
|
18
18
|
gem.add_development_dependency('mocha', '0.9.4')
|
19
19
|
gem.add_development_dependency('jnunemaker-matchy', '0.4.0')
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/mongomapper.rb
CHANGED
@@ -3,7 +3,7 @@ require 'rubygems'
|
|
3
3
|
|
4
4
|
gem 'activesupport'
|
5
5
|
gem 'mongodb-mongo', '0.9'
|
6
|
-
gem 'jnunemaker-validatable', '1.7.
|
6
|
+
gem 'jnunemaker-validatable', '1.7.1'
|
7
7
|
|
8
8
|
require 'activesupport'
|
9
9
|
require 'mongo'
|
@@ -16,6 +16,20 @@ require dir + 'finder_options'
|
|
16
16
|
require dir + 'rails_compatibility'
|
17
17
|
require dir + 'save_with_validation'
|
18
18
|
require dir + 'serialization'
|
19
|
+
require dir + 'callbacks'
|
20
|
+
require dir + 'observing'
|
21
|
+
require dir + 'validations'
|
22
|
+
|
23
|
+
require dir + 'associations/proxy'
|
24
|
+
require dir + 'associations/array_proxy'
|
25
|
+
require dir + 'associations/base'
|
26
|
+
|
27
|
+
require dir + 'associations/has_many_proxy'
|
28
|
+
require dir + 'associations/has_many_embedded_proxy'
|
29
|
+
require dir + 'associations/belongs_to_proxy'
|
30
|
+
require dir + 'associations/polymorphic_belongs_to_proxy'
|
31
|
+
require dir + 'associations'
|
32
|
+
|
19
33
|
require dir + 'embedded_document'
|
20
34
|
require dir + 'document'
|
21
35
|
|
@@ -27,20 +41,20 @@ module MongoMapper
|
|
27
41
|
super("Validation failed: #{@document.errors.full_messages.join(", ")}")
|
28
42
|
end
|
29
43
|
end
|
30
|
-
|
44
|
+
|
31
45
|
def self.connection
|
32
46
|
@@connection ||= XGen::Mongo::Driver::Mongo.new
|
33
47
|
end
|
34
|
-
|
48
|
+
|
35
49
|
def self.connection=(new_connection)
|
36
50
|
@@connection = new_connection
|
37
51
|
end
|
38
|
-
|
52
|
+
|
39
53
|
def self.database=(name)
|
40
54
|
@@database = MongoMapper.connection.db(name)
|
41
55
|
end
|
42
|
-
|
56
|
+
|
43
57
|
def self.database
|
44
58
|
@@database
|
45
59
|
end
|
46
|
-
end
|
60
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
module ClassMethods
|
4
|
+
def belongs_to(association_id, options = {})
|
5
|
+
association = create_association(:belongs_to, association_id, options)
|
6
|
+
|
7
|
+
ref_id = "#{association_id}_id"
|
8
|
+
key ref_id, String
|
9
|
+
|
10
|
+
define_method("#{ref_id}=") do |value|
|
11
|
+
write_attribute(ref_id, value)
|
12
|
+
end
|
13
|
+
|
14
|
+
if options[:polymorphic]
|
15
|
+
ref_type = "#{association_id}_type"
|
16
|
+
key ref_type, String
|
17
|
+
|
18
|
+
define_method("#{ref_type}=") do |value|
|
19
|
+
write_attribute(ref_type, value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
define_association_methods(association)
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def many(association_id, options = {})
|
29
|
+
association = create_association(:many, association_id, options)
|
30
|
+
define_association_methods(association)
|
31
|
+
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def associations
|
36
|
+
@associations ||= HashWithIndifferentAccess.new
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def create_association(type, name, options)
|
41
|
+
association = Associations::Base.new(type, name, options)
|
42
|
+
associations[association.name] = association
|
43
|
+
association
|
44
|
+
end
|
45
|
+
|
46
|
+
def define_association_methods(association)
|
47
|
+
define_method(association.name) do
|
48
|
+
get_proxy(association)
|
49
|
+
end
|
50
|
+
|
51
|
+
define_method("#{association.name}=") do |value|
|
52
|
+
get_proxy(association).replace(value)
|
53
|
+
value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module InstanceMethods
|
59
|
+
def get_proxy(association)
|
60
|
+
proxy = self.instance_variable_get(association.ivar)
|
61
|
+
if proxy.nil?
|
62
|
+
proxy = association.proxy_class.new(self, association)
|
63
|
+
self.instance_variable_set(association.ivar, proxy)
|
64
|
+
end
|
65
|
+
proxy
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class Base
|
4
|
+
attr_reader :type, :name, :options
|
5
|
+
|
6
|
+
def initialize(type, name, options = {})
|
7
|
+
@options = options
|
8
|
+
@type = type
|
9
|
+
@name = name
|
10
|
+
end
|
11
|
+
|
12
|
+
def klass
|
13
|
+
class_name.constantize
|
14
|
+
end
|
15
|
+
|
16
|
+
def class_name
|
17
|
+
@class_name ||= begin
|
18
|
+
if cn = options[:class_name]
|
19
|
+
cn
|
20
|
+
elsif @type == :many
|
21
|
+
name.to_s.singularize.camelize
|
22
|
+
else
|
23
|
+
name.to_s.camelize
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def ivar
|
29
|
+
@ivar ||= "@_#{name}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def proxy_class
|
33
|
+
case @type
|
34
|
+
when :belongs_to
|
35
|
+
if @options[:polymorphic]
|
36
|
+
PolymorphicBelongsToProxy
|
37
|
+
else
|
38
|
+
BelongsToProxy
|
39
|
+
end
|
40
|
+
when :many
|
41
|
+
if self.klass.embeddable?
|
42
|
+
HasManyEmbeddedProxy
|
43
|
+
else
|
44
|
+
HasManyProxy
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class BelongsToProxy < Proxy
|
4
|
+
def replace(v)
|
5
|
+
ref_id = "#{@association.name}_id"
|
6
|
+
|
7
|
+
if v
|
8
|
+
v.save if v.new?
|
9
|
+
@owner.__send__(:write_attribute, ref_id, v.id)
|
10
|
+
else
|
11
|
+
@owner.__send__(:write_attribute, ref_id, nil)
|
12
|
+
end
|
13
|
+
|
14
|
+
reload_target
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
def find_target
|
19
|
+
ref = @owner.__send__(:read_attribute, "#{@association.name}_id")
|
20
|
+
if ref
|
21
|
+
@association.klass.find(ref)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class HasManyEmbeddedProxy < ArrayProxy
|
4
|
+
def replace(v)
|
5
|
+
@_values = v.map { |e| e.kind_of?(EmbeddedDocument) ? e.attributes : e }
|
6
|
+
@target = nil
|
7
|
+
|
8
|
+
reload_target
|
9
|
+
end
|
10
|
+
|
11
|
+
protected
|
12
|
+
def find_target
|
13
|
+
(@_values || []).map do |e|
|
14
|
+
@association.klass.new(e)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class HasManyProxy < ArrayProxy
|
4
|
+
def replace(v)
|
5
|
+
if load_target
|
6
|
+
@target.map(&:destroy)
|
7
|
+
end
|
8
|
+
|
9
|
+
v.each do |o|
|
10
|
+
@owner.save if @owner.new?
|
11
|
+
o.__send__(:write_attribute, self.foreign_key, @owner.id)
|
12
|
+
o.save
|
13
|
+
o
|
14
|
+
end
|
15
|
+
reload_target
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
def find_target
|
20
|
+
@association.klass.find(:all, {:conditions => {self.foreign_key => @owner.id}})
|
21
|
+
end
|
22
|
+
|
23
|
+
def foreign_key
|
24
|
+
@association.options[:foreign_key] || @owner.class.name.underscore.gsub("/", "_") + "_id"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class PolymorphicBelongsToProxy < Proxy
|
4
|
+
def replace(v)
|
5
|
+
ref_id = "#{@association.name}_id"
|
6
|
+
ref_type = "#{@association.name}_type"
|
7
|
+
|
8
|
+
if v
|
9
|
+
v.save if v.new?
|
10
|
+
@owner.__send__(:write_attribute, ref_id, v.id)
|
11
|
+
@owner.__send__(:write_attribute, ref_type, v.class.name)
|
12
|
+
else
|
13
|
+
@owner.__send__(:write_attribute, ref_id, nil)
|
14
|
+
@owner.__send__(:write_attribute, ref_type, nil)
|
15
|
+
end
|
16
|
+
@owner.save
|
17
|
+
|
18
|
+
reload_target
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
def find_target
|
23
|
+
ref_id = @owner.__send__(:read_attribute, "#{@association.name}_id")
|
24
|
+
ref_type = @owner.__send__(:read_attribute, "#{@association.name}_type")
|
25
|
+
if ref_id && ref_type
|
26
|
+
ref_type.constantize.find(ref_id)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Associations
|
3
|
+
class Proxy
|
4
|
+
attr_reader :owner, :association
|
5
|
+
|
6
|
+
instance_methods.each do |m|
|
7
|
+
undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(owner, association)
|
11
|
+
@owner= owner
|
12
|
+
@association = association
|
13
|
+
|
14
|
+
reset
|
15
|
+
end
|
16
|
+
|
17
|
+
def respond_to?(*methods)
|
18
|
+
(load_target && @target.respond_to?(*methods))
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset
|
22
|
+
@target = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def reload_target
|
26
|
+
reset
|
27
|
+
load_target
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def send(method, *args)
|
32
|
+
load_target
|
33
|
+
@target.send(method, *args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace(v)
|
37
|
+
raise NotImplementedError
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
def method_missing(method, *args)
|
42
|
+
if load_target
|
43
|
+
if block_given?
|
44
|
+
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
45
|
+
else
|
46
|
+
@target.send(method, *args)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def load_target
|
52
|
+
@target ||= find_target
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_target
|
56
|
+
raise NotImplementedError
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Callbacks
|
3
|
+
def self.included(model) #:nodoc:
|
4
|
+
model.class_eval do
|
5
|
+
extend Observable
|
6
|
+
include ActiveSupport::Callbacks
|
7
|
+
|
8
|
+
define_callbacks *%w(
|
9
|
+
before_save after_save before_create after_create before_update after_update before_validation
|
10
|
+
after_validation before_validation_on_create after_validation_on_create before_validation_on_update
|
11
|
+
after_validation_on_update before_destroy after_destroy
|
12
|
+
)
|
13
|
+
|
14
|
+
[:create_or_update, :valid?, :create, :update, :destroy].each do |method|
|
15
|
+
alias_method_chain method, :callbacks
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def before_save() end
|
21
|
+
|
22
|
+
def after_save() end
|
23
|
+
def create_or_update_with_callbacks #:nodoc:
|
24
|
+
return false if callback(:before_save) == false
|
25
|
+
if result = create_or_update_without_callbacks
|
26
|
+
callback(:after_save)
|
27
|
+
end
|
28
|
+
result
|
29
|
+
end
|
30
|
+
private :create_or_update_with_callbacks
|
31
|
+
|
32
|
+
def before_create() end
|
33
|
+
|
34
|
+
def after_create() end
|
35
|
+
def create_with_callbacks #:nodoc:
|
36
|
+
return false if callback(:before_create) == false
|
37
|
+
result = create_without_callbacks
|
38
|
+
callback(:after_create)
|
39
|
+
result
|
40
|
+
end
|
41
|
+
private :create_with_callbacks
|
42
|
+
|
43
|
+
def before_update() end
|
44
|
+
|
45
|
+
def after_update() end
|
46
|
+
|
47
|
+
def update_with_callbacks(*args) #:nodoc:
|
48
|
+
return false if callback(:before_update) == false
|
49
|
+
result = update_without_callbacks(*args)
|
50
|
+
callback(:after_update)
|
51
|
+
result
|
52
|
+
end
|
53
|
+
private :update_with_callbacks
|
54
|
+
|
55
|
+
def before_validation() end
|
56
|
+
|
57
|
+
def after_validation() end
|
58
|
+
|
59
|
+
def before_validation_on_create() end
|
60
|
+
|
61
|
+
def after_validation_on_create() end
|
62
|
+
|
63
|
+
def before_validation_on_update() end
|
64
|
+
|
65
|
+
def after_validation_on_update() end
|
66
|
+
|
67
|
+
def valid_with_callbacks? #:nodoc:
|
68
|
+
return false if callback(:before_validation) == false
|
69
|
+
result = new? ? callback(:before_validation_on_create) : callback(:before_validation_on_update)
|
70
|
+
return false if false == result
|
71
|
+
|
72
|
+
result = valid_without_callbacks?
|
73
|
+
callback(:after_validation)
|
74
|
+
|
75
|
+
new? ? callback(:after_validation_on_create) : callback(:after_validation_on_update)
|
76
|
+
return result
|
77
|
+
end
|
78
|
+
|
79
|
+
def before_destroy() end
|
80
|
+
|
81
|
+
def after_destroy() end
|
82
|
+
def destroy_with_callbacks #:nodoc:
|
83
|
+
return false if callback(:before_destroy) == false
|
84
|
+
result = destroy_without_callbacks
|
85
|
+
callback(:after_destroy)
|
86
|
+
result
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
def callback(method)
|
91
|
+
result = run_callbacks(method) { |result, object| false == result }
|
92
|
+
|
93
|
+
if result != false && respond_to_without_attributes?(method)
|
94
|
+
result = send(method)
|
95
|
+
end
|
96
|
+
|
97
|
+
notify(method)
|
98
|
+
return result
|
99
|
+
end
|
100
|
+
|
101
|
+
def notify(method) #:nodoc:
|
102
|
+
self.class.changed
|
103
|
+
self.class.notify_observers(method, self)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|