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,29 @@
|
|
1
|
+
module MongoModel
|
2
|
+
module PrettyInspect
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
# Returns a string like 'Post(title:String, body:String)'
|
7
|
+
def inspect
|
8
|
+
if [Document, EmbeddedDocument].include?(self)
|
9
|
+
super
|
10
|
+
else
|
11
|
+
attr_list = model_properties.map { |name, property| "#{name}: #{property.type.inspect}" } * ', '
|
12
|
+
"#{super}(#{attr_list})"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the contents of the document as a nicely formatted string.
|
18
|
+
def inspect
|
19
|
+
"#<#{self.class.name} #{attributes_for_inspect}>"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def attributes_for_inspect
|
24
|
+
attrs = self.class.model_properties.map { |name, property| "#{name}: #{send(name).inspect}" }
|
25
|
+
attrs.unshift "id: #{id}" if self.class.properties.include?(:id)
|
26
|
+
attrs * ', '
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'active_support/core_ext/class/inheritable_attributes'
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
require 'active_support/core_ext/array/extract_options'
|
4
|
+
require 'active_support/core_ext/hash/except'
|
5
|
+
|
6
|
+
module MongoModel
|
7
|
+
module Properties
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
class_inheritable_accessor :properties
|
12
|
+
self.properties = ActiveSupport::OrderedHash.new
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def property(name, type, options={})
|
17
|
+
properties[name.to_sym] = Property.new(name, type, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def model_properties
|
21
|
+
properties.reject { |k, p| p.internal? }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Property
|
26
|
+
delegate :cast, :boolean, :to_mongo, :from_mongo, :to => :type_converter
|
27
|
+
|
28
|
+
attr_reader :name, :type, :options
|
29
|
+
|
30
|
+
def initialize(name, type, options={})
|
31
|
+
@name, @type, @options = name.to_sym, type, options
|
32
|
+
end
|
33
|
+
|
34
|
+
def as
|
35
|
+
options[:as] || name.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def default(instance)
|
39
|
+
default = options[:default]
|
40
|
+
|
41
|
+
if default.respond_to?(:call)
|
42
|
+
case default.arity
|
43
|
+
when 0 then default.call
|
44
|
+
else default.call(instance)
|
45
|
+
end
|
46
|
+
else
|
47
|
+
default.duplicable? ? default.dup : default
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def ==(other)
|
52
|
+
other.is_a?(self.class) && name == other.name && type == other.type && options == other.options
|
53
|
+
end
|
54
|
+
|
55
|
+
def embeddable?
|
56
|
+
type.ancestors.include?(EmbeddedDocument)
|
57
|
+
end
|
58
|
+
|
59
|
+
def internal?
|
60
|
+
as =~ /^_/ || options[:internal]
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def type_converter
|
65
|
+
@type_converter ||= Types.converter_for(type)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'active_support/core_ext/module/aliasing'
|
2
|
+
|
3
|
+
module MongoModel
|
4
|
+
module RecordStatus
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
alias_method_chain :initialize, :record_status
|
9
|
+
end
|
10
|
+
|
11
|
+
def new_record?
|
12
|
+
@_new_record
|
13
|
+
end
|
14
|
+
|
15
|
+
def destroyed?
|
16
|
+
@_destroyed
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize_with_record_status(*args, &block)
|
20
|
+
set_new_record(true)
|
21
|
+
set_destroyed(false)
|
22
|
+
|
23
|
+
initialize_without_record_status(*args, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
def set_new_record(value)
|
28
|
+
set_record_status(:new_record, value)
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_destroyed(value)
|
32
|
+
set_record_status(:destroyed, value)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def set_record_status(type, value)
|
37
|
+
instance_variable_set("@_#{type}", value)
|
38
|
+
embedded_documents.each { |doc| doc.send(:set_record_status, type, value) }
|
39
|
+
value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module MongoModel
|
2
|
+
# MongoModel automatically timestamps create and update operations if the document has properties
|
3
|
+
# named created_at/created_on or updated_at/updated_on.
|
4
|
+
module Timestamps #:nodoc:
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
before_save :set_update_timestamps
|
9
|
+
before_create :set_create_timestamps
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# Defines timestamp properties created_at and updated_at.
|
14
|
+
# When the document is created or updated, these properties will be respectively updated.
|
15
|
+
def timestamps!
|
16
|
+
property :created_at, Time
|
17
|
+
property :updated_at, Time
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def set_update_timestamps
|
23
|
+
write_attribute(:updated_at, Time.now) if properties.include?(:updated_at)
|
24
|
+
write_attribute(:updated_on, Time.now) if properties.include?(:updated_on)
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_create_timestamps
|
28
|
+
write_attribute(:created_at, Time.now) if properties.include?(:created_at) && !query_attribute(:created_at)
|
29
|
+
write_attribute(:created_on, Time.now) if properties.include?(:created_on) && !query_attribute(:created_on)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module MongoModel
|
2
|
+
module Validations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
include ActiveModel::Validations
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def property(name, *args, &block) #:nodoc:
|
9
|
+
property = super
|
10
|
+
|
11
|
+
validates_associated(name) if property.embeddable?
|
12
|
+
validates_presence_of(name) if property.options[:required]
|
13
|
+
validates_format_of(name, property.options[:format]) if property.options[:format]
|
14
|
+
|
15
|
+
property
|
16
|
+
end
|
17
|
+
|
18
|
+
# Set the i18n scope to overwrite ActiveModel.
|
19
|
+
def i18n_scope #:nodoc:
|
20
|
+
:mongomodel
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid?
|
25
|
+
errors.clear
|
26
|
+
|
27
|
+
@_on_validate = new_record? ? :create : :update
|
28
|
+
run_callbacks(:validate)
|
29
|
+
|
30
|
+
errors.empty?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
|
36
|
+
filename = File.basename(path)
|
37
|
+
require "mongomodel/concerns/validations/#{filename}"
|
38
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module MongoModel
|
2
|
+
module Validations
|
3
|
+
module ClassMethods
|
4
|
+
# Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
|
5
|
+
#
|
6
|
+
# class Book < MongoModel::Document
|
7
|
+
# has_many :pages
|
8
|
+
# belongs_to :library
|
9
|
+
#
|
10
|
+
# validates_associated :pages, :library
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Warning: If, after the above definition, you then wrote:
|
14
|
+
#
|
15
|
+
# class Page < MongoModel::Document
|
16
|
+
# belongs_to :book
|
17
|
+
#
|
18
|
+
# validates_associated :book
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# this would specify a circular dependency and cause infinite recursion.
|
22
|
+
#
|
23
|
+
# NOTE: This validation will not fail if the association hasn't been assigned. If you want to ensure that the association
|
24
|
+
# is both present and guaranteed to be valid, you also need to use +validates_presence_of+.
|
25
|
+
#
|
26
|
+
# Configuration options:
|
27
|
+
# * <tt>:message</tt> - A custom error message (default is: "is invalid")
|
28
|
+
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
29
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
30
|
+
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
|
31
|
+
# method, proc or string should return or evaluate to a true or false value.
|
32
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
33
|
+
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
34
|
+
# method, proc or string should return or evaluate to a true or false value.
|
35
|
+
def validates_associated(*attr_names)
|
36
|
+
configuration = attr_names.extract_options!
|
37
|
+
|
38
|
+
validates_each(attr_names, configuration) do |record, attr_name, value|
|
39
|
+
unless (value.is_a?(Array) ? value : [value]).collect { |r| r.nil? || r.valid? }.all?
|
40
|
+
record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
2
|
+
require 'active_support/core_ext/hash/deep_merge'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
|
5
|
+
module MongoModel
|
6
|
+
class Document < EmbeddedDocument
|
7
|
+
include DocumentExtensions::Persistence
|
8
|
+
include DocumentExtensions::OptimisticLocking
|
9
|
+
|
10
|
+
extend DocumentExtensions::Finders
|
11
|
+
extend DocumentExtensions::DynamicFinders
|
12
|
+
include DocumentExtensions::Indexes
|
13
|
+
|
14
|
+
include DocumentExtensions::Scopes
|
15
|
+
include DocumentExtensions::Validations
|
16
|
+
include DocumentExtensions::Callbacks
|
17
|
+
|
18
|
+
self.abstract_class = true
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module MongoModel
|
2
|
+
module DocumentExtensions
|
3
|
+
module Callbacks
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
[:instantiate, :create_or_update, :create, :update, :destroy].each do |method|
|
8
|
+
alias_method_chain method, :callbacks
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def instantiate_with_callbacks(*args) #:nodoc:
|
13
|
+
instantiate_without_callbacks(*args)
|
14
|
+
run_callbacks_with_embedded(:find)
|
15
|
+
end
|
16
|
+
private :instantiate_with_callbacks
|
17
|
+
|
18
|
+
def create_or_update_with_callbacks #:nodoc:
|
19
|
+
run_callbacks_with_embedded(:save) do
|
20
|
+
create_or_update_without_callbacks
|
21
|
+
end
|
22
|
+
end
|
23
|
+
private :create_or_update_with_callbacks
|
24
|
+
|
25
|
+
def create_with_callbacks #:nodoc:
|
26
|
+
run_callbacks_with_embedded(:create) do
|
27
|
+
create_without_callbacks
|
28
|
+
end
|
29
|
+
end
|
30
|
+
private :create_with_callbacks
|
31
|
+
|
32
|
+
def update_with_callbacks(*args) #:nodoc:
|
33
|
+
run_callbacks_with_embedded(:update) do
|
34
|
+
update_without_callbacks(*args)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
private :update_with_callbacks
|
38
|
+
|
39
|
+
def destroy_with_callbacks #:nodoc:
|
40
|
+
run_callbacks_with_embedded(:destroy) do
|
41
|
+
destroy_without_callbacks
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module MongoModel
|
2
|
+
module DocumentExtensions
|
3
|
+
module DynamicFinders
|
4
|
+
def respond_to?(method_id, include_private = false)
|
5
|
+
if DynamicFinder.match(self, method_id)
|
6
|
+
true
|
7
|
+
else
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(method_id, *args, &block)
|
13
|
+
if finder = DynamicFinder.match(self, method_id)
|
14
|
+
finder.execute(*args)
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class DynamicFinder
|
22
|
+
def initialize(model, attribute_names, finder=:first, bang=false)
|
23
|
+
@model, @attribute_names, @finder, @bang = model, attribute_names, finder, bang
|
24
|
+
end
|
25
|
+
|
26
|
+
def execute(*args)
|
27
|
+
options = args.extract_options!
|
28
|
+
conditions = build_conditions(args)
|
29
|
+
|
30
|
+
result = @model.send(instantiator? ? :first : @finder, options.deep_merge(:conditions => conditions))
|
31
|
+
|
32
|
+
if result.nil?
|
33
|
+
if bang?
|
34
|
+
raise DocumentNotFound, "Couldn't find #{@model.to_s} with #{conditions.inspect}"
|
35
|
+
elsif instantiator?
|
36
|
+
return @model.send(@finder, conditions)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
result
|
41
|
+
end
|
42
|
+
|
43
|
+
def bang?
|
44
|
+
@bang
|
45
|
+
end
|
46
|
+
|
47
|
+
def instantiator?
|
48
|
+
@finder == :new || @finder == :create
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.match(model, method)
|
52
|
+
finder = :first
|
53
|
+
bang = false
|
54
|
+
|
55
|
+
case method.to_s
|
56
|
+
when /^find_(all_by|last_by|by)_([_a-zA-Z]\w*)$/
|
57
|
+
finder = :last if $1 == 'last_by'
|
58
|
+
finder = :all if $1 == 'all_by'
|
59
|
+
names = $2
|
60
|
+
when /^find_by_([_a-zA-Z]\w*)\!$/
|
61
|
+
bang = true
|
62
|
+
names = $1
|
63
|
+
when /^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/
|
64
|
+
finder = ($1 == 'initialize' ? :new : :create)
|
65
|
+
names = $2
|
66
|
+
else
|
67
|
+
return nil
|
68
|
+
end
|
69
|
+
|
70
|
+
names = names.split('_and_')
|
71
|
+
if names.all? { |n| model.properties.include?(n.to_sym) }
|
72
|
+
new(model, names, finder, bang)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def build_conditions(args)
|
78
|
+
result = {}
|
79
|
+
|
80
|
+
@attribute_names.zip(args) do |attribute, value|
|
81
|
+
result[attribute.to_sym] = value
|
82
|
+
end
|
83
|
+
|
84
|
+
result
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'active_support/core_ext/hash/keys'
|
2
|
+
|
3
|
+
module MongoModel
|
4
|
+
module DocumentExtensions
|
5
|
+
module Finders
|
6
|
+
def find(*args)
|
7
|
+
options = args.extract_options!
|
8
|
+
|
9
|
+
case args.first
|
10
|
+
when :first then find_first(options)
|
11
|
+
when :last then find_last(options)
|
12
|
+
when :all then find_all(options)
|
13
|
+
else find_by_ids(args, options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def first(options={})
|
18
|
+
find(:first, options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def last(options={})
|
22
|
+
find(:last, options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def all(options={})
|
26
|
+
find(:all, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def count(conditions={})
|
30
|
+
_find(:conditions => conditions).count
|
31
|
+
end
|
32
|
+
|
33
|
+
def exists?(id_or_conditions)
|
34
|
+
case id_or_conditions
|
35
|
+
when String
|
36
|
+
exists?(:id => id_or_conditions)
|
37
|
+
else
|
38
|
+
count(id_or_conditions) > 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
def find_first(options={})
|
44
|
+
_find_and_instantiate(options.merge(:limit => 1)).first
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_last(options={})
|
48
|
+
order = MongoOrder.parse(options[:order]) || :id.asc
|
49
|
+
_find_and_instantiate(options.merge(:order => order.reverse, :limit => 1)).first
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_all(options={})
|
53
|
+
_find_and_instantiate(options)
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_by_ids(ids, options={})
|
57
|
+
ids.flatten!
|
58
|
+
|
59
|
+
case ids.size
|
60
|
+
when 0
|
61
|
+
raise ArgumentError, "At least one id must be specified"
|
62
|
+
when 1
|
63
|
+
id = ids.first.to_s
|
64
|
+
_find_and_instantiate(options.deep_merge(:conditions => { :id => id })).first || raise(DocumentNotFound, "Couldn't find document with id: #{id}")
|
65
|
+
else
|
66
|
+
docs = _find_and_instantiate(options.deep_merge(:conditions => { :id.in => ids.map { |id| id.to_s } }))
|
67
|
+
raise DocumentNotFound if docs.size != ids.size
|
68
|
+
docs.sort_by { |doc| ids.index(doc.id) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def _find(options={})
|
73
|
+
selector, options = MongoOptions.new(self, options).to_a
|
74
|
+
collection.find(selector, options)
|
75
|
+
end
|
76
|
+
|
77
|
+
def _find_and_instantiate(options={})
|
78
|
+
_find(options).to_a.map { |doc| from_mongo(doc) }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|