massive_record 0.2.1 → 0.2.2.rc1

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 (135) hide show
  1. data/CHANGELOG.md +58 -2
  2. data/Gemfile.lock +17 -17
  3. data/README.md +98 -41
  4. data/lib/massive_record.rb +2 -1
  5. data/lib/massive_record/adapters/thrift/hbase/hbase.rb +2425 -2154
  6. data/lib/massive_record/adapters/thrift/hbase/hbase_constants.rb +3 -3
  7. data/lib/massive_record/adapters/thrift/hbase/hbase_types.rb +195 -195
  8. data/lib/massive_record/adapters/thrift/row.rb +35 -4
  9. data/lib/massive_record/adapters/thrift/table.rb +49 -12
  10. data/lib/massive_record/orm/attribute_methods.rb +77 -5
  11. data/lib/massive_record/orm/attribute_methods/cast_numbers_on_write.rb +24 -0
  12. data/lib/massive_record/orm/attribute_methods/dirty.rb +18 -0
  13. data/lib/massive_record/orm/attribute_methods/time_zone_conversion.rb +24 -3
  14. data/lib/massive_record/orm/attribute_methods/write.rb +8 -1
  15. data/lib/massive_record/orm/base.rb +62 -8
  16. data/lib/massive_record/orm/column.rb +7 -11
  17. data/lib/massive_record/orm/default_id.rb +1 -1
  18. data/lib/massive_record/orm/embedded.rb +66 -0
  19. data/lib/massive_record/orm/errors.rb +17 -0
  20. data/lib/massive_record/orm/finders.rb +124 -71
  21. data/lib/massive_record/orm/finders/rescue_missing_table_on_find.rb +1 -1
  22. data/lib/massive_record/orm/finders/scope.rb +58 -34
  23. data/lib/massive_record/orm/id_factory.rb +22 -105
  24. data/lib/massive_record/orm/id_factory/atomic_incrementation.rb +117 -0
  25. data/lib/massive_record/orm/id_factory/timestamp.rb +60 -0
  26. data/lib/massive_record/orm/identity_map.rb +256 -0
  27. data/lib/massive_record/orm/log_subscriber.rb +18 -0
  28. data/lib/massive_record/orm/observer.rb +69 -0
  29. data/lib/massive_record/orm/persistence.rb +47 -119
  30. data/lib/massive_record/orm/persistence/operations.rb +100 -0
  31. data/lib/massive_record/orm/persistence/operations/atomic_operation.rb +71 -0
  32. data/lib/massive_record/orm/persistence/operations/destroy.rb +17 -0
  33. data/lib/massive_record/orm/persistence/operations/embedded/destroy.rb +26 -0
  34. data/lib/massive_record/orm/persistence/operations/embedded/insert.rb +27 -0
  35. data/lib/massive_record/orm/persistence/operations/embedded/operation_helpers.rb +66 -0
  36. data/lib/massive_record/orm/persistence/operations/embedded/reload.rb +39 -0
  37. data/lib/massive_record/orm/persistence/operations/embedded/update.rb +29 -0
  38. data/lib/massive_record/orm/persistence/operations/insert.rb +19 -0
  39. data/lib/massive_record/orm/persistence/operations/reload.rb +26 -0
  40. data/lib/massive_record/orm/persistence/operations/suppress.rb +15 -0
  41. data/lib/massive_record/orm/persistence/operations/table_operation_helpers.rb +106 -0
  42. data/lib/massive_record/orm/persistence/operations/update.rb +25 -0
  43. data/lib/massive_record/orm/query_instrumentation.rb +26 -49
  44. data/lib/massive_record/orm/raw_data.rb +47 -0
  45. data/lib/massive_record/orm/relations.rb +4 -0
  46. data/lib/massive_record/orm/relations/interface.rb +134 -0
  47. data/lib/massive_record/orm/relations/metadata.rb +58 -12
  48. data/lib/massive_record/orm/relations/proxy.rb +17 -12
  49. data/lib/massive_record/orm/relations/proxy/embedded_in.rb +54 -0
  50. data/lib/massive_record/orm/relations/proxy/embedded_in_polymorphic.rb +15 -0
  51. data/lib/massive_record/orm/relations/proxy/embeds_many.rb +215 -0
  52. data/lib/massive_record/orm/relations/proxy/references_many.rb +112 -88
  53. data/lib/massive_record/orm/relations/proxy/references_one.rb +1 -1
  54. data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +1 -1
  55. data/lib/massive_record/orm/relations/proxy_collection.rb +84 -0
  56. data/lib/massive_record/orm/schema/column_family.rb +3 -2
  57. data/lib/massive_record/orm/schema/{column_interface.rb → embedded_interface.rb} +38 -4
  58. data/lib/massive_record/orm/schema/field.rb +2 -0
  59. data/lib/massive_record/orm/schema/table_interface.rb +19 -2
  60. data/lib/massive_record/orm/single_table_inheritance.rb +37 -2
  61. data/lib/massive_record/orm/timestamps.rb +17 -7
  62. data/lib/massive_record/orm/validations.rb +4 -0
  63. data/lib/massive_record/orm/validations/associated.rb +50 -0
  64. data/lib/massive_record/rails/railtie.rb +31 -0
  65. data/lib/massive_record/version.rb +1 -1
  66. data/lib/massive_record/wrapper/cell.rb +8 -1
  67. data/massive_record.gemspec +4 -4
  68. data/spec/adapter/thrift/atomic_increment_spec.rb +16 -0
  69. data/spec/adapter/thrift/table_find_spec.rb +14 -2
  70. data/spec/adapter/thrift/table_spec.rb +6 -6
  71. data/spec/adapter/thrift/utf8_encoding_of_id_spec.rb +71 -0
  72. data/spec/orm/cases/attribute_methods_spec.rb +215 -22
  73. data/spec/orm/cases/auto_generate_id_spec.rb +1 -1
  74. data/spec/orm/cases/change_id_spec.rb +62 -0
  75. data/spec/orm/cases/default_id_spec.rb +25 -6
  76. data/spec/orm/cases/default_values_spec.rb +6 -3
  77. data/spec/orm/cases/dirty_spec.rb +150 -102
  78. data/spec/orm/cases/embedded_spec.rb +250 -0
  79. data/spec/orm/cases/{finder_default_scope.rb → finder_default_scope_spec.rb} +4 -0
  80. data/spec/orm/cases/finder_scope_spec.rb +96 -29
  81. data/spec/orm/cases/finders_spec.rb +57 -10
  82. data/spec/orm/cases/id_factory/atomic_incrementation_spec.rb +72 -0
  83. data/spec/orm/cases/id_factory/timestamp_spec.rb +61 -0
  84. data/spec/orm/cases/identity_map/identity_map_spec.rb +357 -0
  85. data/spec/orm/cases/identity_map/middleware_spec.rb +74 -0
  86. data/spec/orm/cases/log_subscriber_spec.rb +15 -2
  87. data/spec/orm/cases/observing_spec.rb +61 -0
  88. data/spec/orm/cases/persistence_spec.rb +151 -60
  89. data/spec/orm/cases/raw_data_spec.rb +58 -0
  90. data/spec/orm/cases/single_table_inheritance_spec.rb +58 -2
  91. data/spec/orm/cases/table_spec.rb +3 -3
  92. data/spec/orm/cases/time_zone_awareness_spec.rb +27 -0
  93. data/spec/orm/cases/timestamps_spec.rb +23 -109
  94. data/spec/orm/cases/validation_spec.rb +9 -0
  95. data/spec/orm/models/address.rb +5 -1
  96. data/spec/orm/models/address_with_timestamp.rb +12 -0
  97. data/spec/orm/models/car.rb +5 -0
  98. data/spec/orm/models/person.rb +13 -1
  99. data/spec/orm/models/person_with_timestamp.rb +4 -2
  100. data/spec/orm/models/test_class.rb +1 -0
  101. data/spec/orm/persistence/operations/atomic_operation_spec.rb +58 -0
  102. data/spec/orm/persistence/operations/destroy_spec.rb +22 -0
  103. data/spec/orm/persistence/operations/embedded/destroy_spec.rb +71 -0
  104. data/spec/orm/persistence/operations/embedded/insert_spec.rb +59 -0
  105. data/spec/orm/persistence/operations/embedded/operation_helpers_spec.rb +92 -0
  106. data/spec/orm/persistence/operations/embedded/reload_spec.rb +67 -0
  107. data/spec/orm/persistence/operations/embedded/update_spec.rb +60 -0
  108. data/spec/orm/persistence/operations/insert_spec.rb +31 -0
  109. data/spec/orm/persistence/operations/reload_spec.rb +48 -0
  110. data/spec/orm/persistence/operations/suppress_spec.rb +17 -0
  111. data/spec/orm/persistence/operations/table_operation_helpers_spec.rb +98 -0
  112. data/spec/orm/persistence/operations/update_spec.rb +25 -0
  113. data/spec/orm/persistence/operations_spec.rb +58 -0
  114. data/spec/orm/relations/interface_spec.rb +188 -0
  115. data/spec/orm/relations/metadata_spec.rb +92 -15
  116. data/spec/orm/relations/proxy/embedded_in_polymorphic_spec.rb +37 -0
  117. data/spec/orm/relations/proxy/embedded_in_spec.rb +66 -0
  118. data/spec/orm/relations/proxy/embeds_many_spec.rb +651 -0
  119. data/spec/orm/relations/proxy/references_many_spec.rb +466 -2
  120. data/spec/orm/schema/column_family_spec.rb +21 -0
  121. data/spec/orm/schema/embedded_interface_spec.rb +181 -0
  122. data/spec/orm/schema/field_spec.rb +7 -0
  123. data/spec/orm/schema/table_interface_spec.rb +31 -1
  124. data/spec/shared/orm/id_factories.rb +44 -0
  125. data/spec/shared/orm/model_with_timestamps.rb +132 -0
  126. data/spec/shared/orm/persistence/a_persistence_embedded_operation_class.rb +3 -0
  127. data/spec/shared/orm/persistence/a_persistence_operation_class.rb +11 -0
  128. data/spec/shared/orm/persistence/a_persistence_table_operation_class.rb +11 -0
  129. data/spec/shared/orm/relations/proxy.rb +9 -2
  130. data/spec/spec_helper.rb +9 -0
  131. data/spec/support/mock_massive_record_connection.rb +2 -1
  132. metadata +106 -21
  133. data/spec/orm/cases/column_spec.rb +0 -49
  134. data/spec/orm/cases/id_factory_spec.rb +0 -92
  135. data/spec/orm/schema/column_interface_spec.rb +0 -136
