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
@@ -2,6 +2,33 @@ module MongoMapper
|
|
2
2
|
class FinderOptions
|
3
3
|
attr_reader :options
|
4
4
|
|
5
|
+
def self.to_mongo_criteria(conditions)
|
6
|
+
conditions = conditions.dup
|
7
|
+
criteria = {}
|
8
|
+
conditions.each_pair do |field, value|
|
9
|
+
case value
|
10
|
+
when Array
|
11
|
+
criteria[field] = {'$in' => value}
|
12
|
+
when Hash
|
13
|
+
criteria[field] = to_mongo_criteria(value)
|
14
|
+
else
|
15
|
+
criteria[field] = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
criteria
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.to_mongo_options(options)
|
23
|
+
options = options.dup
|
24
|
+
{
|
25
|
+
:fields => to_mongo_fields(options.delete(:fields) || options.delete(:select)),
|
26
|
+
:offset => (options.delete(:offset) || 0).to_i,
|
27
|
+
:limit => (options.delete(:limit) || 0).to_i,
|
28
|
+
:sort => to_mongo_sort(options.delete(:order))
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
5
32
|
def initialize(options)
|
6
33
|
raise ArgumentError, "FinderOptions must be a hash" unless options.is_a?(Hash)
|
7
34
|
@options = options.symbolize_keys
|
@@ -9,11 +36,11 @@ module MongoMapper
|
|
9
36
|
end
|
10
37
|
|
11
38
|
def criteria
|
12
|
-
|
39
|
+
self.class.to_mongo_criteria(@conditions)
|
13
40
|
end
|
14
41
|
|
15
42
|
def options
|
16
|
-
|
43
|
+
self.class.to_mongo_options(@options)
|
17
44
|
end
|
18
45
|
|
19
46
|
def to_a
|
@@ -21,46 +48,21 @@ module MongoMapper
|
|
21
48
|
end
|
22
49
|
|
23
50
|
private
|
24
|
-
def
|
25
|
-
criteria = {}
|
26
|
-
conditions.each_pair do |field, value|
|
27
|
-
case value
|
28
|
-
when Array
|
29
|
-
criteria[field] = {'$in' => value}
|
30
|
-
when Hash
|
31
|
-
criteria[field] = convert_conditions(value)
|
32
|
-
else
|
33
|
-
criteria[field] = value
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
criteria
|
38
|
-
end
|
39
|
-
|
40
|
-
def convert_options(options)
|
41
|
-
{
|
42
|
-
:fields => convert_fields(options.delete(:fields) || options.delete(:select)),
|
43
|
-
:offset => (options.delete(:offset) || 0).to_i,
|
44
|
-
:limit => (options.delete(:limit) || 0).to_i,
|
45
|
-
:sort => convert_sort(options.delete(:order))
|
46
|
-
}
|
47
|
-
end
|
48
|
-
|
49
|
-
def convert_fields(fields)
|
51
|
+
def self.to_mongo_fields(fields)
|
50
52
|
return if fields.blank?
|
51
|
-
|
53
|
+
|
52
54
|
if fields.is_a?(String)
|
53
55
|
fields.split(',').map { |field| field.strip }
|
54
56
|
else
|
55
57
|
fields.flatten.compact
|
56
58
|
end
|
57
59
|
end
|
58
|
-
|
59
|
-
def
|
60
|
+
|
61
|
+
def self.to_mongo_sort(sort)
|
60
62
|
return if sort.blank?
|
61
63
|
pieces = sort.split(',')
|
62
|
-
pairs = pieces.map { |s|
|
63
|
-
|
64
|
+
pairs = pieces.map { |s| to_mongo_sort_piece(s) }
|
65
|
+
|
64
66
|
hash = OrderedHash.new
|
65
67
|
pairs.each do |pair|
|
66
68
|
field, sort_direction = pair
|
@@ -68,8 +70,8 @@ module MongoMapper
|
|
68
70
|
end
|
69
71
|
hash.symbolize_keys
|
70
72
|
end
|
71
|
-
|
72
|
-
def
|
73
|
+
|
74
|
+
def self.to_mongo_sort_piece(str)
|
73
75
|
field, direction = str.strip.split(' ')
|
74
76
|
direction ||= 'ASC'
|
75
77
|
direction = direction.upcase == 'ASC' ? 1 : -1
|
data/lib/mongomapper/key.rb
CHANGED
@@ -1,34 +1,35 @@
|
|
1
1
|
class Boolean; end
|
2
|
+
class Ref; end
|
2
3
|
|
3
4
|
module MongoMapper
|
4
5
|
class Key
|
5
6
|
# DateTime and Date are currently not supported by mongo's bson so just use Time
|
6
|
-
NativeTypes = [String, Float, Time, Integer, Boolean, Array, Hash]
|
7
|
-
|
7
|
+
NativeTypes = [String, Float, Time, Integer, Boolean, Array, Hash, Ref]
|
8
|
+
|
8
9
|
attr_accessor :name, :type, :options, :default_value
|
9
|
-
|
10
|
+
|
10
11
|
def initialize(name, type, options={})
|
11
12
|
@name, @type = name.to_s, type
|
12
13
|
self.options = options.symbolize_keys
|
13
14
|
self.default_value = options.delete(:default)
|
14
15
|
end
|
15
|
-
|
16
|
+
|
16
17
|
def ==(other)
|
17
18
|
@name == other.name && @type == other.type
|
18
19
|
end
|
19
|
-
|
20
|
+
|
20
21
|
def set(value)
|
21
22
|
typecast(value)
|
22
23
|
end
|
23
|
-
|
24
|
+
|
24
25
|
def native?
|
25
26
|
@native ||= NativeTypes.include?(type)
|
26
27
|
end
|
27
|
-
|
28
|
+
|
28
29
|
def embedded_document?
|
29
30
|
type.ancestors.include?(EmbeddedDocument) && !type.ancestors.include?(Document)
|
30
31
|
end
|
31
|
-
|
32
|
+
|
32
33
|
def get(value)
|
33
34
|
return default_value if value.nil? && !default_value.nil?
|
34
35
|
if type == Array
|
@@ -39,7 +40,7 @@ module MongoMapper
|
|
39
40
|
value
|
40
41
|
end
|
41
42
|
end
|
42
|
-
|
43
|
+
|
43
44
|
private
|
44
45
|
def typecast(value)
|
45
46
|
return HashWithIndifferentAccess.new(value) if value.is_a?(Hash) && type == Hash
|
@@ -73,7 +74,7 @@ module MongoMapper
|
|
73
74
|
value
|
74
75
|
end
|
75
76
|
end
|
76
|
-
|
77
|
+
|
77
78
|
def typecast_embedded_document(value)
|
78
79
|
value.is_a?(type) ? value : type.new(value)
|
79
80
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'observer'
|
2
|
+
require 'singleton'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module MongoMapper
|
6
|
+
module Observing #:nodoc:
|
7
|
+
def self.included(model)
|
8
|
+
model.class_eval do
|
9
|
+
extend Observable
|
10
|
+
extend ClassMethods
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def observers=(*observers)
|
16
|
+
@observers = observers.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
def observers
|
20
|
+
@observers ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def instantiate_observers
|
24
|
+
return if @observers.blank?
|
25
|
+
@observers.each do |observer|
|
26
|
+
if observer.respond_to?(:to_sym) # Symbol or String
|
27
|
+
observer.to_s.camelize.constantize.instance
|
28
|
+
elsif observer.respond_to?(:instance)
|
29
|
+
observer.instance
|
30
|
+
else
|
31
|
+
raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
def inherited(subclass)
|
38
|
+
super
|
39
|
+
changed
|
40
|
+
notify_observers :observed_class_inherited, subclass
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Observer
|
46
|
+
include Singleton
|
47
|
+
|
48
|
+
class << self
|
49
|
+
def observe(*models)
|
50
|
+
models.flatten!
|
51
|
+
models.collect! { |model| model.is_a?(Symbol) ? model.to_s.camelize.constantize : model }
|
52
|
+
define_method(:observed_classes) { Set.new(models) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def observed_class
|
56
|
+
if observed_class_name = name[/(.*)Observer/, 1]
|
57
|
+
observed_class_name.constantize
|
58
|
+
else
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
|
66
|
+
end
|
67
|
+
|
68
|
+
def update(observed_method, object) #:nodoc:
|
69
|
+
send(observed_method, object) if respond_to?(observed_method)
|
70
|
+
end
|
71
|
+
|
72
|
+
def observed_class_inherited(subclass) #:nodoc:
|
73
|
+
self.class.observe(observed_classes + [subclass])
|
74
|
+
add_observer!(subclass)
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
def observed_classes
|
79
|
+
Set.new([self.class.observed_class].compact.flatten)
|
80
|
+
end
|
81
|
+
|
82
|
+
def observed_subclasses
|
83
|
+
observed_classes.sum([]) { |klass| klass.send(:subclasses) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_observer!(klass)
|
87
|
+
klass.add_observer(self)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -5,14 +5,17 @@ module MongoMapper
|
|
5
5
|
alias_method :new_record?, :new?
|
6
6
|
extend ClassMethods
|
7
7
|
end
|
8
|
+
class << model
|
9
|
+
alias_method :has_many, :many
|
10
|
+
end
|
8
11
|
end
|
9
|
-
|
12
|
+
|
10
13
|
module ClassMethods
|
11
14
|
def column_names
|
12
15
|
keys.keys
|
13
16
|
end
|
14
17
|
end
|
15
|
-
|
18
|
+
|
16
19
|
def to_param
|
17
20
|
id
|
18
21
|
end
|
@@ -2,48 +2,18 @@ module MongoMapper
|
|
2
2
|
module SaveWithValidation
|
3
3
|
def self.included(base)
|
4
4
|
base.class_eval do
|
5
|
-
alias_method_chain :valid?, :callbacks
|
6
5
|
alias_method_chain :save, :validation
|
7
|
-
|
8
|
-
define_callbacks :before_validation_on_create, :before_validation_on_update,
|
9
|
-
:before_validation, :after_validation,
|
10
|
-
:validate, :validate_on_create, :validate_on_update
|
6
|
+
alias_method_chain :save!, :validation
|
11
7
|
end
|
12
8
|
end
|
13
9
|
|
14
|
-
def save!
|
15
|
-
save_with_validation || raise(DocumentNotValid.new(self))
|
16
|
-
end
|
17
|
-
|
18
10
|
private
|
19
|
-
def save_with_validation
|
20
|
-
|
21
|
-
save_without_validation
|
22
|
-
else
|
23
|
-
false
|
24
|
-
end
|
11
|
+
def save_with_validation
|
12
|
+
valid? ? save_without_validation : false
|
25
13
|
end
|
26
|
-
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
if new?
|
31
|
-
run_callbacks(:before_validation_on_create)
|
32
|
-
else
|
33
|
-
run_callbacks(:before_validation_on_update)
|
34
|
-
end
|
35
|
-
|
36
|
-
run_callbacks(:validate)
|
37
|
-
|
38
|
-
if new?
|
39
|
-
run_callbacks(:validate_on_create)
|
40
|
-
else
|
41
|
-
run_callbacks(:validate_on_update)
|
42
|
-
end
|
43
|
-
|
44
|
-
is_valid = valid_without_callbacks?
|
45
|
-
run_callbacks(:after_validation) if is_valid
|
46
|
-
is_valid
|
14
|
+
|
15
|
+
def save_with_validation!
|
16
|
+
valid? ? save_without_validation! : raise(DocumentNotValid.new(self))
|
47
17
|
end
|
48
18
|
end
|
49
19
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Validations
|
3
|
+
class ValidatesUniquenessOf < Validatable::ValidationBase
|
4
|
+
def valid?(instance)
|
5
|
+
# TODO: scope
|
6
|
+
doc = instance.class.find(:first, :conditions => {self.attribute => instance[attribute]}, :limit => 1)
|
7
|
+
doc.nil? || instance.id == doc.id
|
8
|
+
end
|
9
|
+
|
10
|
+
def message(instance)
|
11
|
+
super || "has already been taken"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ValidatesExclusionOf < Validatable::ValidationBase
|
16
|
+
required_option :within
|
17
|
+
|
18
|
+
def valid?(instance)
|
19
|
+
value = instance[attribute]
|
20
|
+
return true if allow_nil && value.nil?
|
21
|
+
return true if allow_blank && value.blank?
|
22
|
+
|
23
|
+
!within.include?(instance[attribute])
|
24
|
+
end
|
25
|
+
|
26
|
+
def message(instance)
|
27
|
+
super || "is reserved"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ValidatesInclusionOf < Validatable::ValidationBase
|
32
|
+
required_option :within
|
33
|
+
|
34
|
+
def valid?(instance)
|
35
|
+
value = instance[attribute]
|
36
|
+
return true if allow_nil && value.nil?
|
37
|
+
return true if allow_blank && value.blank?
|
38
|
+
|
39
|
+
within.include?(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def message(instance)
|
43
|
+
super || "is not in the list"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/mongomapper.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{mongomapper}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.2.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["John Nunemaker"]
|
9
|
-
s.date = %q{2009-07-
|
9
|
+
s.date = %q{2009-07-07}
|
10
10
|
s.email = %q{nunemaker@gmail.com}
|
11
11
|
s.extra_rdoc_files = [
|
12
12
|
"LICENSE",
|
@@ -20,14 +20,25 @@ Gem::Specification.new do |s|
|
|
20
20
|
"Rakefile",
|
21
21
|
"VERSION",
|
22
22
|
"lib/mongomapper.rb",
|
23
|
+
"lib/mongomapper/associations.rb",
|
24
|
+
"lib/mongomapper/associations/array_proxy.rb",
|
25
|
+
"lib/mongomapper/associations/base.rb",
|
26
|
+
"lib/mongomapper/associations/belongs_to_proxy.rb",
|
27
|
+
"lib/mongomapper/associations/has_many_embedded_proxy.rb",
|
28
|
+
"lib/mongomapper/associations/has_many_proxy.rb",
|
29
|
+
"lib/mongomapper/associations/polymorphic_belongs_to_proxy.rb",
|
30
|
+
"lib/mongomapper/associations/proxy.rb",
|
31
|
+
"lib/mongomapper/callbacks.rb",
|
23
32
|
"lib/mongomapper/document.rb",
|
24
33
|
"lib/mongomapper/embedded_document.rb",
|
25
34
|
"lib/mongomapper/finder_options.rb",
|
26
35
|
"lib/mongomapper/key.rb",
|
36
|
+
"lib/mongomapper/observing.rb",
|
27
37
|
"lib/mongomapper/rails_compatibility.rb",
|
28
38
|
"lib/mongomapper/save_with_validation.rb",
|
29
39
|
"lib/mongomapper/serialization.rb",
|
30
40
|
"lib/mongomapper/serializers/json_serializer.rb",
|
41
|
+
"lib/mongomapper/validations.rb",
|
31
42
|
"mongomapper.gemspec",
|
32
43
|
"test/serializers/test_json_serializer.rb",
|
33
44
|
"test/test_associations.rb",
|
@@ -38,6 +49,7 @@ Gem::Specification.new do |s|
|
|
38
49
|
"test/test_helper.rb",
|
39
50
|
"test/test_key.rb",
|
40
51
|
"test/test_mongomapper.rb",
|
52
|
+
"test/test_observing.rb",
|
41
53
|
"test/test_rails_compatibility.rb",
|
42
54
|
"test/test_serializations.rb",
|
43
55
|
"test/test_validations.rb"
|
@@ -59,6 +71,7 @@ Gem::Specification.new do |s|
|
|
59
71
|
"test/test_helper.rb",
|
60
72
|
"test/test_key.rb",
|
61
73
|
"test/test_mongomapper.rb",
|
74
|
+
"test/test_observing.rb",
|
62
75
|
"test/test_rails_compatibility.rb",
|
63
76
|
"test/test_serializations.rb",
|
64
77
|
"test/test_validations.rb"
|
@@ -71,20 +84,20 @@ Gem::Specification.new do |s|
|
|
71
84
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
72
85
|
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
|
73
86
|
s.add_runtime_dependency(%q<mongodb-mongo>, ["= 0.9"])
|
74
|
-
s.add_runtime_dependency(%q<jnunemaker-validatable>, ["= 1.7.
|
87
|
+
s.add_runtime_dependency(%q<jnunemaker-validatable>, ["= 1.7.1"])
|
75
88
|
s.add_development_dependency(%q<mocha>, ["= 0.9.4"])
|
76
89
|
s.add_development_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
|
77
90
|
else
|
78
91
|
s.add_dependency(%q<activesupport>, [">= 0"])
|
79
92
|
s.add_dependency(%q<mongodb-mongo>, ["= 0.9"])
|
80
|
-
s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.
|
93
|
+
s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.1"])
|
81
94
|
s.add_dependency(%q<mocha>, ["= 0.9.4"])
|
82
95
|
s.add_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
|
83
96
|
end
|
84
97
|
else
|
85
98
|
s.add_dependency(%q<activesupport>, [">= 0"])
|
86
99
|
s.add_dependency(%q<mongodb-mongo>, ["= 0.9"])
|
87
|
-
s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.
|
100
|
+
s.add_dependency(%q<jnunemaker-validatable>, ["= 1.7.1"])
|
88
101
|
s.add_dependency(%q<mocha>, ["= 0.9.4"])
|
89
102
|
s.add_dependency(%q<jnunemaker-matchy>, ["= 0.4.0"])
|
90
103
|
end
|