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