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