massive_record 0.1.1 → 0.2.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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