@@ -0,0 +1,250 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/address'
3
+
4
+ describe MassiveRecord::ORM::Embedded do
5
+ subject { Address.new(:street => "Asker", :number => 5) }
6
+ let(:person) { Person.new "person-id", :name => "Thorbjorn", :age => "22" }
7
+
8
+ it "should have known_attribute_names" do
9
+ Address.should have(4).known_attribute_names
10
+ Address.known_attribute_names.should include("street", "number", "nice_place")
11
+ end
12
+
13
+ it "should have default_attributes_from_schema" do
14
+ Address.default_attributes_from_schema["street"].should be_nil
15
+ Address.default_attributes_from_schema["number"].should be_nil
16
+ Address.default_attributes_from_schema["nice_place"].should be_true
17
+ end
18
+
19
+ it "should have attributes_schema" do
20
+ Address.attributes_schema["street"].should be_instance_of MassiveRecord::ORM::Schema::Field
21
+ end
22
+
23
+ it "should have a default value set" do
24
+ subject.nice_place.should be_true
25
+ end
26
+
27
+
28
+ # TODO We might want to remove this when we have implemented
29
+ # associations correctly. Since Columns are contained within
30
+ # tables, calling save should do something on it's proxy_owner object.
31
+ describe "not be possible to persist (at least for now...)" do
32
+ %w(first last all exists? destroy_all).each do |method|
33
+ it "should not respond to class method #{method}" do
34
+ Address.should_not respond_to method
35
+ end
36
+ end
37
+
38
+ %w(
39
+ reload save save! save
40
+ update_attribute update_attributes update_attributes! touch destroy
41
+ delete increment! decrement! atomic_increment! atomic_decrement!
42
+ ).each do |method|
43
+ it "do respond to #{method}" do
44
+ subject.should respond_to method
45
+ end
46
+ end
47
+ end
48
+
49
+
50
+ describe "persistence" do
51
+ include SetUpHbaseConnectionBeforeAll
52
+ include SetTableNamesToTestTable
53
+
54
+ describe "#save" do
55
+ context "not embedded" do
56
+ before { subject.person = nil }
57
+
58
+ it "raises error" do
59
+ expect { subject.save }.to raise_error MassiveRecord::ORM::NotAssignedToEmbeddedCollection
60
+ end
61
+ end
62
+
63
+ context "embedded in a collection" do
64
+ context "collection owner not persisted" do
65
+ before { subject.person = person }
66
+
67
+ it "gets assigned an id" do
68
+ subject.save
69
+ subject.id.should_not be_blank
70
+ end
71
+
72
+ it "saves both embedded record and embedded in record" do
73
+ subject.save
74
+
75
+ person.should be_persisted
76
+ subject.should be_persisted
77
+ end
78
+
79
+ it "does nothing if validations fail on embedded" do
80
+ subject.street = nil
81
+ subject.save
82
+ subject.errors.should_not be_empty
83
+
84
+ person.should_not be_persisted
85
+ subject.should_not be_persisted
86
+ end
87
+
88
+ describe "validations fails on owner" do
89
+ before { person.name = nil }
90
+
91
+ it "does not persist owner" do
92
+ subject.save
93
+ person.should_not be_persisted
94
+ end
95
+
96
+ it "does not mark embedded as persisted" do
97
+ subject.save
98
+ subject.should_not be_persisted
99
+ end
100
+ end
101
+ end
102
+
103
+ context "colletion owner persisted" do
104
+ before do
105
+ person.save!
106
+ subject.person = person
107
+ end
108
+
109
+ it "gets assigned an id" do
110
+ subject.id.should_not be_blank
111
+ end
112
+
113
+ it "persists address" do
114
+ subject.street = "new_address"
115
+ subject.save
116
+ person.reload.addresses.first.street.should eq "new_address"
117
+ end
118
+
119
+ it "will not save changes in owner when embedded is saved" do
120
+ subject.street += "_NEW"
121
+ person.name += "_NEW"
122
+
123
+ subject.save
124
+
125
+ person.should be_name_changed
126
+ end
127
+
128
+ it "does nothing if validations fail on embedded" do
129
+ subject.street = nil
130
+ subject.save
131
+ subject.errors.should_not be_empty
132
+
133
+ subject.should be_changed
134
+ end
135
+
136
+ it "save even if validations fails on owner of collection embedded in" do
137
+ person.name = nil
138
+ subject.street += "_NEW"
139
+ subject.save
140
+
141
+ subject.should_not be_changed
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+
148
+ describe "#destroy" do
149
+ context "not emedded" do
150
+ before { subject.person = nil }
151
+
152
+ it "marks itself as destroyed" do
153
+ subject.destroy
154
+ subject.should be_destroyed
155
+ end
156
+ end
157
+
158
+ context "embedded" do
159
+ context "collection owner new record" do
160
+ before { subject.person = person }
161
+
162
+ it "marks itself as destroyed" do
163
+ subject.destroy
164
+ subject.should be_destroyed
165
+ end
166
+ end
167
+
168
+ context "collection owner persisted" do
169
+ before do
170
+ subject.person = person
171
+ person.save!
172
+ end
173
+
174
+ it "marks itself as destroyed" do
175
+ subject.destroy
176
+ subject.should be_destroyed
177
+ end
178
+
179
+ it "is removed from embeds_many collection" do
180
+ subject.destroy
181
+ person.addresses.should be_empty
182
+ end
183
+
184
+ it "is actually removed from collection" do
185
+ subject.destroy
186
+ person.reload.addresses.should be_empty
187
+ end
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ describe "id" do
194
+ include SetUpHbaseConnectionBeforeAll
195
+ include SetTableNamesToTestTable
196
+
197
+ describe "assignment on first save" do
198
+ it "has no id when first instantiated" do
199
+ subject.id.should be_nil
200
+ end
201
+
202
+ it "gets an id on explicit save" do
203
+ subject.person = person
204
+ subject.save
205
+ subject.id.should_not be_nil
206
+ end
207
+
208
+ it "gets an id when saved through persisted parent" do
209
+ person.save
210
+ person.addresses << subject
211
+ subject.id.should_not be_nil
212
+ end
213
+ end
214
+
215
+ describe "#database_id" do
216
+ let(:base_class) { Address.base_class.to_s.underscore }
217
+
218
+ describe "reader" do
219
+ it "has non when first instantiated" do
220
+ subject.database_id.should be_nil
221
+ end
222
+
223
+ it "gets one on explicit save" do
224
+ subject.person = person
225
+ subject.save
226
+ subject.database_id.should eq [base_class, subject.id].join(MassiveRecord::ORM::Embedded::DATABASE_ID_SEPARATOR)
227
+ end
228
+
229
+ it "gets one when saved through persisted parent" do
230
+ person.save
231
+ person.addresses << subject
232
+ subject.database_id.should eq [base_class, subject.id].join(MassiveRecord::ORM::Embedded::DATABASE_ID_SEPARATOR)
233
+ end
234
+ end
235
+
236
+ describe "writer" do
237
+ it "splits base_class and id and assigns id to id" do
238
+ subject.database_id = "address#{MassiveRecord::ORM::Embedded::DATABASE_ID_SEPARATOR}166"
239
+ subject.id.should eq "166"
240
+ end
241
+
242
+ it "raises an error if database id could not be parsed" do
243
+ expect {
244
+ subject.database_id = "address-166"
245
+ }.to raise_error MassiveRecord::ORM::InvalidEmbeddedDatabaseId
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end
@@ -56,5 +56,9 @@ describe "Default scope in" do
56
56
  Person.default_scoping.should be_instance_of MassiveRecord::ORM::Finders::Scope
