mongomodel 0.1.6 → 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/README.md +6 -0
- data/bin/console +4 -4
- data/lib/mongomodel.rb +4 -4
- data/lib/mongomodel/concerns/associations/base/definition.rb +4 -0
- data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +7 -16
- data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +6 -12
- data/lib/mongomodel/concerns/attributes.rb +1 -7
- data/lib/mongomodel/document.rb +0 -1
- data/lib/mongomodel/document/dynamic_finders.rb +2 -69
- data/lib/mongomodel/document/indexes.rb +0 -6
- data/lib/mongomodel/document/persistence.rb +1 -21
- data/lib/mongomodel/document/scopes.rb +59 -135
- data/lib/mongomodel/document/validations/uniqueness.rb +7 -5
- data/lib/mongomodel/support/dynamic_finder.rb +68 -0
- data/lib/mongomodel/support/mongo_operator.rb +29 -0
- data/lib/mongomodel/support/mongo_options.rb +0 -101
- data/lib/mongomodel/support/mongo_order.rb +78 -0
- data/lib/mongomodel/support/scope.rb +186 -0
- data/lib/mongomodel/support/scope/dynamic_finders.rb +21 -0
- data/lib/mongomodel/support/scope/finder_methods.rb +61 -0
- data/lib/mongomodel/support/scope/query_methods.rb +43 -0
- data/lib/mongomodel/support/scope/spawn_methods.rb +35 -0
- data/lib/mongomodel/version.rb +1 -1
- data/mongomodel.gemspec +20 -3
- data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +1 -1
- data/spec/mongomodel/concerns/associations/has_many_by_ids_spec.rb +1 -1
- data/spec/mongomodel/document/dynamic_finders_spec.rb +0 -1
- data/spec/mongomodel/document/finders_spec.rb +0 -144
- data/spec/mongomodel/document/indexes_spec.rb +2 -2
- data/spec/mongomodel/document/persistence_spec.rb +1 -15
- data/spec/mongomodel/document/scopes_spec.rb +64 -167
- data/spec/mongomodel/support/mongo_operator_spec.rb +29 -0
- data/spec/mongomodel/support/mongo_options_spec.rb +0 -150
- data/spec/mongomodel/support/mongo_order_spec.rb +127 -0
- data/spec/mongomodel/support/scope_spec.rb +932 -0
- data/spec/support/helpers/document_finder_stubs.rb +40 -0
- data/spec/support/matchers/find_with.rb +36 -0
- metadata +22 -5
- data/lib/mongomodel/document/finders.rb +0 -82
data/README.md
CHANGED
@@ -11,6 +11,10 @@ MongoModel is distributed as a gem. Install with:
|
|
11
11
|
|
12
12
|
gem install mongomodel
|
13
13
|
|
14
|
+
For performance, you should probably also install the BSON C extensions:
|
15
|
+
|
16
|
+
gem install bson_ext
|
17
|
+
|
14
18
|
|
15
19
|
Sample Usage
|
16
20
|
============
|
@@ -30,5 +34,7 @@ Sample Usage
|
|
30
34
|
validates_presence_of :title, :body
|
31
35
|
|
32
36
|
belongs_to :author, :class => User
|
37
|
+
|
38
|
+
scope :published, where(:published_at.ne => nil)
|
33
39
|
end
|
34
40
|
|
data/bin/console
CHANGED
@@ -31,15 +31,15 @@ IRB.conf[:MAIN_CONTEXT] = irb.context
|
|
31
31
|
examples = <<-EXAMPLES
|
32
32
|
|
33
33
|
class Article < MongoModel::Document
|
34
|
-
property :title, String
|
34
|
+
property :title, String, :required => true
|
35
35
|
property :published, Boolean, :default => false
|
36
36
|
|
37
37
|
timestamps!
|
38
38
|
|
39
|
-
default_scope
|
39
|
+
default_scope order(:title.asc)
|
40
40
|
|
41
|
-
|
42
|
-
|
41
|
+
scope :published, where(:published => true)
|
42
|
+
scope :latest, lambda { |num| order(:created_at.desc).limit(num) }
|
43
43
|
end
|
44
44
|
|
45
45
|
EXAMPLES
|
data/lib/mongomodel.rb
CHANGED
@@ -29,10 +29,12 @@ module MongoModel
|
|
29
29
|
autoload :ActiveModelCompatibility, 'mongomodel/concerns/activemodel'
|
30
30
|
|
31
31
|
autoload :MongoOptions, 'mongomodel/support/mongo_options'
|
32
|
-
autoload :MongoOrder, 'mongomodel/support/
|
33
|
-
autoload :MongoOperator, 'mongomodel/support/
|
32
|
+
autoload :MongoOrder, 'mongomodel/support/mongo_order'
|
33
|
+
autoload :MongoOperator, 'mongomodel/support/mongo_operator'
|
34
|
+
autoload :Scope, 'mongomodel/support/scope'
|
34
35
|
autoload :Types, 'mongomodel/support/types'
|
35
36
|
autoload :Configuration, 'mongomodel/support/configuration'
|
37
|
+
autoload :DynamicFinder, 'mongomodel/support/dynamic_finder'
|
36
38
|
|
37
39
|
autoload :Collection, 'mongomodel/support/collection'
|
38
40
|
|
@@ -66,11 +68,9 @@ module MongoModel
|
|
66
68
|
module DocumentExtensions
|
67
69
|
autoload :Persistence, 'mongomodel/document/persistence'
|
68
70
|
autoload :OptimisticLocking, 'mongomodel/document/optimistic_locking'
|
69
|
-
autoload :Finders, 'mongomodel/document/finders'
|
70
71
|
autoload :DynamicFinders, 'mongomodel/document/dynamic_finders'
|
71
72
|
autoload :Indexes, 'mongomodel/document/indexes'
|
72
73
|
autoload :Scopes, 'mongomodel/document/scopes'
|
73
|
-
autoload :Scope, 'mongomodel/document/scopes'
|
74
74
|
autoload :Validations, 'mongomodel/document/validations'
|
75
75
|
autoload :Callbacks, 'mongomodel/document/callbacks'
|
76
76
|
end
|
@@ -23,17 +23,17 @@ module MongoModel
|
|
23
23
|
delegate :foreign_key, :inverse_of, :to => :definition
|
24
24
|
|
25
25
|
def find_target
|
26
|
-
|
26
|
+
scoped + new_documents
|
27
27
|
end
|
28
28
|
|
29
29
|
def build(*args, &block)
|
30
|
-
doc =
|
30
|
+
doc = scoped.new(*args, &block)
|
31
31
|
new_documents << doc
|
32
32
|
doc
|
33
33
|
end
|
34
34
|
|
35
35
|
def create(*args, &block)
|
36
|
-
|
36
|
+
scoped.create(*args) do |doc|
|
37
37
|
if doc.respond_to?("#{inverse_of}=")
|
38
38
|
doc.send("#{inverse_of}=", instance)
|
39
39
|
else
|
@@ -45,7 +45,7 @@ module MongoModel
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def create!(*args, &block)
|
48
|
-
|
48
|
+
scoped.create!(*args) do |doc|
|
49
49
|
if doc.respond_to?("#{inverse_of}=")
|
50
50
|
doc.send("#{inverse_of}=", instance)
|
51
51
|
else
|
@@ -82,17 +82,8 @@ module MongoModel
|
|
82
82
|
doc.save(false) unless doc.new_record?
|
83
83
|
end
|
84
84
|
|
85
|
-
def
|
86
|
-
|
87
|
-
fk_conditions = { foreign_key => instance.id }
|
88
|
-
|
89
|
-
klass.instance_eval do
|
90
|
-
with_scope(:find => { :conditions => fk_conditions }) do
|
91
|
-
with_scope(:find => scope_options) do
|
92
|
-
send(*args, &block)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
85
|
+
def scoped
|
86
|
+
definition.scope.where(foreign_key => instance.id)
|
96
87
|
end
|
97
88
|
|
98
89
|
protected
|
@@ -183,7 +174,7 @@ module MongoModel
|
|
183
174
|
if target.respond_to?(method_id) && !OVERRIDE_METHODS.include?(method_id.to_sym)
|
184
175
|
super(method_id, *args, &block)
|
185
176
|
else
|
186
|
-
association.
|
177
|
+
association.scoped.send(method_id, *args, &block)
|
187
178
|
end
|
188
179
|
end
|
189
180
|
end
|
@@ -29,27 +29,21 @@ module MongoModel
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def find_target
|
32
|
-
ids.any? ? Array(
|
32
|
+
ids.any? ? Array.wrap(definition.scope.find(ids - new_document_ids)) + new_documents : []
|
33
33
|
end
|
34
34
|
|
35
35
|
def build(*args, &block)
|
36
|
-
doc =
|
36
|
+
doc = scoped.new(*args, &block)
|
37
37
|
new_documents << doc
|
38
38
|
doc
|
39
39
|
end
|
40
40
|
|
41
41
|
def create(*args, &block)
|
42
|
-
|
42
|
+
scoped.create(*args, &block)
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
klass.instance_eval do
|
49
|
-
with_scope(:find => { :conditions => { :id.in => scope_ids } }) do
|
50
|
-
send(*args, &block)
|
51
|
-
end
|
52
|
-
end
|
45
|
+
def scoped
|
46
|
+
definition.scope.where(:id.in => ids)
|
53
47
|
end
|
54
48
|
|
55
49
|
protected
|
@@ -166,7 +160,7 @@ module MongoModel
|
|
166
160
|
if target.respond_to?(method_id) && !OVERRIDE_METHODS.include?(method_id.to_sym)
|
167
161
|
super(method_id, *args, &block)
|
168
162
|
else
|
169
|
-
association.
|
163
|
+
association.scoped.send(method_id, *args, &block)
|
170
164
|
end
|
171
165
|
end
|
172
166
|
end
|
@@ -4,13 +4,7 @@ module MongoModel
|
|
4
4
|
module Attributes
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
-
|
8
|
-
alias_method_chain :initialize, :attributes
|
9
|
-
end
|
10
|
-
|
11
|
-
def initialize_with_attributes(attrs={})
|
12
|
-
initialize_without_attributes
|
13
|
-
|
7
|
+
def initialize(attrs={})
|
14
8
|
self.attributes = attrs
|
15
9
|
yield self if block_given?
|
16
10
|
end
|
data/lib/mongomodel/document.rb
CHANGED
@@ -2,7 +2,7 @@ module MongoModel
|
|
2
2
|
module DocumentExtensions
|
3
3
|
module DynamicFinders
|
4
4
|
def respond_to?(method_id, include_private = false)
|
5
|
-
if DynamicFinder.match(
|
5
|
+
if DynamicFinder.match(scoped, method_id)
|
6
6
|
true
|
7
7
|
else
|
8
8
|
super
|
@@ -10,79 +10,12 @@ module MongoModel
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def method_missing(method_id, *args, &block)
|
13
|
-
if finder = DynamicFinder.match(
|
13
|
+
if finder = DynamicFinder.match(scoped, method_id)
|
14
14
|
finder.execute(*args)
|
15
15
|
else
|
16
16
|
super
|
17
17
|
end
|
18
18
|
end
|
19
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
20
|
end
|
88
21
|
end
|
@@ -19,7 +19,7 @@ module MongoModel
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def delete
|
22
|
-
self.class.delete(id)
|
22
|
+
self.class.unscoped.delete(id)
|
23
23
|
set_destroyed(true)
|
24
24
|
freeze
|
25
25
|
end
|
@@ -65,14 +65,6 @@ module MongoModel
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
def delete(id_or_conditions)
|
69
|
-
collection.remove(MongoOptions.new(self, :conditions => id_to_conditions(id_or_conditions)).selector)
|
70
|
-
end
|
71
|
-
|
72
|
-
def destroy(id_or_conditions)
|
73
|
-
find(:all, :conditions => id_to_conditions(id_or_conditions)).each { |instance| instance.destroy }
|
74
|
-
end
|
75
|
-
|
76
68
|
def from_mongo(document)
|
77
69
|
instance = super
|
78
70
|
instance.send(:instantiate, document)
|
@@ -102,18 +94,6 @@ module MongoModel
|
|
102
94
|
def save_safely=(val)
|
103
95
|
@save_safely = val
|
104
96
|
end
|
105
|
-
|
106
|
-
private
|
107
|
-
def id_to_conditions(id_or_conditions)
|
108
|
-
case id_or_conditions
|
109
|
-
when String
|
110
|
-
{ :id => id_or_conditions }
|
111
|
-
when Array
|
112
|
-
{ :id.in => id_or_conditions }
|
113
|
-
else
|
114
|
-
id_or_conditions
|
115
|
-
end
|
116
|
-
end
|
117
97
|
end
|
118
98
|
|
119
99
|
private
|
@@ -1,161 +1,85 @@
|
|
1
|
-
require 'active_support/core_ext/module/aliasing'
|
2
|
-
require 'active_support/core_ext/module/delegation'
|
3
|
-
require 'active_support/core_ext/kernel/singleton_class'
|
4
|
-
|
5
1
|
module MongoModel
|
6
2
|
module DocumentExtensions
|
7
3
|
module Scopes
|
8
4
|
extend ActiveSupport::Concern
|
9
|
-
|
10
|
-
included do
|
11
|
-
class << self
|
12
|
-
[:find, :count].each do |method|
|
13
|
-
alias_method_chain method, :scope
|
14
|
-
end
|
15
|
-
end
|
16
5
|
|
17
|
-
|
6
|
+
delegate :current_scope, :to => "self.class"
|
7
|
+
|
8
|
+
def initialize(*)
|
9
|
+
self.attributes = current_scope.options_for_create
|
10
|
+
super
|
18
11
|
end
|
19
|
-
|
12
|
+
|
20
13
|
module ClassMethods
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
named_scopes[name] = options
|
14
|
+
delegate :find, :first, :last, :all, :exists?, :count, :to => :scoped
|
15
|
+
delegate :delete, :delete_all, :destroy, :destroy_all, :to => :scoped
|
16
|
+
delegate :select, :order, :where, :limit, :offset, :from, :to => :scoped
|
25
17
|
|
26
|
-
|
27
|
-
|
28
|
-
scope(name).apply(*args)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
#
|
34
|
-
def default_scope(options={})
|
35
|
-
if options.empty?
|
36
|
-
read_inheritable_attribute(:default_scope) || write_inheritable_attribute(:default_scope, Scope.new(self))
|
37
|
-
else
|
38
|
-
write_inheritable_attribute(:default_scope, default_scope.merge(:find => options))
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
#
|
43
|
-
def with_scope(options={}, &block)
|
44
|
-
push_scope(current_scope.merge(options), &block)
|
18
|
+
def unscoped
|
19
|
+
@unscoped ||= MongoModel::Scope.new(self)
|
45
20
|
end
|
46
|
-
|
47
|
-
#
|
48
|
-
def with_exclusive_scope(options={}, &block)
|
49
|
-
push_scope(Scope.new(self, options), &block)
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
def push_scope(scope, &block)
|
54
|
-
scopes << scope
|
55
|
-
yield
|
56
|
-
ensure
|
57
|
-
scopes.pop
|
58
|
-
end
|
59
|
-
|
60
|
-
def find_with_scope(*args)
|
61
|
-
options = args.extract_options!
|
62
|
-
options = current_scope.options_for(:find).deep_merge(options)
|
63
21
|
|
64
|
-
|
22
|
+
def scoped
|
23
|
+
current_scope.clone
|
65
24
|
end
|
66
|
-
|
67
|
-
def count_with_scope(conditions={})
|
68
|
-
scope_conditions = current_scope.options_for(:find)[:conditions] || {}
|
69
25
|
|
70
|
-
|
26
|
+
def scopes
|
27
|
+
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
|
71
28
|
end
|
72
|
-
|
73
|
-
def
|
74
|
-
|
29
|
+
|
30
|
+
def scope(name, scope)
|
31
|
+
name = name.to_sym
|
32
|
+
|
33
|
+
if !scopes[name] && respond_to?(name, true)
|
34
|
+
logger.warn "Creating scope :#{name}. " \
|
35
|
+
"Overwriting existing method #{self.name}.#{name}."
|
36
|
+
end
|
37
|
+
|
38
|
+
scopes[name] = lambda do |*args|
|
39
|
+
scope.is_a?(Proc) ? scope.call(*args) : scope
|
40
|
+
end
|
41
|
+
|
42
|
+
singleton_class.class_eval do
|
43
|
+
define_method(name) do |*args|
|
44
|
+
scopes[name].call(*args)
|
45
|
+
end
|
46
|
+
end
|
75
47
|
end
|
76
|
-
|
77
|
-
def scope
|
78
|
-
|
48
|
+
|
49
|
+
def default_scope(scope)
|
50
|
+
reset_current_scopes
|
51
|
+
previous_scope = default_scoping.last || unscoped
|
52
|
+
default_scoping << previous_scope.merge(scope)
|
79
53
|
end
|
80
54
|
|
81
|
-
|
82
|
-
|
55
|
+
protected
|
56
|
+
def with_scope(scope, &block)
|
57
|
+
current_scopes << current_scope.merge(scope)
|
58
|
+
|
59
|
+
begin
|
60
|
+
yield
|
61
|
+
ensure
|
62
|
+
current_scopes.pop
|
63
|
+
end
|
83
64
|
end
|
84
|
-
|
65
|
+
|
66
|
+
private
|
85
67
|
def current_scope
|
86
|
-
|
68
|
+
current_scopes.last || unscoped
|
87
69
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
class Scope
|
92
|
-
attr_reader :model, :options
|
93
|
-
|
94
|
-
delegate :with_scope, :with_exclusive_scope, :scope, :to => :model
|
95
|
-
delegate :inspect, :to => :proxy_found
|
96
|
-
|
97
|
-
def initialize(model, options={})
|
98
|
-
if options.is_a?(Proc) || options.has_key?(:find) || options.has_key?(:create)
|
99
|
-
@options = options
|
100
|
-
else
|
101
|
-
@exclusive = options.delete(:exclusive)
|
102
|
-
@options = { :find => options }
|
70
|
+
|
71
|
+
def current_scopes
|
72
|
+
Thread.current[:"#{self}_scopes"] ||= default_scoping.dup
|
103
73
|
end
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
def merge(scope)
|
109
|
-
raise ArgumentError, "Scope must be applied before it can be merged" unless options.is_a?(Hash)
|
110
|
-
options_to_merge = scope.is_a?(self.class) ? scope.options : scope
|
111
|
-
self.class.new(model, options.deep_merge(options_to_merge))
|
112
|
-
end
|
113
|
-
|
114
|
-
def merge!(scope)
|
115
|
-
raise ArgumentError, "Scope must be applied before it can be merged" unless options.is_a?(Hash)
|
116
|
-
options_to_merge = scope.is_a?(self.class) ? scope.options : scope
|
117
|
-
options.deep_merge!(options_to_merge)
|
118
|
-
self
|
119
|
-
end
|
120
|
-
|
121
|
-
def reload
|
122
|
-
@found = nil
|
123
|
-
self
|
124
|
-
end
|
125
|
-
|
126
|
-
def apply(*args)
|
127
|
-
if options.is_a?(Hash)
|
128
|
-
reload
|
129
|
-
else
|
130
|
-
self.class.new(model, options.call(*args))
|
74
|
+
|
75
|
+
def reset_current_scopes
|
76
|
+
Thread.current[:"#{self}_scopes"] = nil
|
131
77
|
end
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
options[action] || {}
|
136
|
-
end
|
137
|
-
|
138
|
-
def exclusive?
|
139
|
-
@exclusive == true
|
140
|
-
end
|
141
|
-
|
142
|
-
protected
|
143
|
-
def proxy_found
|
144
|
-
@found ||= find(:all)
|
145
|
-
end
|
146
|
-
|
147
|
-
private
|
148
|
-
def method_missing(method, *args, &block)
|
149
|
-
if scope = scope(method)
|
150
|
-
scope.exclusive? ? scope : merge(scope.apply(*args))
|
151
|
-
else
|
152
|
-
send(scope_type, options) { model.send(method, *args, &block) }
|
78
|
+
|
79
|
+
def default_scoping
|
80
|
+
read_inheritable_attribute(:default_scoping) || write_inheritable_attribute(:default_scoping, [])
|
153
81
|
end
|
154
82
|
end
|
155
|
-
|
156
|
-
def scope_type
|
157
|
-
exclusive? ? :with_exclusive_scope : :with_scope
|
158
|
-
end
|
159
83
|
end
|
160
84
|
end
|
161
85
|
end
|