jnunemaker-mongomapper 0.1.2 → 0.2.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/.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
|