57
57
  TestClass.default_scoping.should be_nil
58
58
  end
59
+
60
+ it "returns a new scope object when default_scope are set" do
61
+ Person.finder_scope.should_not eq Person.finder_scope
62
+ end
59
63
  end
60
64
  end
@@ -21,8 +21,8 @@ describe MassiveRecord::ORM::Finders::Scope do
21
21
  (MassiveRecord::ORM::Finders::Scope::MULTI_VALUE_METHODS + MassiveRecord::ORM::Finders::Scope::SINGLE_VALUE_METHODS).each do |method|
22
22
  it { should respond_to(method) }
23
23
 
24
- it "should return self after #{method}()" do
25
- subject.send(method, nil).should == subject
24
+ it "should return an instance of self #{method}()" do
25
+ subject.send(method, nil).should be_instance_of described_class
26
26
  end
27
27
  end
28
28
 
@@ -31,33 +31,27 @@ describe MassiveRecord::ORM::Finders::Scope do
31
31
  describe "multi value methods" do
32
32
  describe "select" do
33
33
  it "should not add nil values" do
34
- subject.select(nil)
35
- subject.select_values.should be_empty
34
+ subject.select(nil).select_values.should be_empty
36
35
  end
37
36
 
38
37
  it "should add incomming value to list" do
