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,33 @@
|
|
|
1
|
+
module MongoModel
|
|
2
|
+
module Associations
|
|
3
|
+
module Base
|
|
4
|
+
class Association
|
|
5
|
+
attr_reader :definition, :instance
|
|
6
|
+
delegate :name, :klass, :polymorphic?, :to => :definition
|
|
7
|
+
|
|
8
|
+
def initialize(definition, instance)
|
|
9
|
+
@definition, @instance = definition, instance
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def proxy
|
|
13
|
+
@proxy ||= proxy_class.new(self)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def replace(obj)
|
|
17
|
+
proxy.target = obj
|
|
18
|
+
proxy
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
protected
|
|
22
|
+
def ensure_class(value)
|
|
23
|
+
raise AssociationTypeMismatch, "expected instance of #{klass} but got #{value.class}" unless value.is_a?(klass)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
def proxy_class
|
|
28
|
+
self.class.parent::Proxy rescue Base::Proxy
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module MongoModel
|
|
2
|
+
module Associations
|
|
3
|
+
module Base
|
|
4
|
+
class Definition
|
|
5
|
+
attr_reader :owner, :name, :options
|
|
6
|
+
|
|
7
|
+
def initialize(owner, name, options={})
|
|
8
|
+
@owner, @name, @options = owner, name, options
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def for(instance)
|
|
12
|
+
association_class.new(self, instance)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def define!
|
|
16
|
+
owner.instance_exec(self, &self.class.properties) if self.class.properties
|
|
17
|
+
owner.instance_exec(self, &self.class.methods) if self.class.methods
|
|
18
|
+
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def klass
|
|
23
|
+
case options[:class]
|
|
24
|
+
when Class
|
|
25
|
+
options[:class]
|
|
26
|
+
when String
|
|
27
|
+
options[:class].constantize
|
|
28
|
+
else
|
|
29
|
+
name.to_s.classify.constantize
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def singular_name
|
|
34
|
+
name.to_s.singularize
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def polymorphic?
|
|
38
|
+
options[:polymorphic]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.properties(&block)
|
|
42
|
+
block_given? ? write_inheritable_attribute(:properties, block) : read_inheritable_attribute(:properties)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.methods(&block)
|
|
46
|
+
block_given? ? write_inheritable_attribute(:methods, block) : read_inheritable_attribute(:methods)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
def association_class
|
|
51
|
+
self.class::Association rescue Base::Association
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module MongoModel
|
|
2
|
+
module Associations
|
|
3
|
+
module Base
|
|
4
|
+
class Proxy
|
|
5
|
+
alias_method :proxy_respond_to?, :respond_to?
|
|
6
|
+
alias_method :proxy_extend, :extend
|
|
7
|
+
|
|
8
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
|
9
|
+
|
|
10
|
+
attr_reader :association
|
|
11
|
+
|
|
12
|
+
def initialize(association)
|
|
13
|
+
@association = association
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def target=(new_target)
|
|
17
|
+
@target = new_target
|
|
18
|
+
loaded!
|
|
19
|
+
@target
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def target
|
|
23
|
+
load_target
|
|
24
|
+
@target
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def loaded?
|
|
28
|
+
@loaded
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def loaded!
|
|
32
|
+
@loaded = true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def reset
|
|
36
|
+
@loaded = false
|
|
37
|
+
@target = nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def respond_to?(*args)
|
|
41
|
+
proxy_respond_to?(*args) || target.respond_to?(*args)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
def method_missing(*args, &block)
|
|
46
|
+
target.send(*args, &block)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def load_target
|
|
50
|
+
@target = @association.find_target unless loaded?
|
|
51
|
+
loaded!
|
|
52
|
+
rescue MongoModel::DocumentNotFound
|
|
53
|
+
reset
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module MongoModel
|
|
2
|
+
module Associations
|
|
3
|
+
class BelongsTo < Base::Definition
|
|
4
|
+
def foreign_key
|
|
5
|
+
:"#{name}_id"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def type_key
|
|
9
|
+
:"#{name}_type"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
properties do |association|
|
|
13
|
+
property association.foreign_key, String, :internal => true
|
|
14
|
+
property association.type_key, String, :internal => true if association.polymorphic?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
methods do |association|
|
|
18
|
+
define_method(association.name) do |*args|
|
|
19
|
+
force_reload = args.first || false
|
|
20
|
+
|
|
21
|
+
associations[association.name].proxy.reset if force_reload
|
|
22
|
+
associations[association.name].proxy
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
define_method("#{association.name}=") { |obj| associations[association.name].replace(obj) }
|
|
26
|
+
|
|
27
|
+
unless association.polymorphic?
|
|
28
|
+
define_method("build_#{association.name}") do |*args|
|
|
29
|
+
associations[association.name].replace(association.klass.new(*args))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
define_method("create_#{association.name}") do |*args|
|
|
33
|
+
associations[association.name].replace(association.klass.create(*args))
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class Association < Base::Association
|
|
39
|
+
delegate :foreign_key, :type_key, :to => :definition
|
|
40
|
+
|
|
41
|
+
def target_id
|
|
42
|
+
instance[foreign_key]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def target_class
|
|
46
|
+
if polymorphic?
|
|
47
|
+
instance[type_key].constantize rescue nil
|
|
48
|
+
else
|
|
49
|
+
klass
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def replace(obj)
|
|
54
|
+
ensure_class(obj) unless polymorphic?
|
|
55
|
+
|
|
56
|
+
instance[foreign_key] = obj.id
|
|
57
|
+
instance[type_key] = obj.class if polymorphic?
|
|
58
|
+
|
|
59
|
+
super
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def find_target
|
|
63
|
+
target_class.find(target_id) unless target_id.nil? || target_class.nil?
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
module MongoModel
|
|
2
|
+
module Associations
|
|
3
|
+
class HasManyByForeignKey < Base::Definition
|
|
4
|
+
def foreign_key
|
|
5
|
+
options[:foreign_key] || :"#{inverse_of}_id"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def inverse_of
|
|
9
|
+
options[:inverse_of] || owner.to_s.downcase.demodulize.singularize.to_sym
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def define!
|
|
13
|
+
raise "has_many :by => :foreign_key is only valid on Document" unless owner.ancestors.include?(Document)
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
methods do |association|
|
|
18
|
+
define_method(association.name) { associations[association.name].proxy }
|
|
19
|
+
define_method("#{association.name}=") { |obj| associations[association.name].replace(obj) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Association < Base::Association
|
|
23
|
+
delegate :foreign_key, :inverse_of, :to => :definition
|
|
24
|
+
|
|
25
|
+
def find_target
|
|
26
|
+
klass.find(:all, :conditions => { foreign_key => instance.id }) + new_documents
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build(*args, &block)
|
|
30
|
+
doc = klass.new(*args, &block)
|
|
31
|
+
new_documents << doc
|
|
32
|
+
doc
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def create(*args, &block)
|
|
36
|
+
klass.create(*args) do |doc|
|
|
37
|
+
if doc.respond_to?("#{inverse_of}=")
|
|
38
|
+
doc.send("#{inverse_of}=", instance)
|
|
39
|
+
else
|
|
40
|
+
doc[foreign_key] = instance.id
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
block.call(doc) if block
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def replace(array)
|
|
48
|
+
ensure_class(array)
|
|
49
|
+
array.each { |doc| assign(doc) }
|
|
50
|
+
super
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def assign(doc)
|
|
54
|
+
if doc.respond_to?("#{inverse_of}=")
|
|
55
|
+
doc.send("#{inverse_of}=", instance)
|
|
56
|
+
else
|
|
57
|
+
doc[foreign_key] = instance.id
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
doc.save(false) unless doc.new_record?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def send_to_klass_with_scope(*args, &block)
|
|
64
|
+
fk = foreign_key
|
|
65
|
+
id = instance.id
|
|
66
|
+
|
|
67
|
+
klass.instance_eval do
|
|
68
|
+
with_scope(:find => { :conditions => { fk => id } }) do
|
|
69
|
+
send(*args, &block)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
protected
|
|
75
|
+
def new_documents
|
|
76
|
+
@new_documents ||= []
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def ensure_class(array)
|
|
80
|
+
array.is_a?(Array) ? array.each { |i| super(i) } : super
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class Proxy < Base::Proxy
|
|
85
|
+
# Pass these methods to the association class rather than the Array target
|
|
86
|
+
OVERRIDE_METHODS = [ :find ]
|
|
87
|
+
|
|
88
|
+
delegate :ensure_class, :to => :association
|
|
89
|
+
|
|
90
|
+
def build(*args, &block)
|
|
91
|
+
doc = association.build(*args, &block)
|
|
92
|
+
self << doc
|
|
93
|
+
doc
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def create(*args, &block)
|
|
97
|
+
doc = association.create(*args, &block)
|
|
98
|
+
self << doc
|
|
99
|
+
doc
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def []=(index, doc)
|
|
103
|
+
ensure_class(doc)
|
|
104
|
+
association.assign(doc)
|
|
105
|
+
super if loaded?
|
|
106
|
+
self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def <<(doc)
|
|
110
|
+
ensure_class(doc)
|
|
111
|
+
association.assign(doc)
|
|
112
|
+
super if loaded?
|
|
113
|
+
self
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def concat(documents)
|
|
117
|
+
ensure_class(documents)
|
|
118
|
+
documents.each { |doc| association.assign(doc) }
|
|
119
|
+
super if loaded?
|
|
120
|
+
self
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def insert(index, doc)
|
|
124
|
+
ensure_class(doc)
|
|
125
|
+
association.assign(doc)
|
|
126
|
+
super if loaded?
|
|
127
|
+
self
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def push(*documents)
|
|
131
|
+
ensure_class(documents)
|
|
132
|
+
documents.each { |doc| association.assign(doc) }
|
|
133
|
+
super if loaded?
|
|
134
|
+
self
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def unshift(*documents)
|
|
138
|
+
ensure_class(documents)
|
|
139
|
+
documents.each { |doc| association.assign(doc) }
|
|
140
|
+
super if loaded?
|
|
141
|
+
self
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def ids
|
|
145
|
+
target.map { |doc| doc.id }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private
|
|
149
|
+
def method_missing(method_id, *args, &block)
|
|
150
|
+
if target.respond_to?(method_id) && !OVERRIDE_METHODS.include?(method_id.to_sym)
|
|
151
|
+
super(method_id, *args, &block)
|
|
152
|
+
else
|
|
153
|
+
association.send_to_klass_with_scope(method_id, *args, &block)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
module MongoModel
|
|
2
|
+
module Associations
|
|
3
|
+
class HasManyByIds < Base::Definition
|
|
4
|
+
def property_name
|
|
5
|
+
:"#{singular_name}_ids"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
properties do |association|
|
|
9
|
+
property association.property_name, Collection[String], :internal => true, :default => []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
methods do |association|
|
|
13
|
+
define_method(association.name) { associations[association.name].proxy }
|
|
14
|
+
define_method("#{association.name}=") { |obj| associations[association.name].replace(obj) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
class Association < Base::Association
|
|
18
|
+
delegate :property_name, :to => :definition
|
|
19
|
+
|
|
20
|
+
def ids
|
|
21
|
+
instance[property_name]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def replace(array)
|
|
25
|
+
ensure_class(array)
|
|
26
|
+
|
|
27
|
+
instance[property_name] = array.map { |i| i.id }
|
|
28
|
+
super
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find_target
|
|
32
|
+
ids.any? ? Array(klass.find(*(ids - new_document_ids))) + new_documents : []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def build(*args, &block)
|
|
36
|
+
doc = klass.new(*args, &block)
|
|
37
|
+
new_documents << doc
|
|
38
|
+
doc
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create(*args, &block)
|
|
42
|
+
klass.create(*args, &block)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def send_to_klass_with_scope(*args, &block)
|
|
46
|
+
scope_ids = ids
|
|
47
|
+
|
|
48
|
+
klass.instance_eval do
|
|
49
|
+
with_scope(:find => { :conditions => { :id.in => scope_ids } }) do
|
|
50
|
+
send(*args, &block)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
protected
|
|
56
|
+
def new_documents
|
|
57
|
+
@new_documents ||= []
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def new_document_ids
|
|
61
|
+
new_documents.map { |doc| doc.id }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def ensure_class(array)
|
|
65
|
+
array.is_a?(Array) ? array.each { |i| super(i) } : super
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
class Proxy < Base::Proxy
|
|
70
|
+
# Pass these methods to the association class rather than the Array target
|
|
71
|
+
OVERRIDE_METHODS = [ :find ]
|
|
72
|
+
|
|
73
|
+
delegate :ensure_class, :to => :association
|
|
74
|
+
|
|
75
|
+
def build(*args, &block)
|
|
76
|
+
doc = association.build(*args, &block)
|
|
77
|
+
self << doc
|
|
78
|
+
doc
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def create(*args, &block)
|
|
82
|
+
doc = association.create(*args, &block)
|
|
83
|
+
self << doc
|
|
84
|
+
doc
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def []=(index, doc)
|
|
88
|
+
ensure_class(doc)
|
|
89
|
+
super if loaded?
|
|
90
|
+
ids[index] = doc.id
|
|
91
|
+
self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def <<(doc)
|
|
95
|
+
ensure_class(doc)
|
|
96
|
+
super if loaded?
|
|
97
|
+
ids << doc.id
|
|
98
|
+
self
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def concat(documents)
|
|
102
|
+
ensure_class(documents)
|
|
103
|
+
super if loaded?
|
|
104
|
+
ids.concat(documents.map { |doc| doc.id })
|
|
105
|
+
self
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def insert(index, doc)
|
|
109
|
+
ensure_class(doc)
|
|
110
|
+
super if loaded?
|
|
111
|
+
ids.insert(index, doc.id)
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def replace(documents)
|
|
116
|
+
ensure_class(documents)
|
|
117
|
+
super if loaded?
|
|
118
|
+
ids.replace(documents.map { |doc| doc.id })
|
|
119
|
+
self
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def push(*documents)
|
|
123
|
+
ensure_class(documents)
|
|
124
|
+
super if loaded?
|
|
125
|
+
ids.push(*documents.map { |doc| doc.id })
|
|
126
|
+
self
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def unshift(*documents)
|
|
130
|
+
ensure_class(documents)
|
|
131
|
+
super if loaded?
|
|
132
|
+
ids.unshift(*documents.map { |doc| doc.id })
|
|
133
|
+
self
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def clear
|
|
137
|
+
super if loaded?
|
|
138
|
+
ids.clear
|
|
139
|
+
self
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def delete(doc)
|
|
143
|
+
super if loaded?
|
|
144
|
+
ids.delete(doc.id)
|
|
145
|
+
self
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def delete_at(index)
|
|
149
|
+
super if loaded?
|
|
150
|
+
ids.delete_at(index)
|
|
151
|
+
self
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def delete_if(&block)
|
|
155
|
+
super
|
|
156
|
+
ids.replace(map { |doc| doc.id })
|
|
157
|
+
self
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def ids
|
|
161
|
+
association.ids
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private
|
|
165
|
+
def method_missing(method_id, *args, &block)
|
|
166
|
+
if target.respond_to?(method_id) && !OVERRIDE_METHODS.include?(method_id.to_sym)
|
|
167
|
+
super(method_id, *args, &block)
|
|
168
|
+
else
|
|
169
|
+
association.send_to_klass_with_scope(method_id, *args, &block)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|