datamapper 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/MIT-LICENSE +22 -0
- data/README +1 -0
- data/example.rb +25 -0
- data/lib/data_mapper.rb +30 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +229 -0
- data/lib/data_mapper/adapters/mysql_adapter.rb +171 -0
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +189 -0
- data/lib/data_mapper/associations.rb +19 -0
- data/lib/data_mapper/associations/belongs_to_association.rb +111 -0
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +100 -0
- data/lib/data_mapper/associations/has_many_association.rb +101 -0
- data/lib/data_mapper/associations/has_one_association.rb +107 -0
- data/lib/data_mapper/base.rb +160 -0
- data/lib/data_mapper/callbacks.rb +47 -0
- data/lib/data_mapper/database.rb +134 -0
- data/lib/data_mapper/extensions/active_record_impersonation.rb +69 -0
- data/lib/data_mapper/extensions/callback_helpers.rb +35 -0
- data/lib/data_mapper/identity_map.rb +21 -0
- data/lib/data_mapper/loaded_set.rb +45 -0
- data/lib/data_mapper/mappings/column.rb +78 -0
- data/lib/data_mapper/mappings/schema.rb +28 -0
- data/lib/data_mapper/mappings/table.rb +99 -0
- data/lib/data_mapper/queries/conditions.rb +141 -0
- data/lib/data_mapper/queries/connection.rb +34 -0
- data/lib/data_mapper/queries/create_table_statement.rb +38 -0
- data/lib/data_mapper/queries/delete_statement.rb +17 -0
- data/lib/data_mapper/queries/drop_table_statement.rb +17 -0
- data/lib/data_mapper/queries/insert_statement.rb +29 -0
- data/lib/data_mapper/queries/reader.rb +42 -0
- data/lib/data_mapper/queries/result.rb +19 -0
- data/lib/data_mapper/queries/select_statement.rb +103 -0
- data/lib/data_mapper/queries/table_exists_statement.rb +17 -0
- data/lib/data_mapper/queries/truncate_table_statement.rb +17 -0
- data/lib/data_mapper/queries/update_statement.rb +25 -0
- data/lib/data_mapper/session.rb +240 -0
- data/lib/data_mapper/support/blank_slate.rb +3 -0
- data/lib/data_mapper/support/connection_pool.rb +117 -0
- data/lib/data_mapper/support/enumerable.rb +27 -0
- data/lib/data_mapper/support/inflector.rb +329 -0
- data/lib/data_mapper/support/proc.rb +69 -0
- data/lib/data_mapper/support/string.rb +23 -0
- data/lib/data_mapper/support/symbol.rb +91 -0
- data/lib/data_mapper/support/weak_hash.rb +46 -0
- data/lib/data_mapper/unit_of_work.rb +38 -0
- data/lib/data_mapper/validations/confirmation_validator.rb +55 -0
- data/lib/data_mapper/validations/contextual_validations.rb +50 -0
- data/lib/data_mapper/validations/format_validator.rb +85 -0
- data/lib/data_mapper/validations/formats/email.rb +78 -0
- data/lib/data_mapper/validations/generic_validator.rb +27 -0
- data/lib/data_mapper/validations/length_validator.rb +75 -0
- data/lib/data_mapper/validations/required_field_validator.rb +47 -0
- data/lib/data_mapper/validations/unique_validator.rb +65 -0
- data/lib/data_mapper/validations/validation_errors.rb +34 -0
- data/lib/data_mapper/validations/validation_helper.rb +60 -0
- data/performance.rb +156 -0
- data/profile_data_mapper.rb +18 -0
- data/rakefile.rb +80 -0
- data/spec/basic_finder.rb +67 -0
- data/spec/belongs_to.rb +47 -0
- data/spec/fixtures/animals.yaml +32 -0
- data/spec/fixtures/exhibits.yaml +90 -0
- data/spec/fixtures/fruit.yaml +6 -0
- data/spec/fixtures/people.yaml +15 -0
- data/spec/fixtures/zoos.yaml +20 -0
- data/spec/has_and_belongs_to_many.rb +25 -0
- data/spec/has_many.rb +34 -0
- data/spec/legacy.rb +14 -0
- data/spec/models/animal.rb +7 -0
- data/spec/models/exhibit.rb +6 -0
- data/spec/models/fruit.rb +6 -0
- data/spec/models/person.rb +7 -0
- data/spec/models/post.rb +4 -0
- data/spec/models/sales_person.rb +4 -0
- data/spec/models/zoo.rb +5 -0
- data/spec/new_record.rb +24 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/sub_select.rb +16 -0
- data/spec/symbolic_operators.rb +21 -0
- data/spec/validates_confirmation_of.rb +36 -0
- data/spec/validates_format_of.rb +61 -0
- data/spec/validates_length_of.rb +101 -0
- data/spec/validates_uniqueness_of.rb +45 -0
- data/spec/validations.rb +63 -0
- metadata +134 -0
data/spec/legacy.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
context 'Legacy Database' do
|
2
|
+
|
3
|
+
specify('should allow models to map with custom attribute names') do
|
4
|
+
Fruit.first.name.should == 'Kiwi'
|
5
|
+
end
|
6
|
+
|
7
|
+
specify('should allow custom foreign-key mappings') do
|
8
|
+
database do
|
9
|
+
Fruit[:name => 'Watermelon'].devourer_of_souls.should == Animal[:name => 'Cup']
|
10
|
+
Animal[:name => 'Cup'].favourite_fruit.should == Fruit[:name => 'Watermelon']
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
data/spec/models/post.rb
ADDED
data/spec/models/zoo.rb
ADDED
data/spec/new_record.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
context 'A new record' do
|
2
|
+
|
3
|
+
setup do
|
4
|
+
@bob = Person.new(:name => 'Bob', :age => 30, :occupation => 'Sales')
|
5
|
+
end
|
6
|
+
|
7
|
+
specify 'should be dirty' do
|
8
|
+
@bob.dirty?.should == true
|
9
|
+
end
|
10
|
+
|
11
|
+
specify 'set attributes should be dirty' do
|
12
|
+
attributes = @bob.attributes.dup.reject { |k,v| k == :id }
|
13
|
+
@bob.dirty_attributes.should == { :name => 'Bob', :age => 30, :occupation => 'Sales' }
|
14
|
+
end
|
15
|
+
|
16
|
+
specify 'should be marked as new' do
|
17
|
+
@bob.new_record?.should == true
|
18
|
+
end
|
19
|
+
|
20
|
+
specify 'should have a nil id' do
|
21
|
+
@bob.id.should == nil
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../lib/data_mapper'
|
2
|
+
require 'yaml'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
log_path = File.dirname(__FILE__) + '/../spec.log'
|
6
|
+
|
7
|
+
require 'fileutils'
|
8
|
+
FileUtils::rm log_path if File.exists?(log_path)
|
9
|
+
|
10
|
+
case ENV['ADAPTER']
|
11
|
+
when 'sqlite3' then
|
12
|
+
DataMapper::Database.setup do
|
13
|
+
adapter 'sqlite3'
|
14
|
+
database 'data_mapper_1.db'
|
15
|
+
log_stream 'spec.log'
|
16
|
+
log_level Logger::DEBUG
|
17
|
+
end
|
18
|
+
else
|
19
|
+
DataMapper::Database.setup do
|
20
|
+
adapter 'mysql'
|
21
|
+
database 'data_mapper_1'
|
22
|
+
username 'root'
|
23
|
+
log_stream 'spec.log'
|
24
|
+
log_level Logger::DEBUG
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Dir[File.dirname(__FILE__) + '/models/*.rb'].each do |path|
|
29
|
+
load path
|
30
|
+
end
|
31
|
+
|
32
|
+
database do |db|
|
33
|
+
db.schema.each do |table|
|
34
|
+
db.create_table(table.klass)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
at_exit do
|
39
|
+
database do |db|
|
40
|
+
db.schema.each do |table|
|
41
|
+
db.drop_table(table.klass)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end if ENV['DROP'] == '1'
|
45
|
+
|
46
|
+
# Define a fixtures helper method to load up our test data.
|
47
|
+
def fixtures(name)
|
48
|
+
entry = YAML::load_file(File.dirname(__FILE__) + "/fixtures/#{name}.yaml")
|
49
|
+
klass = Kernel::const_get(Inflector.classify(Inflector.singularize(name)))
|
50
|
+
|
51
|
+
klass.truncate!
|
52
|
+
|
53
|
+
(entry.kind_of?(Array) ? entry : [entry]).each do |hash|
|
54
|
+
klass::create(hash)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Pre-fill the database so non-destructive tests don't need to reload fixtures.
|
59
|
+
Dir[File.dirname(__FILE__) + "/fixtures/*.yaml"].each do |path|
|
60
|
+
fixtures(File::basename(path).sub(/\.yaml$/, ''))
|
61
|
+
end
|
data/spec/sub_select.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
=begin
|
2
|
+
context 'Sub-selection' do
|
3
|
+
|
4
|
+
specify 'should return a Cup' do
|
5
|
+
Animal[:id.select => { :name => 'cup' }].name.should == 'Cup'
|
6
|
+
end
|
7
|
+
|
8
|
+
specify 'should return all exhibits for Galveston zoo' do
|
9
|
+
Exhibit.all(:zoo_id.select(Zoo) => { :name => 'Galveston' }).size.should == 3
|
10
|
+
end
|
11
|
+
|
12
|
+
specify 'should allow a sub-select in the select-list' do
|
13
|
+
Animal[:select => [ :id.count ]]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
=end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
context 'Symbolic Operators' do
|
2
|
+
|
3
|
+
specify('should use greater_than_or_equal_to to limit results') do
|
4
|
+
Person.all(:age.gte => 28).size.should == 3
|
5
|
+
end
|
6
|
+
|
7
|
+
specify('use an Array for in-clauses') do
|
8
|
+
family = Person.all(:id => [1, 2, 4])
|
9
|
+
family[0].name.should == 'Sam'
|
10
|
+
family[1].name.should == 'Amy'
|
11
|
+
family[2].name.should == 'Josh'
|
12
|
+
end
|
13
|
+
|
14
|
+
specify('use "not" for not-equal operations') do
|
15
|
+
Person.all(:name.not => 'Bob').size.should == 4
|
16
|
+
end
|
17
|
+
|
18
|
+
specify('age should not be nil') do
|
19
|
+
Person.all(:age.not => nil).size.should == 5
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
context 'A Cow' do
|
2
|
+
|
3
|
+
setup do
|
4
|
+
class Cow
|
5
|
+
|
6
|
+
include DataMapper::Extensions::ValidationHelper
|
7
|
+
|
8
|
+
attr_accessor :name, :name_confirmation, :age
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
specify('') do
|
13
|
+
class Cow
|
14
|
+
validations.clear!
|
15
|
+
validates_confirmation_of :name, :context => :save
|
16
|
+
end
|
17
|
+
|
18
|
+
betsy = Cow.new
|
19
|
+
betsy.valid?.should == true
|
20
|
+
|
21
|
+
betsy.name = 'Betsy'
|
22
|
+
betsy.name_confirmation = ''
|
23
|
+
betsy.valid?(:save).should == false
|
24
|
+
betsy.errors.full_messages.first.should == 'Name does not match the confirmation'
|
25
|
+
|
26
|
+
betsy.name = ''
|
27
|
+
betsy.name_confirmation = 'Betsy'
|
28
|
+
betsy.valid?(:save).should == false
|
29
|
+
betsy.errors.full_messages.first.should == 'Name does not match the confirmation'
|
30
|
+
|
31
|
+
betsy.name = 'Betsy'
|
32
|
+
betsy.name_confirmation = 'Betsy'
|
33
|
+
betsy.valid?(:save).should == true
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
context 'An Employee' do
|
2
|
+
|
3
|
+
setup do
|
4
|
+
class Employee
|
5
|
+
|
6
|
+
include DataMapper::Extensions::ValidationHelper
|
7
|
+
|
8
|
+
attr_accessor :email
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
specify('must have a valid email address') do
|
13
|
+
class Employee
|
14
|
+
validations.clear!
|
15
|
+
validates_format_of :email, :as => :email_address, :on => :save
|
16
|
+
end
|
17
|
+
|
18
|
+
e = Employee.new
|
19
|
+
e.valid?.should == true
|
20
|
+
|
21
|
+
[
|
22
|
+
'test test@example.com', 'test@example', 'test#example.com',
|
23
|
+
'tester@exampl$.com', '[scizzle]@example.com', '.test@example.com'
|
24
|
+
].all? { |test_email|
|
25
|
+
e.email = test_email
|
26
|
+
e.valid?(:save).should == false
|
27
|
+
e.errors.full_messages.first.should == "#{test_email} is not a valid email address"
|
28
|
+
}
|
29
|
+
|
30
|
+
e.email = 'test@example.com'
|
31
|
+
e.valid?(:save).should == true
|
32
|
+
end
|
33
|
+
|
34
|
+
specify('must have a valid organization code') do
|
35
|
+
class Employee
|
36
|
+
validations.clear!
|
37
|
+
|
38
|
+
attr_accessor :organization_code
|
39
|
+
|
40
|
+
# WARNING: contrived example
|
41
|
+
# The organization code must be A#### or B######X12
|
42
|
+
validates_format_of :organization_code, :on => :save, :with => lambda { |code|
|
43
|
+
(code =~ /A\d{4}/) || (code =~ /[B-Z]\d{6}X12/)
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
e = Employee.new
|
48
|
+
e.valid?.should == true
|
49
|
+
|
50
|
+
e.organization_code = 'BLAH :)'
|
51
|
+
e.valid?(:save).should == false
|
52
|
+
e.errors.full_messages.first.should == 'Organization code is invalid'
|
53
|
+
|
54
|
+
e.organization_code = 'A1234'
|
55
|
+
e.valid?(:save).should == true
|
56
|
+
|
57
|
+
e.organization_code = 'B123456X12'
|
58
|
+
e.valid?(:save).should == true
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
context 'A Cow' do
|
2
|
+
|
3
|
+
setup do
|
4
|
+
class Cow
|
5
|
+
|
6
|
+
include DataMapper::Extensions::ValidationHelper
|
7
|
+
|
8
|
+
attr_accessor :name, :age
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
specify('should not have a name shorter than 3 characters') do
|
13
|
+
class Cow
|
14
|
+
validations.clear!
|
15
|
+
validates_length_of :name, :min => 3, :context => :save
|
16
|
+
end
|
17
|
+
|
18
|
+
betsy = Cow.new
|
19
|
+
betsy.valid?.should == true
|
20
|
+
|
21
|
+
betsy.valid?(:save).should == false
|
22
|
+
betsy.errors.full_messages.first.should == 'Name must be more than 3 characters long'
|
23
|
+
|
24
|
+
betsy.name = 'Be'
|
25
|
+
betsy.valid?(:save).should == false
|
26
|
+
betsy.errors.full_messages.first.should == 'Name must be more than 3 characters long'
|
27
|
+
|
28
|
+
betsy.name = 'Bet'
|
29
|
+
betsy.valid?(:save).should == true
|
30
|
+
|
31
|
+
betsy.name = 'Bets'
|
32
|
+
betsy.valid?(:save).should == true
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
specify('should not have a name longer than 10 characters') do
|
37
|
+
class Cow
|
38
|
+
validations.clear!
|
39
|
+
validates_length_of :name, :max => 10, :context => :save
|
40
|
+
end
|
41
|
+
|
42
|
+
betsy = Cow.new
|
43
|
+
betsy.valid?.should == true
|
44
|
+
betsy.valid?(:save).should == true
|
45
|
+
|
46
|
+
betsy.name = 'Testicular Fortitude'
|
47
|
+
betsy.valid?(:save).should == false
|
48
|
+
betsy.errors.full_messages.first.should == 'Name must be less than 10 characters long'
|
49
|
+
|
50
|
+
betsy.name = 'Betsy'
|
51
|
+
betsy.valid?(:save).should == true
|
52
|
+
end
|
53
|
+
|
54
|
+
specify('should have a name that is 8 characters long') do
|
55
|
+
class Cow
|
56
|
+
validations.clear!
|
57
|
+
validates_length_of :name, :is => 8, :context => :save
|
58
|
+
end
|
59
|
+
|
60
|
+
# Context is not save
|
61
|
+
betsy = Cow.new
|
62
|
+
betsy.valid?.should == true
|
63
|
+
|
64
|
+
# Context is :save
|
65
|
+
betsy.valid?(:save).should == false
|
66
|
+
|
67
|
+
betsy.name = 'Testicular Fortitude'
|
68
|
+
betsy.valid?(:save).should == false
|
69
|
+
betsy.errors.full_messages.first.should == 'Name must be 8 characters long'
|
70
|
+
|
71
|
+
betsy.name = 'Samooela'
|
72
|
+
betsy.valid?(:save).should == true
|
73
|
+
end
|
74
|
+
|
75
|
+
specify('should have a name that is between 10 and 15 characters long') do
|
76
|
+
class Cow
|
77
|
+
validations.clear!
|
78
|
+
validates_length_of :name, :in => (10..15), :context => :save
|
79
|
+
end
|
80
|
+
|
81
|
+
# Context is not save
|
82
|
+
betsy = Cow.new
|
83
|
+
betsy.valid?.should == true
|
84
|
+
|
85
|
+
# Context is :save
|
86
|
+
betsy.valid?(:save)
|
87
|
+
betsy.errors.full_messages.first
|
88
|
+
|
89
|
+
betsy.valid?(:save).should == false
|
90
|
+
betsy.errors.full_messages.first.should == 'Name must be between 10 and 15 characters long'
|
91
|
+
|
92
|
+
betsy.name = 'Smoooooot'
|
93
|
+
betsy.valid?(:save).should == false
|
94
|
+
|
95
|
+
betsy.name = 'Smooooooooooooooooooot'
|
96
|
+
betsy.valid?(:save).should == false
|
97
|
+
|
98
|
+
betsy.name = 'Smootenstein'
|
99
|
+
betsy.valid?(:save).should == true
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
context 'An Animal' do
|
2
|
+
|
3
|
+
specify('must have a unique name') do
|
4
|
+
class Animal
|
5
|
+
validations.clear!
|
6
|
+
validates_uniqueness_of :name, :context => :save
|
7
|
+
end
|
8
|
+
|
9
|
+
bugaboo = Animal.new
|
10
|
+
bugaboo.valid?.should == true
|
11
|
+
|
12
|
+
bugaboo.name = 'Bear'
|
13
|
+
bugaboo.valid?(:save).should == false
|
14
|
+
bugaboo.errors.full_messages.first.should == 'Name has already been taken'
|
15
|
+
|
16
|
+
bugaboo.name = 'Bugaboo'
|
17
|
+
bugaboo.valid?(:save).should == true
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'A Person' do
|
23
|
+
setup do
|
24
|
+
fixtures 'people'
|
25
|
+
end
|
26
|
+
|
27
|
+
specify('must have a unique name for their occupation') do
|
28
|
+
class Person
|
29
|
+
validations.clear!
|
30
|
+
validates_uniqueness_of :name, :context => :save, :scope => :occupation
|
31
|
+
end
|
32
|
+
|
33
|
+
new_programmer_scott = Person.new(:name => 'Scott', :age => 29, :occupation => 'Programmer')
|
34
|
+
garbage_man_scott = Person.new(:name => 'Scott', :age => 25, :occupation => 'Garbage Man')
|
35
|
+
|
36
|
+
# Should be valid even though there is another 'Scott' already in the database
|
37
|
+
garbage_man_scott.valid?(:save).should == true
|
38
|
+
|
39
|
+
# Should NOT be valid, there is already a Programmer names Scott, adding one more
|
40
|
+
# would destroy the universe or something
|
41
|
+
new_programmer_scott.valid?(:save).should == false
|
42
|
+
new_programmer_scott.errors.full_messages.first.should == "Name has already been taken"
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/spec/validations.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
context 'Validations' do
|
2
|
+
|
3
|
+
setup do
|
4
|
+
class Cow
|
5
|
+
|
6
|
+
include DataMapper::Extensions::ValidationHelper
|
7
|
+
|
8
|
+
attr_accessor :name, :age
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
specify('should allow you to specify not-null fields in different contexts') do
|
13
|
+
class Cow
|
14
|
+
validations.clear!
|
15
|
+
validates_presence_of :name, :context => :save
|
16
|
+
end
|
17
|
+
|
18
|
+
betsy = Cow.new
|
19
|
+
betsy.valid?.should == true
|
20
|
+
|
21
|
+
betsy.valid?(:save).should == false
|
22
|
+
betsy.errors.full_messages.first.should == 'Name must not be blank'
|
23
|
+
|
24
|
+
betsy.name = 'Betsy'
|
25
|
+
betsy.valid?(:save).should == true
|
26
|
+
end
|
27
|
+
|
28
|
+
specify('should be able to use ":on" for a context alias') do
|
29
|
+
class Cow
|
30
|
+
validations.clear!
|
31
|
+
validates_presence_of :name, :age, :on => :create
|
32
|
+
end
|
33
|
+
|
34
|
+
maggie = Cow.new
|
35
|
+
maggie.valid?.should == true
|
36
|
+
|
37
|
+
maggie.valid?(:create).should == false
|
38
|
+
maggie.errors.full_messages.should include('Age must not be blank')
|
39
|
+
maggie.errors.full_messages.should include('Name must not be blank')
|
40
|
+
|
41
|
+
maggie.name = 'Maggie'
|
42
|
+
maggie.age = 29
|
43
|
+
maggie.valid?(:create).should == true
|
44
|
+
end
|
45
|
+
|
46
|
+
specify('should default to a general context if unspecified') do
|
47
|
+
class Cow
|
48
|
+
validations.clear!
|
49
|
+
validates_presence_of :name, :age
|
50
|
+
end
|
51
|
+
|
52
|
+
rhonda = Cow.new
|
53
|
+
rhonda.valid?.should == false
|
54
|
+
|
55
|
+
rhonda.errors.full_messages.should include('Age must not be blank')
|
56
|
+
rhonda.errors.full_messages.should include('Name must not be blank')
|
57
|
+
|
58
|
+
rhonda.name = 'Rhonda'
|
59
|
+
rhonda.age = 44
|
60
|
+
rhonda.valid?.should == true
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|