39
- subject.select(:info)
40
- subject.select_values.should include 'info'
38
+ subject.select(:info).select_values.should include 'info'
41
39
  end
42
40
 
43
41
  it "should be adding values if called twice" do
44
- subject.select(:info).select(:base)
45
- subject.select_values.should include 'info', 'base'
42
+ subject.select(:info).select(:base).select_values.should include 'info', 'base'
46
43
  end
47
44
 
48
45
  it "should add multiple arguments" do
49
- subject.select(:info, :base)
50
- subject.select_values.should include 'info', 'base'
46
+ subject.select(:info, :base).select_values.should include 'info', 'base'
51
47
  end
52
48
 
53
49
  it "should add multiple values given as array" do
54
- subject.select([:info, :base])
55
- subject.select_values.should include 'info', 'base'
50
+ subject.select([:info, :base]).select_values.should include 'info', 'base'
56
51
  end
57
52
 
58
53
  it "should not add same value twice" do
59
- subject.select(:info).select('info')
60
- subject.select_values.should == ['info']
54
+ subject.select(:info).select('info').select_values.should == ['info']
61
55
  end
62
56
  end
63
57
  end
@@ -67,13 +61,31 @@ describe MassiveRecord::ORM::Finders::Scope do
67
61
  describe "singel value methods" do
