massive_record 0.1.1 → 0.2.0.beta

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 (83) hide show
  1. data/CHANGELOG.md +28 -5
  2. data/Gemfile.lock +12 -12
  3. data/README.md +29 -1
  4. data/lib/massive_record/adapters/initialize.rb +18 -0
  5. data/lib/massive_record/adapters/thrift/adapter.rb +25 -0
  6. data/lib/massive_record/adapters/thrift/column_family.rb +24 -0
  7. data/lib/massive_record/adapters/thrift/connection.rb +73 -0
  8. data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase.rb +0 -0
  9. data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_constants.rb +0 -0
  10. data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_types.rb +0 -0
  11. data/lib/massive_record/adapters/thrift/row.rb +150 -0
  12. data/lib/massive_record/adapters/thrift/scanner.rb +59 -0
  13. data/lib/massive_record/adapters/thrift/table.rb +169 -0
  14. data/lib/massive_record/orm/attribute_methods/read.rb +2 -1
  15. data/lib/massive_record/orm/base.rb +61 -3
  16. data/lib/massive_record/orm/coders/chained.rb +71 -0
  17. data/lib/massive_record/orm/coders/json.rb +17 -0
  18. data/lib/massive_record/orm/coders/yaml.rb +15 -0
  19. data/lib/massive_record/orm/coders.rb +3 -0
  20. data/lib/massive_record/orm/errors.rb +15 -2
  21. data/lib/massive_record/orm/finders/scope.rb +166 -0
  22. data/lib/massive_record/orm/finders.rb +45 -24
  23. data/lib/massive_record/orm/persistence.rb +4 -4
  24. data/lib/massive_record/orm/relations/interface.rb +170 -0
  25. data/lib/massive_record/orm/relations/metadata.rb +150 -0
  26. data/lib/massive_record/orm/relations/proxy/references_many.rb +229 -0
  27. data/lib/massive_record/orm/relations/proxy/references_one.rb +40 -0
  28. data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +49 -0
  29. data/lib/massive_record/orm/relations/proxy.rb +174 -0
  30. data/lib/massive_record/orm/relations.rb +6 -0
  31. data/lib/massive_record/orm/schema/column_interface.rb +1 -1
  32. data/lib/massive_record/orm/schema/field.rb +62 -27
  33. data/lib/massive_record/orm/single_table_inheritance.rb +21 -0
  34. data/lib/massive_record/version.rb +1 -1
  35. data/lib/massive_record/wrapper/adapter.rb +6 -0
  36. data/lib/massive_record/wrapper/base.rb +6 -7
  37. data/lib/massive_record/wrapper/cell.rb +9 -32
  38. data/lib/massive_record/wrapper/column_families_collection.rb +2 -2
  39. data/lib/massive_record/wrapper/errors.rb +10 -0
  40. data/lib/massive_record/wrapper/tables_collection.rb +1 -1
  41. data/lib/massive_record.rb +5 -12
  42. data/spec/orm/cases/attribute_methods_spec.rb +5 -1
  43. data/spec/orm/cases/base_spec.rb +77 -4
  44. data/spec/orm/cases/column_spec.rb +1 -1
  45. data/spec/orm/cases/finder_default_scope.rb +53 -0
  46. data/spec/orm/cases/finder_scope_spec.rb +288 -0
  47. data/spec/orm/cases/finders_spec.rb +56 -13
  48. data/spec/orm/cases/persistence_spec.rb +20 -5
  49. data/spec/orm/cases/single_table_inheritance_spec.rb +26 -0
  50. data/spec/orm/cases/table_spec.rb +1 -1
  51. data/spec/orm/cases/timestamps_spec.rb +16 -16
  52. data/spec/orm/coders/chained_spec.rb +73 -0
  53. data/spec/orm/coders/json_spec.rb +6 -0
  54. data/spec/orm/coders/yaml_spec.rb +6 -0
  55. data/spec/orm/models/best_friend.rb +7 -0
  56. data/spec/orm/models/friend.rb +4 -0
  57. data/spec/orm/models/person.rb +20 -6
  58. data/spec/orm/models/{person_with_timestamps.rb → person_with_timestamp.rb} +1 -1
  59. data/spec/orm/models/test_class.rb +3 -0
  60. data/spec/orm/relations/interface_spec.rb +207 -0
  61. data/spec/orm/relations/metadata_spec.rb +202 -0
  62. data/spec/orm/relations/proxy/references_many_spec.rb +624 -0
  63. data/spec/orm/relations/proxy/references_one_polymorphic_spec.rb +106 -0
  64. data/spec/orm/relations/proxy/references_one_spec.rb +111 -0
  65. data/spec/orm/relations/proxy_spec.rb +13 -0
  66. data/spec/orm/schema/field_spec.rb +101 -2
  67. data/spec/shared/orm/coders/an_orm_coder.rb +14 -0
  68. data/spec/shared/orm/relations/proxy.rb +154 -0
  69. data/spec/shared/orm/relations/singular_proxy.rb +68 -0
  70. data/spec/spec_helper.rb +1 -0
  71. data/spec/thrift/cases/encoding_spec.rb +28 -7
  72. data/spec/wrapper/cases/adapter_spec.rb +9 -0
  73. data/spec/wrapper/cases/connection_spec.rb +13 -10
  74. data/spec/wrapper/cases/table_spec.rb +85 -85
  75. metadata +74 -22
  76. data/TODO.md +0 -8
  77. data/lib/massive_record/exceptions.rb +0 -11
  78. data/lib/massive_record/wrapper/column_family.rb +0 -22
  79. data/lib/massive_record/wrapper/connection.rb +0 -71
  80. data/lib/massive_record/wrapper/row.rb +0 -173
  81. data/lib/massive_record/wrapper/scanner.rb +0 -61
  82. data/lib/massive_record/wrapper/table.rb +0 -149
  83. data/spec/orm/cases/hbase/connection_spec.rb +0 -13
