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,69 @@
1
+ require File.dirname(__FILE__) + '/blank_slate'
2
+
3
+ class Proc
4
+
5
+ # Yeah, all this is kinda the suck.
6
+ def to_hash
7
+ Conditions.new(&self).__to_hash__
8
+ end
9
+
10
+ class Conditions < BlankSlate
11
+
12
+ def initialize(&block)
13
+ @block = block
14
+ @conditions = []
15
+ end
16
+
17
+ def method_missing(sym, *args)
18
+ attribute = Attribute.new(sym)
19
+ @conditions << attribute
20
+ attribute
21
+ end
22
+
23
+ def __to_hash__
24
+ instance_eval(&@block)
25
+ @conditions.inject({}) do |h,attribute|
26
+ h[attribute.__operator__] = *attribute.__args__; h
27
+ end
28
+ end
29
+
30
+ class Attribute < BlankSlate
31
+
32
+ def initialize(message)
33
+ @message = message
34
+ @args = nil
35
+ @operator = nil
36
+ end
37
+
38
+ def method_missing(sym, *args)
39
+ op = case sym
40
+ when :==, :===, :in then :eql
41
+ when :=~ then :like
42
+ when :"<=>" then :not
43
+ when :< then :lt
44
+ when :<= then :lte
45
+ when :> then :gt
46
+ when :>= then :gte
47
+ else sym
48
+ end
49
+
50
+ @operator = Symbol::Operator.new(@message, op)
51
+ @args = args
52
+ end
53
+
54
+ def __operator__
55
+ @operator
56
+ end
57
+
58
+ def __args__
59
+ @args
60
+ end
61
+
62
+ def __message__
63
+ @message
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,23 @@
1
+ module DataMapper
2
+ module Support
3
+ module String
4
+
5
+ def ensure_starts_with(part)
6
+ [0,1] == part ? self : (part + self)
7
+ end
8
+
9
+ def ensure_ends_with(part)
10
+ [-1,1] == part ? self : (self + part)
11
+ end
12
+
13
+ def ensure_wrapped_with(a, b = nil)
14
+ ensure_starts_with(a).ensure_ends_with(b || a)
15
+ end
16
+
17
+ end # module String
18
+ end # module Support
19
+ end # module DataMapper
20
+
21
+ class String #:nodoc:
22
+ include DataMapper::Support::String
23
+ end
@@ -0,0 +1,91 @@
1
+ module DataMapper
2
+ module Support
3
+ module Symbol
4
+
5
+ class Operator
6
+
7
+ attr_reader :value, :type, :options
8
+
9
+ def initialize(value, type, options = nil)
10
+ @value, @type, @options = value, type, options
11
+ end
12
+
13
+ def to_sym
14
+ @value
15
+ end
16
+ end
17
+
18
+ def gt
19
+ Operator.new(self, :gt)
20
+ end
21
+
22
+ def gte
23
+ Operator.new(self, :gte)
24
+ end
25
+
26
+ def lt
27
+ Operator.new(self, :lt)
28
+ end
29
+
30
+ def lte
31
+ Operator.new(self, :lte)
32
+ end
33
+
34
+ def not
35
+ Operator.new(self, :not)
36
+ end
37
+
38
+ def eql
39
+ Operator.new(self, :eql)
40
+ end
41
+
42
+ def like
43
+ Operator.new(self, :like)
44
+ end
45
+
46
+ def in
47
+ Operator.new(self, :in)
48
+ end
49
+
50
+ def select(klass = nil)
51
+ Operator.new(self, :select, { :class => klass })
52
+ end
53
+
54
+ def to_s
55
+ @string_form || (@string_form = id2name.freeze)
56
+ end
57
+
58
+ def to_proc
59
+ lambda { |value| value.send(self) }
60
+ end
61
+
62
+ def as_instance_variable_name
63
+ @instance_variable_name_form || (@instance_variable_name_form = "@#{id2name}".freeze)
64
+ end
65
+
66
+ # Calculations:
67
+
68
+ def count
69
+ Operator.new(self, :count)
70
+ end
71
+
72
+ def max
73
+ Operator.new(self, :max)
74
+ end
75
+
76
+ def avg
77
+ Operator.new(self, :avg)
78
+ end
79
+ alias average avg
80
+
81
+ def min
82
+ Operator.new(self, :min)
83
+ end
84
+
85
+ end # module Symbol
86
+ end # module Support
87
+ end # module DataMapper
88
+
89
+ class Symbol #:nodoc:
90
+ include DataMapper::Support::Symbol
91
+ end
@@ -0,0 +1,46 @@
1
+ module DataMapper
2
+ module Support
3
+ class WeakHash
4
+ attr_reader :cache
5
+ def initialize( cache = Hash.new )
6
+ @cache = cache
7
+ @key_map = {}
8
+ @rev_cache = Hash.new{|h,k| h[k] = {}}
9
+ @reclaim_value = lambda do |value_id|
10
+ if @rev_cache.has_key? value_id
11
+ @rev_cache[value_id].each_key{|key| @cache.delete key}
12
+ @rev_cache.delete value_id
13
+ end
14
+ end
15
+ @reclaim_key = lambda do |key_id|
16
+ if @key_map.has_key? key_id
17
+ @cache.delete @key_map[key_id]
18
+ end
19
+ end
20
+ end
21
+
22
+ def []( key )
23
+ value_id = @cache[key]
24
+ return ObjectSpace._id2ref(value_id) unless value_id.nil?
25
+ nil
26
+ rescue RangeError
27
+ nil
28
+ end
29
+
30
+ def []=( key, value )
31
+ case key
32
+ when Fixnum, Symbol, true, false
33
+ key2 = key
34
+ else
35
+ key2 = key.dup
36
+ end
37
+ @rev_cache[value.object_id][key2] = true
38
+ @cache[key2] = value.object_id
39
+ @key_map[key.object_id] = key2
40
+
41
+ ObjectSpace.define_finalizer(value, @reclaim_value)
42
+ ObjectSpace.define_finalizer(key, @reclaim_key)
43
+ end
44
+ end # class WeakHash
45
+ end # module Support
46
+ end # module DataMapper
@@ -0,0 +1,38 @@
1
+ module DataMapper
2
+
3
+ # Should this track relations?
4
+ module UnitOfWork
5
+
6
+ def new_record?
7
+ @new_record.nil? || @new_record
8
+ end
9
+
10
+ def dirty?(name = nil)
11
+ if name.nil?
12
+ session.schema[self.class].columns.any? { |column| self.instance_variable_get(column.instance_variable_name).hash != original_hashes[column.name] }
13
+ else
14
+ key = name.kind_of?(Symbol) ? name : name.to_sym
15
+ self.instance_variable_get("@#{name}").hash != original_hashes[key]
16
+ end
17
+ end
18
+
19
+ def dirty_attributes
20
+ if new_record?
21
+ session.schema[self.class].columns.reject do |column|
22
+ instance_variable_get(column.instance_variable_name).nil?
23
+ end
24
+ else
25
+ session.schema[self.class].columns.select do |column|
26
+ column.name != :id && instance_variable_get(column.instance_variable_name).hash != original_hashes[column.name]
27
+ end
28
+ end.inject({}) do |fields, column|
29
+ fields[column.name] = instance_variable_get(column.instance_variable_name); fields
30
+ end
31
+ end
32
+
33
+ def original_hashes
34
+ @original_hashes || (@original_hashes = {})
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,55 @@
1
+ module DataMapper
2
+ module Validations
3
+
4
+ class ConfirmationValidator < GenericValidator
5
+
6
+ ERROR_MESSAGES = {
7
+ :confirmation => '#{field} does not match the confirmation'
8
+ }
9
+
10
+ def initialize(field_name, options = {})
11
+ @options = options
12
+ @field_name, @confirm_field_name = field_name, (options[:confirm] || "#{field_name}_confirmation").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[:confirmation], 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
+ confirm_value = target.instance_variable_get("@#{@confirm_field_name}")
32
+ field_value == confirm_value
33
+ end
34
+
35
+ end
36
+
37
+ module ValidatesConfirmationOf
38
+ def self.included(base)
39
+ base.extend(ClassMethods)
40
+ end
41
+
42
+ module ClassMethods
43
+ # No bueno?
44
+ DEFAULT_OPTIONS = { :on => :save }
45
+
46
+ def validates_confirmation_of(field, options = {})
47
+ opts = retrieve_options_from_arguments_for_validators([options], DEFAULT_OPTIONS)
48
+ validations.context(opts[:context]) << Validations::ConfirmationValidator.new(field, opts)
49
+ end
50
+
51
+ end
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ module DataMapper
2
+
3
+ class ContextualValidations
4
+
5
+ # This will be raised when you try to access
6
+ # a context that's not a member of the DEFAULT_CONTEXTS array.
7
+ class UnknownContextError < StandardError
8
+ end
9
+
10
+ # Add your custom contexts here.
11
+ DEFAULT_CONTEXTS = [
12
+ :general, :create, :save, :update
13
+ ]
14
+
15
+ def initialize
16
+ @contexts = Hash.new { |h,k| h[k.to_sym] = [] }
17
+ end
18
+
19
+ # Retrieves a context by symbol.
20
+ # Raises an exception if the symbol isn't a member of DEFAULT_CONTEXTS.
21
+ # This isn't to keep you from adding your own contexts, it's just to
22
+ # prevent errors due to typos. When adding your own contexts just
23
+ # remember to add it to DEFAULT_CONTEXTS first.
24
+ def context(name)
25
+ raise UnknownContextError.new(name) unless DEFAULT_CONTEXTS.include?(name)
26
+ @contexts[name]
27
+ end
28
+
29
+ # Clear out all the currently defined validators.
30
+ # This makes testing easier.
31
+ def clear!
32
+ @contexts.clear
33
+ end
34
+
35
+ # Execute all validations against an instance for a specified context,
36
+ # including the "always-on" :general context.
37
+ def execute(context_name, target)
38
+ target.errors.clear!
39
+
40
+ validations = context(context_name)
41
+ validations += context(:general) unless context_name == :general
42
+
43
+ validations.inject(true) do |result, validator|
44
+ result & validator.call(target)
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,85 @@
1
+ require File.dirname(__FILE__) + '/formats/email'
2
+
3
+ module DataMapper
4
+ module Validations
5
+
6
+ class FormatValidator < GenericValidator
7
+
8
+ # Seems to me that all this email garbage belongs somewhere else... Where's the best
9
+ # place to stick it?
10
+ include DataMapper::Validations::Helpers::Email
11
+
12
+ ERROR_MESSAGES = {
13
+ :invalid => '#{field} is invalid',
14
+ :invalid_email => '#{value} is not a valid email address'
15
+ }
16
+
17
+ FORMATS = {
18
+ :email_address => [lambda { |email_address| email_address =~ DataMapper::Validations::Helpers::Email::RFC2822::EmailAddress }, :invalid_email]
19
+ }
20
+
21
+ def initialize(field_name, options = {}, &b)
22
+ @field_name, @options = field_name, options
23
+ end
24
+
25
+ def call(target)
26
+ field_value = target.instance_variable_get("@#{@field_name}")
27
+ return true if @options[:allow_nil] && field_value.nil?
28
+
29
+ validation = (@options[:as] || @options[:with])
30
+ message_key = :invalid
31
+
32
+ # Figure out what to use as the actual validator. If a symbol is passed to :as, look up
33
+ # the canned validation in FORMATS.
34
+ validator = if validation.is_a? Symbol
35
+ if FORMATS[validation].is_a? Array
36
+ message_key = FORMATS[validation][1] || :invalid
37
+ FORMATS[validation][0]
38
+ else
39
+ FORMATS[validation] || validation
40
+ end
41
+ else
42
+ validation
43
+ end
44
+
45
+ valid = case validator
46
+ when Proc then validator.call(field_value)
47
+ when Regexp then validator =~ field_value
48
+ else raise UnknownValidationFormat, "Can't determine how to validate #{target.class}##{@field_name} with #{validator.inspect}"
49
+ end
50
+
51
+ unless valid
52
+ field = Inflector.humanize(@field_name)
53
+ value = target.instance_variable_get("@#{@field_name}")
54
+
55
+ error_message = validation_error_message(ERROR_MESSAGES[message_key], nil, binding)
56
+ add_error(target, error_message , @field_name)
57
+ end
58
+
59
+ return valid
60
+ end
61
+
62
+ class UnknownValidationFormat < StandardError
63
+ end
64
+
65
+ end
66
+
67
+ module ValidatesFormatOf
68
+ def self.included(base)
69
+ base.extend(ClassMethods)
70
+ end
71
+
72
+ module ClassMethods
73
+ # No bueno?
74
+ DEFAULT_OPTIONS = { :on => :save }
75
+
76
+ def validates_format_of(field, options = {})
77
+ opts = retrieve_options_from_arguments_for_validators([options], DEFAULT_OPTIONS)
78
+ validations.context(opts[:context]) << Validations::FormatValidator.new(field, opts)
79
+ end
80
+
81
+ end
82
+ end
83
+
84
+ end
85
+ end