mrkurt-mongo_mapper 0.6.8
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 +10 -0
- data/LICENSE +20 -0
- data/README.rdoc +38 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/mmconsole +60 -0
- data/lib/mongo_mapper.rb +139 -0
- data/lib/mongo_mapper/associations.rb +72 -0
- data/lib/mongo_mapper/associations/base.rb +113 -0
- data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +26 -0
- data/lib/mongo_mapper/associations/belongs_to_proxy.rb +21 -0
- data/lib/mongo_mapper/associations/collection.rb +19 -0
- data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +26 -0
- data/lib/mongo_mapper/associations/many_documents_proxy.rb +115 -0
- data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +31 -0
- data/lib/mongo_mapper/associations/many_embedded_proxy.rb +54 -0
- data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
- data/lib/mongo_mapper/associations/one_proxy.rb +61 -0
- data/lib/mongo_mapper/associations/proxy.rb +111 -0
- data/lib/mongo_mapper/callbacks.rb +61 -0
- data/lib/mongo_mapper/dirty.rb +117 -0
- data/lib/mongo_mapper/document.rb +496 -0
- data/lib/mongo_mapper/dynamic_finder.rb +74 -0
- data/lib/mongo_mapper/embedded_document.rb +380 -0
- data/lib/mongo_mapper/finder_options.rb +145 -0
- data/lib/mongo_mapper/key.rb +36 -0
- data/lib/mongo_mapper/mongo_mapper.rb +125 -0
- data/lib/mongo_mapper/pagination.rb +66 -0
- data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
- data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +28 -0
- data/lib/mongo_mapper/serialization.rb +54 -0
- data/lib/mongo_mapper/serializers/json_serializer.rb +48 -0
- data/lib/mongo_mapper/support.rb +192 -0
- data/lib/mongo_mapper/validations.rb +39 -0
- data/mongo_mapper.gemspec +173 -0
- data/specs.watchr +30 -0
- data/test/NOTE_ON_TESTING +1 -0
- data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
- data/test/functional/associations/test_belongs_to_proxy.rb +91 -0
- data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
- data/test/functional/associations/test_many_documents_proxy.rb +477 -0
- data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +156 -0
- data/test/functional/associations/test_many_embedded_proxy.rb +192 -0
- data/test/functional/associations/test_many_polymorphic_proxy.rb +339 -0
- data/test/functional/associations/test_one_proxy.rb +131 -0
- data/test/functional/test_associations.rb +44 -0
- data/test/functional/test_binary.rb +33 -0
- data/test/functional/test_callbacks.rb +85 -0
- data/test/functional/test_dirty.rb +159 -0
- data/test/functional/test_document.rb +1198 -0
- data/test/functional/test_embedded_document.rb +135 -0
- data/test/functional/test_logger.rb +20 -0
- data/test/functional/test_modifiers.rb +242 -0
- data/test/functional/test_pagination.rb +95 -0
- data/test/functional/test_rails_compatibility.rb +25 -0
- data/test/functional/test_string_id_compatibility.rb +72 -0
- data/test/functional/test_validations.rb +361 -0
- data/test/models.rb +271 -0
- data/test/support/custom_matchers.rb +55 -0
- data/test/support/timing.rb +16 -0
- data/test/test_helper.rb +27 -0
- data/test/unit/associations/test_base.rb +182 -0
- data/test/unit/associations/test_proxy.rb +91 -0
- data/test/unit/serializers/test_json_serializer.rb +189 -0
- data/test/unit/test_document.rb +236 -0
- data/test/unit/test_dynamic_finder.rb +125 -0
- data/test/unit/test_embedded_document.rb +709 -0
- data/test/unit/test_finder_options.rb +325 -0
- data/test/unit/test_key.rb +172 -0
- data/test/unit/test_mongo_mapper.rb +65 -0
- data/test/unit/test_pagination.rb +119 -0
- data/test/unit/test_rails_compatibility.rb +52 -0
- data/test/unit/test_serializations.rb +52 -0
- data/test/unit/test_support.rb +346 -0
- data/test/unit/test_time_zones.rb +40 -0
- data/test/unit/test_validations.rb +503 -0
- metadata +239 -0
@@ -0,0 +1,145 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
# = Important Note
|
3
|
+
# This class is private to MongoMapper and should not be considered part of
|
4
|
+
# MongoMapper's public API.
|
5
|
+
#
|
6
|
+
class FinderOptions
|
7
|
+
OptionKeys = [:fields, :select, :skip, :offset, :limit, :sort, :order]
|
8
|
+
|
9
|
+
def self.normalized_field(field)
|
10
|
+
field.to_s == 'id' ? :_id : field
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.normalized_order_direction(direction)
|
14
|
+
direction ||= 'ASC'
|
15
|
+
direction.upcase == 'ASC' ? 1 : -1
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(model, options)
|
19
|
+
raise ArgumentError, "Options must be a hash" unless options.is_a?(Hash)
|
20
|
+
options = options.symbolize_keys
|
21
|
+
|
22
|
+
@model = model
|
23
|
+
@options = {}
|
24
|
+
@conditions = options.delete(:conditions) || {}
|
25
|
+
|
26
|
+
options.each_pair do |key, value|
|
27
|
+
if OptionKeys.include?(key)
|
28
|
+
@options[key] = value
|
29
|
+
else
|
30
|
+
@conditions[key] = value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
add_sci_scope
|
35
|
+
end
|
36
|
+
|
37
|
+
def criteria
|
38
|
+
to_mongo_criteria(@conditions)
|
39
|
+
end
|
40
|
+
|
41
|
+
def options
|
42
|
+
fields = @options.delete(:fields) || @options.delete(:select)
|
43
|
+
skip = @options.delete(:skip) || @options.delete(:offset) || 0
|
44
|
+
limit = @options.delete(:limit) || 0
|
45
|
+
sort = @options.delete(:sort) || convert_order_to_sort(@options.delete(:order))
|
46
|
+
|
47
|
+
{:fields => to_mongo_fields(fields), :skip => skip.to_i, :limit => limit.to_i, :sort => sort}
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_a
|
51
|
+
[criteria, options]
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def to_mongo_criteria(conditions, parent_key=nil)
|
56
|
+
criteria = {}
|
57
|
+
|
58
|
+
conditions.each_pair do |field, value|
|
59
|
+
field = self.class.normalized_field(field)
|
60
|
+
|
61
|
+
if @model.object_id_key?(field) && value.is_a?(String)
|
62
|
+
value = Mongo::ObjectID.from_string(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
if field.is_a?(FinderOperator)
|
66
|
+
criteria.update(field.to_criteria(value))
|
67
|
+
next
|
68
|
+
end
|
69
|
+
|
70
|
+
case value
|
71
|
+
when Array
|
72
|
+
criteria[field] = operator?(field) ? value : {'$in' => value}
|
73
|
+
when Hash
|
74
|
+
criteria[field] = to_mongo_criteria(value, field)
|
75
|
+
when Time
|
76
|
+
criteria[field] = value.utc
|
77
|
+
else
|
78
|
+
criteria[field] = value
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
criteria
|
83
|
+
end
|
84
|
+
|
85
|
+
def operator?(field)
|
86
|
+
field.to_s =~ /^\$/
|
87
|
+
end
|
88
|
+
|
89
|
+
# adds _type single collection inheritance scope for models that need it
|
90
|
+
def add_sci_scope
|
91
|
+
if @model.single_collection_inherited?
|
92
|
+
@conditions[:_type] = @model.to_s
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_mongo_fields(fields)
|
97
|
+
return if fields.blank?
|
98
|
+
|
99
|
+
if fields.respond_to?(:flatten, :compact)
|
100
|
+
fields.flatten.compact
|
101
|
+
else
|
102
|
+
fields.split(',').map { |field| field.strip }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def convert_order_to_sort(sort)
|
107
|
+
return if sort.blank?
|
108
|
+
|
109
|
+
if sort.respond_to?(:all?) && sort.all? { |s| s.respond_to?(:to_order) }
|
110
|
+
sort.map { |s| s.to_order }
|
111
|
+
elsif sort.respond_to?(:to_order)
|
112
|
+
[sort.to_order]
|
113
|
+
else
|
114
|
+
pieces = sort.split(',')
|
115
|
+
pieces.map { |s| to_mongo_sort_piece(s) }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_mongo_sort_piece(str)
|
120
|
+
field, direction = str.strip.split(' ')
|
121
|
+
direction = FinderOptions.normalized_order_direction(direction)
|
122
|
+
[field, direction]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class FinderOperator
|
127
|
+
def initialize(field, operator)
|
128
|
+
@field, @operator = field, operator
|
129
|
+
end
|
130
|
+
|
131
|
+
def to_criteria(value)
|
132
|
+
{FinderOptions.normalized_field(@field) => {@operator => value}}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class OrderOperator
|
137
|
+
def initialize(field, direction)
|
138
|
+
@field, @direction = field, direction
|
139
|
+
end
|
140
|
+
|
141
|
+
def to_order
|
142
|
+
[@field.to_s, FinderOptions.normalized_order_direction(@direction)]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
class Key
|
3
|
+
attr_accessor :name, :type, :options, :default_value
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
options = args.extract_options!
|
7
|
+
@name, @type = args.shift.to_s, args.shift
|
8
|
+
self.options = (options || {}).symbolize_keys
|
9
|
+
self.default_value = self.options.delete(:default)
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
@name == other.name && @type == other.type
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(value)
|
17
|
+
type.to_mongo(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def embeddable?
|
21
|
+
type.respond_to?(:embeddable?) && type.embeddable? ? true : false
|
22
|
+
end
|
23
|
+
|
24
|
+
def number?
|
25
|
+
[Integer, Float].include?(type)
|
26
|
+
end
|
27
|
+
|
28
|
+
def get(value)
|
29
|
+
if value.nil? && !default_value.nil?
|
30
|
+
return default_value
|
31
|
+
end
|
32
|
+
|
33
|
+
type.from_mongo(value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# if Gem is defined i'll assume you are using rubygems and lock specific versions
|
2
|
+
# call me crazy but a plain old require will just get the latest version you have installed
|
3
|
+
# so i want to make sure that if you are using gems you do in fact have the correct versions
|
4
|
+
# if there is a better way to do this, please enlighten me!
|
5
|
+
if self.class.const_defined?(:Gem)
|
6
|
+
gem 'activesupport', '>= 2.3'
|
7
|
+
gem 'mongo', '0.18.2'
|
8
|
+
gem 'jnunemaker-validatable', '1.8.1'
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'active_support'
|
12
|
+
require 'mongo'
|
13
|
+
require 'validatable'
|
14
|
+
|
15
|
+
module MongoMapper
|
16
|
+
# generic MM error
|
17
|
+
class MongoMapperError < StandardError; end
|
18
|
+
|
19
|
+
# raised when key expected to exist but not found
|
20
|
+
class KeyNotFound < MongoMapperError; end
|
21
|
+
|
22
|
+
# raised when document expected but not found
|
23
|
+
class DocumentNotFound < MongoMapperError; end
|
24
|
+
|
25
|
+
# raised when document not valid and using !
|
26
|
+
class DocumentNotValid < MongoMapperError
|
27
|
+
def initialize(document)
|
28
|
+
super("Validation failed: #{document.errors.full_messages.join(", ")}")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @api public
|
33
|
+
def self.connection
|
34
|
+
@@connection ||= Mongo::Connection.new
|
35
|
+
end
|
36
|
+
|
37
|
+
# @api public
|
38
|
+
def self.connection=(new_connection)
|
39
|
+
@@connection = new_connection
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api public
|
43
|
+
def self.logger
|
44
|
+
connection.logger
|
45
|
+
end
|
46
|
+
|
47
|
+
# @api public
|
48
|
+
def self.database=(name)
|
49
|
+
@@database = nil
|
50
|
+
@@database_name = name
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api public
|
54
|
+
def self.database
|
55
|
+
if @@database_name.blank?
|
56
|
+
raise 'You forgot to set the default database name: MongoMapper.database = "foobar"'
|
57
|
+
end
|
58
|
+
|
59
|
+
@@database ||= MongoMapper.connection.db(@@database_name)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @api private
|
63
|
+
def self.ensured_indexes
|
64
|
+
@@ensured_indexes ||= []
|
65
|
+
end
|
66
|
+
|
67
|
+
# @api private
|
68
|
+
def self.ensured_indexes=(value)
|
69
|
+
@@ensured_indexes = value
|
70
|
+
end
|
71
|
+
|
72
|
+
# @api private
|
73
|
+
def self.ensure_index(klass, keys, options={})
|
74
|
+
ensured_indexes << {:klass => klass, :keys => keys, :options => options}
|
75
|
+
end
|
76
|
+
|
77
|
+
# @api public
|
78
|
+
def self.ensure_indexes!
|
79
|
+
ensured_indexes.each do |index|
|
80
|
+
unique = index[:options].delete(:unique)
|
81
|
+
index[:klass].collection.create_index(index[:keys], unique)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
def self.use_time_zone?
|
87
|
+
Time.respond_to?(:zone) && Time.zone ? true : false
|
88
|
+
end
|
89
|
+
|
90
|
+
# @api private
|
91
|
+
def self.time_class
|
92
|
+
use_time_zone? ? Time.zone : Time
|
93
|
+
end
|
94
|
+
|
95
|
+
# @api private
|
96
|
+
def self.normalize_object_id(value)
|
97
|
+
value.is_a?(String) ? Mongo::ObjectID.from_string(value) : value
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
require 'mongo_mapper/support'
|
102
|
+
require 'mongo_mapper/callbacks'
|
103
|
+
require 'mongo_mapper/finder_options'
|
104
|
+
require 'mongo_mapper/dirty'
|
105
|
+
require 'mongo_mapper/dynamic_finder'
|
106
|
+
require 'mongo_mapper/key'
|
107
|
+
require 'mongo_mapper/pagination'
|
108
|
+
require 'mongo_mapper/serialization'
|
109
|
+
require 'mongo_mapper/validations'
|
110
|
+
require 'mongo_mapper/rails_compatibility/document'
|
111
|
+
require 'mongo_mapper/rails_compatibility/embedded_document'
|
112
|
+
require 'mongo_mapper/embedded_document'
|
113
|
+
require 'mongo_mapper/document'
|
114
|
+
require 'mongo_mapper/associations'
|
115
|
+
require 'mongo_mapper/associations/base'
|
116
|
+
require 'mongo_mapper/associations/proxy'
|
117
|
+
require 'mongo_mapper/associations/collection'
|
118
|
+
require 'mongo_mapper/associations/many_documents_proxy'
|
119
|
+
require 'mongo_mapper/associations/belongs_to_proxy'
|
120
|
+
require 'mongo_mapper/associations/belongs_to_polymorphic_proxy'
|
121
|
+
require 'mongo_mapper/associations/many_polymorphic_proxy'
|
122
|
+
require 'mongo_mapper/associations/many_embedded_proxy'
|
123
|
+
require 'mongo_mapper/associations/many_embedded_polymorphic_proxy'
|
124
|
+
require 'mongo_mapper/associations/many_documents_as_proxy'
|
125
|
+
require 'mongo_mapper/associations/one_proxy'
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module Pagination
|
3
|
+
class PaginationProxy
|
4
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
5
|
+
|
6
|
+
attr_accessor :subject
|
7
|
+
attr_reader :total_entries, :per_page, :current_page
|
8
|
+
alias limit per_page
|
9
|
+
|
10
|
+
def initialize(total_entries, current_page, per_page=nil)
|
11
|
+
@total_entries = total_entries.to_i
|
12
|
+
self.per_page = per_page
|
13
|
+
self.current_page = current_page
|
14
|
+
end
|
15
|
+
|
16
|
+
def total_pages
|
17
|
+
(total_entries / per_page.to_f).ceil
|
18
|
+
end
|
19
|
+
|
20
|
+
def out_of_bounds?
|
21
|
+
current_page > total_pages
|
22
|
+
end
|
23
|
+
|
24
|
+
def previous_page
|
25
|
+
current_page > 1 ? (current_page - 1) : nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def next_page
|
29
|
+
current_page < total_pages ? (current_page + 1) : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def skip
|
33
|
+
(current_page - 1) * per_page
|
34
|
+
end
|
35
|
+
alias offset skip # for will paginate support
|
36
|
+
|
37
|
+
def send(method, *args, &block)
|
38
|
+
if respond_to?(method)
|
39
|
+
super
|
40
|
+
else
|
41
|
+
subject.send(method, *args, &block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def ===(other)
|
46
|
+
other === subject
|
47
|
+
end
|
48
|
+
|
49
|
+
def method_missing(name, *args, &block)
|
50
|
+
@subject.send(name, *args, &block)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
def per_page=(value)
|
55
|
+
value = 25 if value.blank?
|
56
|
+
@per_page = value.to_i
|
57
|
+
end
|
58
|
+
|
59
|
+
def current_page=(value)
|
60
|
+
value = value.to_i
|
61
|
+
value = 1 if value < 1
|
62
|
+
@current_page = value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module MongoMapper
|
2
|
+
module RailsCompatibility
|
3
|
+
module EmbeddedDocument
|
4
|
+
def self.included(model)
|
5
|
+
model.class_eval do
|
6
|
+
extend ClassMethods
|
7
|
+
|
8
|
+
alias_method :new_record?, :new?
|
9
|
+
end
|
10
|
+
|
11
|
+
class << model
|
12
|
+
alias has_many many
|
13
|
+
alias has_one one
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def column_names
|
19
|
+
keys.keys
|
20
|
+
end
|
21
|
+
|
22
|
+
def human_name
|
23
|
+
self.name.demodulize.titleize
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'active_support/json'
|
2
|
+
|
3
|
+
module MongoMapper
|
4
|
+
module Serialization
|
5
|
+
class Serializer
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize(record, options={})
|
9
|
+
@record, @options = record, options.dup
|
10
|
+
end
|
11
|
+
|
12
|
+
def serializable_key_names
|
13
|
+
key_names = @record.attributes.keys
|
14
|
+
|
15
|
+
if options[:only]
|
16
|
+
options.delete(:except)
|
17
|
+
key_names = key_names & Array(options[:only]).collect { |n| n.to_s }
|
18
|
+
else
|
19
|
+
options[:except] = Array(options[:except])
|
20
|
+
key_names = key_names - options[:except].collect { |n| n.to_s }
|
21
|
+
end
|
22
|
+
|
23
|
+
key_names
|
24
|
+
end
|
25
|
+
|
26
|
+
def serializable_method_names
|
27
|
+
Array(options[:methods]).inject([]) do |method_attributes, name|
|
28
|
+
method_attributes << name if @record.respond_to?(name.to_s)
|
29
|
+
method_attributes
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def serializable_names
|
34
|
+
serializable_key_names + serializable_method_names
|
35
|
+
end
|
36
|
+
|
37
|
+
def serializable_record
|
38
|
+
returning(serializable_record = {}) do
|
39
|
+
serializable_names.each { |name| serializable_record[name] = @record.send(name) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def serialize
|
44
|
+
# overwrite to implement
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s(&block)
|
48
|
+
serialize(&block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
require 'mongo_mapper/serializers/json_serializer'
|