68
62
  describe "limit" do
69
63
  it "should set a limit" do
70
- subject.limit(5)
71
- subject.limit_value.should == 5
64
+ subject.limit(5).limit_value.should == 5
72
65
  end
73
66
 
74
67
  it "should be set to the last value set" do
75
- subject.limit(1).limit(5)
76
- subject.limit_value.should == 5
68
+ subject.limit(1).limit(5).limit_value.should == 5
69
+ end
70
+ end
71
+
72
+ describe "starts_with" do
73
+ it "should set a starts_with" do
74
+ subject.starts_with(5).starts_with_value.should == 5
75
+ end
76
+
77
+ it "should be set to the last value set" do
78
+ subject.starts_with(1).starts_with(5).starts_with_value.should == 5
79
+ end
80
+ end
81
+
82
+ describe "offset" do
83
+ it "should set a offset" do
84
+ subject.offset(5).offset_value.should == 5
85
+ end
86
+
87
+ it "should be set to the last value set" do
88
+ subject.offset(1).offset(5).offset_value.should == 5
77
89
  end
78
90
  end
79
91
  end
@@ -92,6 +104,14 @@ describe MassiveRecord::ORM::Finders::Scope do
92
104
  it "should include selection when asked for it" do
93
105
  subject.select(:info).send(:find_options).should include :select => ['info']