@@ -1,4 +1,7 @@
1
1
  class TestClass < MassiveRecord::ORM::Table
2
+
3
+ references_one :attachable, :polymorphic => true, :store_in => :test_family
4
+
2
5
  column_family :test_family do
3
6
  field :foo, :string
4
7
  end
@@ -0,0 +1,207 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/person'
3
+ require 'orm/models/person_with_timestamp'
4
+
5
+ describe MassiveRecord::ORM::Relations::Interface do
6
+ include SetUpHbaseConnectionBeforeAll
7
+ include SetTableNamesToTestTable
8
+
9
+ describe "class methods" do
10
+ subject { Person }
11
+
12
+ describe "should include" do
13
+ %w(references_one).each do |relation|
14
+ it { should respond_to relation }
15
+ end
16
+ end
17
+
18
+ it "should not share relations" do
19
+ Person.relations.should_not == PersonWithTimestamp.relations
20
+ end
21
+ end
22
+
23
+
24
+ describe "references one" do
25
+ describe "relation's meta data" do
26
+ subject { Person.relations.detect { |relation| relation.name == "boss" } }
27
+
28
+ it "should have the reference one meta data stored in relations" do
29
+ Person.relations.detect { |relation| relation.name == "boss" }.should_not be_nil
30
+ end
31
+
32
+ it "should have type set to references_one" do
33
+ subject.relation_type.should == "references_one"
34
+ end
35
+
36
+ it "should raise an error if the same relaton is called for twice" do
37
+ lambda { Person.references_one :boss }.should raise_error MassiveRecord::ORM::RelationAlreadyDefined
38
+ end
39
+ end
40
+
41
+
42
+ describe "instance" do
43
+ subject { Person.new }
44
+ let(:boss) { PersonWithTimestamp.new }
45
+ let(:proxy) { subject.send(:relation_proxy, "boss") }
46
+
47
+ it { should respond_to :boss }
48
+ it { should respond_to :boss= }
49
+ it { should respond_to :boss_id }
50
+ it { should respond_to :boss_id= }
51
+
52
+
53
+ describe "record getter and setter" do
54
+ it "should return nil if foreign_key is nil" do
55
+ subject.boss.should be_nil
56
+ end
57
+
58
+ it "should return the proxy's proxy_target if boss is set" do
59
+ subject.boss = boss
60
+ subject.boss.should == boss
61
+ end
62
+
63
+ it "should be able to reset the proxy" do
64
+ proxy.should_receive(:load_proxy_target).and_return(true)
65
+ proxy.should_receive(:reset)
66
+ subject.boss.reset
67
+ end
68
+
69
+ it "should be able to reload the proxy" do
70
+ proxy.should_receive(:load_proxy_target).and_return(true)
71
+ proxy.should_receive(:reload)
72
+ subject.boss.reload
73
+ end
74
+
75
+ it "should set the foreign_key in proxy_owner when proxy_target is set" do
76
+ subject.boss = boss
77
+ subject.boss_id.should == boss.id
78
+ end
79
+
80
+ it "should load proxy_target object when read method is called" do
81
+ PersonWithTimestamp.should_receive(:find).and_return(boss)
82
+ subject.boss_id = boss.id
83
+ subject.boss.should == boss
84
+ end
85
+
86
+ it "should not load proxy_target twice" do
87
+ PersonWithTimestamp.should_receive(:find).once.and_return(boss)
88
+ subject.boss_id = boss.id
89
+ 2.times { subject.boss }
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+
96
+ describe "references one polymorphic" do
97
+ describe "relation's meta data" do
98
+ subject { TestClass.relations.detect { |relation| relation.name == "attachable" } }
99
+
100
+ it "should have the reference one polymorphic meta data stored in relations" do
101
+ TestClass.relations.detect { |relation| relation.name == "attachable" }.should_not be_nil
102
+ end
103
+
104
+ it "should have type set to correct type" do
105
+ subject.relation_type.should == "references_one_polymorphic"
106
+ end
107
+
108
+ it "should raise an error if the same relaton is called for twice" do
109
+ lambda { TestClass.references_one :attachable }.should raise_error MassiveRecord::ORM::RelationAlreadyDefined
110
+ end
111
+ end
112
+
113
+
114
+ describe "instance" do
115
+ subject { TestClass.new }
116
+ let(:attachable) { Person.new }
117
+
118
+ it { should respond_to :attachable }
119
+ it { should respond_to :attachable= }
120
+ it { should respond_to :attachable_id }
121
+ it { should respond_to :attachable_id= }
122
+ it { should respond_to :attachable_type }
123
+ it { should respond_to :attachable_type= }
124
+
125
+
126
+ describe "record getter and setter" do
127
+ it "should return nil if foreign_key is nil" do
128
+ subject.attachable.should be_nil
129
+ end
130
+
131
+ it "should return the proxy's proxy_target if attachable is set" do
132
+ subject.attachable = attachable
133
+ subject.attachable.should == attachable
134
+ end
135
+
136
+ it "should set the foreign_key in proxy_owner when proxy_target is set" do
137
+ subject.attachable = attachable
138
+ subject.attachable_id.should == attachable.id
139
+ end
140
+
141
+ it "should set the type in proxy_owner when proxy_target is set" do
142
+ subject.attachable = attachable
143
+ subject.attachable_type.should == attachable.class.to_s.underscore
144
+ end
145
+
146
+
147
+
148
+ [Person, PersonWithTimestamp].each do |polymorphic_class|
149
+ describe "polymorphic association to class #{polymorphic_class}" do
150
+ let (:attachable) { polymorphic_class.new :id => "ID1" }
151
+
152
+ before do
153
+ subject.attachable_id = attachable.id
154
+ subject.attachable_type = polymorphic_class.to_s.underscore
155
+ end
156
+
157
+ it "should load proxy_target object when read method is called" do
158
+ polymorphic_class.should_receive(:find).and_return(attachable)
159
+ subject.attachable.should == attachable
160
+ end
161
+
162
+ it "should not load proxy_target twice" do
163
+ polymorphic_class.should_receive(:find).once.and_return(attachable)
164
+ 2.times { subject.attachable }
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+
173
+
174
+
175
+ describe "references many" do
176
+ describe "relation's meta data" do
177
+ subject { Person.relations.detect { |relation| relation.name == "test_classes" } }
178
+
179
+ it "should have the reference one meta data stored in relations" do
180
+ Person.relations.detect { |relation| relation.name == "test_classes" }.should_not be_nil
181
+ end
182
+
183
+ it "should have type set to references_many" do
184
+ subject.relation_type.should == "references_many"
185
+ end
186
+
187
+ it "should raise an error if the same relaton is called for twice" do
188
+ lambda { Person.references_one :test_classes }.should raise_error MassiveRecord::ORM::RelationAlreadyDefined
189
+ end
190
+ end
191
+
192
+
193
+ describe "instance" do
194
+ subject { Person.new }
195
+ let(:test_class) { TestClass.new }
196
+ let(:proxy) { subject.send(:relation_proxy, "test_classes") }
197
+
198
+ it { should respond_to :test_classes }
199
+ it { should respond_to :test_class_ids }
200
+ it { should respond_to :test_class_ids= }
201
+
202
+ it "should have an array as foreign_key attribute" do
203
+ subject.test_class_ids.should be_instance_of Array
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,202 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/person'
3
+
4
+ describe MassiveRecord::ORM::Relations::Metadata do
5
+ subject { MassiveRecord::ORM::Relations::Metadata.new(nil) }
6
+
7
+ %w(name foreign_key class_name relation_type find_with polymorphic records_starts_from).each do |attr|
8
+ it { should respond_to attr }
9
+ it { should respond_to attr+"=" }
10
+ end
11
+
12
+
13
+ it "should be setting values by initializer" do
14
+ metadata = subject.class.new :car, :foreign_key => :my_car_id, :class_name => "Vehicle", :store_in => :info, :polymorphic => true, :records_starts_from => :records_starts_from
15
+ metadata.name.should == "car"
16
+ metadata.foreign_key.should == "my_car_id"
17
+ metadata.class_name.should == "Vehicle"
18
+ metadata.store_in.should == "info"
19
+ metadata.records_starts_from.should == :records_starts_from
20
+ metadata.should be_polymorphic
21
+ end
22
+
23
+ it "should not be possible to set relation type through initializer" do
24
+ metadata = subject.class.new :car, :relation_type => :foo
25
+ metadata.relation_type.should be_nil
26
+ end
27
+
28
+
29
+ its(:name) { should be_nil }
30
+
31
+ it "should return name as string" do
32
+ subject.name = :foo
33
+ subject.name.should == "foo"
34
+ end
35
+
36
+
37
+ describe "#class_name" do
38
+ it "should return whatever it's being set to" do
39
+ subject.class_name = "Person"
40
+ subject.class_name.should == "Person"
41
+ end
42
+
43
+ it "should return class name as a string" do
44
+ subject.class_name = Person
45
+ subject.class_name.should == "Person"
46
+ end
47
+
48
+ it "should calculate it from name" do
49
+ subject.name = :employee
50
+ subject.class_name.should == "Employee"
51
+ end
52
+
53
+ it "should calculate correct class name if represents a collection" do
54
+ subject.relation_type = "references_many"
55
+ subject.name = "persons"
56
+ subject.class_name.should == "Person"
57
+ end
58
+ end
59
+
60
+
61
+
62
+
63
+ describe "#foreign_key" do
64
+ it "should return whatever it's being set to" do
65
+ subject.foreign_key = "person_id"
66
+ subject.foreign_key.should == "person_id"
67
+ end
68
+
69
+ it "should return foreign key as string" do
70
+ subject.foreign_key = :person_id
71
+ subject.foreign_key.should == "person_id"
72
+ end
73
+
74
+ it "should try and calculate the foreign key from the name" do
75
+ subject.class_name = "PersonWithSomething"
76
+ subject.name = :person
77
+ subject.foreign_key.should == "person_id"
78
+ end
79
+
80
+ it "should return plural for if meta data is representing a many relation" do
81
+ subject.relation_type = :references_many
82
+ subject.name = :persons
83
+ subject.foreign_key.should == "person_ids"
84
+ end
85
+ end
86
+
87
+ describe "#foreign_key_setter" do
88
+ it "should return whatever the foreign_key is pluss =" do
89
+ subject.should_receive(:foreign_key).and_return("custom_key")
90
+ subject.foreign_key_setter.should == "custom_key="
91
+ end
92
+ end
93
+
94
+
95
+
96
+ describe "#store_in" do
97
+ its(:store_in) { should be_nil }
98
+
99
+ it "should be able to set column family to store foreign key in" do
100
+ subject.store_in = :info
101
+ subject.store_in.should == "info"
102
+ end
103
+ end
104
+
105
+ it "should know its persisting foreign key if foreign key stored in has been set" do
106
+ subject.store_in = :info
107
+ should be_persisting_foreign_key
108
+ end
109
+
110
+ it "should not be storing the foreign key if records_starts_from is defined" do
111
+ subject.store_in = :info
112
+ subject.records_starts_from = :method_which_returns_a_starting_point
113
+ should_not be_persisting_foreign_key
114
+ end
115
+
116
+
117
+
118
+ it "should compare two meta datas based on name" do
119
+ other = MassiveRecord::ORM::Relations::Metadata.new(subject.name)
120
+ other.should == subject
121
+ end
122
+
123
+ it "should have the same hash value for the same name" do
124
+ subject.hash == subject.name.hash
125
+ end
126
+
127
+
128
+
129
+ describe "#new_relation_proxy" do
130
+ let(:proxy_owner) { Person.new }
131
+ let(:proxy) { subject.relation_type = "references_one" and subject.new_relation_proxy(proxy_owner) }
132
+
133
+ it "should return a proxy where proxy_owner is assigned" do
134
+ proxy.proxy_owner.should == proxy_owner
135
+ end
136
+
137
+ it "should return a proxy where metadata is assigned" do
138
+ proxy.metadata.should == subject
139
+ end
140
+
141
+ it "should append _polymorphic to the proxy name if it is polymorphic" do
142
+ subject.polymorphic = true
143
+ subject.relation_type = "references_one"
144
+ subject.relation_type.should == "references_one_polymorphic"
145
+ end
146
+ end
147
+
148
+
149
+ describe "#polymorphic_type_column" do
150
+ before do
151
+ subject.polymorphic = true
152
+ end
153
+
154
+ it "should remove _id and add _type to foreign_key" do
155
+ subject.should_receive(:foreign_key).and_return("foo_id")
156
+ subject.polymorphic_type_column.should == "foo_type"
157
+ end
158
+
159
+ it "should simply add _type if foreign_key does not end on _id" do
160
+ subject.should_receive(:foreign_key).and_return("foo_id_b")
161
+ subject.polymorphic_type_column.should == "foo_id_b_type"
162
+ end
163
+
164
+ it "should return setter method" do
165
+ subject.should_receive(:polymorphic_type_column).and_return("yey")
166
+ subject.polymorphic_type_column_setter.should == "yey="
167
+ end
168
+ end
169
+
170
+
171
+ describe "records_starts_from" do
172
+ it "should not have any proc if records_starts_from is nil" do
173
+ subject.find_with = "foo"
174
+ subject.records_starts_from = nil
175
+ subject.find_with.should be_nil
176
+ end
177
+
178
+ it "should buld a proc with records_starts_from set" do
179
+ subject.records_starts_from = :friends_records_starts_from_id
180
+ subject.find_with.should be_instance_of Proc
181
+ end
182
+
183
+ describe "proc" do
184
+ let(:proxy_owner) { Person.new :id => "person-1" }
185
+ let(:find_with_proc) { subject.records_starts_from = :friends_records_starts_from_id; subject.find_with }
186
+
187
+ before do
188
+ subject.class_name = "Person"
189
+ end
190
+
191
+ it "should call proxy_target class with all, start with proxy_owner's start from id response" do
192
+ Person.should_receive(:all).with(hash_including(:start => proxy_owner.friends_records_starts_from_id))
193
+ find_with_proc.call(proxy_owner)
194
+ end
195
+
196
+ it "should be possible to send in options to the proc" do
197
+ Person.should_receive(:all).with(hash_including(:limit => 10, :start => proxy_owner.friends_records_starts_from_id))
198
+ find_with_proc.call(proxy_owner, {:limit => 10})
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,624 @@
1
+ require 'spec_helper'
2
+
3
+ class TestReferencesManyProxy < MassiveRecord::ORM::Relations::Proxy::ReferencesMany; end
4
+
5
+ describe TestReferencesManyProxy do
6
+ include SetUpHbaseConnectionBeforeAll
7
+ include SetTableNamesToTestTable
8
+
9
+ let(:proxy_owner) { Person.new :id => "person-id-1", :name => "Test", :age => 29 }
10
+ let(:proxy_target) { TestClass.new :id => "test-class-id-1" }
11
+ let(:proxy_target_2) { TestClass.new :id => "test-class-id-2" }
12
+ let(:proxy_target_3) { TestClass.new :id => "test-class-id-3" }
13
+ let(:metadata) { subject.metadata }
14
+
15
+ subject { proxy_owner.send(:relation_proxy, 'test_classes') }
16
+
17
+ it_should_behave_like "relation proxy"
18
+
19
+
20
+
21
+ describe "#find_proxy_target" do
22
+ describe "with foreig keys stored in proxy_owner" do
23
+ it "should not try to find proxy_target if foreign_keys is blank" do
24
+ proxy_owner.test_class_ids.clear
25
+ TestClass.should_not_receive(:find)
26
+ subject.load_proxy_target.should be_empty
27
+ end
28
+
29
+ it "should try to load proxy_target if foreign_keys has any keys" do
30
+ proxy_owner.test_class_ids << proxy_target.id
31
+ TestClass.should_receive(:find).with([proxy_target.id], anything).and_return([proxy_target])
32
+ subject.load_proxy_target.should == [proxy_target]
33
+ end
34
+
35
+ it "should not die when loading foreign keys which does not exist in proxy_target table" do
36
+ proxy_owner.save!
37
+ proxy_owner.test_classes << proxy_target
38
+ proxy_owner.test_classes.reset
39
+ proxy_owner.test_class_ids << "does_not_exists"
40
+ proxy_owner.test_classes.reload
41
+ proxy_owner.test_classes.length.should == 1
42
+ end
43
+ end
44
+
45
+ describe "with start from" do
46
+ let(:proxy_target) { Person.new :id => proxy_owner.id+"-friend-1", :name => "T", :age => 2 }
47
+ let(:proxy_target_2) { Person.new :id => proxy_owner.id+"-friend-2", :name => "H", :age => 9 }
48
+ let(:not_proxy_target) { Person.new :id => "foo"+"-friend-2", :name => "H", :age => 1 }
49
+ let(:metadata) { subject.metadata }
50
+
51
+ subject { proxy_owner.send(:relation_proxy, 'friends') }
52
+
53
+ before do
54
+ proxy_target.save!
55
+ proxy_target_2.save!
56
+ not_proxy_target.save!
57
+ end
58
+
59
+ it "should not try to find proxy_target if start from method is blank" do
60
+ proxy_owner.should_receive(:friends_records_starts_from_id).and_return(nil)
61
+ Person.should_not_receive(:all)
62
+ subject.load_proxy_target.should be_empty
63
+ end
64
+
65
+ it "should find all friends when loading" do
66
+ friends = subject.load_proxy_target
67
+ friends.length.should == 2
68
+ friends.should include(proxy_target)
69
+ friends.should include(proxy_target_2)
70
+ friends.should_not include(not_proxy_target)
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "find with proc" do
76
+ let(:test_class) { TestClass.new }
77
+
78
+ before do
79
+ subject.metadata.find_with = Proc.new { |proxy_target| TestClass.find("testing-123") }
80
+ end
81
+
82
+ after do
83
+ subject.metadata.find_with = nil
84
+ end
85
+
86
+ it "should not call find_proxy_target" do
87
+ should_not_receive :find_proxy_target
88
+ subject.load_proxy_target
89
+ end
90
+
91
+ it "should load by given proc" do
92
+ TestClass.should_receive(:find).with("testing-123").and_return([test_class])
93
+ subject.load_proxy_target.should == [test_class]
94
+ end
95
+
96
+ it "should always wrap the proc's result in an array" do
97
+ TestClass.should_receive(:find).with("testing-123").and_return(test_class)
98
+ subject.load_proxy_target.should == [test_class]
99
+ end
100
+
101
+ it "should be empty if the proc return nil" do
102
+ TestClass.should_receive(:find).with("testing-123").and_return(nil)
103
+ subject.load_proxy_target.should be_empty
104
+ end
105
+ end
106
+
107
+
108
+ describe "adding records to collection" do
109
+ [:<<, :push, :concat].each do |add_method|
110
+ describe "by ##{add_method}" do
111
+ it "should include the proxy_target in the proxy" do
112
+ subject.send(add_method, proxy_target)
113
+ subject.proxy_target.should include proxy_target
114
+ end
115
+
116
+ it "should not add invalid objects to collection" do
117
+ proxy_target.should_receive(:valid?).and_return false
118
+ subject.send(add_method, proxy_target).should be_false
119
+ subject.proxy_target.should_not include proxy_target
120
+ end
121
+
122
+ it "should update array of foreign keys in proxy_owner" do
123
+ proxy_owner.test_class_ids.should be_empty
124
+ subject.send(add_method, proxy_target)
125
+ proxy_owner.test_class_ids.should include(proxy_target.id)
126
+ end
127
+
128
+ it "should auto-persist foreign keys if owner has been persisted" do
129
+ proxy_owner.save!
130
+ subject.send(add_method, proxy_target)
131
+ proxy_owner.reload
132
+ proxy_owner.test_class_ids.should include(proxy_target.id)
133
+ end
134
+
135
+ it "should not persist proxy owner (and it's foreign keys) if owner is a new record" do
136
+ subject.send(add_method, proxy_target)
137
+ proxy_owner.should be_new_record
138
+ end
139
+
140
+ it "should not update array of foreign keys in proxy_owner if it does not respond to it" do
141
+ proxy_owner.should_receive(:respond_to?).twice.and_return(false)
142
+ subject.send(add_method, proxy_target)
143
+ proxy_owner.test_class_ids.should_not include(proxy_target.id)
144
+ end
145
+
146
+ it "should not update array of foreign keys in the proxy owner if it has been destroyed" do
147
+ proxy_owner.should_receive(:destroyed?).and_return true
148
+ subject.send(add_method, proxy_target)
149
+ proxy_owner.test_class_ids.should_not include(proxy_target.id)
150
+ end
151
+
152
+ it "should not do anything adding the same record twice" do
153
+ 2.times { subject.send(add_method, proxy_target) }
154
+ subject.proxy_target.length.should == 1
155
+ proxy_owner.test_class_ids.length.should == 1
156
+ end
157
+
158
+ it "should be able to add two records at the same time" do
159
+ subject.send add_method, [proxy_target, proxy_target_2]
160
+ subject.proxy_target.should include proxy_target
161
+ subject.proxy_target.should include proxy_target_2
162
+ end
163
+
164
+ it "should return proxy so calls can be chained" do
165
+ subject.send(add_method, proxy_target).object_id.should == subject.object_id
166
+ end
167
+
168
+ it "should raise an error if there is a type mismatch" do
169
+ lambda { subject.send add_method, Person.new(:name => "Foo", :age => 2) }.should raise_error MassiveRecord::ORM::RelationTypeMismatch
170
+ end
171
+
172
+ it "should not save the pushed proxy_target if proxy_owner is not persisted" do
173
+ proxy_owner.should_receive(:persisted?).and_return false
174
+ proxy_target.should_not_receive(:save)
175
+ subject.send(add_method, proxy_target)
176
+ end
177
+
178
+ it "should not save the proxy_owner object if it has not been persisted before" do
179
+ proxy_owner.should_receive(:persisted?).and_return false
180
+ proxy_owner.should_not_receive(:save)
181
+ subject.send(add_method, proxy_target)
182
+ end
183
+
184
+ it "should save the pushed proxy_target if proxy_owner is persisted" do
185
+ proxy_owner.save!
186
+ proxy_target.should_receive(:save).and_return(true)
187
+ subject.send(add_method, proxy_target)
188
+ end
189
+
190
+ it "should not save anything if one record is invalid" do
191
+ proxy_owner.save!
192
+
193
+ proxy_target.should_receive(:valid?).and_return(true)
194
+ proxy_target_2.should_receive(:valid?).and_return(false)
195
+
196
+ proxy_target.should_not_receive(:save)
197
+ proxy_target_2.should_not_receive(:save)
198
+ proxy_owner.should_not_receive(:save)
199
+
200
+ subject.send(add_method, [proxy_target, proxy_target_2]).should be_false
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ describe "removing records from the collection" do
207
+ [:destroy, :delete].each do |delete_method|
208
+ describe "with ##{delete_method}" do
209
+ before do
210
+ subject << proxy_target
211
+ end
212
+
213
+ it "should not be in proxy after being removed" do
214
+ subject.send(delete_method, proxy_target)
215
+ subject.proxy_target.should_not include proxy_target
216
+ end
217
+
218
+ it "should remove the destroyed records id from proxy_owner foreign keys" do
219
+ subject.send(delete_method, proxy_target)
220
+ proxy_owner.test_class_ids.should_not include(proxy_target.id)
221
+ end
222
+
223
+ it "should not remove foreign keys in proxy_owner if it does not respond to it" do
224
+ proxy_owner.should_receive(:respond_to?).and_return false
225
+ subject.send(delete_method, proxy_target)
226
+ proxy_owner.test_class_ids.should include(proxy_target.id)
227
+ end
228
+
229
+ it "should not save the proxy_owner if it has not been persisted" do
230
+ proxy_owner.should_receive(:persisted?).and_return(false)
231
+ proxy_owner.should_not_receive(:save)
232
+ subject.send(delete_method, proxy_target)
233
+ end
234
+
235
+ it "should save the proxy_owner if it has been persisted" do
236
+ proxy_owner.save!
237
+ proxy_owner.should_receive(:save)
238
+ subject.send(delete_method, proxy_target)
239
+ end
240
+ end
241
+ end
242
+
243
+ describe "with #destroy" do
244
+ before do
245
+ subject << proxy_target
246
+ end
247
+
248
+ it "should ask the record to destroy self" do
249
+ proxy_target.should_receive(:destroy)
250
+ subject.destroy proxy_target
251
+ end
252
+ end
253
+
254
+ describe "with #delete" do
255
+ before do
256
+ subject << proxy_target
257
+ end
258
+
259
+ it "should not ask the record to destroy self" do
260
+ proxy_target.should_not_receive(:destroy)
261
+ proxy_target.should_not_receive(:delete)
262
+ subject.delete(proxy_target)
263
+ end
264
+ end
265
+
266
+
267
+
268
+ describe "with destroy_all" do
269
+ before do
270
+ proxy_owner.save!
271
+ subject << proxy_target << proxy_target_2
272
+ end
273
+
274
+ it "should not include any records after destroying all" do
275
+ subject.destroy_all
276
+ subject.proxy_target.should be_empty
277
+ end
278
+
279
+ it "should remove all foreign keys in proxy_owner" do
280
+ subject.destroy_all
281
+ proxy_owner.test_class_ids.should be_empty
282
+ end
283
+
284
+ it "should call reset after all destroyed" do
285
+ subject.should_receive(:reset)
286
+ subject.destroy_all
287
+ end
288
+
289
+ it "should be loaded after all being destroyed" do
290
+ subject.destroy_all
291
+ should be_loaded
292
+ end
293
+
294
+ it "should call destroy on each record" do
295
+ proxy_target.should_receive(:destroy)
296
+ proxy_target_2.should_receive(:destroy)
297
+ subject.destroy_all
298
+ end
299
+ end
300
+
301
+ describe "with delete_all" do
302
+ before do
303
+ proxy_owner.save!
304
+ subject << proxy_target << proxy_target_2
305
+ end
306
+
307
+ it "should not include any records after destroying all" do
308
+ subject.delete_all
309
+ subject.proxy_target.should be_empty
310
+ end
311
+
312
+ it "should remove all foreign keys in proxy_owner" do
313
+ subject.delete_all
314
+ proxy_owner.test_class_ids.should be_empty
315
+ end
316
+
317
+ it "should call reset after all destroyed" do
318
+ subject.should_receive(:reset)
319
+ subject.delete_all
320
+ end
321
+
322
+ it "should be loaded after all being destroyed" do
323
+ subject.delete_all
324
+ should be_loaded
325
+ end
326
+
327
+ it "should not call destroy on each record" do
328
+ proxy_target.should_not_receive(:destroy)
329
+ proxy_target_2.should_not_receive(:destroy)
330
+ subject.delete_all
331
+ end
332
+ end
333
+ end
334
+
335
+ [:length, :size, :count].each do |method|
336
+ describe "##{method}" do
337
+ [true, false].each do |should_persist_proxy_owner|
338
+ describe "with proxy_owner " + (should_persist_proxy_owner ? "persisted" : "not persisted") do
339
+ before do
340
+ proxy_owner.save! if should_persist_proxy_owner
341
+ subject << proxy_target
342
+ end
343
+
344
+ it "should return the correct #{method} when loaded" do
345
+ subject.reload if should_persist_proxy_owner
346
+ subject.send(method).should == 1
347
+ end
348
+
349
+ it "should return the correct #{method} when not loaded" do
350
+ subject.reset if should_persist_proxy_owner
351
+ subject.send(method).should == 1
352
+ end
353
+
354
+ it "should return the correct #{method} when a record is added" do
355
+ subject << proxy_target_2
356
+ subject.send(method).should == 2
357
+ end
358
+
359
+ it "should return the correct #{method} when a record is added to an unloaded proxy" do
360
+ subject.reset if should_persist_proxy_owner
361
+ subject << proxy_target_2
362
+ subject.send(method).should == 2
363
+ end
364
+ end
365
+ end
366
+ end
367
+ end
368
+
369
+ describe "#include" do
370
+ [true, false].each do |should_persist_proxy_owner|
371
+ describe "with proxy_owner " + (should_persist_proxy_owner ? "persisted" : "not persisted") do
372
+ before do
373
+ proxy_owner.save! if should_persist_proxy_owner
374
+ subject << proxy_target
375
+ end
376
+
377
+ it "should return that it includes it's proxy_target when loaded" do
378
+ subject.reload if should_persist_proxy_owner
379
+ should include proxy_target
380
+ end
381
+
382
+ it "should return that it includes it's proxy_target when not loaded" do
383
+ subject.reset if should_persist_proxy_owner
384
+ should include proxy_target
385
+ end
386
+
387
+ it "should return that it includes it's proxy_target when a record is added" do
388
+ subject << proxy_target_2
389
+ should include proxy_target, proxy_target_2
390
+ end
391
+
392
+ it "should return that it includes it's proxy_target when a record is added to an unloaded proxy" do
393
+ subject.reset if should_persist_proxy_owner
394
+ subject << proxy_target_2
395
+ should include proxy_target, proxy_target_2
396
+ end
397
+ end
398
+ end
399
+ end
400
+
401
+
402
+
403
+ describe "#first" do
404
+ describe "stored foreign keys" do
405
+ before do
406
+ proxy_owner.save!
407
+ subject << proxy_target << proxy_target_2
408
+ subject.reset
409
+ end
410
+
411
+ it "should return nil if no relations are found" do
412
+ subject.destroy_all
413
+ subject.first.should be_nil
414
+ end
415
+
416
+ it "should return the first proxy_target" do
417
+ subject.first.should == proxy_target
418
+ end
419
+
420
+ it "should not be loaded" do
421
+ subject.first
422
+ subject.should_not be_loaded
423
+ end
424
+
425
+ it "should just find the first foreign key" do
426
+ TestClass.should_receive(:find).with(proxy_target.id, anything).and_return(proxy_target)
427
+ subject.first
428
+ end
429
+ end
430
+
431
+ describe "with records_starts_from (proc)" do
432
+ let(:proxy_target) { Person.new :id => proxy_owner.id+"-friend-1", :name => "T", :age => 2 }
433
+ let(:proxy_target_2) { Person.new :id => proxy_owner.id+"-friend-2", :name => "H", :age => 9 }
434
+ let(:metadata) { subject.metadata }
435
+
436
+ subject { proxy_owner.send(:relation_proxy, 'friends') }
437
+
438
+ before do
439
+ proxy_owner.save!
440
+ subject << proxy_target << proxy_target_2
441
+ subject.reset
442
+ end
443
+
444
+ it "should return nil if no relations are found" do
445
+ subject.destroy_all
446
+ subject.first.should be_nil
447
+ end
448
+
449
+ it "should return the first proxy_target" do
450
+ subject.first.should == proxy_target
451
+ end
452
+
453
+ it "should not be loaded" do
454
+ subject.first
455
+ subject.should_not be_loaded
456
+ end
457
+
458
+ it "should find the first with a limit" do
459
+ Person.should_receive(:all).with(hash_including(:limit => 1))
460
+ subject.first
461
+ end
462
+ end
463
+ end
464
+
465
+
466
+ describe "#find" do
467
+ let(:not_among_targets) { proxy_target_3 }
468
+
469
+ describe "stored foreign keys" do
470
+ before do
471
+ proxy_owner.save!
472
+ subject << proxy_target << proxy_target_2
473
+ subject.reset
474
+
475
+ not_among_targets.save!
476
+ end
477
+
478
+ it "should find the object from database if id exists among foreig keys" do
479
+ subject.find(proxy_target.id).should == proxy_target
480
+ end
481
+
482
+ it "should raise error if record is not among records in association" do
483
+ lambda { subject.find(not_among_targets.id) }.should raise_error MassiveRecord::ORM::RecordNotFound
484
+ end
485
+
486
+ it "should not hit database if proxy has been loaded" do
487
+ subject.load_proxy_target
488
+ TestClass.should_not_receive(:find)
489
+ subject.find(proxy_target.id).should == proxy_target
490
+ end
491
+
492
+ it "should raise error if proxy is loaded, but record is not found in association" do
493
+ subject.load_proxy_target
494
+ lambda { subject.find(not_among_targets.id) }.should raise_error MassiveRecord::ORM::RecordNotFound
495
+ end
496
+ end
497
+
498
+
499
+ describe "with records_starts_from (proc)" do
500
+ let(:proxy_target) { Person.new :id => proxy_owner.id+"-friend-1", :name => "T", :age => 2 }
501
+ let(:proxy_target_2) { Person.new :id => proxy_owner.id+"-friend-2", :name => "H", :age => 9 }
502
+ let(:not_among_targets) { Person.new :id => "NOT-friend-1", :name => "H", :age => 9 }
503
+ let(:metadata) { subject.metadata }
504
+
505
+ subject { proxy_owner.send(:relation_proxy, 'friends') }
506
+
507
+ before do
508
+ proxy_owner.save!
509
+ subject << proxy_target << proxy_target_2
510
+ subject.reset
511
+
512
+ not_among_targets.save!
513
+ end
514
+
515
+
516
+ it "should find the object from database if id exists among foreig keys" do
517
+ subject.find(proxy_target.id).should == proxy_target
518
+ end
519
+
520
+ it "should raise error if record is not among records in association" do
521
+ lambda { subject.find(not_among_targets.id) }.should raise_error MassiveRecord::ORM::RecordNotFound
522
+ end
523
+
524
+
525
+
526
+ it "should not hit database if proxy has been loaded" do
527
+ subject.load_proxy_target
528
+ Person.should_not_receive(:find)
529
+ subject.find(proxy_target.id).should == proxy_target
530
+ end
531
+
532
+ it "should raise error if proxy is loaded, but record is not found in association" do
533
+ subject.load_proxy_target
534
+ lambda { subject.find(not_among_targets.id) }.should raise_error MassiveRecord::ORM::RecordNotFound
535
+ end
536
+ end
537
+ end
538
+
539
+
540
+
541
+ describe "#limit" do
542
+ let(:not_among_targets) { proxy_target_3 }
543
+
544
+ describe "stored foreign keys" do
545
+ before do
546
+ proxy_owner.save!
547
+ subject << proxy_target << proxy_target_2
548
+ subject.reset
549
+
550
+ not_among_targets.save!
551
+ end
552
+
553
+ it "should return empty array if no targets are found" do
554
+ subject.destroy_all
555
+ subject.limit(1).should be_empty
556
+ end
557
+
558
+
559
+ it "should do db query with a limited set of ids" do
560
+ subject.limit(1).should == [proxy_target]
561
+ end
562
+
563
+ it "should not be loaded after a limit query" do
564
+ subject.limit(1).should == [proxy_target]
565
+ subject.should_not be_loaded
566
+ end
567
+
568
+ it "should not hit the database if the proxy is loaded" do
569
+ subject.load_proxy_target
570
+ TestClass.should_not_receive(:find)
571
+ subject.limit(1)
572
+ end
573
+
574
+ it "should return correct result set if proxy is loaded" do
575
+ subject.load_proxy_target
576
+ subject.limit(1).should == [proxy_target]
577
+ end
578
+ end
579
+
580
+
581
+ describe "with records_starts_from (proc)" do
582
+ let(:proxy_target) { Person.new :id => proxy_owner.id+"-friend-1", :name => "T", :age => 2 }
583
+ let(:proxy_target_2) { Person.new :id => proxy_owner.id+"-friend-2", :name => "H", :age => 9 }
584
+ let(:not_among_targets) { Person.new :id => "NOT-friend-1", :name => "H", :age => 9 }
585
+ let(:metadata) { subject.metadata }
586
+
587
+ subject { proxy_owner.send(:relation_proxy, 'friends') }
588
+
589
+ before do
590
+ proxy_owner.save!
591
+ subject << proxy_target << proxy_target_2
592
+ subject.reset
593
+
594
+ not_among_targets.save!
595
+ end
596
+
597
+ it "should return empty array if no targets are found" do
598
+ subject.destroy_all
599
+ subject.limit(1).should be_empty
600
+ end
601
+
602
+
603
+ it "should do db query with a limited set of ids" do
604
+ subject.limit(1).should == [proxy_target]
605
+ end
606
+
607
+ it "should not be loaded after a limit query" do
608
+ subject.limit(1).should == [proxy_target]
609
+ subject.should_not be_loaded
610
+ end
611
+
612
+ it "should not hit the database if the proxy is loaded" do
613
+ subject.load_proxy_target
614
+ Person.should_not_receive(:find)
615
+ subject.limit(1)
616
+ end
617
+
618
+ it "should return correct result set if proxy is loaded" do
619
+ subject.load_proxy_target
620
+ subject.limit(1).should == [proxy_target]
621
+ end
622
+ end
623
+ end
624
+ end