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,78 @@
1
+ # http://www.faqs.org/rfcs/rfc2822.html
2
+ module DataMapper
3
+ module Validations
4
+ module Helpers
5
+ module Email
6
+
7
+ module RFC2822
8
+ EmailAddress = begin
9
+ alpha = "a-zA-Z"
10
+ digit = "0-9"
11
+ atext = "[#{alpha}#{digit}\!\#\$\%\&\'\*+\/\=\?\^\_\`\{\|\}\~\-]"
12
+ dot_atom_text = "#{atext}+([.]#{atext}*)*"
13
+ dot_atom = "#{dot_atom_text}"
14
+ qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]'
15
+ text = "[\\x01-\\x09\\x11\\x12\\x14-\\x7f]"
16
+ quoted_pair = "(\\x5c#{text})"
17
+ qcontent = "(?:#{qtext}|#{quoted_pair})"
18
+ quoted_string = "[\"]#{qcontent}+[\"]"
19
+ atom = "#{atext}+"
20
+ word = "(?:#{atom}|#{quoted_string})"
21
+ obs_local_part = "#{word}([.]#{word})*"
22
+ local_part = "(?:#{dot_atom}|#{quoted_string}|#{obs_local_part})"
23
+ no_ws_ctl = "\\x01-\\x08\\x11\\x12\\x14-\\x1f\\x7f"
24
+ dtext = "[#{no_ws_ctl}\\x21-\\x5a\\x5e-\\x7e]"
25
+ dcontent = "(?:#{dtext}|#{quoted_pair})"
26
+ domain_literal = "\\[#{dcontent}+\\]"
27
+ obs_domain = "#{atom}([.]#{atom})*"
28
+ domain = "(?:#{dot_atom}|#{domain_literal}|#{obs_domain})"
29
+ addr_spec = "#{local_part}\@#{domain}"
30
+ pattern = /^#{addr_spec}$/
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ =begin
40
+ addresses = [
41
+ '-- dave --@example.com', # (spaces are invalid unless enclosed in quotation marks)
42
+ '[dave]@example.com', # (square brackets are invalid, unless contained within quotation marks)
43
+ '.dave@example.com', # (the local part of a domain name cannot start with a period)
44
+ 'Max@Job 3:14',
45
+ 'Job@Book of Job',
46
+ 'J. P. \'s-Gravezande, a.k.a. The Hacker!@example.com',
47
+ ]
48
+ addresses.each do |address|
49
+ if address =~ RFC2822::EmailAddress
50
+ puts "#{address} deveria ter sido rejeitado, ERRO"
51
+ else
52
+ puts "#{address} rejeitado, OK"
53
+ end
54
+ end
55
+
56
+
57
+ addresses = [
58
+ '+1~1+@example.com',
59
+ '{_dave_}@example.com',
60
+ '"[[ dave ]]"@example.com',
61
+ 'dave."dave"@example.com',
62
+ 'test@localhost',
63
+ 'test@example.com',
64
+ 'test@example.co.uk',
65
+ 'test@example.com.br',
66
+ '"J. P. \'s-Gravezande, a.k.a. The Hacker!"@example.com',
67
+ 'me@[187.223.45.119]',
68
+ 'someone@123.com',
69
+ 'simon&garfunkel@songs.com'
70
+ ]
71
+ addresses.each do |address|
72
+ if address =~ RFC2822::EmailAddress
73
+ puts "#{address} aceito, OK"
74
+ else
75
+ puts "#{address} deveria ser aceito, ERRO"
76
+ end
77
+ end
78
+ =end
@@ -0,0 +1,27 @@
1
+ module DataMapper
2
+ module Validations
3
+
4
+ # All Validators should inherit from the GenericValidator.
5
+ class GenericValidator
6
+
7
+ # Adds an error message to the target class.
8
+ def add_error(target, message, attribute = :base)
9
+ target.errors.add(attribute, message)
10
+ end
11
+
12
+ # Gets the proper error message
13
+ def validation_error_message(default, custom_message, validation_binding)
14
+ eval("\"#{(custom_message || default)}\"", validation_binding)
15
+ end
16
+
17
+ # Call the validator. We use "call" so the operation
18
+ # is BoundMethod and Block compatible.
19
+ # The result should always be TRUE or FALSE.
20
+ def call(target)
21
+ raise 'You must overwrite this method'
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,75 @@
1
+ module DataMapper
2
+ module Validations
3
+
4
+ class LengthValidator < GenericValidator
5
+
6
+ ERROR_MESSAGES = {
7
+ :range => '#{field} must be between #{min} and #{max} characters long',
8
+ :min => '#{field} must be more than #{min} characters long',
9
+ :max => '#{field} must be less than #{max} characters long',
10
+ :equals => '#{field} must be #{equal} characters long'
11
+ }
12
+
13
+ def initialize(field_name, options)
14
+ @field_name = field_name
15
+ @options = options
16
+
17
+ @min = options[:minimum] || options[:min]
18
+ @max = options[:maximum] || options[:max]
19
+ @equal = options[:is] || options[:equals]
20
+ @range = options[:within] || options[:in]
21
+
22
+ @validation_method ||= :range if @range
23
+ @validation_method ||= :min if @min && @max.nil?
24
+ @validation_method ||= :max if @max && @min.nil?
25
+ @validation_method ||= :equals unless @equal.nil?
26
+ end
27
+
28
+ def call(target)
29
+ field_value = target.instance_variable_get("@#{@field_name}").to_s
30
+ return true if @options[:allow_nil] && field_value.nil?
31
+
32
+ # HACK seems hacky to do this on every validation, probably should do this elsewhere?
33
+ field = Inflector.humanize(@field_name)
34
+ min = @range ? @range.min : @min
35
+ max = @range ? @range.max : @max
36
+ equal = @equal
37
+
38
+ error_message = validation_error_message(ERROR_MESSAGES[@validation_method], nil, binding)
39
+
40
+ valid = case @validation_method
41
+ when :range then
42
+ @range.include?(field_value.size)
43
+ when :min then
44
+ field_value.size >= min
45
+ when :max then
46
+ field_value.size <= max
47
+ when :equals then
48
+ field_value.size == equal
49
+ end
50
+
51
+ add_error(target, error_message, @field_name) unless valid
52
+
53
+ return valid
54
+ end
55
+
56
+ end
57
+
58
+ module ValidatesLengthOf
59
+ def self.included(base)
60
+ base.extend(ClassMethods)
61
+ end
62
+
63
+ module ClassMethods
64
+ DEFAULT_VALIDATES_LENGTH_OF_OPTIONS = { :on => :save }
65
+
66
+ def validates_length_of(field, options = {})
67
+ opts = retrieve_options_from_arguments_for_validators([options], DEFAULT_VALIDATES_LENGTH_OF_OPTIONS)
68
+ validations.context(opts[:context]) << Validations::LengthValidator.new(field, opts)
69
+ end
70
+
71
+ end
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,47 @@
1
+ module DataMapper
2
+ module Validations
3
+
4
+ class RequiredFieldValidator < GenericValidator
5
+
6
+ ERROR_MESSAGES = {
7
+ :required => '#{field} must not be blank'
8
+ }
9
+
10
+ def initialize(field_name)
11
+ @field_name = field_name
12
+ end
13
+
14
+ def call(target)
15
+ field_value = !target.instance_variable_get("@#{@field_name}").nil?
16
+ return true if field_value
17
+
18
+ field = Inflector.humanize(@field_name)
19
+
20
+ error_message = validation_error_message(ERROR_MESSAGES[:required], nil, binding)
21
+ add_error(target, error_message , @field_name)
22
+
23
+ return false
24
+ end
25
+
26
+ end
27
+
28
+ module ValidatesPresenceOf
29
+ def self.included(base)
30
+ base.extend(ClassMethods)
31
+ end
32
+
33
+ module ClassMethods
34
+
35
+ def validates_presence_of(*fields)
36
+ options = retrieve_options_from_arguments_for_validators(fields)
37
+
38
+ fields.each do |field|
39
+ validations.context(options[:context]) << Validations::RequiredFieldValidator.new(field)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,65 @@
1
+ module DataMapper
2
+ module Validations
3
+
4
+ class UniqueValidator < GenericValidator
5
+
6
+ ERROR_MESSAGES = {
7
+ :unique => '#{field} has already been taken'
8
+ }
9
+
10
+ def initialize(field_name, options = {})
11
+ @options = options
12
+ @field_name = field_name.to_sym
13
+ end
14
+
15
+ def call(target)
16
+ field = Inflector.humanize(@field_name)
17
+
18
+ unless valid?(target)
19
+ error_message = validation_error_message(ERROR_MESSAGES[:unique], nil, binding)
20
+ add_error(target, error_message , @field_name)
21
+ return false
22
+ end
23
+
24
+ return true
25
+ end
26
+
27
+ def valid?(target)
28
+ field_value = target.instance_variable_get("@#{@field_name}")
29
+ return true if @options[:allow_nil] && field_value.nil?
30
+
31
+ finder_options = { @field_name => field_value }
32
+
33
+ if @options[:scope]
34
+ scope_value = target.instance_variable_get("@#{@options[:scope]}")
35
+ finder_options.merge! @options[:scope] => scope_value
36
+ end
37
+
38
+ finder_options.merge!({ target.session.mappings[target.class].key.name.not => target.key }) unless target.new_record?
39
+
40
+ # HACK: gotta make sure we're using the same database that this instance was
41
+ # found with unless new_record?
42
+ target.session.find(target.class, :first, finder_options).nil?
43
+ end
44
+
45
+ end
46
+
47
+ module ValidatesUniquenessOf
48
+ def self.included(base)
49
+ base.extend(ClassMethods)
50
+ end
51
+
52
+ module ClassMethods
53
+ # No bueno?
54
+ DEFAULT_OPTIONS = { :on => :save }
55
+
56
+ def validates_uniqueness_of(field, options = {})
57
+ opts = retrieve_options_from_arguments_for_validators([options], DEFAULT_OPTIONS)
58
+ validations.context(opts[:context]) << Validations::UniqueValidator.new(field, opts)
59
+ end
60
+
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,34 @@
1
+ module DataMapper
2
+
3
+ class ValidationErrors
4
+
5
+ def initialize
6
+ @errors = Hash.new { |h,k| h[k.to_sym] = [] }
7
+ end
8
+
9
+ # Clear existing validation errors.
10
+ def clear!
11
+ @errors.clear
12
+ end
13
+
14
+ # Add a validation error. Use the attribute :general if
15
+ # the error doesn't apply to a specific attribute.
16
+ def add(attribute, message)
17
+ @errors[attribute] << message
18
+ end
19
+
20
+ # Collect all errors into a single list.
21
+ def full_messages
22
+ @errors.inject([]) do |list,pair|
23
+ list += pair.last
24
+ end
25
+ end
26
+
27
+ # Are any errors present?
28
+ def empty?
29
+ @errors.empty?
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,60 @@
1
+ dirname = File.dirname(__FILE__)
2
+
3
+ require dirname + '/validation_errors'
4
+ require dirname + '/contextual_validations'
5
+ require dirname + '/generic_validator'
6
+
7
+ Dir[dirname + '/*_validator.rb'].reject do |path|
8
+ path =~ /\/generic_validator/
9
+ end.each do |validator|
10
+ load validator
11
+ end
12
+
13
+ module DataMapper
14
+ module Extensions
15
+
16
+ module ValidationHelper
17
+
18
+ def self.included(base)
19
+ base.extend(ClassMethods)
20
+ base.class_eval do
21
+ include DataMapper::Validations::ValidatesPresenceOf
22
+ include DataMapper::Validations::ValidatesLengthOf
23
+ include DataMapper::Validations::ValidatesConfirmationOf
24
+ include DataMapper::Validations::ValidatesUniquenessOf
25
+ include DataMapper::Validations::ValidatesFormatOf
26
+ end
27
+ end
28
+
29
+ def errors
30
+ @errors ||= ValidationErrors.new
31
+ end
32
+
33
+ def valid?(context = :general)
34
+ self.class.validations.execute(context, self)
35
+ end
36
+
37
+ module ClassMethods
38
+
39
+ def validations
40
+ @validations ||= ContextualValidations.new
41
+ end
42
+
43
+ def retrieve_options_from_arguments_for_validators(args, defaults = nil)
44
+ options = args.last.kind_of?(Hash) ? args.pop : {}
45
+
46
+ context = :general
47
+ context = options[:context] if options.has_key?(:context)
48
+ context = options.delete(:on) if options.has_key?(:on)
49
+ options[:context] = context
50
+
51
+ options.merge!(defaults) unless defaults.nil?
52
+ return options
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,156 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Base.establish_connection :adapter => 'mysql',
4
+ :host => 'localhost',
5
+ :username => 'root',
6
+ :password => '',
7
+ :database => 'data_mapper_1'
8
+
9
+ class ARAnimal < ActiveRecord::Base
10
+ set_table_name 'animals'
11
+ end
12
+
13
+ class ARPerson < ActiveRecord::Base
14
+ set_table_name 'people'
15
+ end
16
+
17
+ $LOAD_PATH.unshift('lib')
18
+ require 'data_mapper'
19
+
20
+ log_path = File.dirname(__FILE__) + '/development.log'
21
+
22
+ require 'fileutils'
23
+ FileUtils::rm log_path if File.exists?(log_path)
24
+
25
+ DataMapper::Database.setup do
26
+ adapter 'mysql'
27
+ username 'root'
28
+ database 'data_mapper_1'
29
+ log_stream 'development.log'
30
+ log_level Logger::DEBUG
31
+ end
32
+
33
+ class DMAnimal < DataMapper::Base
34
+ set_table_name 'animals'
35
+ property :name, :string
36
+ property :notes, :string, :lazy => true
37
+ end
38
+
39
+ class DMPerson < DataMapper::Base
40
+ set_table_name 'people'
41
+ property :name, :string
42
+ property :age, :integer
43
+ property :occupation, :string
44
+ property :notes, :text, :lazy => true
45
+ end
46
+
47
+ class Exhibit < DataMapper::Base
48
+ property :name, :string
49
+ belongs_to :zoo
50
+ end
51
+
52
+ class Zoo < DataMapper::Base
53
+ property :name, :string
54
+ has_many :exhibits
55
+ end
56
+
57
+ class ARZoo < ActiveRecord::Base
58
+ set_table_name 'zoos'
59
+ has_many :exhibits, :class_name => 'ARExhibit', :foreign_key => 'zoo_id'
60
+ end
61
+
62
+ class ARExhibit < ActiveRecord::Base
63
+ set_table_name 'exhibits'
64
+ belongs_to :zoo, :class_name => 'ARZoo', :foreign_key => 'zoo_id'
65
+ end
66
+
67
+ N = (ENV['N'] || 1000).to_i
68
+
69
+ Benchmark::send(ENV['BM'] || :bmbm, 40) do |x|
70
+
71
+ x.report('ActiveRecord:id') do
72
+ N.times { ARAnimal.find(1) }
73
+ end
74
+
75
+ x.report('DataMapper:id') do
76
+ N.times { DMAnimal[1] }
77
+ end
78
+
79
+ x.report('ActiveRecord:conditions') do
80
+ N.times { ARZoo.find(:first, :conditions => ['name = ?', 'Galveston']) }
81
+ end
82
+
83
+ x.report('DataMapper:conditions:short') do
84
+ N.times { Zoo[:name => 'Galveston'] }
85
+ end
86
+
87
+ x.report('DataMapper:conditions:long') do
88
+ N.times { Zoo.find(:first, :conditions => ['name = ?', 'Galveston']) }
89
+ end
90
+
91
+ people = [
92
+ ['Sam', 29, 'Programmer'],
93
+ ['Amy', 28, 'Business Analyst Manager'],
94
+ ['Scott', 25, 'Programmer'],
95
+ ['Josh', 23, 'Supervisor'],
96
+ ['Bob', 40, 'Peon']
97
+ ]
98
+
99
+ DMPerson.truncate!
100
+
101
+ x.report('ActiveRecord:insert') do
102
+ N.times do
103
+ people.each do |a|
104
+ ARPerson::create(:name => a[0], :age => a[1], :occupation => a[2])
105
+ end
106
+ end
107
+ end
108
+
109
+ DMPerson.truncate!
110
+
111
+ x.report('DataMapper:insert') do
112
+ N.times do
113
+ people.each do |a|
114
+ DMPerson::create(:name => a[0], :age => a[1], :occupation => a[2])
115
+ end
116
+ end
117
+ end
118
+
119
+ x.report('ActiveRecord:update') do
120
+ N.times do
121
+ bob = ARAnimal.find(:first, :conditions => ["name = ?", 'elephant'])
122
+ bob.notes = 'Updated by ActiveRecord'
123
+ bob.save
124
+ end
125
+ end
126
+
127
+ x.report('DataMapper:update') do
128
+ N.times do
129
+ bob = DMAnimal.first(:name => 'elephant')
130
+ bob.notes = 'Updated by DataMapper'
131
+ bob.save
132
+ end
133
+ end
134
+
135
+ x.report('ActiveRecord:associations:lazy') do
136
+ N.times do
137
+ zoos = ARZoo.find(:all)
138
+ zoos.each { |zoo| zoo.exhibits.entries }
139
+ end
140
+ end
141
+
142
+ x.report('ActiveRecord:associations:included') do
143
+ N.times do
144
+ zoos = ARZoo.find(:all, :include => [:exhibits])
145
+ zoos.each { |zoo| zoo.exhibits.entries }
146
+ end
147
+ end
148
+
149
+ x.report('DataMapper:associations') do
150
+ N.times do
151
+ database do
152
+ Zoo.all.each { |zoo| zoo.exhibits.entries }
153
+ end
154
+ end
155
+ end
156
+ end