mongomodel 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +34 -0
  3. data/Rakefile +47 -0
  4. data/bin/console +45 -0
  5. data/lib/mongomodel.rb +92 -0
  6. data/lib/mongomodel/attributes/mongo.rb +40 -0
  7. data/lib/mongomodel/attributes/store.rb +30 -0
  8. data/lib/mongomodel/attributes/typecasting.rb +51 -0
  9. data/lib/mongomodel/concerns/abstract_class.rb +17 -0
  10. data/lib/mongomodel/concerns/activemodel.rb +11 -0
  11. data/lib/mongomodel/concerns/associations.rb +103 -0
  12. data/lib/mongomodel/concerns/associations/base/association.rb +33 -0
  13. data/lib/mongomodel/concerns/associations/base/definition.rb +56 -0
  14. data/lib/mongomodel/concerns/associations/base/proxy.rb +58 -0
  15. data/lib/mongomodel/concerns/associations/belongs_to.rb +68 -0
  16. data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +159 -0
  17. data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +175 -0
  18. data/lib/mongomodel/concerns/attribute_methods.rb +55 -0
  19. data/lib/mongomodel/concerns/attribute_methods/before_type_cast.rb +29 -0
  20. data/lib/mongomodel/concerns/attribute_methods/dirty.rb +35 -0
  21. data/lib/mongomodel/concerns/attribute_methods/protected.rb +127 -0
  22. data/lib/mongomodel/concerns/attribute_methods/query.rb +22 -0
  23. data/lib/mongomodel/concerns/attribute_methods/read.rb +29 -0
  24. data/lib/mongomodel/concerns/attribute_methods/write.rb +29 -0
  25. data/lib/mongomodel/concerns/attributes.rb +85 -0
  26. data/lib/mongomodel/concerns/callbacks.rb +294 -0
  27. data/lib/mongomodel/concerns/logging.rb +15 -0
  28. data/lib/mongomodel/concerns/pretty_inspect.rb +29 -0
  29. data/lib/mongomodel/concerns/properties.rb +69 -0
  30. data/lib/mongomodel/concerns/record_status.rb +42 -0
  31. data/lib/mongomodel/concerns/timestamps.rb +32 -0
  32. data/lib/mongomodel/concerns/validations.rb +38 -0
  33. data/lib/mongomodel/concerns/validations/associated.rb +46 -0
  34. data/lib/mongomodel/document.rb +20 -0
  35. data/lib/mongomodel/document/callbacks.rb +46 -0
  36. data/lib/mongomodel/document/dynamic_finders.rb +88 -0
  37. data/lib/mongomodel/document/finders.rb +82 -0
  38. data/lib/mongomodel/document/indexes.rb +91 -0
  39. data/lib/mongomodel/document/optimistic_locking.rb +48 -0
  40. data/lib/mongomodel/document/persistence.rb +143 -0
  41. data/lib/mongomodel/document/scopes.rb +161 -0
  42. data/lib/mongomodel/document/validations.rb +68 -0
  43. data/lib/mongomodel/document/validations/uniqueness.rb +78 -0
  44. data/lib/mongomodel/embedded_document.rb +42 -0
  45. data/lib/mongomodel/locale/en.yml +55 -0
  46. data/lib/mongomodel/support/collection.rb +109 -0
  47. data/lib/mongomodel/support/configuration.rb +35 -0
  48. data/lib/mongomodel/support/core_extensions.rb +10 -0
  49. data/lib/mongomodel/support/exceptions.rb +25 -0
  50. data/lib/mongomodel/support/mongo_options.rb +177 -0
  51. data/lib/mongomodel/support/types.rb +35 -0
  52. data/lib/mongomodel/support/types/array.rb +11 -0
  53. data/lib/mongomodel/support/types/boolean.rb +25 -0
  54. data/lib/mongomodel/support/types/custom.rb +38 -0
  55. data/lib/mongomodel/support/types/date.rb +20 -0
  56. data/lib/mongomodel/support/types/float.rb +13 -0
  57. data/lib/mongomodel/support/types/hash.rb +18 -0
  58. data/lib/mongomodel/support/types/integer.rb +13 -0
  59. data/lib/mongomodel/support/types/object.rb +21 -0
  60. data/lib/mongomodel/support/types/string.rb +9 -0
  61. data/lib/mongomodel/support/types/symbol.rb +9 -0
  62. data/lib/mongomodel/support/types/time.rb +12 -0
  63. data/lib/mongomodel/version.rb +3 -0
  64. data/spec/mongomodel/attributes/store_spec.rb +273 -0
  65. data/spec/mongomodel/concerns/activemodel_spec.rb +61 -0
  66. data/spec/mongomodel/concerns/associations/belongs_to_spec.rb +153 -0
  67. data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +165 -0
  68. data/spec/mongomodel/concerns/associations/has_many_by_ids_spec.rb +192 -0
  69. data/spec/mongomodel/concerns/attribute_methods/before_type_cast_spec.rb +46 -0
  70. data/spec/mongomodel/concerns/attribute_methods/dirty_spec.rb +131 -0
  71. data/spec/mongomodel/concerns/attribute_methods/protected_spec.rb +86 -0
  72. data/spec/mongomodel/concerns/attribute_methods/query_spec.rb +27 -0
  73. data/spec/mongomodel/concerns/attribute_methods/read_spec.rb +52 -0
  74. data/spec/mongomodel/concerns/attribute_methods/write_spec.rb +43 -0
  75. data/spec/mongomodel/concerns/attributes_spec.rb +152 -0
  76. data/spec/mongomodel/concerns/callbacks_spec.rb +90 -0
  77. data/spec/mongomodel/concerns/logging_spec.rb +20 -0
  78. data/spec/mongomodel/concerns/pretty_inspect_spec.rb +68 -0
  79. data/spec/mongomodel/concerns/properties_spec.rb +29 -0
  80. data/spec/mongomodel/concerns/timestamps_spec.rb +170 -0
  81. data/spec/mongomodel/concerns/validations_spec.rb +159 -0
  82. data/spec/mongomodel/document/callbacks_spec.rb +80 -0
  83. data/spec/mongomodel/document/dynamic_finders_spec.rb +183 -0
  84. data/spec/mongomodel/document/finders_spec.rb +231 -0
  85. data/spec/mongomodel/document/indexes_spec.rb +121 -0
  86. data/spec/mongomodel/document/optimistic_locking_spec.rb +57 -0
  87. data/spec/mongomodel/document/persistence_spec.rb +319 -0
  88. data/spec/mongomodel/document/scopes_spec.rb +204 -0
  89. data/spec/mongomodel/document/validations/uniqueness_spec.rb +217 -0
  90. data/spec/mongomodel/document/validations_spec.rb +132 -0
  91. data/spec/mongomodel/document_spec.rb +74 -0
  92. data/spec/mongomodel/embedded_document_spec.rb +66 -0
  93. data/spec/mongomodel/mongomodel_spec.rb +33 -0
  94. data/spec/mongomodel/support/collection_spec.rb +248 -0
  95. data/spec/mongomodel/support/mongo_options_spec.rb +295 -0
  96. data/spec/mongomodel/support/property_spec.rb +83 -0
  97. data/spec/spec.opts +6 -0
  98. data/spec/spec_helper.rb +21 -0
  99. data/spec/specdoc.opts +6 -0
  100. data/spec/support/callbacks.rb +44 -0
  101. data/spec/support/helpers/define_class.rb +24 -0
  102. data/spec/support/helpers/specs_for.rb +11 -0
  103. data/spec/support/matchers/be_a_subclass_of.rb +5 -0
  104. data/spec/support/matchers/respond_to_boolean.rb +17 -0
  105. data/spec/support/matchers/run_callbacks.rb +20 -0
  106. data/spec/support/models.rb +23 -0
  107. data/spec/support/time.rb +6 -0
  108. 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