datamapper 0.1.0

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