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
@@ -0,0 +1,10 @@
1
+ module MassiveRecord
2
+ module Wrapper
3
+ module Errors
4
+
5
+ class ConnectionException < StandardError
6
+ end
7
+
8
+ end
9
+ end
10
+ end
@@ -5,7 +5,7 @@ module MassiveRecord
5
5
  attr_accessor :connection
6
6
 
7
7
  def load(table_name)
8
- Table.new(connection, table_name)
8
+ ADAPTER::Table.new(connection, table_name)
9
9
  end
10
10
 
11
11
  end
@@ -1,15 +1,7 @@
1
- # Thrift Gems
2
- require 'thrift'
3
- require 'thrift/transport/socket'
4
- require 'thrift/protocol/binary_protocol'
1
+ module MassiveRecord; end
5
2
 
6
- # Exceptions
7
- require 'massive_record/exceptions'
8
-
9
- # Generated Ruby classes from Thrift for HBase
10
- require 'massive_record/thrift/hbase_constants'
11
- require 'massive_record/thrift/hbase_types'
12
- require 'massive_record/thrift/hbase'
3
+ # Adapter
4
+ require 'massive_record/adapters/initialize'
13
5
 
14
6
  # Wrapper
15
7
  require 'massive_record/wrapper/base'
@@ -17,6 +9,7 @@ require 'massive_record/wrapper/base'
17
9
  # ORM
18
10
  require 'massive_record/orm/base'
19
11
 
12
+ # Others
20
13
  if defined?(::Rails) && ::Rails::VERSION::MAJOR == 3
21
14
  require 'massive_record/rails/railtie'
22
- end
15
+ end
@@ -3,7 +3,7 @@ require 'orm/models/person'
3
3
 
4
4
  describe "attribute methods" do
5
5
  before do
6
- @model = Person.new :id => 5, :name => "John", :age => 15
6
+ @model = Person.new :id => 5, :name => "John", :age => "15"
7
7
  end
8
8
 
9
9
  it "should define reader method" do
@@ -23,6 +23,10 @@ describe "attribute methods" do
23
23
  it "should be possible to read attributes" do
24
24
  @model.read_attribute(:name).should == "John"
25
25
  end
26
+
27
+ it "should return casted value when read" do
28
+ @model.read_attribute(:age).should == 15
29
+ end
26
30
 
27
31
  it "should contains the id in the attributes getter" do
28
32
  @model.attributes.should include("id")
@@ -1,5 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'orm/models/test_class'
3
+ require 'orm/models/friend'
4
+ require 'orm/models/best_friend'
3
5
 
4
6
  describe MassiveRecord::ORM::Base do
5
7
  include MockMassiveRecordConnection
@@ -7,6 +9,14 @@ describe MassiveRecord::ORM::Base do
7
9
  describe "table name" do
8
10
  before do
9
11
  TestClass.reset_table_name_configuration!
12
+ Friend.reset_table_name_configuration!
13
+ BestFriend.reset_table_name_configuration!
14
+ end
15
+
16
+ after do
17
+ TestClass.reset_table_name_configuration!
18
+ Friend.reset_table_name_configuration!
19
+ BestFriend.reset_table_name_configuration!
10
20
  end
11
21
 
12
22
  it "should have a table name" do
@@ -22,6 +32,14 @@ describe MassiveRecord::ORM::Base do
22
32
  TestClass.table_name_suffix = "_suffix"
23
33
  TestClass.table_name.should == "test_classes_suffix"
24
34
  end
35
+
36
+ it "first sub class should have the same table name as base class" do
37
+ Friend.table_name.should == Person.table_name
38
+ end
39
+
40
+ it "second sub class should have the same table name as base class" do
41
+ BestFriend.table_name.should == Person.table_name
42
+ end
25
43
 
26
44
  describe "set explicitly" do
27
45
  it "should be able to set it" do
@@ -45,6 +63,11 @@ describe MassiveRecord::ORM::Base do
45
63
  TestClass.set_table_name("foo")
46
64
  TestClass.table_name.should == "foo"
47
65
  end
66
+
67
+ it "sub class should have have table name overridden" do
68
+ Friend.table_name = "foo"
69
+ Friend.table_name.should == "foo"
70
+ end
48
71
  end
49
72
  end
50
73
 
@@ -54,14 +77,14 @@ describe MassiveRecord::ORM::Base do
54
77
 
55
78
  describe "#initialize" do
56
79
  it "should take a set of attributes and make them readable" do
57
- model = TestClass.new :foo => :bar
58
- model.foo.should == :bar
80
+ model = TestClass.new :foo => 'bar'
81
+ model.foo.should == 'bar'
59
82
  end