94
106
  end
107
+
108
+ it "includes a starts_with when asked for it" do
109
+ subject.starts_with("id-something").send(:find_options).should include :starts_with => "id-something"
110
+ end
111
+
112
+ it "includes an offset when asked for it" do
113
+ subject.offset("id-something").send(:find_options).should include :offset => "id-something"
114
+ end
95
115
  end
96
116
 
97
117
 
@@ -104,13 +124,31 @@ describe MassiveRecord::ORM::Finders::Scope do
104
124
  end
105
125
  end
106
126
 
107
-
108
127
  describe "#reset" do
109
- it "should reset loaded status" do
128
+ it "resets the loaded status" do
110
129
  subject.loaded = true
111
130
  subject.reset
112
131
  should_not be_loaded
113
132
  end
133
+
134
+ it "resets the loaded records" do
135
+ records = [:foo]
136
+ subject.instance_variable_set(:@records, records)
137
+ subject.reset
138
+ subject.instance_variable_get(:@records).should be_empty
139
+ end
140
+ end
141
+
142
+ describe "a clone" do
143
+ it "resets the state after being cloned" do
144
+ records = [:foo]
145
+ subject.instance_variable_set(:@records, records)
146
+ subject.loaded = true
147
+
148
+ cloned = subject.clone
149
+ cloned.should_not be_loaded
150
+ cloned.instance_variable_get(:@records).should be_empty
151
+ end
114
152
  end
115
153
 
116
154
  describe "#to_a" do
@@ -123,7 +161,7 @@ describe MassiveRecord::ORM::Finders::Scope do
123
161
  end
124
162
 
125
163
 
126
- [:to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?].each do |method|
164
+ [:to_xml, :to_yaml, :length, :size, :collect, :map, :each, :all?, :include?].each do |method|
127
165
  it "should delegate #{method} to to_a" do
128
166
  records = []
129
167
  records.should_receive(method)
@@ -157,6 +195,15 @@ describe MassiveRecord::ORM::Finders::Scope do
157
195
  subject.should_receive(:klass).and_return(klass)
158
196
  subject.select(:foo).find(1)
159
197
  end
198
+
199
+ it "should include finder options" do
200
+ extra_options = {:select => ["foo"], :conditions => 'should_be_passed_on_to_finder'}
201
+ klass = mock(Object)
202
+ klass.should_receive(:do_find).with("ID", hash_including(extra_options)).and_return([])
203
+ subject.instance_variable_set(:@klass, klass)
204
+
205
+ subject.find("ID", extra_options)
206
+ end
160
207
  end
161
208
 
162
209
 
@@ -176,7 +223,7 @@ describe MassiveRecord::ORM::Finders::Scope do
176
223
 
177
224
  klass = mock(Object)
