mongomodel 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +34 -0
- data/Rakefile +47 -0
- data/bin/console +45 -0
- data/lib/mongomodel.rb +92 -0
- data/lib/mongomodel/attributes/mongo.rb +40 -0
- data/lib/mongomodel/attributes/store.rb +30 -0
- data/lib/mongomodel/attributes/typecasting.rb +51 -0
- data/lib/mongomodel/concerns/abstract_class.rb +17 -0
- data/lib/mongomodel/concerns/activemodel.rb +11 -0
- data/lib/mongomodel/concerns/associations.rb +103 -0
- data/lib/mongomodel/concerns/associations/base/association.rb +33 -0
- data/lib/mongomodel/concerns/associations/base/definition.rb +56 -0
- data/lib/mongomodel/concerns/associations/base/proxy.rb +58 -0
- data/lib/mongomodel/concerns/associations/belongs_to.rb +68 -0
- data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +159 -0
- data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +175 -0
- data/lib/mongomodel/concerns/attribute_methods.rb +55 -0
- data/lib/mongomodel/concerns/attribute_methods/before_type_cast.rb +29 -0
- data/lib/mongomodel/concerns/attribute_methods/dirty.rb +35 -0
- data/lib/mongomodel/concerns/attribute_methods/protected.rb +127 -0
- data/lib/mongomodel/concerns/attribute_methods/query.rb +22 -0
- data/lib/mongomodel/concerns/attribute_methods/read.rb +29 -0
- data/lib/mongomodel/concerns/attribute_methods/write.rb +29 -0
- data/lib/mongomodel/concerns/attributes.rb +85 -0
- data/lib/mongomodel/concerns/callbacks.rb +294 -0
- data/lib/mongomodel/concerns/logging.rb +15 -0
- data/lib/mongomodel/concerns/pretty_inspect.rb +29 -0
- data/lib/mongomodel/concerns/properties.rb +69 -0
- data/lib/mongomodel/concerns/record_status.rb +42 -0
- data/lib/mongomodel/concerns/timestamps.rb +32 -0
- data/lib/mongomodel/concerns/validations.rb +38 -0
- data/lib/mongomodel/concerns/validations/associated.rb +46 -0
- data/lib/mongomodel/document.rb +20 -0
- data/lib/mongomodel/document/callbacks.rb +46 -0
- data/lib/mongomodel/document/dynamic_finders.rb +88 -0
- data/lib/mongomodel/document/finders.rb +82 -0
- data/lib/mongomodel/document/indexes.rb +91 -0
- data/lib/mongomodel/document/optimistic_locking.rb +48 -0
- data/lib/mongomodel/document/persistence.rb +143 -0
- data/lib/mongomodel/document/scopes.rb +161 -0
- data/lib/mongomodel/document/validations.rb +68 -0
- data/lib/mongomodel/document/validations/uniqueness.rb +78 -0
- data/lib/mongomodel/embedded_document.rb +42 -0
- data/lib/mongomodel/locale/en.yml +55 -0
- data/lib/mongomodel/support/collection.rb +109 -0
- data/lib/mongomodel/support/configuration.rb +35 -0
- data/lib/mongomodel/support/core_extensions.rb +10 -0
- data/lib/mongomodel/support/exceptions.rb +25 -0
- data/lib/mongomodel/support/mongo_options.rb +177 -0
- data/lib/mongomodel/support/types.rb +35 -0
- data/lib/mongomodel/support/types/array.rb +11 -0
- data/lib/mongomodel/support/types/boolean.rb +25 -0
- data/lib/mongomodel/support/types/custom.rb +38 -0
- data/lib/mongomodel/support/types/date.rb +20 -0
- data/lib/mongomodel/support/types/float.rb +13 -0
- data/lib/mongomodel/support/types/hash.rb +18 -0
- data/lib/mongomodel/support/types/integer.rb +13 -0
- data/lib/mongomodel/support/types/object.rb +21 -0
- data/lib/mongomodel/support/types/string.rb +9 -0
- data/lib/mongomodel/support/types/symbol.rb +9 -0
- data/lib/mongomodel/support/types/time.rb +12 -0
- data/lib/mongomodel/version.rb +3 -0
- data/spec/mongomodel/attributes/store_spec.rb +273 -0
- data/spec/mongomodel/concerns/activemodel_spec.rb +61 -0
- data/spec/mongomodel/concerns/associations/belongs_to_spec.rb +153 -0
- data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +165 -0
- data/spec/mongomodel/concerns/associations/has_many_by_ids_spec.rb +192 -0
- data/spec/mongomodel/concerns/attribute_methods/before_type_cast_spec.rb +46 -0
- data/spec/mongomodel/concerns/attribute_methods/dirty_spec.rb +131 -0
- data/spec/mongomodel/concerns/attribute_methods/protected_spec.rb +86 -0
- data/spec/mongomodel/concerns/attribute_methods/query_spec.rb +27 -0
- data/spec/mongomodel/concerns/attribute_methods/read_spec.rb +52 -0
- data/spec/mongomodel/concerns/attribute_methods/write_spec.rb +43 -0
- data/spec/mongomodel/concerns/attributes_spec.rb +152 -0
- data/spec/mongomodel/concerns/callbacks_spec.rb +90 -0
- data/spec/mongomodel/concerns/logging_spec.rb +20 -0
- data/spec/mongomodel/concerns/pretty_inspect_spec.rb +68 -0
- data/spec/mongomodel/concerns/properties_spec.rb +29 -0
- data/spec/mongomodel/concerns/timestamps_spec.rb +170 -0
- data/spec/mongomodel/concerns/validations_spec.rb +159 -0
- data/spec/mongomodel/document/callbacks_spec.rb +80 -0
- data/spec/mongomodel/document/dynamic_finders_spec.rb +183 -0
- data/spec/mongomodel/document/finders_spec.rb +231 -0
- data/spec/mongomodel/document/indexes_spec.rb +121 -0
- data/spec/mongomodel/document/optimistic_locking_spec.rb +57 -0
- data/spec/mongomodel/document/persistence_spec.rb +319 -0
- data/spec/mongomodel/document/scopes_spec.rb +204 -0
- data/spec/mongomodel/document/validations/uniqueness_spec.rb +217 -0
- data/spec/mongomodel/document/validations_spec.rb +132 -0
- data/spec/mongomodel/document_spec.rb +74 -0
- data/spec/mongomodel/embedded_document_spec.rb +66 -0
- data/spec/mongomodel/mongomodel_spec.rb +33 -0
- data/spec/mongomodel/support/collection_spec.rb +248 -0
- data/spec/mongomodel/support/mongo_options_spec.rb +295 -0
- data/spec/mongomodel/support/property_spec.rb +83 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/specdoc.opts +6 -0
- data/spec/support/callbacks.rb +44 -0
- data/spec/support/helpers/define_class.rb +24 -0
- data/spec/support/helpers/specs_for.rb +11 -0
- data/spec/support/matchers/be_a_subclass_of.rb +5 -0
- data/spec/support/matchers/respond_to_boolean.rb +17 -0
- data/spec/support/matchers/run_callbacks.rb +20 -0
- data/spec/support/models.rb +23 -0
- data/spec/support/time.rb +6 -0
- metadata +232 -0
@@ -0,0 +1,78 @@
|
|
1
|
+
module MongoModel
|
2
|
+
module DocumentExtensions
|
3
|
+
module Validations
|
4
|
+
module ClassMethods
|
5
|
+
# Validates whether the value of the specified attributes are unique across the system. Useful for making sure that only one user
|
6
|
+
# can be named "davidhh".
|
7
|
+
#
|
8
|
+
# class Person < MongoModel::Document
|
9
|
+
# validates_uniqueness_of :user_name, :scope => :account_id
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example,
|
13
|
+
# making sure that a teacher can only be on the schedule once per semester for a particular class.
|
14
|
+
#
|
15
|
+
# class TeacherSchedule < MongoModel::Document
|
16
|
+
# validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# When the document is created, a check is performed to make sure that no document exists in the database with the given value for the specified
|
20
|
+
# attribute (that maps to a property). When the document is updated, the same check is made but disregarding the document itself.
|
21
|
+
#
|
22
|
+
# Configuration options:
|
23
|
+
# * <tt>:message</tt> - Specifies a custom error message (default is: "has already been taken").
|
24
|
+
# * <tt>:scope</tt> - One or more properties by which to limit the scope of the uniqueness constraint.
|
25
|
+
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (+true+ by default).
|
26
|
+
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute is +nil+ (default is +false+).
|
27
|
+
# * <tt>:allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is +false+).
|
28
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
29
|
+
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
|
30
|
+
# method, proc or string should return or evaluate to a true or false value.
|
31
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
32
|
+
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
33
|
+
# method, proc or string should return or evaluate to a true or false value.
|
34
|
+
#
|
35
|
+
# === Concurrency and integrity
|
36
|
+
#
|
37
|
+
# Note that this validation method does not have the same race condition suffered by ActiveRecord and other ORMs.
|
38
|
+
# A unique index is added to the collection to ensure that the collection never ends up in an invalid state.
|
39
|
+
def validates_uniqueness_of(*attr_names)
|
40
|
+
configuration = { :case_sensitive => true }
|
41
|
+
configuration.update(attr_names.extract_options!)
|
42
|
+
|
43
|
+
# Enable safety checks on save
|
44
|
+
self.save_safely = true
|
45
|
+
|
46
|
+
# Create unique indexes to deal with race condition
|
47
|
+
attr_names.each do |attr_name|
|
48
|
+
if configuration[:case_sensitive]
|
49
|
+
index *[attr_name] + Array(configuration[:scope]) << { :unique => true }
|
50
|
+
else
|
51
|
+
lowercase_key = "_lowercase_#{attr_name}"
|
52
|
+
before_save { attributes[lowercase_key] = send(attr_name).downcase }
|
53
|
+
index *[lowercase_key] + Array(configuration[:scope]) << { :unique => true }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
validates_each(attr_names, configuration) do |record, attr_name, value|
|
58
|
+
if configuration[:case_sensitive] || !value.is_a?(String)
|
59
|
+
conditions = { attr_name => value }
|
60
|
+
else
|
61
|
+
conditions = { "_lowercase_#{attr_name}" => value.downcase }
|
62
|
+
end
|
63
|
+
|
64
|
+
Array(configuration[:scope]).each do |scope|
|
65
|
+
conditions[scope] = record.send(scope)
|
66
|
+
end
|
67
|
+
|
68
|
+
conditions.merge!(:id.ne => record.id) unless record.new_record?
|
69
|
+
|
70
|
+
if exists?(conditions)
|
71
|
+
record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module MongoModel
|
2
|
+
class EmbeddedDocument
|
3
|
+
def to_param
|
4
|
+
id
|
5
|
+
end
|
6
|
+
|
7
|
+
def ==(other)
|
8
|
+
other.is_a?(self.class) && other.attributes == attributes
|
9
|
+
end
|
10
|
+
|
11
|
+
include Attributes
|
12
|
+
include Properties
|
13
|
+
|
14
|
+
include Validations
|
15
|
+
include Callbacks
|
16
|
+
|
17
|
+
include Associations
|
18
|
+
|
19
|
+
include AttributeMethods
|
20
|
+
include AttributeMethods::Read
|
21
|
+
include AttributeMethods::Write
|
22
|
+
include AttributeMethods::Query
|
23
|
+
include AttributeMethods::BeforeTypeCast
|
24
|
+
include AttributeMethods::Protected
|
25
|
+
include AttributeMethods::Dirty
|
26
|
+
|
27
|
+
include Logging
|
28
|
+
include RecordStatus
|
29
|
+
include ActiveModelCompatibility
|
30
|
+
include Timestamps
|
31
|
+
include PrettyInspect
|
32
|
+
include AbstractClass
|
33
|
+
|
34
|
+
# Allow Collection class to be used in property definitions
|
35
|
+
Collection = MongoModel::Collection
|
36
|
+
|
37
|
+
undef_method :type if method_defined?(:type)
|
38
|
+
property :type, String, :as => '_type', :default => lambda { |doc| doc.class.name }, :protected => true
|
39
|
+
|
40
|
+
self.abstract_class = true
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
en:
|
2
|
+
mongomodel:
|
3
|
+
errors:
|
4
|
+
# The values :model, :attribute and :value are always available for interpolation
|
5
|
+
# The value :count is available when applicable. Can be used for pluralization.
|
6
|
+
messages:
|
7
|
+
inclusion: "is not included in the list"
|
8
|
+
exclusion: "is reserved"
|
9
|
+
invalid: "is invalid"
|
10
|
+
confirmation: "doesn't match confirmation"
|
11
|
+
accepted: "must be accepted"
|
12
|
+
empty: "can't be empty"
|
13
|
+
blank: "can't be blank"
|
14
|
+
too_long: "is too long (maximum is {{count}} characters)"
|
15
|
+
too_short: "is too short (minimum is {{count}} characters)"
|
16
|
+
wrong_length: "is the wrong length (should be {{count}} characters)"
|
17
|
+
taken: "has already been taken"
|
18
|
+
not_a_number: "is not a number"
|
19
|
+
greater_than: "must be greater than {{count}}"
|
20
|
+
greater_than_or_equal_to: "must be greater than or equal to {{count}}"
|
21
|
+
equal_to: "must be equal to {{count}}"
|
22
|
+
less_than: "must be less than {{count}}"
|
23
|
+
less_than_or_equal_to: "must be less than or equal to {{count}}"
|
24
|
+
odd: "must be odd"
|
25
|
+
even: "must be even"
|
26
|
+
document_invalid: "Validation failed: {{errors}}"
|
27
|
+
# Append your own errors here or at the model/attributes scope.
|
28
|
+
|
29
|
+
# You can define own errors for models or model attributes.
|
30
|
+
# The values :model, :attribute and :value are always available for interpolation.
|
31
|
+
#
|
32
|
+
# For example,
|
33
|
+
# models:
|
34
|
+
# user:
|
35
|
+
# blank: "This is a custom blank message for {{model}}: {{attribute}}"
|
36
|
+
# attributes:
|
37
|
+
# login:
|
38
|
+
# blank: "This is a custom blank message for User login"
|
39
|
+
# Will define custom blank validation message for User model and
|
40
|
+
# custom blank validation message for login attribute of User model.
|
41
|
+
#models:
|
42
|
+
|
43
|
+
# Translate model names. Used in Model.human_name().
|
44
|
+
#models:
|
45
|
+
# For example,
|
46
|
+
# user: "Dude"
|
47
|
+
# will translate User model name to "Dude"
|
48
|
+
|
49
|
+
# Translate model attribute names. Used in Model.human_attribute_name(attribute).
|
50
|
+
#attributes:
|
51
|
+
# For example,
|
52
|
+
# user:
|
53
|
+
# login: "Handle"
|
54
|
+
# will translate User attribute "login" as "Handle"
|
55
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module MongoModel
|
2
|
+
class Collection < Array
|
3
|
+
ARRAY_CONVERTER = Types.converter_for(Array)
|
4
|
+
|
5
|
+
class_inheritable_accessor :type
|
6
|
+
self.type = Object
|
7
|
+
|
8
|
+
def initialize(array=[])
|
9
|
+
super(array.map { |i| convert(i) })
|
10
|
+
end
|
11
|
+
|
12
|
+
def []=(index, value)
|
13
|
+
super(index, convert(value))
|
14
|
+
end
|
15
|
+
|
16
|
+
def <<(value)
|
17
|
+
super(convert(value))
|
18
|
+
end
|
19
|
+
|
20
|
+
def +(other)
|
21
|
+
self.class.new(super(other))
|
22
|
+
end
|
23
|
+
|
24
|
+
def concat(values)
|
25
|
+
super(values.map { |v| convert(v) })
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete(value)
|
29
|
+
super(convert(value))
|
30
|
+
end
|
31
|
+
|
32
|
+
def include?(value)
|
33
|
+
super(convert(value))
|
34
|
+
end
|
35
|
+
|
36
|
+
def index(value)
|
37
|
+
super(convert(value))
|
38
|
+
end
|
39
|
+
|
40
|
+
def insert(index, value)
|
41
|
+
super(index, convert(value))
|
42
|
+
end
|
43
|
+
|
44
|
+
def push(*values)
|
45
|
+
super(*values.map { |v| convert(v) })
|
46
|
+
end
|
47
|
+
|
48
|
+
def rindex(value)
|
49
|
+
super(convert(value))
|
50
|
+
end
|
51
|
+
|
52
|
+
def unshift(*values)
|
53
|
+
super(*values.map { |v| convert(v) })
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_mongo
|
57
|
+
ARRAY_CONVERTER.to_mongo(self)
|
58
|
+
end
|
59
|
+
|
60
|
+
def embedded_documents
|
61
|
+
select { |item| item.is_a?(EmbeddedDocument) }
|
62
|
+
end
|
63
|
+
|
64
|
+
class << self
|
65
|
+
def inspect
|
66
|
+
if type == Object
|
67
|
+
"Collection"
|
68
|
+
else
|
69
|
+
"Collection[#{type}]"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def [](type)
|
74
|
+
@collection_class_cache ||= {}
|
75
|
+
@collection_class_cache[type] ||= begin
|
76
|
+
collection = Class.new(Collection)
|
77
|
+
collection.type = type
|
78
|
+
collection
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def from_mongo(array)
|
83
|
+
new(array.map { |i| instantiate(i) })
|
84
|
+
end
|
85
|
+
|
86
|
+
def converter
|
87
|
+
@converter ||= Types.converter_for(type)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
def instantiate(item)
|
92
|
+
if item.is_a?(Hash) && item['_type']
|
93
|
+
item['_type'].constantize.from_mongo(item)
|
94
|
+
else
|
95
|
+
converter.from_mongo(item)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
def convert(value)
|
102
|
+
converter.cast(value)
|
103
|
+
end
|
104
|
+
|
105
|
+
def converter
|
106
|
+
self.class.converter
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_support/core_ext/hash/keys'
|
2
|
+
|
3
|
+
module MongoModel
|
4
|
+
class Configuration
|
5
|
+
def initialize(options)
|
6
|
+
@options = DEFAULTS.merge(options).stringify_keys
|
7
|
+
end
|
8
|
+
|
9
|
+
def host
|
10
|
+
@options['host']
|
11
|
+
end
|
12
|
+
|
13
|
+
def port
|
14
|
+
@options['port']
|
15
|
+
end
|
16
|
+
|
17
|
+
def database
|
18
|
+
@options['database']
|
19
|
+
end
|
20
|
+
|
21
|
+
def establish_connection
|
22
|
+
Mongo::Connection.new(host, port).db(database)
|
23
|
+
end
|
24
|
+
|
25
|
+
DEFAULTS = {
|
26
|
+
'host' => 'localhost',
|
27
|
+
'port' => 27017,
|
28
|
+
'database' => 'mongomodel-default'
|
29
|
+
}
|
30
|
+
|
31
|
+
def self.defaults
|
32
|
+
new({})
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Boolean < TrueClass; end
|
2
|
+
|
3
|
+
class Symbol
|
4
|
+
[:lt, :lte, :gt, :gte, :ne, :in, :nin, :mod, :all, :size, :exists].each do |operator|
|
5
|
+
define_method(operator) { MongoModel::MongoOperator.new(self, operator) }
|
6
|
+
end
|
7
|
+
|
8
|
+
define_method(:asc) { MongoModel::MongoOrder::Clause.new(self, :ascending) }
|
9
|
+
define_method(:desc) { MongoModel::MongoOrder::Clause.new(self, :descending) }
|
10
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module MongoModel
|
2
|
+
class DocumentNotFound < StandardError; end
|
3
|
+
|
4
|
+
class DocumentNotSaved < StandardError; end
|
5
|
+
|
6
|
+
# Raised by <tt>save!</tt> and <tt>create!</tt> when the document is invalid. Use the
|
7
|
+
# +document+ method to retrieve the document which did not validate.
|
8
|
+
# begin
|
9
|
+
# complex_operation_that_calls_save!_internally
|
10
|
+
# rescue MongoModel::DocumentInvalid => invalid
|
11
|
+
# puts invalid.document.errors
|
12
|
+
# end
|
13
|
+
class DocumentInvalid < DocumentNotSaved
|
14
|
+
attr_reader :document
|
15
|
+
|
16
|
+
def initialize(document)
|
17
|
+
@document = document
|
18
|
+
|
19
|
+
errors = @document.errors.full_messages.join(I18n.t('support.array.words_connector', :default => ', '))
|
20
|
+
super(I18n.t('mongomodel.errors.messages.document_invalid', :errors => errors))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class AssociationTypeMismatch < StandardError; end
|
25
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module MongoModel
|
2
|
+
class MongoOptions
|
3
|
+
ValidKeys = [ :conditions, :select, :offset, :limit, :order ]
|
4
|
+
|
5
|
+
attr_reader :selector, :options
|
6
|
+
|
7
|
+
def initialize(model, options={})
|
8
|
+
options.assert_valid_keys(ValidKeys)
|
9
|
+
|
10
|
+
@model = model
|
11
|
+
|
12
|
+
@selector = extract_conditions(options)
|
13
|
+
@options = extract_options(options)
|
14
|
+
|
15
|
+
add_type_to_selector
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_a
|
19
|
+
[selector, options]
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def extract_conditions(options)
|
24
|
+
result = {}
|
25
|
+
|
26
|
+
(options[:conditions] || {}).each do |k, v|
|
27
|
+
if k.is_a?(MongoOperator)
|
28
|
+
key = k.field
|
29
|
+
value = k.to_mongo_selector(v)
|
30
|
+
else
|
31
|
+
key = k
|
32
|
+
value = v
|
33
|
+
end
|
34
|
+
|
35
|
+
property = @model.properties[key]
|
36
|
+
|
37
|
+
result[property ? property.as : key] = value
|
38
|
+
end
|
39
|
+
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
def extract_options(options)
|
44
|
+
result = {}
|
45
|
+
|
46
|
+
result[:fields] = options[:select] if options[:select]
|
47
|
+
result[:skip] = options[:offset] if options[:offset]
|
48
|
+
result[:limit] = options[:limit] if options[:limit]
|
49
|
+
result[:sort] = MongoOrder.parse(options[:order]).to_sort(@model) if options[:order]
|
50
|
+
|
51
|
+
result
|
52
|
+
end
|
53
|
+
|
54
|
+
def convert_order(order)
|
55
|
+
case order
|
56
|
+
when Array
|
57
|
+
order.map { |clause|
|
58
|
+
key, sort = clause.split(/ /)
|
59
|
+
|
60
|
+
property = @model.properties[key.to_sym]
|
61
|
+
sort = (sort =~ /desc/i) ? :descending : :ascending
|
62
|
+
|
63
|
+
[property ? property.as : key, sort]
|
64
|
+
} if order.size > 0
|
65
|
+
when String, Symbol
|
66
|
+
convert_order(order.to_s.split(/,/).map { |c| c.strip })
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def add_type_to_selector
|
71
|
+
unless selector['_type'] || @model.superclass.abstract_class?
|
72
|
+
selector['_type'] = { '$in' => [@model.to_s] + @model.subclasses }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class MongoOrder
|
78
|
+
attr_reader :clauses
|
79
|
+
|
80
|
+
def initialize(*clauses)
|
81
|
+
@clauses = clauses
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
clauses.map { |c| c.to_s }.join(', ')
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_sort(model)
|
89
|
+
clauses.map { |c| c.to_sort(model.properties[c.field]) }
|
90
|
+
end
|
91
|
+
|
92
|
+
def ==(other)
|
93
|
+
other.is_a?(self.class) && clauses == other.clauses
|
94
|
+
end
|
95
|
+
|
96
|
+
def reverse
|
97
|
+
self.class.new(*clauses.map { |c| c.reverse })
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.parse(order)
|
101
|
+
case order
|
102
|
+
when MongoOrder
|
103
|
+
order
|
104
|
+
when Clause
|
105
|
+
new(order)
|
106
|
+
when Symbol
|
107
|
+
new(Clause.new(order))
|
108
|
+
when String
|
109
|
+
new(*order.split(',').map { |c| Clause.parse(c) })
|
110
|
+
when Array
|
111
|
+
new(*order.map { |c| Clause.parse(c) })
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class Clause
|
116
|
+
attr_reader :field, :order
|
117
|
+
|
118
|
+
def initialize(field, order=:ascending)
|
119
|
+
@field, @order = field.to_sym, order.to_sym
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_s
|
123
|
+
"#{field} #{order}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_sort(property)
|
127
|
+
[property ? property.as : field.to_s, order]
|
128
|
+
end
|
129
|
+
|
130
|
+
def reverse
|
131
|
+
self.class.new(field, order == :ascending ? :descending : :ascending)
|
132
|
+
end
|
133
|
+
|
134
|
+
def ==(other)
|
135
|
+
other.is_a?(self.class) && field == other.field && order == other.order
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.parse(clause)
|
139
|
+
case clause
|
140
|
+
when Clause
|
141
|
+
clause
|
142
|
+
when String, Symbol
|
143
|
+
field, order = clause.to_s.strip.split(/ /)
|
144
|
+
new(field, order =~ /^desc/i ? :descending : :ascending)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class MongoOperator
|
151
|
+
attr_reader :field, :operator
|
152
|
+
|
153
|
+
def initialize(field, operator)
|
154
|
+
@field, @operator = field, operator
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_mongo_selector(value)
|
158
|
+
{ "$#{operator}" => value }
|
159
|
+
end
|
160
|
+
|
161
|
+
def inspect
|
162
|
+
"#{field.inspect}.#{operator}"
|
163
|
+
end
|
164
|
+
|
165
|
+
def ==(other)
|
166
|
+
other.is_a?(self.class) && field == other.field && operator == other.operator
|
167
|
+
end
|
168
|
+
|
169
|
+
def hash
|
170
|
+
field.hash ^ operator.hash
|
171
|
+
end
|
172
|
+
|
173
|
+
def eql?(other)
|
174
|
+
self == other
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|