60
83
 
61
84
  it "should initialize an object via init_with()" do
62
85
  model = TestClass.allocate
63
- model.init_with 'attributes' => {:foo => :bar}
64
- model.foo.should == :bar
86
+ model.init_with 'attributes' => {:foo => 'bar'}
87
+ model.foo.should == 'bar'
65
88
  end
66
89
 
67
90
  it "should stringify keys set on attributes" do
@@ -73,6 +96,10 @@ describe MassiveRecord::ORM::Base do
73
96
  it "should return nil as id by default" do
74
97
  TestClass.new.id.should be_nil
75
98
  end
99
+
100
+ it "should be possible to create an object with nil as argument" do
101
+ lambda { TestClass.new(nil) }.should_not raise_error
102
+ end
76
103
  end
77
104
 
78
105
  describe "equality" do
@@ -196,4 +223,50 @@ describe MassiveRecord::ORM::Base do
196
223
  test.should be_readonly
197
224
  end
198
225
  end
226
+
227
+
228
+ describe "#base_class" do
229
+ it "should return correct base class for direct descendant of Base" do
230
+ Person.base_class.should == Person
231
+ end
232
+
233
+ it "should return Person when asking a descendant of Person" do
234
+ Friend.base_class.should == Person
235
+ end
236
+
237
+ it "should return Person when asking a descendant of Person multiple levels" do
238
+ BestFriend.base_class.should == Person
239
+ end
240
+ end
241
+
242
+
243
+ describe "#clone" do
244
+ before do
245
+ @test_object = TestClass.create!(:id => "1", :foo => 'bar')
246
+ @clone_object = @test_object.clone
247
+ end
248
+
249
+ it "should be the same object class" do
250
+ @test_object.class.should == @clone_object.class
251
+ end
252
+
253
+ it "should have a different object_id" do
254
+ @test_object.object_id.should_not == @clone_object.object_id
255
+ end
256
+
257
+ it "should have the same attributes" do
258
+ @test_object.foo.should == @clone_object.foo
259
+ end
260
+
261
+ it "should have a nil id" do
262
+ @clone_object.id.should be_nil
263
+ end
264
+ end
265
+
266
+
267
+ describe "coder" do
268
+ it "should have a default coder" do
269
+ Person.coder.should be_instance_of MassiveRecord::ORM::Coders::JSON
270
+ end
271
+ end
199
272
  end
@@ -28,7 +28,7 @@ describe "column classes" do
28
28
 
29
29
  # TODO We might want to remove this when we have implemented
30
30
  # associations correctly. Since Columns are contained within
31
- # tables, calling save should do something on it's owner object.
31
+ # tables, calling save should do something on it's proxy_owner object.
32
32
  describe "not be possible to persist (at least for now...)" do
33
33
  %w(first last all exists? destroy_all).each do |method|
