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,90 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(EmbeddedDocument) do
5
+ describe "callbacks" do
6
+ define_class(:ChildDocument, EmbeddedDocument) do
7
+ include MongoModel::CallbackHelpers
8
+ end
9
+
10
+ define_class(:ParentDocument, Document) do
11
+ property :child, ChildDocument
12
+ end
13
+
14
+ let(:child) { ChildDocument.new }
15
+ let(:parent) { ParentDocument.create!(:child => child) }
16
+
17
+ it "should run each type of callback when initializing" do
18
+ instance = ChildDocument.new
19
+ instance.should run_callbacks(:after_initialize)
20
+ end
21
+
22
+ it "should run each type of callback on find" do
23
+ instance = ParentDocument.find(parent.id).child
24
+ instance.should run_callbacks(:after_initialize, :after_find)
25
+ end
26
+
27
+ it "should run each type of callback when validating a new document" do
28
+ instance = ChildDocument.new
29
+ instance.valid?
30
+ instance.should run_callbacks(:after_initialize, :before_validation, :after_validation)
31
+ end
32
+
33
+ it "should run each type of callback when validating an existing document" do
34
+ instance = ParentDocument.find(parent.id).child
35
+ instance.valid?
36
+ instance.should run_callbacks(:after_initialize, :after_find, :before_validation, :after_validation)
37
+ end
38
+
39
+ it "should run each type of callback when creating a document" do
40
+ instance = ParentDocument.create!(:child => ChildDocument.new)
41
+ instance.child.should run_callbacks(:after_initialize, :before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save)
42
+ end
43
+
44
+ it "should run each type of callback when saving an existing document" do
45
+ instance = ParentDocument.find(parent.id)
46
+ instance.save
47
+ instance.child.should run_callbacks(:after_initialize, :after_find, :before_validation, :after_validation, :before_save, :before_update, :after_update, :after_save)
48
+ end
49
+
50
+ it "should run each type of callback when destroying a document" do
51
+ instance = ParentDocument.find(parent.id)
52
+ instance.destroy
53
+ instance.child.should run_callbacks(:after_initialize, :after_find, :before_destroy, :after_destroy)
54
+ end
55
+
56
+ it "should not run destroy callbacks when deleting a document" do
57
+ instance = ParentDocument.find(parent.id)
58
+ instance.delete
59
+ instance.child.should run_callbacks(:after_initialize, :after_find)
60
+ end
61
+ end
62
+
63
+ [ :before_save, :before_create ].each do |callback|
64
+ context "#{callback} callback return false" do
65
+ define_class(:ChildDocument, EmbeddedDocument) do
66
+ send(callback) { false }
67
+ end
68
+
69
+ define_class(:ParentDocument, Document) do
70
+ property :child, ChildDocument
71
+ end
72
+
73
+ let(:child) { ChildDocument.new }
74
+ let(:parent) { ParentDocument.new(:child => child) }
75
+
76
+ describe "#save" do
77
+ it "should return false" do
78
+ parent.save.should be_false
79
+ end
80
+ end
81
+
82
+ describe "#save!" do
83
+ it "should raise a MongoModel::DocumentNotSaved exception" do
84
+ lambda { parent.save! }.should raise_error(MongoModel::DocumentNotSaved)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(Document, EmbeddedDocument) do
5
+ describe "logging" do
6
+ define_class(:TestDocument, described_class)
7
+
8
+ let(:logger) { mock('logger') }
9
+ before(:all) { MongoModel.logger = logger }
10
+
11
+ it "should have a logger reader on the class" do
12
+ TestDocument.logger.should == logger
13
+ end
14
+
15
+ it "should have a logger reader on the instance" do
16
+ TestDocument.new.logger.should == logger
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(Document, EmbeddedDocument) do
5
+ describe "#inspect" do
6
+ context "on base class" do
7
+ it "should return class name" do
8
+ described_class.inspect.should == described_class.name
9
+ end
10
+ end
11
+
12
+ context "on subclasses" do
13
+ context "without properties" do
14
+ define_class(:TestDocument, described_class)
15
+
16
+ it "should return class name" do
17
+ TestDocument.inspect.should == 'TestDocument()'
18
+ end
19
+ end
20
+
21
+ context "with properties" do
22
+ define_class(:TestDocument, described_class) do
23
+ property :name, String
24
+ property :age, Integer
25
+ end
26
+
27
+ it "should return class name and property definitions" do
28
+ TestDocument.inspect.should == 'TestDocument(name: String, age: Integer)'
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ specs_for(Document) do
36
+ describe "#inspect" do
37
+ context "on subclass instances" do
38
+ define_class(:TestDocument, Document) do
39
+ property :name, String
40
+ property :age, Integer
41
+ end
42
+
43
+ subject { TestDocument.new(:id => 'abc-123', :name => 'Doc name', :age => 54) }
44
+
45
+ it "should return class name and property values" do
46
+ subject.inspect.should == '#<TestDocument id: abc-123, name: "Doc name", age: 54>'
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ specs_for(EmbeddedDocument) do
53
+ describe "#inspect" do
54
+ context "on subclass instances" do
55
+ define_class(:TestDocument, EmbeddedDocument) do
56
+ property :name, String
57
+ property :age, Integer
58
+ end
59
+
60
+ subject { TestDocument.new(:name => 'Doc name', :age => 54) }
61
+
62
+ it "should return class name and property values" do
63
+ subject.inspect.should == '#<TestDocument name: "Doc name", age: 54>'
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(Document, EmbeddedDocument) do
5
+ define_class(:Person, described_class) do
6
+ property :name, String
7
+ property :age, Integer, :default => 21
8
+ end
9
+
10
+ define_class(:SkilledPerson, :Person) do
11
+ property :skill, String
12
+ end
13
+
14
+ it "should have a populated hash of properties" do
15
+ Person.properties.should include({
16
+ :name => Properties::Property.new(:name, String),
17
+ :age => Properties::Property.new(:age, Integer, :default => 21)
18
+ })
19
+ end
20
+
21
+ it "should extend properties in subclasses" do
22
+ SkilledPerson.properties.should include({
23
+ :name => Properties::Property.new(:name, String),
24
+ :age => Properties::Property.new(:age, Integer, :default => 21),
25
+ :skill => Properties::Property.new(:skill, String)
26
+ })
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,170 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(Document, EmbeddedDocument) do
5
+ describe "#timestamps!" do
6
+ define_class(:TestDocument, described_class) do
7
+ timestamps!
8
+ end
9
+
10
+ it "should define a Time property updated_at" do
11
+ TestDocument.properties.should include(:updated_at)
12
+ TestDocument.properties[:updated_at].type.should == Time
13
+ end
14
+
15
+ it "should define a Time property created_at" do
16
+ TestDocument.properties.should include(:created_at)
17
+ TestDocument.properties[:created_at].type.should == Time
18
+ end
19
+ end
20
+
21
+ context "with updated_at property" do
22
+ define_class(:TestDocument, described_class) do
23
+ property :updated_at, Time
24
+ end
25
+
26
+ if specing?(EmbeddedDocument)
27
+ define_class(:ParentDocument, Document) do
28
+ property :child, TestDocument
29
+ end
30
+
31
+ let(:parent) { ParentDocument.new(:child => subject) }
32
+ let(:doc) { parent }
33
+ else
34
+ let(:doc) { subject }
35
+ end
36
+
37
+ subject { TestDocument.new }
38
+
39
+ before(:each) do
40
+ @now = Time.now.utc
41
+ Time.stub!(:now).and_return(@now)
42
+ end
43
+
44
+ it "should set the updated_at property to the current time when saved" do
45
+ doc.save
46
+ subject.updated_at.should == @now
47
+ end
48
+ end
49
+
50
+ context "with updated_on property" do
51
+ define_class(:TestDocument, described_class) do
52
+ property :updated_on, Date
53
+ end
54
+
55
+ if specing?(EmbeddedDocument)
56
+ define_class(:ParentDocument, Document) do
57
+ property :child, TestDocument
58
+ end
59
+
60
+ let(:parent) { ParentDocument.new(:child => subject) }
61
+ let(:doc) { parent }
62
+ else
63
+ let(:doc) { subject }
64
+ end
65
+
66
+ subject { TestDocument.new }
67
+
68
+ it "should set the updated_on property to the current date when saved" do
69
+ doc.save
70
+ subject.updated_on.should == Date.today
71
+ end
72
+ end
73
+
74
+ context "with created_at property" do
75
+ define_class(:TestDocument, described_class) do
76
+ property :created_at, Time
77
+ end
78
+
79
+ if specing?(EmbeddedDocument)
80
+ define_class(:ParentDocument, Document) do
81
+ property :child, TestDocument
82
+ end
83
+
84
+ let(:parent) { ParentDocument.new(:child => subject) }
85
+ let(:doc) { parent }
86
+ else
87
+ let(:doc) { subject }
88
+ end
89
+
90
+ subject { TestDocument.new }
91
+
92
+ before(:each) do
93
+ @now = Time.now.utc
94
+ Time.stub!(:now).and_return(@now)
95
+ end
96
+
97
+ it "should set the created_at property to the current time when created" do
98
+ doc.save
99
+ subject.created_at.should == @now
100
+ end
101
+
102
+ it "should not change the created_at property when updated" do
103
+ @next = 1.day.from_now
104
+
105
+ Time.stub!(:now).and_return(@now)
106
+
107
+ doc.save
108
+
109
+ Time.stub!(:now).and_return(@next)
110
+
111
+ doc.save
112
+ subject.created_at.should == @now
113
+ end
114
+
115
+ it "should preserve created_at attribute when set explicitly" do
116
+ @a_year_ago = 1.year.ago
117
+
118
+ subject.created_at = @a_year_ago
119
+ doc.save
120
+
121
+ subject.created_at.should == @a_year_ago
122
+ end
123
+ end
124
+
125
+ context "with created_on property" do
126
+ define_class(:TestDocument, described_class) do
127
+ property :created_on, Date
128
+ end
129
+
130
+ if specing?(EmbeddedDocument)
131
+ define_class(:ParentDocument, Document) do
132
+ property :child, TestDocument
133
+ end
134
+
135
+ let(:parent) { ParentDocument.new(:child => subject) }
136
+ let(:doc) { parent }
137
+ else
138
+ let(:doc) { subject }
139
+ end
140
+
141
+ subject { TestDocument.new }
142
+
143
+ it "should set the created_on property to the current date when created" do
144
+ doc.save
145
+ subject.created_on.should == Date.today
146
+ end
147
+
148
+ it "should not change the created_on property when updated" do
149
+ doc.save
150
+
151
+ @today = Date.today
152
+ @tomorrow = 1.day.from_now
153
+
154
+ Time.stub!(:now).and_return(@tomorrow)
155
+
156
+ doc.save
157
+ subject.created_on.should == @today
158
+ end
159
+
160
+ it "should preserve created_on attribute when set explicitly" do
161
+ @a_year_ago = 1.year.ago.to_date
162
+
163
+ subject.created_on = @a_year_ago
164
+ doc.save
165
+
166
+ subject.created_on.should == @a_year_ago
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,159 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ module ValidationHelpers
5
+ def clear_validations!
6
+ reset_callbacks(:validate)
7
+ end
8
+ end
9
+
10
+ specs_for(Document, EmbeddedDocument) do
11
+ describe "validations" do
12
+ define_class(:TestDocument, described_class) do
13
+ property :title, String
14
+ validates_presence_of :title
15
+
16
+ extend MongoModel::ValidationHelpers
17
+ end
18
+
19
+ if specing?(EmbeddedDocument)
20
+ define_class(:ParentDocument, Document) do
21
+ property :child, TestDocument
22
+ end
23
+
24
+ let(:child) { TestDocument.new }
25
+ let(:parent) { ParentDocument.new(:child => child) }
26
+ let(:doc) { parent }
27
+
28
+ subject { parent.child }
29
+ else
30
+ let(:doc) { TestDocument.new }
31
+
32
+ subject { doc }
33
+ end
34
+
35
+ it "should have an errors collection" do
36
+ subject.errors.should be_an_instance_of(ActiveModel::Errors)
37
+ end
38
+
39
+ context "when validations are not met" do
40
+ before(:each) { subject.valid? }
41
+
42
+ it { should_not be_valid }
43
+ it { should have(1).error }
44
+ end
45
+
46
+ describe "validation on create" do
47
+ before(:each) do
48
+ TestDocument.clear_validations!
49
+ TestDocument.validates_presence_of :title, :on => :create
50
+ end
51
+
52
+ context "new document" do
53
+ it { should_not be_valid }
54
+ end
55
+
56
+ context "existing document" do
57
+ before(:each) do
58
+ subject.title = 'Valid title'
59
+ doc.save!
60
+ subject.title = nil
61
+ end
62
+
63
+ it { should be_valid }
64
+ end
65
+ end
66
+
67
+ describe "validation on update" do
68
+ before(:each) do
69
+ TestDocument.clear_validations!
70
+ TestDocument.validates_presence_of :title, :on => :update
71
+ end
72
+
73
+ context "new document" do
74
+ it { should be_valid }
75
+ end
76
+
77
+ context "existing document" do
78
+ before(:each) do
79
+ subject.title = 'Valid title'
80
+ doc.save!
81
+ subject.title = nil
82
+ end
83
+
84
+ it { should_not be_valid }
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "validation shortcuts" do
90
+ define_class(:TestDocument, described_class)
91
+
92
+ describe ":required => true" do
93
+ it "should add a validates_presence_of validation" do
94
+ TestDocument.should_receive(:validates_presence_of).with(:title)
95
+ TestDocument.property :title, String, :required => true
96
+ end
97
+ end
98
+
99
+ describe ":format => /regex/" do
100
+ it "should add a validates_format_of validation" do
101
+ TestDocument.should_receive(:validates_format_of).with(:email, /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i)
102
+ TestDocument.property :email, String, :format => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ specs_for(EmbeddedDocument) do
109
+ describe "validations" do
110
+ define_class(:ChildDocument, EmbeddedDocument) do
111
+ property :title, String
112
+ validates_presence_of :title
113
+
114
+ extend MongoModel::ValidationHelpers
115
+ end
116
+
117
+ define_class(:ParentDocument, Document) do
118
+ property :child, ChildDocument
119
+ end
120
+
121
+ let(:child) { ChildDocument.new }
122
+ let(:parent) { ParentDocument.new(:child => child) }
123
+
124
+ subject { parent.child }
125
+
126
+ context "child document is invalid" do
127
+ before(:each) { parent.valid? }
128
+
129
+ specify "parent should be invalid" do
130
+ parent.should_not be_valid
131
+ end
132
+
133
+ specify "parent should have error on child" do
134
+ parent.should have(1).error
135
+ parent.errors[:child].should_not be_nil
136
+ end
137
+
138
+ specify "child should be invalid" do
139
+ child.should_not be_valid
140
+ end
141
+
142
+ specify "child should have error" do
143
+ child.errors[:title].should_not be_nil
144
+ end
145
+ end
146
+
147
+ context "parent has no child" do
148
+ before(:each) do
149
+ parent.child = nil
150
+ parent.valid?
151
+ end
152
+
153
+ specify "parent should be valid" do
154
+ parent.should be_valid
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end