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.
- data/CHANGELOG.md +28 -5
- data/Gemfile.lock +12 -12
- data/README.md +29 -1
- data/lib/massive_record/adapters/initialize.rb +18 -0
- data/lib/massive_record/adapters/thrift/adapter.rb +25 -0
- data/lib/massive_record/adapters/thrift/column_family.rb +24 -0
- data/lib/massive_record/adapters/thrift/connection.rb +73 -0
- data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase.rb +0 -0
- data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_constants.rb +0 -0
- data/lib/massive_record/{thrift → adapters/thrift/hbase}/hbase_types.rb +0 -0
- data/lib/massive_record/adapters/thrift/row.rb +150 -0
- data/lib/massive_record/adapters/thrift/scanner.rb +59 -0
- data/lib/massive_record/adapters/thrift/table.rb +169 -0
- data/lib/massive_record/orm/attribute_methods/read.rb +2 -1
- data/lib/massive_record/orm/base.rb +61 -3
- data/lib/massive_record/orm/coders/chained.rb +71 -0
- data/lib/massive_record/orm/coders/json.rb +17 -0
- data/lib/massive_record/orm/coders/yaml.rb +15 -0
- data/lib/massive_record/orm/coders.rb +3 -0
- data/lib/massive_record/orm/errors.rb +15 -2
- data/lib/massive_record/orm/finders/scope.rb +166 -0
- data/lib/massive_record/orm/finders.rb +45 -24
- data/lib/massive_record/orm/persistence.rb +4 -4
- data/lib/massive_record/orm/relations/interface.rb +170 -0
- data/lib/massive_record/orm/relations/metadata.rb +150 -0
- data/lib/massive_record/orm/relations/proxy/references_many.rb +229 -0
- data/lib/massive_record/orm/relations/proxy/references_one.rb +40 -0
- data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +49 -0
- data/lib/massive_record/orm/relations/proxy.rb +174 -0
- data/lib/massive_record/orm/relations.rb +6 -0
- data/lib/massive_record/orm/schema/column_interface.rb +1 -1
- data/lib/massive_record/orm/schema/field.rb +62 -27
- data/lib/massive_record/orm/single_table_inheritance.rb +21 -0
- data/lib/massive_record/version.rb +1 -1
- data/lib/massive_record/wrapper/adapter.rb +6 -0
- data/lib/massive_record/wrapper/base.rb +6 -7
- data/lib/massive_record/wrapper/cell.rb +9 -32
- data/lib/massive_record/wrapper/column_families_collection.rb +2 -2
- data/lib/massive_record/wrapper/errors.rb +10 -0
- data/lib/massive_record/wrapper/tables_collection.rb +1 -1
- data/lib/massive_record.rb +5 -12
- data/spec/orm/cases/attribute_methods_spec.rb +5 -1
- data/spec/orm/cases/base_spec.rb +77 -4
- data/spec/orm/cases/column_spec.rb +1 -1
- data/spec/orm/cases/finder_default_scope.rb +53 -0
- data/spec/orm/cases/finder_scope_spec.rb +288 -0
- data/spec/orm/cases/finders_spec.rb +56 -13
- data/spec/orm/cases/persistence_spec.rb +20 -5
- data/spec/orm/cases/single_table_inheritance_spec.rb +26 -0
- data/spec/orm/cases/table_spec.rb +1 -1
- data/spec/orm/cases/timestamps_spec.rb +16 -16
- data/spec/orm/coders/chained_spec.rb +73 -0
- data/spec/orm/coders/json_spec.rb +6 -0
- data/spec/orm/coders/yaml_spec.rb +6 -0
- data/spec/orm/models/best_friend.rb +7 -0
- data/spec/orm/models/friend.rb +4 -0
- data/spec/orm/models/person.rb +20 -6
- data/spec/orm/models/{person_with_timestamps.rb → person_with_timestamp.rb} +1 -1
- data/spec/orm/models/test_class.rb +3 -0
- data/spec/orm/relations/interface_spec.rb +207 -0
- data/spec/orm/relations/metadata_spec.rb +202 -0
- data/spec/orm/relations/proxy/references_many_spec.rb +624 -0
- data/spec/orm/relations/proxy/references_one_polymorphic_spec.rb +106 -0
- data/spec/orm/relations/proxy/references_one_spec.rb +111 -0
- data/spec/orm/relations/proxy_spec.rb +13 -0
- data/spec/orm/schema/field_spec.rb +101 -2
- data/spec/shared/orm/coders/an_orm_coder.rb +14 -0
- data/spec/shared/orm/relations/proxy.rb +154 -0
- data/spec/shared/orm/relations/singular_proxy.rb +68 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/thrift/cases/encoding_spec.rb +28 -7
- data/spec/wrapper/cases/adapter_spec.rb +9 -0
- data/spec/wrapper/cases/connection_spec.rb +13 -10
- data/spec/wrapper/cases/table_spec.rb +85 -85
- metadata +74 -22
- data/TODO.md +0 -8
- data/lib/massive_record/exceptions.rb +0 -11
- data/lib/massive_record/wrapper/column_family.rb +0 -22
- data/lib/massive_record/wrapper/connection.rb +0 -71
- data/lib/massive_record/wrapper/row.rb +0 -173
- data/lib/massive_record/wrapper/scanner.rb +0 -61
- data/lib/massive_record/wrapper/table.rb +0 -149
- data/spec/orm/cases/hbase/connection_spec.rb +0 -13
@@ -7,7 +7,7 @@ describe "finders" do
|
|
7
7
|
include MockMassiveRecordConnection
|
8
8
|
|
9
9
|
before do
|
10
|
-
@mocked_table = mock(MassiveRecord::Wrapper::Table).as_null_object
|
10
|
+
@mocked_table = mock(MassiveRecord::Wrapper::Table, :to_ary => []).as_null_object
|
11
11
|
Person.stub(:table).and_return(@mocked_table)
|
12
12
|
|
13
13
|
@row = MassiveRecord::Wrapper::Row.new
|
@@ -28,9 +28,9 @@ describe "finders" do
|
|
28
28
|
Person.first.should be_nil
|
29
29
|
end
|
30
30
|
|
31
|
-
it "should
|
31
|
+
it "should raise record not found error on find if table does not exists" do
|
32
32
|
Person.table.should_receive(:exists?).and_return false
|
33
|
-
Person.find(1).should
|
33
|
+
lambda { Person.find(1) }.should raise_error MassiveRecord::ORM::RecordNotFound
|
34
34
|
end
|
35
35
|
|
36
36
|
it "should simply return empty array if table does not exists" do
|
@@ -98,7 +98,6 @@ describe "finders" do
|
|
98
98
|
end
|
99
99
|
|
100
100
|
it "should return nil on first if no results was found" do
|
101
|
-
@mocked_table.should_receive(:first).and_return(nil)
|
102
101
|
Person.first.should be_nil
|
103
102
|
end
|
104
103
|
|
@@ -113,20 +112,37 @@ describe "finders" do
|
|
113
112
|
end
|
114
113
|
end
|
115
114
|
|
116
|
-
|
117
|
-
it "should respond to
|
118
|
-
TestClass.should respond_to
|
115
|
+
describe "all" do
|
116
|
+
it "should respond to all" do
|
117
|
+
TestClass.should respond_to :all
|
119
118
|
end
|
120
119
|
|
121
|
-
it "should
|
122
|
-
TestClass.should_receive(:
|
123
|
-
TestClass.
|
120
|
+
it "should call find with :all" do
|
121
|
+
TestClass.should_receive(:do_find).with(:all, anything)
|
122
|
+
TestClass.all
|
124
123
|
end
|
125
124
|
|
126
|
-
it "should delegate
|
125
|
+
it "should delegate all's call to find with it's args as second argument" do
|
127
126
|
options = {:foo => :bar}
|
128
|
-
TestClass.should_receive(:
|
129
|
-
TestClass.
|
127
|
+
TestClass.should_receive(:do_find).with(anything, options)
|
128
|
+
TestClass.all options
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "first" do
|
133
|
+
it "should respond to first" do
|
134
|
+
TestClass.should respond_to :first
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should call find with :first" do
|
138
|
+
TestClass.should_receive(:do_find).with(:all, {:limit => 1}).and_return([])
|
139
|
+
TestClass.first
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should delegate first's call to find with it's args as second argument" do
|
143
|
+
options = {:foo => :bar}
|
144
|
+
TestClass.should_receive(:do_find).with(anything, hash_including(options)).and_return([])
|
145
|
+
TestClass.first options
|
130
146
|
end
|
131
147
|
end
|
132
148
|
|
@@ -167,6 +183,19 @@ describe "finders" do
|
|
167
183
|
all.should include @person, @bob
|
168
184
|
all.length.should == 2
|
169
185
|
end
|
186
|
+
|
187
|
+
it "should find all persons, even if it is more than 10" do
|
188
|
+
15.times { |i| Person.create! :id => "id-#{i}", :name => "Going to die :-(", :age => i + 20 }
|
189
|
+
Person.all.length.should > 10
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should raise error if not all requested records was found" do
|
193
|
+
lambda { Person.find(["ID1", "not exists"]) }.should raise_error MassiveRecord::ORM::RecordNotFound
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should return what it finds if asked to" do
|
197
|
+
lambda { Person.find(["ID1", "not exists"], :skip_expected_result_check => true) }.should_not raise_error MassiveRecord::ORM::RecordNotFound
|
198
|
+
end
|
170
199
|
end
|
171
200
|
|
172
201
|
describe "#find_in_batches" do
|
@@ -183,6 +212,20 @@ describe "finders" do
|
|
183
212
|
end
|
184
213
|
group_number.should == @table_size / 3
|
185
214
|
end
|
215
|
+
|
216
|
+
it "should not do a thing if table does not exist" do
|
217
|
+
Person.table.should_receive(:exists?).and_return false
|
218
|
+
|
219
|
+
counter = 0
|
220
|
+
|
221
|
+
Person.find_in_batches(:batch_size => 3) do |rows|
|
222
|
+
rows.each do |row|
|
223
|
+
counter += 1
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
counter.should == 0
|
228
|
+
end
|
186
229
|
|
187
230
|
it "should iterate through a collection of rows using a batch process" do
|
188
231
|
rows_number = 0
|
@@ -76,8 +76,9 @@ describe "persistence" do
|
|
76
76
|
@person.reload.should == @person
|
77
77
|
end
|
78
78
|
|
79
|
-
it "should
|
80
|
-
|
79
|
+
it "should not do anything on reload when record is not persisted" do
|
80
|
+
Person.should_not_receive :find
|
81
|
+
Person.new.reload
|
81
82
|
end
|
82
83
|
end
|
83
84
|
|
@@ -104,8 +105,8 @@ describe "persistence" do
|
|
104
105
|
end
|
105
106
|
|
106
107
|
it "should include the 'pts' field in the database which has 'points' as an alias" do
|
107
|
-
@person.send(:attributes_to_row_values_hash)["
|
108
|
-
@person.send(:attributes_to_row_values_hash)["
|
108
|
+
@person.send(:attributes_to_row_values_hash)["base"].keys.should include("pts")
|
109
|
+
@person.send(:attributes_to_row_values_hash)["base"].keys.should_not include("points")
|
109
110
|
end
|
110
111
|
end
|
111
112
|
|
@@ -181,6 +182,9 @@ describe "persistence" do
|
|
181
182
|
column_family :bar do
|
182
183
|
field :foo
|
183
184
|
end
|
185
|
+
|
186
|
+
column_family :empty_family do
|
187
|
+
end
|
184
188
|
end
|
185
189
|
|
186
190
|
@new_instance = @new_class.new :id => "id_of_foo", :foo => "bar"
|
@@ -202,7 +206,7 @@ describe "persistence" do
|
|
202
206
|
|
203
207
|
it "should create correct column families" do
|
204
208
|
@new_instance.save
|
205
|
-
@new_class.table.fetch_column_families.collect(&:name).should
|
209
|
+
@new_class.table.fetch_column_families.collect(&:name).should include "bar", "empty_family"
|
206
210
|
end
|
207
211
|
|
208
212
|
it "should store the new instance" do
|
@@ -343,6 +347,11 @@ describe "persistence" do
|
|
343
347
|
@person.should be_destroyed
|
344
348
|
Person.all.length.should == 0
|
345
349
|
end
|
350
|
+
|
351
|
+
it "should be able to call destroy on new records" do
|
352
|
+
person = Person.new
|
353
|
+
person.destroy
|
354
|
+
end
|
346
355
|
|
347
356
|
describe "#destroy_all" do
|
348
357
|
it "should remove all when calling remove_all" do
|
@@ -354,6 +363,12 @@ describe "persistence" do
|
|
354
363
|
it "should return an array of all removed objects" do
|
355
364
|
Person.destroy_all.should == [@person]
|
356
365
|
end
|
366
|
+
|
367
|
+
it "should destroy all even if it is above 10 rows (obviously)" do
|
368
|
+
15.times { |i| Person.create! :id => "id-#{i}", :name => "Going to die :-(", :age => i + 20 }
|
369
|
+
Person.destroy_all
|
370
|
+
Person.all.length.should == 0
|
371
|
+
end
|
357
372
|
end
|
358
373
|
end
|
359
374
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'orm/models/friend'
|
3
|
+
require 'orm/models/best_friend'
|
4
|
+
|
5
|
+
describe "Single table inheritance" do
|
6
|
+
include SetUpHbaseConnectionBeforeAll
|
7
|
+
include SetTableNamesToTestTable
|
8
|
+
|
9
|
+
[Friend, BestFriend, BestFriend::SuperBestFriend].each do |klass|
|
10
|
+
describe klass do
|
11
|
+
let(:subject) { klass.new(:id => "ID1", :name => "Person1", :email => "one@person.com", :age => 11, :points => 111, :status => true) }
|
12
|
+
|
13
|
+
its(:type) { should == klass.to_s }
|
14
|
+
|
15
|
+
it "should instantiate correct class when reading from database" do
|
16
|
+
subject.save!
|
17
|
+
Person.find(subject.id).should == subject
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should not set type if record being saved is base class" do
|
23
|
+
person = Person.new :id => "ID1", :name => "Person1", :email => "one@person.com", :age => 11, :points => 111, :status => true
|
24
|
+
person.type.should be_nil
|
25
|
+
end
|
26
|
+
end
|
@@ -52,7 +52,7 @@ describe "Person which is a table" do
|
|
52
52
|
end
|
53
53
|
|
54
54
|
it "should keep different column families per sub class" do
|
55
|
-
Person.column_families.collect(&:name).should
|
55
|
+
Person.column_families.collect(&:name).should include "info", "base"
|
56
56
|
TestClass.column_families.collect(&:name).should == ["test_family"]
|
57
57
|
end
|
58
58
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'orm/models/person'
|
3
|
-
require 'orm/models/
|
3
|
+
require 'orm/models/person_with_timestamp'
|
4
4
|
|
5
5
|
describe "timestamps" do
|
6
6
|
include CreatePersonBeforeEach
|
@@ -56,11 +56,11 @@ describe "timestamps" do
|
|
56
56
|
|
57
57
|
describe "#created_at" do
|
58
58
|
before do
|
59
|
-
@
|
59
|
+
@person_with_timestamp = PersonWithTimestamp.create! :name => "John Doe", :email => "john@base.com", :age => "20"
|
60
60
|
end
|
61
61
|
|
62
62
|
it "should have created at" do
|
63
|
-
@
|
63
|
+
@person_with_timestamp.should be_set_created_at_on_create
|
64
64
|
end
|
65
65
|
|
66
66
|
it "should not have created at on create if model does not have created at" do
|
@@ -68,13 +68,13 @@ describe "timestamps" do
|
|
68
68
|
end
|
69
69
|
|
70
70
|
it "should have updated at equal to nil on new records" do
|
71
|
-
|
71
|
+
PersonWithTimestamp.new.created_at.should be_nil
|
72
72
|
end
|
73
73
|
|
74
74
|
it "should not be possible to set updated at" do
|
75
|
-
lambda { @
|
76
|
-
lambda { @
|
77
|
-
lambda { @
|
75
|
+
lambda { @person_with_timestamp.created_at = Time.now }.should raise_error MassiveRecord::ORM::CantBeManuallyAssigned
|
76
|
+
lambda { @person_with_timestamp['created_at'] = Time.now }.should raise_error MassiveRecord::ORM::CantBeManuallyAssigned
|
77
|
+
lambda { @person_with_timestamp.write_attribute(:created_at, Time.now) }.should raise_error MassiveRecord::ORM::CantBeManuallyAssigned
|
78
78
|
end
|
79
79
|
|
80
80
|
it "should not raise cant-set-error if object has no timestamps" do
|
@@ -82,24 +82,24 @@ describe "timestamps" do
|
|
82
82
|
end
|
83
83
|
|
84
84
|
it "should have created_at on a persisted record" do
|
85
|
-
@
|
85
|
+
@person_with_timestamp.created_at.should be_a_kind_of Time
|
86
86
|
end
|
87
87
|
|
88
88
|
it "should not alter created at on update" do
|
89
|
-
created_at_was = @
|
89
|
+
created_at_was = @person_with_timestamp.created_at
|
90
90
|
|
91
91
|
sleep(1)
|
92
92
|
|
93
|
-
@
|
94
|
-
@
|
93
|
+
@person_with_timestamp.update_attribute :name, @person_with_timestamp.name + "NEW"
|
94
|
+
@person_with_timestamp.created_at.should == created_at_was
|
95
95
|
end
|
96
96
|
|
97
97
|
it "should be included in the list of known_attribute_names_for_inspect" do
|
98
|
-
@
|
98
|
+
@person_with_timestamp.send(:known_attribute_names_for_inspect).should include 'created_at'
|
99
99
|
end
|
100
100
|
|
101
101
|
it "should include created_at in inspect" do
|
102
|
-
@
|
102
|
+
@person_with_timestamp.inspect.should include(%q{created_at:})
|
103
103
|
end
|
104
104
|
|
105
105
|
it "should not include created_at if object does not have it" do
|
@@ -107,11 +107,11 @@ describe "timestamps" do
|
|
107
107
|
end
|
108
108
|
|
109
109
|
it "should raise error if created_at is not time" do
|
110
|
-
|
110
|
+
PersonWithTimestamp.attributes_schema['created_at'].type = :string
|
111
111
|
|
112
|
-
lambda {
|
112
|
+
lambda { PersonWithTimestamp.create! }.should raise_error "created_at must be of type time"
|
113
113
|
|
114
|
-
|
114
|
+
PersonWithTimestamp.attributes_schema['created_at'].type = :time
|
115
115
|
end
|
116
116
|
end
|
117
117
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MassiveRecord::ORM::Coders::Chained do
|
4
|
+
describe "initialize" do
|
5
|
+
it "should be able to assign coder to load- and dump with" do
|
6
|
+
coders = MassiveRecord::ORM::Coders::JSON.new
|
7
|
+
coder = MassiveRecord::ORM::Coders::Chained.new(coders)
|
8
|
+
coder.loaders.should include coders
|
9
|
+
coder.dumpers.should include coders
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should be able to assign array of coders to load- and dump with" do
|
13
|
+
coders = [MassiveRecord::ORM::Coders::JSON.new, MassiveRecord::ORM::Coders::YAML.new]
|
14
|
+
coder = MassiveRecord::ORM::Coders::Chained.new(coders)
|
15
|
+
coder.loaders.should include *coders
|
16
|
+
coder.dumpers.should include *coders
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be possible to assign load_with explicitly" do
|
20
|
+
coders = MassiveRecord::ORM::Coders::JSON.new
|
21
|
+
load_with = MassiveRecord::ORM::Coders::YAML.new
|
22
|
+
coder = MassiveRecord::ORM::Coders::Chained.new(coders, :load_with => load_with)
|
23
|
+
coder.loaders.should include load_with
|
24
|
+
coder.dumpers.should include coders
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should be possible to assign dump_with explicitly" do
|
28
|
+
coders = MassiveRecord::ORM::Coders::JSON.new
|
29
|
+
dump_with = MassiveRecord::ORM::Coders::YAML.new
|
30
|
+
coder = MassiveRecord::ORM::Coders::Chained.new(coders, :dump_with => dump_with)
|
31
|
+
coder.loaders.should include coders
|
32
|
+
coder.dumpers.should include dump_with
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
describe "one coder in chain" do
|
39
|
+
let(:subject) { MassiveRecord::ORM::Coders::Chained.new(MassiveRecord::ORM::Coders::JSON.new) }
|
40
|
+
let(:code_with) { lambda { |value| ActiveSupport::JSON.encode(value) } }
|
41
|
+
|
42
|
+
it_should_behave_like "an orm coder"
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
describe "two loaders in chain" do
|
48
|
+
let(:subject) do
|
49
|
+
MassiveRecord::ORM::Coders::Chained.new({
|
50
|
+
:load_with => [MassiveRecord::ORM::Coders::JSON.new, MassiveRecord::ORM::Coders::YAML.new],
|
51
|
+
:dump_with => MassiveRecord::ORM::Coders::YAML.new
|
52
|
+
})
|
53
|
+
end
|
54
|
+
|
55
|
+
let(:code_with) { lambda { |value| YAML.dump(value) } }
|
56
|
+
|
57
|
+
it_should_behave_like "an orm coder"
|
58
|
+
|
59
|
+
it "it should try the next loader in the line if the first one fails" do
|
60
|
+
data = {:foo => {'sdf' => "wef"}} # Will make the json encoder fail when YAML serialized
|
61
|
+
subject.load(YAML.dump(data)).should == data
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should raise ParseError if all fails" do
|
65
|
+
lambda { subject.load("{{}]") }.should raise_error MassiveRecord::ORM::Coders::ParseError
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should raise EncodeError if all fails" do
|
69
|
+
subject.dumpers.first.should_receive(:dump).and_raise(StandardError)
|
70
|
+
lambda { subject.dump("unable to dump") }.should raise_error MassiveRecord::ORM::Coders::EncodeError
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/spec/orm/models/person.rb
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
class Person < MassiveRecord::ORM::Table
|
2
|
-
validates_presence_of :name, :age
|
3
|
-
validates_numericality_of :age, :greater_than_or_equal_to => 0
|
4
|
-
validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :allow_blank => true
|
5
|
-
|
6
2
|
column_family :info do
|
7
3
|
field :name
|
8
4
|
field :email
|
9
5
|
field :age, :integer
|
10
|
-
field :points, :integer, :default => 1, :column => :pts
|
11
6
|
field :date_of_birth, :date
|
12
|
-
field :status, :boolean, :default => false
|
13
7
|
field :addresses, :hash, :default => {}
|
8
|
+
field :type
|
9
|
+
end
|
10
|
+
|
11
|
+
column_family :base do
|
12
|
+
field :points, :integer, :default => 1, :column => :pts
|
13
|
+
field :status, :boolean, :default => false
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
references_one :boss, :class_name => "PersonWithTimestamp", :store_in => :info
|
18
|
+
references_many :test_classes, :store_in => :info
|
19
|
+
references_many :friends, :class_name => "Person", :records_starts_from => :friends_records_starts_from_id
|
20
|
+
|
21
|
+
validates_presence_of :name, :age
|
22
|
+
validates_numericality_of :age, :greater_than_or_equal_to => 0
|
23
|
+
validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :allow_blank => true
|
24
|
+
|
25
|
+
|
26
|
+
def friends_records_starts_from_id
|
27
|
+
id+'-'
|
14
28
|
end
|
15
29
|
end
|