data-anonymization 0.8.0 → 0.8.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +2 -1
- data/.ruby-version +1 -1
- data/.travis.yml +4 -3
- data/README.md +24 -8
- data/commands.txt +4 -0
- data/data-anonymization.gemspec +7 -7
- data/examples/blacklist_dsl.rb +3 -3
- data/examples/mongodb_blacklist_dsl.rb +5 -5
- data/examples/mongodb_whitelist_dsl.rb +7 -7
- data/examples/whitelist_dsl.rb +4 -4
- data/examples/whitelist_dsl_threads.rb +66 -0
- data/lib/core/field.rb +1 -1
- data/lib/core/table_errors.rb +1 -1
- data/lib/data-anonymization.rb +22 -22
- data/lib/strategy/base.rb +41 -1
- data/lib/strategy/blacklist.rb +3 -2
- data/lib/strategy/field/datetime/anonymize_time.rb +3 -3
- data/lib/strategy/field/default_anon.rb +1 -0
- data/lib/strategy/field/number/random_big_decimal_delta.rb +1 -1
- data/lib/strategy/field/string/random_url.rb +1 -1
- data/lib/strategy/field/string/select_from_database.rb +14 -7
- data/lib/strategy/strategies.rb +1 -1
- data/lib/thor/helpers/mongodb_dsl_generator.rb +2 -3
- data/lib/utils/database.rb +1 -1
- data/lib/utils/random_int.rb +2 -2
- data/lib/utils/template_helper.rb +4 -2
- data/lib/version.rb +1 -1
- data/spec/acceptance/mongodb_blacklist_spec.rb +39 -39
- data/spec/acceptance/mongodb_whitelist_spec.rb +45 -45
- data/spec/acceptance/rdbms_whitelist_spec.rb +1 -1
- data/spec/acceptance/rdbms_whitelist_with_primary_key_spec.rb +8 -8
- data/spec/core/fields_missing_strategy_spec.rb +15 -15
- data/spec/strategy/field/contact/random_address_spec.rb +2 -2
- data/spec/strategy/field/default_anon_spec.rb +3 -3
- data/spec/strategy/field/number/random_big_decimal_delta_spec.rb +1 -1
- data/spec/strategy/field/random_boolean_spec.rb +1 -1
- data/spec/strategy/field/whitelist_spec.rb +1 -1
- data/spec/strategy/mongodb/anonymize_field_spec.rb +11 -11
- data/spec/utils/database_spec.rb +4 -4
- data/spec/utils/template_helper_spec.rb +6 -6
- metadata +19 -18
data/lib/strategy/blacklist.rb
CHANGED
@@ -3,15 +3,16 @@ module DataAnon
|
|
3
3
|
class Blacklist < DataAnon::Strategy::Base
|
4
4
|
|
5
5
|
def process_record index, record
|
6
|
+
updates = {}
|
6
7
|
@fields.each do |field, strategy|
|
7
8
|
database_field_name = record.attributes.select { |k,v| k == field }.keys[0]
|
8
9
|
field_value = record.attributes[database_field_name]
|
9
10
|
unless field_value.nil? || is_primary_key?(database_field_name)
|
10
11
|
field = DataAnon::Core::Field.new(database_field_name, field_value, index, record, @name)
|
11
|
-
|
12
|
+
updates[database_field_name] = strategy.anonymize(field)
|
12
13
|
end
|
13
14
|
end
|
14
|
-
record.
|
15
|
+
record.update_columns(updates) if updates.any?
|
15
16
|
end
|
16
17
|
|
17
18
|
end
|
@@ -58,8 +58,8 @@ module DataAnon
|
|
58
58
|
month = @anonymize_month? DataAnon::Utils::RandomInt.generate(1,12) : original_time.month
|
59
59
|
days_in_month = Time.new(year,month,1,1,1,1).end_of_month.day
|
60
60
|
day = @anonymize_day? DataAnon::Utils::RandomInt.generate(1,days_in_month) : original_time.day
|
61
|
-
hour = @anonymize_hour? DataAnon::Utils::RandomInt.generate(
|
62
|
-
min = @anonymize_min? DataAnon::Utils::RandomInt.generate(
|
61
|
+
hour = @anonymize_hour? DataAnon::Utils::RandomInt.generate(0,23) : original_time.hour
|
62
|
+
min = @anonymize_min? DataAnon::Utils::RandomInt.generate(0,59) : original_time.min
|
63
63
|
sec = original_time.sec
|
64
64
|
|
65
65
|
create_object(year, month, day, hour, min, sec)
|
@@ -74,4 +74,4 @@ module DataAnon
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
end
|
77
|
-
end
|
77
|
+
end
|
@@ -5,6 +5,7 @@ module DataAnon
|
|
5
5
|
class DefaultAnon
|
6
6
|
|
7
7
|
DEFAULT_STRATEGIES = {:string => FieldStrategy::RandomString.new,
|
8
|
+
:integer => FieldStrategy::RandomIntegerDelta.new(5),
|
8
9
|
:fixnum => FieldStrategy::RandomIntegerDelta.new(5),
|
9
10
|
:bignum => FieldStrategy::RandomIntegerDelta.new(5000),
|
10
11
|
:float => FieldStrategy::RandomFloatDelta.new(5.0),
|
@@ -16,7 +16,7 @@ module DataAnon
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def anonymize field
|
19
|
-
return BigDecimal
|
19
|
+
return BigDecimal("#{field.value + DataAnon::Utils::RandomFloat.generate(-@delta, +@delta)}")
|
20
20
|
end
|
21
21
|
|
22
22
|
end
|
@@ -12,16 +12,23 @@ module DataAnon
|
|
12
12
|
include Utils::Logging
|
13
13
|
|
14
14
|
def initialize table_name, field_name, connection_spec
|
15
|
-
|
16
|
-
|
17
|
-
@
|
18
|
-
logger.debug "For field strategy #{table_name}:#{field_name} using values #{@values} "
|
19
|
-
|
15
|
+
@table_name = table_name
|
16
|
+
@field_name = field_name
|
17
|
+
@connection_spec = connection_spec
|
20
18
|
end
|
21
19
|
|
22
|
-
|
23
|
-
|
20
|
+
def anonymize field
|
21
|
+
@values ||= begin
|
22
|
+
DataAnon::Utils::SourceDatabase.establish_connection @connection_spec
|
23
|
+
source = Utils::SourceTable.create @table_name, []
|
24
|
+
values = source.select(@field_name).distinct.collect { |record| record[@field_name]}
|
25
|
+
logger.debug "For field strategy #{@table_name}:#{@field_name} using values #{values} "
|
26
|
+
values
|
27
|
+
end
|
24
28
|
|
29
|
+
super
|
30
|
+
end
|
31
|
+
end
|
25
32
|
end
|
26
33
|
end
|
27
34
|
end
|
data/lib/strategy/strategies.rb
CHANGED
@@ -9,5 +9,5 @@ begin
|
|
9
9
|
require 'strategy/mongodb/whitelist'
|
10
10
|
require 'strategy/mongodb/blacklist'
|
11
11
|
rescue LoadError
|
12
|
-
|
12
|
+
'Ignoring the mongodb specific libraries if monog driver is not specified in gem'
|
13
13
|
end
|
@@ -18,13 +18,13 @@ module DataAnon
|
|
18
18
|
|
19
19
|
def generate
|
20
20
|
|
21
|
-
db = Mongo::
|
21
|
+
db = Mongo::Client.new(@mongodb_uri, :database => @configuration_hash[:database])
|
22
22
|
collections = db.collections
|
23
23
|
collections.each do |collection|
|
24
24
|
unless collection.name.start_with?('system.')
|
25
25
|
depth = 2
|
26
26
|
@output << "\tcollection '#{collection.name}' do"
|
27
|
-
document = collection.
|
27
|
+
document = collection.find({}).first
|
28
28
|
process_document(depth, document)
|
29
29
|
@output << "\tend\n"
|
30
30
|
end
|
@@ -63,4 +63,3 @@ module DataAnon
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
66
|
-
|
data/lib/utils/database.rb
CHANGED
@@ -25,7 +25,7 @@ module DataAnon
|
|
25
25
|
|
26
26
|
def self.create_table database, table_name, primary_keys = []
|
27
27
|
klass_name = table_name.to_s.downcase.capitalize
|
28
|
-
return database.const_get
|
28
|
+
return database.const_get(klass_name, false) if database.const_defined?(klass_name, false)
|
29
29
|
database.const_set(klass_name, Class.new(database) do
|
30
30
|
self.table_name = table_name
|
31
31
|
self.primary_keys = primary_keys if primary_keys.length > 1
|
data/lib/utils/random_int.rb
CHANGED
@@ -5,9 +5,11 @@ module DataAnon
|
|
5
5
|
def self.source_connection_specs_rdbms config_hash
|
6
6
|
|
7
7
|
config_hash.keys.reject{|key| config_hash[key].nil? }.collect { |key|
|
8
|
-
if ((config_hash[key].class.to_s.downcase ==
|
8
|
+
if ((config_hash[key].class.to_s.downcase == 'string'))
|
9
9
|
":#{key} => '#{config_hash[key]}'"
|
10
|
-
elsif ((config_hash[key].class.to_s.downcase ==
|
10
|
+
elsif ((config_hash[key].class.to_s.downcase == 'integer'))
|
11
|
+
":#{key} => #{config_hash[key]}"
|
12
|
+
elsif ((config_hash[key].class.to_s.downcase == 'fixnum'))
|
11
13
|
":#{key} => #{config_hash[key]}"
|
12
14
|
end
|
13
15
|
}.join ', '
|
data/lib/version.rb
CHANGED
@@ -1,56 +1,56 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
require 'mongo'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe 'End 2 End MongoDB Blacklist Acceptance Test' do
|
5
5
|
|
6
6
|
before(:each) do
|
7
|
-
Mongo::Client.new(
|
7
|
+
Mongo::Client.new('mongodb://localhost/test').database().drop()
|
8
8
|
users = [
|
9
9
|
{
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
10
|
+
'_id' => 1,
|
11
|
+
'USER_ID' => 'sunitparekh',
|
12
|
+
'date_of_birth' => Time.new(2012, 7, 14, 13, 1, 0),
|
13
|
+
'email' => 'parekh-sunit@mailinator.com',
|
14
|
+
'password' => 'TfqIK8Pd8GlbMDFZCX4l/5EtnOkfLCeynOL85tJQuxum&382knaflk@@',
|
15
|
+
'failed_attempts' => 0,
|
16
|
+
'first_name' => 'Sunit',
|
17
|
+
'last_name' => 'Parekh',
|
18
|
+
'password_reset_answer' => 'manza',
|
19
|
+
'password_reset_question' => 'My new car modal?',
|
20
|
+
'updated_at' => Time.new(2012, 8, 15, 13, 1, 0),
|
21
|
+
'alternate_emails' => ['abc@test.com', 'abc2@test.com']
|
22
22
|
|
23
23
|
},
|
24
24
|
{
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
25
|
+
'_id' => 2,
|
26
|
+
'USER_ID' => 'anandagrawal',
|
27
|
+
'date_of_birth' => Time.new(2011, 8, 11, 13, 1, 0),
|
28
|
+
'email' => 'anand-agrawal@mailinator.com',
|
29
|
+
'password' => 'Tz548O0RWusldVAWkwqfzO3jK/X4l/5EtnOkfLCeynOL85tJQuxum',
|
30
|
+
'failed_attempts' => 0,
|
31
|
+
'first_name' => 'Anand',
|
32
|
+
'last_name' => 'Agrawal',
|
33
|
+
'password_reset_answer' => 'android',
|
34
|
+
'password_reset_question' => 'My phone?',
|
35
|
+
'updated_at' => Time.new(2012, 2, 11, 13, 1, 0),
|
36
|
+
'alternate_emails' => ['abc@test.com', 'abc2@test.com']
|
37
37
|
}
|
38
38
|
]
|
39
|
-
users_coll = Mongo::Client.new(
|
39
|
+
users_coll = Mongo::Client.new('mongodb://localhost/test').database().collection('users')
|
40
40
|
users.each { |p| users_coll.insert_one p }
|
41
41
|
end
|
42
42
|
|
43
|
-
it
|
43
|
+
it 'should anonymize plans collection' do
|
44
44
|
|
45
45
|
database 'test' do
|
46
46
|
strategy DataAnon::Strategy::MongoDB::Blacklist
|
47
|
-
source_db :mongodb_uri =>
|
47
|
+
source_db :mongodb_uri => 'mongodb://localhost/test', :database => 'test'
|
48
48
|
|
49
49
|
collection 'users' do
|
50
50
|
anonymize('date_of_birth').using FieldStrategy::TimeDelta.new(5,30)
|
51
51
|
anonymize('USER_ID').using FieldStrategy::StringTemplate.new('user-#{row_number}')
|
52
52
|
anonymize('email').using FieldStrategy::RandomMailinatorEmail.new
|
53
|
-
anonymize('password') { |field|
|
53
|
+
anonymize('password') { |field| 'password'}
|
54
54
|
anonymize('first_name').using FieldStrategy::RandomFirstName.new
|
55
55
|
anonymize('last_name').using FieldStrategy::RandomLastName.new
|
56
56
|
anonymize('alternate_emails').using FieldStrategy::AnonymizeArray.new(FieldStrategy::RandomMailinatorEmail.new)
|
@@ -58,20 +58,20 @@ describe "End 2 End MongoDB Blacklist Acceptance Test" do
|
|
58
58
|
|
59
59
|
end
|
60
60
|
|
61
|
-
users_coll = Mongo::Client.new(
|
61
|
+
users_coll = Mongo::Client.new('mongodb://localhost/test').database().collection('users')
|
62
62
|
users_coll.find.count.to_int.should be 2
|
63
63
|
user = users_coll.find({'_id' => 1}).to_a[0]
|
64
64
|
|
65
65
|
user['_id'].should == 1
|
66
|
-
user['USER_ID'].should ==
|
66
|
+
user['USER_ID'].should == 'user-1'
|
67
67
|
user['date_of_birth'].to_i.should_not == Time.new(2012, 7, 14, 13, 1, 0).to_i
|
68
|
-
user['email'].should_not ==
|
69
|
-
user['password'].should ==
|
68
|
+
user['email'].should_not == 'parekh-sunit@mailinator.com'
|
69
|
+
user['password'].should == 'password'
|
70
70
|
user['failed_attempts'].should == 0
|
71
|
-
user['first_name'].should_not be
|
72
|
-
user['last_name'].should_not be
|
73
|
-
user['password_reset_answer'].should ==
|
74
|
-
user['password_reset_question'].should ==
|
71
|
+
user['first_name'].should_not be 'Sunit'
|
72
|
+
user['last_name'].should_not be 'Parekh'
|
73
|
+
user['password_reset_answer'].should == 'manza'
|
74
|
+
user['password_reset_question'].should == 'My new car modal?'
|
75
75
|
user['updated_at'].to_i.should == Time.new(2012, 8, 15, 13, 1, 0).to_i
|
76
76
|
user['alternate_emails'].length.should == 2
|
77
77
|
user['alternate_emails'][0].should_not == 'abc@test.com'
|
@@ -1,65 +1,65 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
require 'mongo'
|
3
3
|
|
4
|
-
describe
|
4
|
+
describe 'End 2 End MongoDB Whitelist Acceptance Test' do
|
5
5
|
|
6
6
|
before(:each) do
|
7
|
-
Mongo::Client.new(
|
8
|
-
Mongo::Client.new(
|
7
|
+
Mongo::Client.new('mongodb://localhost/test').database.drop
|
8
|
+
Mongo::Client.new('mongodb://localhost/dest').database.drop
|
9
9
|
plans = [
|
10
10
|
{
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
'_id' => 1,
|
12
|
+
'name' => 'Free',
|
13
|
+
'nick_names' => ['Name1', 'Name2'],
|
14
|
+
'features' => [
|
15
15
|
{
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
'max_storage' => 21474836480,
|
17
|
+
'type' => 'AmazonS3',
|
18
|
+
'users' => {'max' => 1, 'additional' => false}
|
19
19
|
},
|
20
20
|
{
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
'max_storage' => 21474836480,
|
22
|
+
'type' => 'DropBox',
|
23
|
+
'users' => {'max' => 1, 'additional' => false}
|
24
24
|
}
|
25
25
|
],
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
'term' => 'month',
|
27
|
+
'public_sharing' => false,
|
28
|
+
'photo_sharing' => true,
|
29
|
+
'created_at' => Time.new(2012, 6, 21, 13, 30, 0)
|
30
30
|
},
|
31
31
|
{
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
'_id' => 2,
|
33
|
+
'name' => 'Team',
|
34
|
+
'plan_aliases' => ['Business', 'Paid'],
|
35
|
+
'features' => [
|
36
36
|
{
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
'max_storage' => 53687091200,
|
38
|
+
'type' => 'AmazonS3',
|
39
|
+
'users' => {'max' => 5, 'additional' => true}
|
40
40
|
},
|
41
41
|
{
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
'max_storage' => 53687091200,
|
43
|
+
'type' => 'DropBox',
|
44
|
+
'users' => {'max' => 5, 'additional' => true}
|
45
45
|
}
|
46
46
|
],
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
'term' => 'month',
|
48
|
+
'public_sharing' => true,
|
49
|
+
'photo_sharing' => true,
|
50
|
+
'created_at' => Time.new(2012, 8, 11, 13, 1, 0)
|
51
51
|
}
|
52
52
|
]
|
53
|
-
plans_coll = Mongo::Client.new(
|
53
|
+
plans_coll = Mongo::Client.new('mongodb://localhost/test').database.collection('plans')
|
54
54
|
plans.each { |p| plans_coll.insert_one p }
|
55
55
|
end
|
56
56
|
|
57
|
-
it
|
57
|
+
it 'should anonymize plans collection' do
|
58
58
|
|
59
59
|
database 'dest' do
|
60
60
|
strategy DataAnon::Strategy::MongoDB::Whitelist
|
61
|
-
source_db :mongodb_uri =>
|
62
|
-
destination_db :mongodb_uri =>
|
61
|
+
source_db :mongodb_uri => 'mongodb://localhost/test', :database => 'test'
|
62
|
+
destination_db :mongodb_uri => 'mongodb://localhost/dest', :database => 'dest'
|
63
63
|
|
64
64
|
collection 'plans' do
|
65
65
|
whitelist '_id', 'name', 'term', 'created_at'
|
@@ -78,15 +78,15 @@ describe "End 2 End MongoDB Whitelist Acceptance Test" do
|
|
78
78
|
|
79
79
|
end
|
80
80
|
|
81
|
-
plans_coll = Mongo::Client.new(
|
81
|
+
plans_coll = Mongo::Client.new('mongodb://localhost/dest').database.collection('plans')
|
82
82
|
plans_coll.find.count.to_int.should be 2
|
83
83
|
plan = plans_coll.find({ '_id' => 1}).to_a[0]
|
84
84
|
|
85
85
|
plan['_id'].should == 1
|
86
|
-
plan['name'].should ==
|
87
|
-
plan['nick_names'][0].should_not ==
|
88
|
-
plan['nick_names'][1].should_not ==
|
89
|
-
plan['term'].should ==
|
86
|
+
plan['name'].should == 'Free'
|
87
|
+
plan['nick_names'][0].should_not == 'Name1'
|
88
|
+
plan['nick_names'][1].should_not == 'Name2'
|
89
|
+
plan['term'].should == 'month'
|
90
90
|
plan['created_at'].should == Time.new(2012, 6, 21, 13, 30, 0)
|
91
91
|
plan['plan_aliases'].should be_nil
|
92
92
|
[true,false].should include(plan['public_sharing'])
|
@@ -94,14 +94,14 @@ describe "End 2 End MongoDB Whitelist Acceptance Test" do
|
|
94
94
|
plan['features'].length.should == 2
|
95
95
|
feature1 = plan['features'][0]
|
96
96
|
[10737418240, 21474836480, 53687091200].should include(feature1['max_storage'])
|
97
|
-
feature1['type'].should ==
|
98
|
-
feature1['users']['max'].should be_kind_of(
|
97
|
+
feature1['type'].should == 'AmazonS3'
|
98
|
+
feature1['users']['max'].should be_kind_of(Integer)
|
99
99
|
[true,false].should include(feature1['users']['additional'])
|
100
100
|
|
101
101
|
|
102
102
|
plan = plans_coll.find({ '_id' => 2}).to_a[0]
|
103
103
|
plan['plan_aliases'].length.should == 2
|
104
|
-
[
|
105
|
-
[
|
104
|
+
['Free', 'Team', 'Business', 'Paid'].should include(plan['plan_aliases'][0])
|
105
|
+
['Free', 'Team', 'Business', 'Paid'].should include(plan['plan_aliases'][1])
|
106
106
|
end
|
107
107
|
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
|
-
describe
|
3
|
+
describe 'End 2 End RDBMS Whitelist Acceptance Test using SQLite database' do
|
4
4
|
|
5
5
|
source_connection_spec = {:adapter => 'sqlite3', :database => 'tmp/customer.sqlite'}
|
6
6
|
dest_connection_spec = {:adapter => 'sqlite3', :database => 'tmp/customer-dest.sqlite'}
|
@@ -13,9 +13,9 @@ describe "End 2 End RDBMS Whitelist Acceptance Test using SQLite database" do
|
|
13
13
|
CustomerSample.create_schema dest_connection_spec
|
14
14
|
end
|
15
15
|
|
16
|
-
it
|
16
|
+
it 'should anonymize customer table record ' do
|
17
17
|
|
18
|
-
database
|
18
|
+
database 'Customer' do
|
19
19
|
strategy DataAnon::Strategy::Whitelist
|
20
20
|
source_db source_connection_spec
|
21
21
|
destination_db dest_connection_spec
|
@@ -37,14 +37,14 @@ describe "End 2 End RDBMS Whitelist Acceptance Test using SQLite database" do
|
|
37
37
|
|
38
38
|
DataAnon::Utils::DestinationDatabase.establish_connection dest_connection_spec
|
39
39
|
dest_table = DataAnon::Utils::DestinationTable.create 'customers'
|
40
|
-
new_rec = dest_table.where(
|
41
|
-
new_rec.first_name.should_not be(
|
42
|
-
new_rec.last_name.should_not be(
|
40
|
+
new_rec = dest_table.where('cust_id' => CustomerSample::SAMPLE_DATA[0][:cust_id]).first
|
41
|
+
new_rec.first_name.should_not be('Sunit')
|
42
|
+
new_rec.last_name.should_not be('Parekh')
|
43
43
|
new_rec.birth_date.should_not be(Date.new(1977,7,8))
|
44
44
|
new_rec.address.should == 'F 501 Shanti Nagar'
|
45
45
|
['Gujrat','Karnataka'].should include(new_rec.state)
|
46
46
|
new_rec.zipcode.should == '411048'
|
47
|
-
new_rec.phone.should_not be
|
47
|
+
new_rec.phone.should_not be '9923700662'
|
48
48
|
new_rec.email.should == 'test+1@gmail.com'
|
49
49
|
[true,false].should include(new_rec.terms_n_condition)
|
50
50
|
new_rec.age.should be_between(0,100)
|