mongomodel 0.1
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/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
|