178
225
  klass.should_receive(:do_find).with(anything, hash_including(extra_options)).and_return([])
179
- subject.should_receive(:klass).and_return(klass)
226
+ subject.instance_variable_set(:@klass, klass)
180
227
 
181
228
  subject.first(extra_options)
182
229
  end
@@ -194,7 +241,7 @@ describe MassiveRecord::ORM::Finders::Scope do
194
241
 
195
242
  klass = mock(Object)
196
243
  klass.should_receive(:do_find).with(anything, extra_options)
197
- subject.should_receive(:klass).and_return(klass)
244
+ subject.instance_variable_set(:@klass, klass)
198
245
 
199
246
  subject.all(extra_options)
200
247
  end
@@ -219,10 +266,12 @@ describe MassiveRecord::ORM::Finders::Scope do
219
266
  describe "with a person" do
220
267
  let(:person_1) { Person.create "ID1", :name => "Person1", :email => "one@person.com", :age => 11, :points => 111, :status => true }
221
268
  let(:person_2) { Person.create "ID2", :name => "Person2", :email => "two@person.com", :age => 22, :points => 222, :status => false }
269
+ let(:person_3) { Person.create "other", :name => "Person3", :email => "three@person.com", :age => 33, :points => 333, :status => true }
222
270
 
223
271
  before do
224
272
  person_1.save!
225
273
  person_2.save!
274
+ person_3.save!
226
275
  end
227
276
 
228
277
  (MassiveRecord::ORM::Finders::Scope::MULTI_VALUE_METHODS + MassiveRecord::ORM::Finders::Scope::SINGLE_VALUE_METHODS).each do |method|
@@ -244,15 +293,33 @@ describe MassiveRecord::ORM::Finders::Scope do
244
293
  person_from_db.status.should be_nil
245
294
  end
246
295
 
296
+ it "applying scope on a loaded scope returns a cloned and reset scope" do
297
+ scope = Person.limit(2)
298
+ scope.all.should eq [person_1, person_2]
299
+ scope.should be_loaded
300
+
301
+ new_scope = scope.offset("ID2")
302
+ new_scope.should_not be_loaded
303
+ new_scope.all.should eq [person_2, person_3]
304
+ end
305
+
247
306
  it "should not return read only objects when select is used" do
248
307
  person = Person.select(:info).first
249
308
  person.should_not be_readonly
250
309
  end
251
310
 
311
+ it "ensures records starts with string" do
312
+ Person.starts_with("ID").should == [person_1, person_2]
313
+ end
314
+
315
+ it "sets an offset point to begin read rows from" do
316
+ Person.offset("ID2").should == [person_2, person_3]
317
+ end
318
+
252
319
  it "should be possible to iterate over a collection with each" do
253
320
  result = []
254
321
 
255
- Person.limit(5).each do |person|
322
+ Person.starts_with("ID").limit(2).each do |person|
256
323
  result << person.name
257
324
  end
258
325
 
@@ -260,7 +327,7 @@ describe MassiveRecord::ORM::Finders::Scope do
260
327
  end
261
328
 
262
329
  it "should be possible to collect" do
263
- Person.select(:info).collect(&:name).should == ["Person1", "Person2"]
330
+ Person.select(:info).collect(&:name).should == ["Person1", "Person2", "Person3"]
264
331
  end
265
332
 
266
333
  it "should be possible to checkc if it includes something" do
@@ -272,13 +339,13 @@ describe MassiveRecord::ORM::Finders::Scope do
272
339
 
273
340
  describe "#apply_finder_options" do
274
341
  it "should apply limit correctly" do
275
- subject.should_receive(:limit).with(30)
276
- subject.send :apply_finder_options, :limit => 30
342
+ scope = subject.send :apply_finder_options, :limit => 30
343
+ scope.limit_value.should eq 30
277
344
  end
278
345
 
279
346
  it "should apply select correctly" do
280
- subject.should_receive(:select).with(:foo)
281
- subject.send :apply_finder_options, :select => :foo
347
+ scope = subject.send :apply_finder_options, :select => :foo
348
+ scope.select_values.should include 'foo'
282
349
  end
283
350
 
284
351
  it "should raise unknown scope error if options is unkown" do