datamapper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/CHANGELOG +2 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README +1 -0
  4. data/example.rb +25 -0
  5. data/lib/data_mapper.rb +30 -0
  6. data/lib/data_mapper/adapters/abstract_adapter.rb +229 -0
  7. data/lib/data_mapper/adapters/mysql_adapter.rb +171 -0
  8. data/lib/data_mapper/adapters/sqlite3_adapter.rb +189 -0
  9. data/lib/data_mapper/associations.rb +19 -0
  10. data/lib/data_mapper/associations/belongs_to_association.rb +111 -0
  11. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +100 -0
  12. data/lib/data_mapper/associations/has_many_association.rb +101 -0
  13. data/lib/data_mapper/associations/has_one_association.rb +107 -0
  14. data/lib/data_mapper/base.rb +160 -0
  15. data/lib/data_mapper/callbacks.rb +47 -0
  16. data/lib/data_mapper/database.rb +134 -0
  17. data/lib/data_mapper/extensions/active_record_impersonation.rb +69 -0
  18. data/lib/data_mapper/extensions/callback_helpers.rb +35 -0
  19. data/lib/data_mapper/identity_map.rb +21 -0
  20. data/lib/data_mapper/loaded_set.rb +45 -0
  21. data/lib/data_mapper/mappings/column.rb +78 -0
  22. data/lib/data_mapper/mappings/schema.rb +28 -0
  23. data/lib/data_mapper/mappings/table.rb +99 -0
  24. data/lib/data_mapper/queries/conditions.rb +141 -0
  25. data/lib/data_mapper/queries/connection.rb +34 -0
  26. data/lib/data_mapper/queries/create_table_statement.rb +38 -0
  27. data/lib/data_mapper/queries/delete_statement.rb +17 -0
  28. data/lib/data_mapper/queries/drop_table_statement.rb +17 -0
  29. data/lib/data_mapper/queries/insert_statement.rb +29 -0
  30. data/lib/data_mapper/queries/reader.rb +42 -0
  31. data/lib/data_mapper/queries/result.rb +19 -0
  32. data/lib/data_mapper/queries/select_statement.rb +103 -0
  33. data/lib/data_mapper/queries/table_exists_statement.rb +17 -0
  34. data/lib/data_mapper/queries/truncate_table_statement.rb +17 -0
  35. data/lib/data_mapper/queries/update_statement.rb +25 -0
  36. data/lib/data_mapper/session.rb +240 -0
  37. data/lib/data_mapper/support/blank_slate.rb +3 -0
  38. data/lib/data_mapper/support/connection_pool.rb +117 -0
  39. data/lib/data_mapper/support/enumerable.rb +27 -0
  40. data/lib/data_mapper/support/inflector.rb +329 -0
  41. data/lib/data_mapper/support/proc.rb +69 -0
  42. data/lib/data_mapper/support/string.rb +23 -0
  43. data/lib/data_mapper/support/symbol.rb +91 -0
  44. data/lib/data_mapper/support/weak_hash.rb +46 -0
  45. data/lib/data_mapper/unit_of_work.rb +38 -0
  46. data/lib/data_mapper/validations/confirmation_validator.rb +55 -0
  47. data/lib/data_mapper/validations/contextual_validations.rb +50 -0
  48. data/lib/data_mapper/validations/format_validator.rb +85 -0
  49. data/lib/data_mapper/validations/formats/email.rb +78 -0
  50. data/lib/data_mapper/validations/generic_validator.rb +27 -0
  51. data/lib/data_mapper/validations/length_validator.rb +75 -0
  52. data/lib/data_mapper/validations/required_field_validator.rb +47 -0
  53. data/lib/data_mapper/validations/unique_validator.rb +65 -0
  54. data/lib/data_mapper/validations/validation_errors.rb +34 -0
  55. data/lib/data_mapper/validations/validation_helper.rb +60 -0
  56. data/performance.rb +156 -0
  57. data/profile_data_mapper.rb +18 -0
  58. data/rakefile.rb +80 -0
  59. data/spec/basic_finder.rb +67 -0
  60. data/spec/belongs_to.rb +47 -0
  61. data/spec/fixtures/animals.yaml +32 -0
  62. data/spec/fixtures/exhibits.yaml +90 -0
  63. data/spec/fixtures/fruit.yaml +6 -0
  64. data/spec/fixtures/people.yaml +15 -0
  65. data/spec/fixtures/zoos.yaml +20 -0
  66. data/spec/has_and_belongs_to_many.rb +25 -0
  67. data/spec/has_many.rb +34 -0
  68. data/spec/legacy.rb +14 -0
  69. data/spec/models/animal.rb +7 -0
  70. data/spec/models/exhibit.rb +6 -0
  71. data/spec/models/fruit.rb +6 -0
  72. data/spec/models/person.rb +7 -0
  73. data/spec/models/post.rb +4 -0
  74. data/spec/models/sales_person.rb +4 -0
  75. data/spec/models/zoo.rb +5 -0
  76. data/spec/new_record.rb +24 -0
  77. data/spec/spec_helper.rb +61 -0
  78. data/spec/sub_select.rb +16 -0
  79. data/spec/symbolic_operators.rb +21 -0
  80. data/spec/validates_confirmation_of.rb +36 -0
  81. data/spec/validates_format_of.rb +61 -0
  82. data/spec/validates_length_of.rb +101 -0
  83. data/spec/validates_uniqueness_of.rb +45 -0
  84. data/spec/validations.rb +63 -0
  85. metadata +134 -0
@@ -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
@@ -0,0 +1,7 @@
1
+ class Animal < DataMapper::Base
2
+ property :name, :string
3
+ property :notes, :text, :lazy => true
4
+
5
+ has_one :favourite_fruit, :class => 'Fruit', :foreign_key => 'devourer_id'
6
+ has_and_belongs_to_many :exhibits
7
+ end
@@ -0,0 +1,6 @@
1
+ class Exhibit < DataMapper::Base
2
+ property :name, :string
3
+
4
+ belongs_to :zoo
5
+ has_and_belongs_to_many :animals
6
+ end
@@ -0,0 +1,6 @@
1
+ class Fruit < DataMapper::Base
2
+ set_table_name 'fruit'
3
+ property :name, :string, :column => 'fruit_name'
4
+
5
+ belongs_to :devourer_of_souls, :class => 'Animal', :foreign_key => 'devourer_id'
6
+ end
@@ -0,0 +1,7 @@
1
+ class Person < DataMapper::Base
2
+ property :name, :string
3
+ property :age, :integer
4
+ property :occupation, :string
5
+ property :type, :class
6
+ property :notes, :text, :lazy => true
7
+ end
@@ -0,0 +1,4 @@
1
+ class Post < DataMapper::Base
2
+ property :title, :string
3
+ property :created_at, :datetime
4
+ end
@@ -0,0 +1,4 @@
1
+ require File.dirname(__FILE__) + '/person'
2
+
3
+ class SalesPerson < Person
4
+ end
@@ -0,0 +1,5 @@
1
+ class Zoo < DataMapper::Base
2
+ property :name, :string
3
+
4
+ has_many :exhibits
5
+ end
@@ -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
@@ -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
@@ -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
@@ -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