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
@@ -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