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,61 @@
1
+ require 'spec_helper'
2
+
3
+ # Specs ported from ActiveModel::Lint::Tests
4
+ module MongoModel
5
+ specs_for(Document, EmbeddedDocument) do
6
+ define_class(:TestModel, described_class)
7
+
8
+ subject { TestModel.new.to_model }
9
+
10
+ # valid?
11
+ # ------
12
+ #
13
+ # Returns a boolean that specifies whether the object is in a valid or invalid
14
+ # state.
15
+ it { should respond_to_boolean(:valid?) }
16
+
17
+ # new_record?
18
+ # -----------
19
+ #
20
+ # Returns a boolean that specifies whether the object has been persisted yet.
21
+ # This is used when calculating the URL for an object. If the object is
22
+ # not persisted, a form for that object, for instance, will be POSTed to the
23
+ # collection. If it is persisted, a form for the object will put PUTed to the
24
+ # URL for the object.
25
+ it { should respond_to_boolean(:new_record?) }
26
+ it { should respond_to_boolean(:destroyed?) }
27
+
28
+ # errors
29
+ # ------
30
+ #
31
+ # Returns an object that has :[] and :full_messages defined on it. See below
32
+ # for more details.
33
+ describe "errors" do
34
+ it { should respond_to(:errors) }
35
+
36
+ # Returns an Array of Strings that are the errors for the attribute in
37
+ # question. If localization is used, the Strings should be localized
38
+ # for the current locale. If no error is present, this method should
39
+ # return an empty Array.
40
+ describe "#[]" do
41
+ it "should return an Array" do
42
+ subject.errors[:hello].should be_an(Array)
43
+ end
44
+ end
45
+
46
+ # Returns an Array of all error messages for the object. Each message
47
+ # should contain information about the field, if applicable.
48
+ describe "#full_messages" do
49
+ it "should return an Array" do
50
+ subject.errors.full_messages.should be_an(Array)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe "#model_name" do
56
+ it "should return an ActiveModel::Name object" do
57
+ TestModel.model_name.should == ActiveModel::Name.new('TestModel')
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ shared_examples_for "assigning correct class to belongs_to association" do
5
+ define_class(:User, Document)
6
+ define_class(:SpecialUser, :User)
7
+
8
+ let(:user) { User.create! }
9
+ let(:special_user) { SpecialUser.create! }
10
+
11
+ subject { Article.new }
12
+
13
+ context "when uninitialized" do
14
+ it "should be nil" do
15
+ subject.user.should be_nil
16
+ end
17
+
18
+ it "should be settable" do
19
+ subject.user = user
20
+ subject.user.should == user
21
+ end
22
+
23
+ describe "setting a subclass type" do
24
+ it "should set successfully" do
25
+ subject.user = special_user
26
+ subject.user.should == special_user
27
+ end
28
+ end
29
+ end
30
+
31
+ context "when loading from database" do
32
+ subject { Article.new(:user => user) }
33
+
34
+ if specing?(EmbeddedDocument)
35
+ define_class(:ArticleParent, Document) do
36
+ property :article, Article
37
+ end
38
+
39
+ let(:parent) { ArticleParent.create!(:article => subject) }
40
+ let(:reloaded) { ArticleParent.find(parent.id).article }
41
+ else
42
+ before(:each) { subject.save! }
43
+ let(:reloaded) { Article.find(subject.id) }
44
+ end
45
+
46
+ it "should access the user through the association" do
47
+ reloaded.user.should == user
48
+ end
49
+
50
+ it "should allow the user to be reloaded" do
51
+ reloaded.user.inspect
52
+ reloaded.user.loaded?.should be_true
53
+
54
+ reloaded.user(true)
55
+ reloaded.user.loaded?.should be_false
56
+ end
57
+
58
+ describe "setting a subclass type" do
59
+ subject { Article.new(:user => special_user) }
60
+
61
+ it "should load successfully" do
62
+ reloaded.user.should == special_user
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ specs_for(Document, EmbeddedDocument) do
69
+ describe "belongs_to association" do
70
+ define_class(:Article, described_class) do
71
+ belongs_to :user
72
+ end
73
+
74
+ it_should_behave_like "assigning correct class to belongs_to association"
75
+
76
+ describe "setting a different class type" do
77
+ define_class(:NonUser, Document)
78
+
79
+ let(:non_user) { NonUser.create! }
80
+
81
+ it "should raise a AssociationTypeMismatch exception" do
82
+ lambda { subject.user = non_user }.should raise_error(AssociationTypeMismatch, "expected instance of User but got NonUser")
83
+ end
84
+ end
85
+
86
+ describe "#build_user" do
87
+ subject { Article.new }
88
+
89
+ let(:user) { subject.build_user(:id => '123') }
90
+
91
+ it "should return a new unsaved user with the given attributes" do
92
+ user.should be_an_instance_of(User)
93
+ user.should be_a_new_record
94
+ user.id.should == '123'
95
+ end
96
+ end
97
+
98
+ describe "#create_user" do
99
+ subject { Article.new }
100
+
101
+ it "should return a new saved user with the given attributes" do
102
+ user = subject.create_user(:id => '123')
103
+ user.should be_an_instance_of(User)
104
+ user.should_not be_a_new_record
105
+ user.id.should == '123'
106
+ end
107
+ end
108
+ end
109
+
110
+ describe "polymorphic belongs_to association" do
111
+ define_class(:Article, described_class) do
112
+ belongs_to :user, :polymorphic => true
113
+ end
114
+
115
+ define_class(:NonUser, Document)
116
+
117
+ let(:non_user) { NonUser.create! }
118
+
119
+ it_should_behave_like "assigning correct class to belongs_to association"
120
+
121
+ describe "setting a different class type" do
122
+ it "should set successfully" do
123
+ subject.user = non_user
124
+ subject.user.should == non_user
125
+ end
126
+ end
127
+
128
+ context "when loading from database" do
129
+ subject { Article.new(:user => user) }
130
+
131
+ if specing?(EmbeddedDocument)
132
+ define_class(:ArticleParent, Document) do
133
+ property :article, Article
134
+ end
135
+
136
+ let(:parent) { ArticleParent.create!(:article => subject) }
137
+ let(:reloaded) { ArticleParent.find(parent.id).article }
138
+ else
139
+ before(:each) { subject.save! }
140
+ let(:reloaded) { Article.find(subject.id) }
141
+ end
142
+
143
+ describe "setting a different class type" do
144
+ subject { Article.new(:user => non_user) }
145
+
146
+ it "should load successfully" do
147
+ reloaded.user.should == non_user
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,165 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(Document) do
5
+ describe "has_many association" do
6
+ define_class(:Book, Document) do
7
+ has_many :chapters
8
+ end
9
+
10
+ it "should default to :by => :foreign_key" do
11
+ Book.associations[:chapters].should be_a(Associations::HasManyByForeignKey)
12
+ end
13
+ end
14
+
15
+ describe "has_many :by => :foreign_key association" do
16
+ define_class(:Chapter, Document) do
17
+ belongs_to :book
18
+ end
19
+ define_class(:IllustratedChapter, :Chapter)
20
+ define_class(:Book, Document) do
21
+ has_many :chapters, :by => :foreign_key
22
+ end
23
+ define_class(:NonChapter, Document)
24
+
25
+ let(:chapter1) { Chapter.create!(:id => '1') }
26
+ let(:chapter2) { IllustratedChapter.create!(:id => '2') }
27
+ let(:chapter3) { Chapter.create!(:id => '3') }
28
+ let(:nonchapter) { NonChapter.create! }
29
+
30
+ context "when uninitialized" do
31
+ subject { Book.new }
32
+
33
+ it "should be empty" do
34
+ subject.chapters.should be_empty
35
+ end
36
+ end
37
+
38
+ shared_examples_for "accessing and manipulating a has_many :by => :foreign_key association" do
39
+ it "should access chapters" do
40
+ subject.chapters.should include(chapter1, chapter2)
41
+ end
42
+
43
+ it "should access chapter ids through association" do
44
+ subject.chapters.ids.should include(chapter1.id, chapter2.id)
45
+ end
46
+
47
+ it "should add chapters with <<" do
48
+ subject.chapters << chapter3
49
+ subject.chapters.should include(chapter1, chapter2, chapter3)
50
+ chapter3.book.should == subject
51
+ end
52
+
53
+ it "should add/change chapters with []=" do
54
+ subject.chapters[2] = chapter3
55
+ subject.chapters.should include(chapter1, chapter2, chapter3)
56
+ chapter3.book.should == subject
57
+ end
58
+
59
+ it "should add chapters with concat" do
60
+ subject.chapters.concat([chapter3])
61
+ subject.chapters.should include(chapter1, chapter2, chapter3)
62
+ chapter3.book.should == subject
63
+ end
64
+
65
+ it "should insert chapters" do
66
+ subject.chapters.insert(1, chapter3)
67
+ subject.chapters.should include(chapter1, chapter2, chapter3)
68
+ chapter3.book.should == subject
69
+ end
70
+
71
+ # it "should replace chapters" do
72
+ # subject.chapters.replace([chapter2, chapter3])
73
+ # subject.chapters.should == [chapter2, chapter3]
74
+ # subject.chapter_ids.should == [chapter2.id, chapter3.id]
75
+ # end
76
+
77
+ it "should add chapters with push" do
78
+ subject.chapters.push(chapter3)
79
+ subject.chapters.should include(chapter1, chapter2, chapter3)
80
+ chapter3.book.should == subject
81
+ end
82
+
83
+ it "should add chapters with unshift" do
84
+ subject.chapters.unshift(chapter3)
85
+ subject.chapters.should include(chapter3, chapter1, chapter2)
86
+ chapter3.book.should == subject
87
+ end
88
+
89
+ # it "should clear chapters" do
90
+ # subject.chapters.clear
91
+ # subject.chapters.should be_empty
92
+ # subject.chapter_ids.should be_empty
93
+ # end
94
+ #
95
+ # it "should remove chapters with delete" do
96
+ # subject.chapters.delete(chapter1)
97
+ # subject.chapters.should == [chapter2]
98
+ # subject.chapter_ids.should == [chapter2.id]
99
+ # end
100
+ #
101
+ # it "should remove chapters with delete_at" do
102
+ # subject.chapters.delete_at(0)
103
+ # subject.chapters.should == [chapter2]
104
+ # subject.chapter_ids.should == [chapter2.id]
105
+ # end
106
+ #
107
+ # it "should remove chapters with delete_if" do
108
+ # subject.chapters.delete_if { |c| c.id == chapter1.id }
109
+ # subject.chapters.should == [chapter2]
110
+ # subject.chapter_ids.should == [chapter2.id]
111
+ # end
112
+
113
+ it "should build a chapter" do
114
+ chapter4 = subject.chapters.build(:id => '4')
115
+ subject.chapters.should include(chapter1, chapter2, chapter4)
116
+
117
+ chapter4.should be_a_new_record
118
+ chapter4.id.should == '4'
119
+ chapter4.book.should == subject
120
+ chapter4.book_id.should == subject.id
121
+ end
122
+
123
+ it "should create a chapter" do
124
+ chapter4 = subject.chapters.create(:id => '4')
125
+ subject.chapters.should == [chapter1, chapter2, chapter4]
126
+
127
+ chapter4.should_not be_a_new_record
128
+ chapter4.id.should == '4'
129
+ chapter4.book.should == subject
130
+ chapter4.book_id.should == subject.id
131
+ end
132
+
133
+ it "should find chapters" do
134
+ # Create bogus chapters
135
+ Chapter.create!(:id => '999')
136
+ Chapter.create!(:id => '998')
137
+
138
+ result = subject.chapters.find(:all, :order => :id.desc)
139
+ result.should == [chapter2, chapter1]
140
+ end
141
+ end
142
+
143
+ context "with chapters set" do
144
+ subject { Book.new(:chapters => [chapter1, chapter2]) }
145
+ it_should_behave_like "accessing and manipulating a has_many :by => :foreign_key association"
146
+ end
147
+
148
+ context "when loaded from database" do
149
+ let(:book) { Book.create!(:chapters => [chapter1, chapter2]) }
150
+ subject { Book.find(book.id) }
151
+ it_should_behave_like "accessing and manipulating a has_many :by => :foreign_key association"
152
+ end
153
+ end
154
+ end
155
+
156
+ specs_for(EmbeddedDocument) do
157
+ describe "defining a has_many :by => :foreign_key association" do
158
+ define_class(:Book, EmbeddedDocument)
159
+
160
+ it "should raise an exception" do
161
+ lambda { Book.has_many :chapters, :by => :foreign_key }.should raise_error
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,192 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(EmbeddedDocument) do
5
+ describe "has_many association" do
6
+ define_class(:Book, EmbeddedDocument) do
7
+ has_many :chapters
8
+ end
9
+
10
+ it "should default to :by => :ids" do
11
+ Book.associations[:chapters].should be_a(Associations::HasManyByIds)
12
+ end
13
+ end
14
+ end
15
+
16
+ specs_for(Document, EmbeddedDocument) do
17
+ describe "has_many :by => :ids association" do
18
+ define_class(:Chapter, Document)
19
+ define_class(:IllustratedChapter, :Chapter)
20
+ define_class(:Book, described_class) do
21
+ has_many :chapters, :by => :ids
22
+ end
23
+ define_class(:NonChapter, Document)
24
+
25
+ let(:chapter1) { Chapter.create!(:id => '1') }
26
+ let(:chapter2) { IllustratedChapter.create!(:id => '2') }
27
+ let(:chapter3) { Chapter.create!(:id => '3') }
28
+ let(:nonchapter) { NonChapter.create! }
29
+
30
+ context "when uninitialized" do
31
+ subject { Book.new }
32
+
33
+ it "should be empty" do
34
+ subject.chapters.should be_empty
35
+ end
36
+
37
+ it "should have an empty ids array" do
38
+ subject.chapter_ids.should be_empty
39
+ end
40
+ end
41
+
42
+ shared_examples_for "accessing and manipulating a has_many :by => :ids association" do
43
+ it "should access chapters" do
44
+ subject.chapters.should == [chapter1, chapter2]
45
+ end
46
+
47
+ it "should access chapter ids through association" do
48
+ subject.chapters.ids.should == [chapter1.id, chapter2.id]
49
+ end
50
+
51
+ it "should have chapter ids" do
52
+ subject.chapter_ids.should == [chapter1.id, chapter2.id]
53
+ end
54
+
55
+ it "should add chapters with <<" do
56
+ subject.chapters << chapter3
57
+ subject.chapters.should == [chapter1, chapter2, chapter3]
58
+ subject.chapter_ids.should == [chapter1.id, chapter2.id, chapter3.id]
59
+ end
60
+
61
+ it "should add/change chapters with []=" do
62
+ subject.chapters[2] = chapter3
63
+ subject.chapters.should == [chapter1, chapter2, chapter3]
64
+ subject.chapter_ids.should == [chapter1.id, chapter2.id, chapter3.id]
65
+ end
66
+
67
+ it "should add chapters with concat" do
68
+ subject.chapters.concat([chapter3])
69
+ subject.chapters.should == [chapter1, chapter2, chapter3]
70
+ subject.chapter_ids.should == [chapter1.id, chapter2.id, chapter3.id]
71
+ end
72
+
73
+ it "should insert chapters" do
74
+ subject.chapters.insert(1, chapter3)
75
+ subject.chapters.should == [chapter1, chapter3, chapter2]
76
+ subject.chapter_ids.should == [chapter1.id, chapter3.id, chapter2.id]
77
+ end
78
+
79
+ it "should replace chapters" do
80
+ subject.chapters.replace([chapter2, chapter3])
81
+ subject.chapters.should == [chapter2, chapter3]
82
+ subject.chapter_ids.should == [chapter2.id, chapter3.id]
83
+ end
84
+
85
+ it "should add chapters with push" do
86
+ subject.chapters.push(chapter3)
87
+ subject.chapters.should == [chapter1, chapter2, chapter3]
88
+ subject.chapter_ids.should == [chapter1.id, chapter2.id, chapter3.id]
89
+ end
90
+
91
+ it "should add chapters with unshift" do
92
+ subject.chapters.unshift(chapter3)
93
+ subject.chapters.should == [chapter3, chapter1, chapter2]
94
+ subject.chapter_ids.should == [chapter3.id, chapter1.id, chapter2.id]
95
+ end
96
+
97
+ it "should clear chapters" do
98
+ subject.chapters.clear
99
+ subject.chapters.should be_empty
100
+ subject.chapter_ids.should be_empty
101
+ end
102
+
103
+ it "should remove chapters with delete" do
104
+ subject.chapters.delete(chapter1)
105
+ subject.chapters.should == [chapter2]
106
+ subject.chapter_ids.should == [chapter2.id]
107
+ end
108
+
109
+ it "should remove chapters with delete_at" do
110
+ subject.chapters.delete_at(0)
111
+ subject.chapters.should == [chapter2]
112
+ subject.chapter_ids.should == [chapter2.id]
113
+ end
114
+
115
+ it "should remove chapters with delete_if" do
116
+ subject.chapters.delete_if { |c| c.id == chapter1.id }
117
+ subject.chapters.should == [chapter2]
118
+ subject.chapter_ids.should == [chapter2.id]
119
+ end
120
+
121
+ it "should build a chapter" do
122
+ chapter4 = subject.chapters.build(:id => '4')
123
+ subject.chapters.should == [chapter1, chapter2, chapter4]
124
+ subject.chapter_ids.should == [chapter1.id, chapter2.id, chapter4.id]
125
+
126
+ chapter4.should be_a_new_record
127
+ chapter4.id.should == '4'
128
+ end
129
+
130
+ it "should create a chapter" do
131
+ chapter4 = subject.chapters.create(:id => '4')
132
+ subject.chapters.should == [chapter1, chapter2, chapter4]
133
+ subject.chapter_ids.should == [chapter1.id, chapter2.id, chapter4.id]
134
+
135
+ chapter4.should_not be_a_new_record
136
+ chapter4.id.should == '4'
137
+ end
138
+
139
+ it "should find chapters" do
140
+ # Create bogus chapters
141
+ Chapter.create!(:id => '999')
142
+ Chapter.create!(:id => '998')
143
+
144
+ result = subject.chapters.find(:all, :order => :id.desc)
145
+ result.should == [chapter2, chapter1]
146
+ end
147
+
148
+ describe "adding a non-chapter" do
149
+ def self.should_raise(message, &block)
150
+ it "should raise an AsssociationTypeMismatch error when #{message}" do
151
+ lambda { instance_eval(&block) }.should raise_error(AssociationTypeMismatch, "expected instance of Chapter but got NonChapter")
152
+ end
153
+ end
154
+
155
+ should_raise("assigning an array containing non-chapters") { subject.chapters = [nonchapter] }
156
+ should_raise("adding a non-chapter using <<") { subject.chapters << nonchapter }
157
+ should_raise("adding non-chapters with concat") { subject.chapters.concat([nonchapter]) }
158
+ should_raise("inserting chapters") { subject.chapters.insert(1, nonchapter) }
159
+ should_raise("replacing chapters") { subject.chapters.replace([nonchapter]) }
160
+ should_raise("addding chapters with push") { subject.chapters.push(nonchapter) }
161
+ should_raise("addding chapters with unshift") { subject.chapters.unshift(nonchapter) }
162
+ end
163
+ end
164
+
165
+ context "with chapters set" do
166
+ subject { Book.new(:chapters => [chapter1, chapter2]) }
167
+ it_should_behave_like "accessing and manipulating a has_many :by => :ids association"
168
+ end
169
+
170
+ context "with chapter ids set" do
171
+ subject { Book.new(:chapter_ids => [chapter1.id, chapter2.id]) }
172
+ it_should_behave_like "accessing and manipulating a has_many :by => :ids association"
173
+ end
174
+
175
+ context "when loaded from database" do
176
+ if specing?(Document)
177
+ let(:book) { Book.create!(:chapter_ids => [chapter1.id, chapter2.id]) }
178
+ subject { Book.find(book.id) }
179
+ else
180
+ define_class(:Bookshelf, Document) do
181
+ property :book, Book
182
+ end
183
+ let(:book) { Book.new(:chapter_ids => [chapter1.id, chapter2.id]) }
184
+ let(:shelf) { Bookshelf.create!(:book => book) }
185
+ subject { Bookshelf.find(shelf.id).book }
186
+ end
187
+
188
+ it_should_behave_like "accessing and manipulating a has_many :by => :ids association"
189
+ end
190
+ end
191
+ end
192
+ end