34
34
  it "should not respond to class method #{method}" do
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/person'
3
+
4
+ describe "Default scope in" do
5
+ include SetUpHbaseConnectionBeforeAll
6
+ include SetTableNamesToTestTable
7
+
8
+ describe Person do
9
+ let(:subject) { Person.new :id => "ID1", :name => "Person1", :email => "one@person.com", :age => 11, :points => 111, :status => true }
10
+
11
+ before do
12
+ subject.save!
13
+
14
+ Person.class_eval do
15
+ default_scope select(:info)
16
+ end
17
+ end
18
+
19
+ after do
20
+ Person.class_eval do
21
+ default_scope nil
22
+ end
23
+ end
24
+
25
+
26
+
27
+ it "should be possible to find the a record and use the default scope" do
28
+ Person.find(subject.id).points.should be_nil
29
+ end
30
+
31
+ it "should only load column family info as a default with first" do
32
+ Person.first.points.should be_nil # its in :base
33
+ end
34
+
35
+ it "should only load column family info as default with all" do
36
+ Person.all.first.points.should be_nil
37
+ end
38
+
39
+ it "should be possible to bypass default scope by unscoped" do
40
+ Person.unscoped.first.points.should == 111
41
+ end
42
+
43
+ it "should be possible to set default_scope with a hash" do
44
+ Person.class_eval do
45
+ default_scope :select => :base
46
+ end
47
+
48
+ person = Person.first
49
+ person.points.should == 111
50
+ person.name.should be_nil
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,288 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/person'
3
+
4
+ describe MassiveRecord::ORM::Finders::Scope do
5
+ let(:subject) { MassiveRecord::ORM::Finders::Scope.new(nil) }
6
+
7
+ MassiveRecord::ORM::Finders::Scope::MULTI_VALUE_METHODS.each do |multi_values|
8
+ it "should have #{multi_values} as an empty array as default" do
9
+ subject.send(multi_values+"_values").should == []
10
+ end
11
+ end
12
+
13
+ MassiveRecord::ORM::Finders::Scope::SINGLE_VALUE_METHODS.each do |singel_value|
14
+ it "should have #{singel_value} as nil as default" do
15
+ subject.send(singel_value+"_value").should be_nil
16
+ end
17
+ end
18
+
19
+
20
+ describe "scoping methods" do
21
+ (MassiveRecord::ORM::Finders::Scope::MULTI_VALUE_METHODS + MassiveRecord::ORM::Finders::Scope::SINGLE_VALUE_METHODS).each do |method|
22
+ it { should respond_to(method) }
23
+
24
+ it "should return self after #{method}()" do
25
+ subject.send(method, nil).should == subject
26
+ end
27
+ end
28
+
29
+
30
+
31
+ describe "multi value methods" do
32
+ describe "select" do
33
+ it "should not add nil values" do
34
+ subject.select(nil)
35
+ subject.select_values.should be_empty
36
+ end
37
+
38
+ it "should add incomming value to list" do
39
+ subject.select(:info)
40
+ subject.select_values.should include 'info'
41
+ end
42
+
43
+ it "should be adding values if called twice" do
44
+ subject.select(:info).select(:base)
45
+ subject.select_values.should include 'info', 'base'
46
+ end
47
+
48
+ it "should add multiple arguments" do
49
+ subject.select(:info, :base)
50
+ subject.select_values.should include 'info', 'base'
51
+ end
52
+
53
+ it "should add multiple values given as array" do
54
+ subject.select([:info, :base])
55
+ subject.select_values.should include 'info', 'base'
56
+ end
57
+
58
+ it "should not add same value twice" do
59
+ subject.select(:info).select('info')
60
+ subject.select_values.should == ['info']
61
+ end
62
+ end
63
+ end
64
+
65
+
66
+
67
+ describe "singel value methods" do
68
+ describe "limit" do
69
+ it "should set a limit" do
70
+ subject.limit(5)
71
+ subject.limit_value.should == 5
72
+ end
73
+
74
+ it "should be set to the last value set" do
75
+ subject.limit(1).limit(5)
76
+ subject.limit_value.should == 5
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+
83
+ describe "#find_options" do
84
+ it "should return an empty hash when no limitations are set" do
85
+ subject.send(:find_options).should == {}
86
+ end
87
+
88
+ it "should include a limit if asked to be limited" do
89
+ subject.limit(5).send(:find_options).should include :limit => 5
90
+ end
91
+
92
+ it "should include selection when asked for it" do
93
+ subject.select(:info).send(:find_options).should include :select => ['info']
94
+ end
95
+ end
96
+
97
+
98
+ describe "loaded" do
99
+ it { should_not be_loaded }
100
+
101
+ it "should be loaded if set to true" do
102
+ subject.loaded = true
103
+ should be_loaded
104
+ end
105
+ end
106
+
107
+
108
+ describe "#reset" do
109
+ it "should reset loaded status" do
110
+ subject.loaded = true
111
+ subject.reset
112
+ should_not be_loaded
113
+ end
114
+ end
115
+
116
+ describe "#to_a" do
117
+ it "should return @records if loaded" do
118
+ records = [:foo]
119
+ subject.instance_variable_set(:@records, records)
120
+ subject.loaded = true
121
+
122
+ subject.to_a.should == records
123
+ end
124
+
125
+
126
+ [:to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?].each do |method|
127
+ it "should delegate #{method} to to_a" do
128
+ records = []
129
+ records.should_receive(method)
130
+
131
+ subject.instance_variable_set(:@records, records)
132
+ subject.loaded = true
133
+
134
+ subject.send(method)
135
+ end
136
+ end
137
+
138
+ it "should always return an array, even though results are single object" do
139
+ record = mock(Object)
140
+ subject.should_receive(:load_records).and_return(record)
141
+ subject.to_a.should be_instance_of Array
142
+ end
143
+ end
144
+
145
+ describe "#find" do
146
+ it "should return nil if not found" do
147
+ klass = mock(Object)
148
+ klass.should_receive(:do_find).and_return(nil)
149
+ subject.should_receive(:klass).and_return(klass)
150
+
151
+ subject.find(1).should be_nil
152
+ end
153
+
154
+ it "should be possible to add scopes" do
155
+ klass = mock(Object)
156
+ klass.should_receive(:do_find).with(1, :select => ['foo']).and_return(nil)
157
+ subject.should_receive(:klass).and_return(klass)
158
+ subject.select(:foo).find(1)
159
+ end
160
+ end
161
+
162
+
163
+
164
+ describe "#first" do
165
+ it "should return first record if loaded" do
166
+ records = []
167
+ records.should_receive(:first).and_return(:first_record)
168
+ subject.instance_variable_set(:@records, records)
169
+ subject.loaded = true
170
+
171
+ subject.first.should == :first_record
172
+ end
173
+
174
+ it "should include finder options" do
175
+ extra_options = {:select => ["foo"], :conditions => 'should_be_passed_on_to_finder'}
176
+
177
+ klass = mock(Object)
178
+ klass.should_receive(:do_find).with(anything, hash_including(extra_options)).and_return([])
179
+ subject.should_receive(:klass).and_return(klass)
180
+
181
+ subject.first(extra_options)
182
+ end
183
+ end
184
+
185
+ describe "#all" do
186
+ it "should simply call to_a" do
187
+ subject.should_receive(:to_a).and_return []
188
+ subject.all
189
+ end
190
+
191
+
192
+ it "should include finder options" do
193
+ extra_options = {:select => ["foo"], :conditions => 'should_be_passed_on_to_finder'}
194
+
195
+ klass = mock(Object)
196
+ klass.should_receive(:do_find).with(anything, extra_options)
197
+ subject.should_receive(:klass).and_return(klass)
198
+
199
+ subject.all(extra_options)
200
+ end
201
+ end
202
+
203
+
204
+ describe "#==" do
205
+ it "should be counted equal if it's records are the same as asked what being compared to" do
206
+ subject.instance_variable_set(:@records, [:foo])
207
+ subject.loaded = true
208
+
209
+ subject.==([:foo]).should == true
210
+ end
211
+ end
212
+
213
+
214
+
215
+ describe "real world test" do
216
+ include SetUpHbaseConnectionBeforeAll
217
+ include SetTableNamesToTestTable
218
+
219
+ describe "with a person" do
220
+ let(:person_1) { Person.create :id => "ID1", :name => "Person1", :email => "one@person.com", :age => 11, :points => 111, :status => true }
221
+ let(:person_2) { Person.create :id => "ID2", :name => "Person2", :email => "two@person.com", :age => 22, :points => 222, :status => false }
222
+
223
+ before do
224
+ person_1.save!
225
+ person_2.save!
226
+ end
227
+
228
+ (MassiveRecord::ORM::Finders::Scope::MULTI_VALUE_METHODS + MassiveRecord::ORM::Finders::Scope::SINGLE_VALUE_METHODS).each do |method|
229
+ it "should not load from database when Person.#{method}() is called" do
230
+ Person.should_not_receive(:find)
231
+ Person.send(method, 5)
232
+ end
233
+ end
234
+
235
+ it "should find just one record when asked for it" do
236
+ Person.limit(1).should == [person_1]
237
+ end
238
+
239
+ it "should find only selected column families when asked for it" do
240
+ records = Person.select(:info).limit(1)
241
+ person_from_db = records.first
242
+
243
+ person_from_db.points.should be_nil
244
+ person_from_db.status.should be_nil
245
+ end
246
+
247
+ it "should not return read only objects when select is used" do
248
+ person = Person.select(:info).first
249
+ person.should_not be_readonly
250
+ end
251
+
252
+ it "should be possible to iterate over a collection with each" do
253
+ result = []
254
+
255
+ Person.limit(5).each do |person|
256
+ result << person.name
257
+ end
258
+
259
+ result.should == ["Person1", "Person2"]
260
+ end
261
+
262
+ it "should be possible to collect" do
263
+ Person.select(:info).collect(&:name).should == ["Person1", "Person2"]
264
+ end
265
+
266
+ it "should be possible to checkc if it includes something" do
267
+ Person.limit(1).include?(person_2).should be_false
268
+ end
269
+ end
270
+ end
271
+
272
+
273
+ describe "#apply_finder_options" do
274
+ it "should apply limit correctly" do
275
+ subject.should_receive(:limit).with(30)
276
+ subject.send :apply_finder_options, :limit => 30
277
+ end
278
+
279
+ it "should apply select correctly" do
280
+ subject.should_receive(:select).with(:foo)
281
+ subject.send :apply_finder_options, :select => :foo
282
+ end
283
+
284
+ it "should raise unknown scope error if options is unkown" do
285
+ lambda { subject.send(:apply_finder_options, :unkown => false) }.should_not raise_error
286
+ end
287
+ end
288
+ end