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.
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