datamapper 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/example.rb +5 -5
  2. data/lib/data_mapper/adapters/abstract_adapter.rb +2 -2
  3. data/lib/data_mapper/adapters/data_object_adapter.rb +141 -147
  4. data/lib/data_mapper/adapters/mysql_adapter.rb +14 -1
  5. data/lib/data_mapper/adapters/postgresql_adapter.rb +123 -18
  6. data/lib/data_mapper/adapters/sql/coersion.rb +21 -9
  7. data/lib/data_mapper/adapters/sql/commands/load_command.rb +36 -19
  8. data/lib/data_mapper/adapters/sql/mappings/column.rb +111 -17
  9. data/lib/data_mapper/adapters/sql/mappings/schema.rb +27 -0
  10. data/lib/data_mapper/adapters/sql/mappings/table.rb +256 -29
  11. data/lib/data_mapper/adapters/sqlite3_adapter.rb +93 -8
  12. data/lib/data_mapper/associations/belongs_to_association.rb +53 -54
  13. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +157 -25
  14. data/lib/data_mapper/associations/has_many_association.rb +45 -15
  15. data/lib/data_mapper/associations/has_n_association.rb +79 -20
  16. data/lib/data_mapper/associations/has_one_association.rb +2 -2
  17. data/lib/data_mapper/associations/reference.rb +1 -1
  18. data/lib/data_mapper/auto_migrations.rb +40 -0
  19. data/lib/data_mapper/base.rb +201 -98
  20. data/lib/data_mapper/context.rb +16 -10
  21. data/lib/data_mapper/database.rb +22 -11
  22. data/lib/data_mapper/dependency_queue.rb +28 -0
  23. data/lib/data_mapper/embedded_value.rb +61 -17
  24. data/lib/data_mapper/property.rb +4 -0
  25. data/lib/data_mapper/support/active_record_impersonation.rb +13 -5
  26. data/lib/data_mapper/support/errors.rb +5 -0
  27. data/lib/data_mapper/support/serialization.rb +8 -4
  28. data/lib/data_mapper/validatable_extensions/errors.rb +12 -0
  29. data/lib/data_mapper/validatable_extensions/macros.rb +7 -0
  30. data/lib/data_mapper/validatable_extensions/validatable_instance_methods.rb +62 -0
  31. data/lib/data_mapper/validatable_extensions/validation_base.rb +18 -0
  32. data/lib/data_mapper/validatable_extensions/validations/formats/email.rb +43 -0
  33. data/lib/data_mapper/validatable_extensions/validations/validates_acceptance_of.rb +7 -0
  34. data/lib/data_mapper/validatable_extensions/validations/validates_confirmation_of.rb +7 -0
  35. data/lib/data_mapper/validatable_extensions/validations/validates_each.rb +7 -0
  36. data/lib/data_mapper/validatable_extensions/validations/validates_format_of.rb +28 -0
  37. data/lib/data_mapper/validatable_extensions/validations/validates_length_of.rb +15 -0
  38. data/lib/data_mapper/validatable_extensions/validations/validates_numericality_of.rb +7 -0
  39. data/lib/data_mapper/validatable_extensions/validations/validates_presence_of.rb +7 -0
  40. data/lib/data_mapper/validatable_extensions/validations/validates_true_for.rb +7 -0
  41. data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +33 -0
  42. data/lib/data_mapper/validations.rb +20 -0
  43. data/lib/data_mapper.rb +39 -34
  44. data/performance.rb +24 -18
  45. data/plugins/dataobjects/do_rb +0 -0
  46. data/rakefile.rb +12 -2
  47. data/spec/active_record_impersonation_spec.rb +133 -0
  48. data/spec/acts_as_tree_spec.rb +25 -9
  49. data/spec/associations_spec.rb +124 -4
  50. data/spec/attributes_spec.rb +13 -0
  51. data/spec/auto_migrations_spec.rb +44 -0
  52. data/spec/base_spec.rb +189 -1
  53. data/spec/column_spec.rb +85 -7
  54. data/spec/conditions_spec.rb +2 -2
  55. data/spec/dependency_spec.rb +25 -0
  56. data/spec/embedded_value_spec.rb +123 -3
  57. data/spec/fixtures/animals.yaml +1 -0
  58. data/spec/fixtures/careers.yaml +5 -0
  59. data/spec/fixtures/comments.yaml +1 -0
  60. data/spec/fixtures/people.yaml +14 -9
  61. data/spec/fixtures/projects.yaml +4 -0
  62. data/spec/fixtures/sections.yaml +5 -0
  63. data/spec/fixtures/serializers.yaml +6 -0
  64. data/spec/fixtures/users.yaml +1 -0
  65. data/spec/load_command_spec.rb +5 -4
  66. data/spec/mock_adapter.rb +2 -2
  67. data/spec/models/animal.rb +2 -1
  68. data/spec/models/animals_exhibit.rb +2 -2
  69. data/spec/models/career.rb +6 -0
  70. data/spec/models/comment.rb +4 -0
  71. data/spec/models/exhibit.rb +4 -0
  72. data/spec/models/person.rb +3 -13
  73. data/spec/models/project.rb +1 -1
  74. data/spec/models/serializer.rb +3 -0
  75. data/spec/models/user.rb +4 -0
  76. data/spec/models/zoo.rb +8 -1
  77. data/spec/natural_key_spec.rb +36 -0
  78. data/spec/paranoia_spec.rb +36 -0
  79. data/spec/property_spec.rb +70 -0
  80. data/spec/schema_spec.rb +10 -2
  81. data/spec/serialization_spec.rb +6 -3
  82. data/spec/serialize_spec.rb +19 -0
  83. data/spec/single_table_inheritance_spec.rb +7 -1
  84. data/spec/spec_helper.rb +26 -8
  85. data/spec/table_spec.rb +33 -0
  86. data/spec/validates_confirmation_of_spec.rb +20 -4
  87. data/spec/validates_format_of_spec.rb +22 -8
  88. data/spec/validates_length_of_spec.rb +26 -13
  89. data/spec/validates_uniqueness_of_spec.rb +18 -5
  90. data/spec/validations_spec.rb +55 -10
  91. data/tasks/fixtures.rb +13 -7
  92. metadata +189 -153
  93. data/lib/data_mapper/validations/confirmation_validator.rb +0 -53
  94. data/lib/data_mapper/validations/contextual_validations.rb +0 -50
  95. data/lib/data_mapper/validations/format_validator.rb +0 -85
  96. data/lib/data_mapper/validations/formats/email.rb +0 -78
  97. data/lib/data_mapper/validations/generic_validator.rb +0 -22
  98. data/lib/data_mapper/validations/length_validator.rb +0 -76
  99. data/lib/data_mapper/validations/required_field_validator.rb +0 -41
  100. data/lib/data_mapper/validations/unique_validator.rb +0 -56
  101. data/lib/data_mapper/validations/validation_errors.rb +0 -37
  102. data/lib/data_mapper/validations/validation_helper.rb +0 -77
  103. data/plugins/dataobjects/REVISION +0 -1
  104. data/plugins/dataobjects/Rakefile +0 -9
  105. data/plugins/dataobjects/do.rb +0 -348
  106. data/plugins/dataobjects/do_mysql.rb +0 -212
  107. data/plugins/dataobjects/do_postgres.rb +0 -196
  108. data/plugins/dataobjects/do_sqlite3.rb +0 -157
  109. data/plugins/dataobjects/spec/do_spec.rb +0 -150
  110. data/plugins/dataobjects/spec/spec_helper.rb +0 -81
  111. data/plugins/dataobjects/swig_mysql/extconf.rb +0 -45
  112. data/plugins/dataobjects/swig_mysql/mysql_c.c +0 -16602
  113. data/plugins/dataobjects/swig_mysql/mysql_c.i +0 -67
  114. data/plugins/dataobjects/swig_mysql/mysql_supp.i +0 -46
  115. data/plugins/dataobjects/swig_postgres/extconf.rb +0 -29
  116. data/plugins/dataobjects/swig_postgres/postgres_c.c +0 -8185
  117. data/plugins/dataobjects/swig_postgres/postgres_c.i +0 -73
  118. data/plugins/dataobjects/swig_sqlite/extconf.rb +0 -9
  119. data/plugins/dataobjects/swig_sqlite/sqlite3_c.c +0 -4725
  120. data/plugins/dataobjects/swig_sqlite/sqlite_c.i +0 -168
  121. data/tasks/drivers.rb +0 -20
@@ -1,27 +1,67 @@
1
1
  module DataMapper
2
2
 
3
3
  class EmbeddedValue
4
+ EMBEDDED_PROPERTIES = []
4
5
 
5
6
  def initialize(instance)
6
7
  @instance = instance
8
+ @container_prefix = ''
9
+
10
+ # force lazy load on access to any lazy-loaded embed property
11
+ @instance.lazy_load!(*self.class::EMBEDDED_PROPERTIES) # if @container_lazy
12
+ end
13
+
14
+ def self.inherited(base)
15
+ base.const_set('EMBEDDED_PROPERTIES', [])
7
16
  end
8
17
 
18
+ # add an embedded property
9
19
  def self.property(name, type, options = {})
10
- mapping = database.schema[containing_class].add_column(name, type, options)
11
- define_property_getter(name, mapping)
12
- define_property_setter(name, mapping)
20
+ # set lazy option on the mapping if defined in the embed block
21
+ options[:lazy] ||= @container_lazy
22
+
23
+ visibility_options = [:public, :protected, :private]
24
+ reader_visibility = options[:reader] || options[:accessor] || @container_reader_visibility
25
+ writer_visibility = options[:writer] || options[:accessor] || @container_writer_visibility
26
+ writer_visibility = :protected if options[:protected]
27
+ writer_visibility = :private if options[:private]
28
+
29
+ raise(ArgumentError.new, "property visibility must be :public, :protected, or :private") unless visibility_options.include?(reader_visibility) && visibility_options.include?(writer_visibility)
30
+
31
+ mapping = database.schema[containing_class].add_column("#{@container_prefix}#{name}", type, options)
32
+
33
+ self::EMBEDDED_PROPERTIES << "#{@container_prefix}#{name}"
34
+ define_property_getter(name, mapping, reader_visibility)
35
+ define_property_setter(name, mapping, writer_visibility)
13
36
  end
14
37
 
15
- def self.define_property_getter(name, mapping)
38
+ # define embedded property getters
39
+ def self.define_property_getter(name, mapping, visibility = :public)
40
+ # add convenience method on non-embedded base for #update_attributes
41
+ self.containing_class.property_getter(mapping, visibility)
42
+
43
+ # add the method on the embedded class
16
44
  class_eval <<-EOS
45
+ #{visibility.to_s}
17
46
  def #{name}
18
47
  @instance.instance_variable_get(#{mapping.instance_variable_name.inspect})
19
48
  end
20
49
  EOS
50
+
51
+ # add a shortcut boolean? method if applicable (ex: activated?)
52
+ if mapping.type == :boolean
53
+ class_eval("alias #{name}? #{name}")
54
+ end
21
55
  end
22
56
 
23
- def self.define_property_setter(name, mapping)
57
+ # define embedded property setters
58
+ def self.define_property_setter(name, mapping, visibility = :public)
59
+ # add convenience method on non-embedded base for #update_attributes
60
+ self.containing_class.property_setter(mapping, visibility)
61
+
62
+ # add the method on the embedded class
24
63
  class_eval <<-EOS
64
+ #{visibility.to_s}
25
65
  def #{name.to_s.sub(/\?$/, '')}=(value)
26
66
  @instance.instance_variable_set(#{mapping.instance_variable_name.inspect}, value)
27
67
  end
@@ -36,21 +76,25 @@ module DataMapper
36
76
  end
37
77
  end
38
78
 
39
- def self.define(container, class_or_name, &block)
40
- embedded_class, embedded_class_name, accessor_name = nil
79
+ def self.define(container, name, options, &block)
80
+ embedded_class, embedded_class_name, accessor_name = nil
41
81
 
42
- if class_or_name.kind_of?(Class)
43
- embedded_class = class_or_name
44
- embedded_class_name = class_or_name.name.split('::').last
45
- accessor_name = Inflector.underscore(embedded_class_name)
46
- else
47
- accessor_name = class_or_name.to_s
48
- embedded_class_name = Inflector.camelize(accessor_name)
49
- embedded_class = Class.new(EmbeddedValue)
50
- container.const_set(embedded_class_name, embedded_class) unless container.const_defined?(embedded_class_name)
82
+ accessor_name = name.to_s
83
+ embedded_class_name = Inflector.camelize(accessor_name)
84
+ embedded_class = Class.new(EmbeddedValue)
85
+ container.const_set(embedded_class_name, embedded_class) unless container.const_defined?(embedded_class_name)
86
+
87
+ if options[:prefix]
88
+ container_prefix = options[:prefix].kind_of?(String) ? options[:prefix] : "#{accessor_name}_"
89
+ embedded_class.instance_variable_set('@container_prefix', container_prefix)
51
90
  end
52
91
 
53
92
  embedded_class.instance_variable_set('@containing_class', container)
93
+
94
+ embedded_class.instance_variable_set('@container_lazy', !!options[:lazy])
95
+ embedded_class.instance_variable_set('@container_reader_visibility', options[:reader] || options[:accessor] || :public)
96
+ embedded_class.instance_variable_set('@container_writer_visibility', options[:writer] || options[:accessor] || :public)
97
+
54
98
  embedded_class.class_eval(&block) if block_given?
55
99
 
56
100
  container.class_eval <<-EOS
@@ -62,4 +106,4 @@ module DataMapper
62
106
 
63
107
  end
64
108
 
65
- end # module DataMapper
109
+ end # module DataMapper
@@ -0,0 +1,4 @@
1
+ module DataMapper
2
+ class Property
3
+ end
4
+ end
@@ -8,11 +8,13 @@ module DataMapper
8
8
  end
9
9
 
10
10
  def save
11
- session.save(self)
11
+ database_context.save(self)
12
12
  end
13
13
 
14
14
  def reload!
15
- session.first(self.class, key, :select => original_values.keys, :reload => true)
15
+ database_context.first(self.class, key, :select => original_values.keys, :reload => true)
16
+ self.loaded_associations.each { |association| association.reload! }
17
+ self
16
18
  end
17
19
 
18
20
  def reload
@@ -20,12 +22,12 @@ module DataMapper
20
22
  end
21
23
 
22
24
  def destroy!
23
- session.destroy(self)
25
+ database_context.destroy(self)
24
26
  end
25
27
 
26
28
  module ClassMethods
27
29
 
28
- def find_or_create(search_attributes, create_attributes = nil)
30
+ def find_or_create(search_attributes, create_attributes = {})
29
31
  first(search_attributes) || create(search_attributes.merge(create_attributes))
30
32
  end
31
33
 
@@ -70,8 +72,14 @@ module DataMapper
70
72
  instance.save
71
73
  instance
72
74
  end
75
+
76
+ def create!(attributes)
77
+ instance = self.new(attributes)
78
+ raise InvalidRecord.new(instance) unless instance.save
79
+ instance
80
+ end
73
81
  end
74
82
  end
75
83
 
76
84
  end
77
- end
85
+ end
@@ -0,0 +1,5 @@
1
+ module DataMapper
2
+
3
+ class InvalidRecord < StandardError; end
4
+
5
+ end
@@ -14,7 +14,7 @@ module DataMapper
14
14
 
15
15
  YAML::quick_emit( object_id, opts ) do |out|
16
16
  out.map(nil, to_yaml_style ) do |map|
17
- session.table(self).columns.each do |column|
17
+ database_context.table(self).columns.each do |column|
18
18
  lazy_load!(column.name) if column.lazy?
19
19
  value = instance_variable_get(column.instance_variable_name)
20
20
  map.add(column.to_s, value.is_a?(Class) ? value.to_s : value)
@@ -28,9 +28,13 @@ module DataMapper
28
28
  end
29
29
 
30
30
  def to_xml
31
+ to_xml_document.to_s
32
+ end
33
+
34
+ def to_xml_document
31
35
  doc = REXML::Document.new
32
36
 
33
- table = session.table(self.class)
37
+ table = database_context.table(self.class)
34
38
  root = doc.add_element(Inflector.underscore(self.class.name))
35
39
 
36
40
  key_attribute = root.attributes << REXML::Attribute.new(table.key.to_s, key)
@@ -49,11 +53,11 @@ module DataMapper
49
53
  node << REXML::Text.new(copy_frozen_value(value).to_s) unless value.nil?
50
54
  end
51
55
 
52
- doc.to_s
56
+ doc
53
57
  end
54
58
 
55
59
  def to_json(*a)
56
- table = session.table(self.class)
60
+ table = database_context.table(self.class)
57
61
 
58
62
  result = '{ '
59
63
 
@@ -0,0 +1,12 @@
1
+ module Validatable
2
+ class Errors
3
+ def on(attribute)
4
+ raw(attribute)
5
+ end
6
+
7
+ def full_messages
8
+ errors.values.flatten.compact
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ module Validatable
2
+ module Macros
3
+ def validates_uniqueness_of(*args)
4
+ add_validations(args, ValidatesUniquenessOf)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,62 @@
1
+ # = Validations
2
+ # DataMapper uses the 'Validatable' gem to validate models.
3
+ #
4
+ # Example:
5
+ # class Person < DataMapper::Base
6
+ # property :name, :string
7
+ # property :email, :string
8
+ # property :password, :string
9
+ #
10
+ # validates_presence_of :name, :email
11
+ # validates_length_of :password, :minimum => 6, :on => :create
12
+ # validates_format_of :email, :with => :email_address, :message => 'Please provide a valid email address.'
13
+ # end
14
+ #
15
+ # p = Person.new
16
+ # p.valid? #=> false
17
+ # p.errors.full_messages #=> ["Email must not be blank", "Please provide a valid email address.", "Name must not be blank"]
18
+ #
19
+ # p.save #=> false
20
+ # p.errors.full_messages #=> ["Password must be more than 5 characters long", "Email must not be blank",
21
+ # "Please provide a valid email address.", "Name must not be blank"]
22
+ #
23
+ #
24
+
25
+ module Validatable
26
+ alias_method :valid_in_all_cases?, :valid? #:nodoc:
27
+
28
+ # Returns true if no errors were added otherwise false. Only executes validations that have no :groups option specified
29
+ def valid?(event = :validate)
30
+ validate_recursively(event, Set.new)
31
+ end
32
+
33
+ #TODO should callbacks really affect the flow? shouldn't validations themselves do that?
34
+ def validate_recursively(event, cleared) #:nodoc:
35
+ return true if cleared.include?(self)
36
+ cleared << self
37
+
38
+ self.class.callbacks.execute(:before_validation, self)
39
+
40
+ # Validatable clears the errors list when running valid_for_some_group?, so we save general errors
41
+ # and then merge them back in after we've validated for events
42
+ generally_valid = valid_for_group?(nil)
43
+ general_errors = self.errors.errors.dup
44
+
45
+ if respond_to?(:"valid_for_#{event}?")
46
+ valid_for_events = send(:"valid_for_#{event}?")
47
+ self.errors.errors.merge!(general_errors) {|key, val1, val2| val1+val2 }
48
+ return false unless generally_valid && valid_for_events
49
+ end
50
+
51
+ return false unless generally_valid
52
+
53
+ if self.respond_to?(:loaded_associations)
54
+ return false unless self.loaded_associations.all? do |association|
55
+ association.validate_recursively(event, cleared)
56
+ end
57
+ end
58
+
59
+ self.class.callbacks.execute(:after_validation, self)
60
+ return true
61
+ end
62
+ end
@@ -0,0 +1,18 @@
1
+ module Validatable
2
+ class ValidationBase
3
+ alias_method :old_init, :initialize
4
+
5
+ DEFAULT_EVENTS = [:validate, :create, :save, :update]
6
+
7
+ def initialize(klass, attribute, options={})
8
+ events = [options.delete(:on)].flatten.compact + [options.delete(:event)].flatten.compact
9
+ raise ArgumentError.new("Events must be one of #{DEFAULT_EVENTS.inspect}") unless (events & DEFAULT_EVENTS).size == events.size
10
+ options[:groups] ||= events unless events.empty? ### <- Danger will robinson
11
+ old_init(klass, attribute, options)
12
+ end
13
+
14
+ def humanized_attribute
15
+ @humanized_attribute ||= Inflector.humanize(self.attribute.to_s)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,43 @@
1
+ module Validatable
2
+ module Helpers #:nodoc:
3
+ module Formats #:nodoc:
4
+
5
+ # Allows you to validate fields as email addresses
6
+ # validates_format_of :email_field, :with => :email_address
7
+
8
+ module Email
9
+
10
+ def self.included(base)
11
+ base::FORMATS.merge!( :email_address => [ EmailAddress,
12
+ lambda { |attribute| lambda { "%s is not a valid email address".t(self.send(attribute)) } }] )
13
+ end
14
+
15
+ EmailAddress = begin
16
+ alpha = "a-zA-Z"
17
+ digit = "0-9"
18
+ atext = "[#{alpha}#{digit}\!\#\$\%\&\'\*+\/\=\?\^\_\`\{\|\}\~\-]"
19
+ dot_atom_text = "#{atext}+([.]#{atext}*)*"
20
+ dot_atom = "#{dot_atom_text}"
21
+ qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]'
22
+ text = "[\\x01-\\x09\\x11\\x12\\x14-\\x7f]"
23
+ quoted_pair = "(\\x5c#{text})"
24
+ qcontent = "(?:#{qtext}|#{quoted_pair})"
25
+ quoted_string = "[\"]#{qcontent}+[\"]"
26
+ atom = "#{atext}+"
27
+ word = "(?:#{atom}|#{quoted_string})"
28
+ obs_local_part = "#{word}([.]#{word})*"
29
+ local_part = "(?:#{dot_atom}|#{quoted_string}|#{obs_local_part})"
30
+ no_ws_ctl = "\\x01-\\x08\\x11\\x12\\x14-\\x1f\\x7f"
31
+ dtext = "[#{no_ws_ctl}\\x21-\\x5a\\x5e-\\x7e]"
32
+ dcontent = "(?:#{dtext}|#{quoted_pair})"
33
+ domain_literal = "\\[#{dcontent}+\\]"
34
+ obs_domain = "#{atom}([.]#{atom})*"
35
+ domain = "(?:#{dot_atom}|#{domain_literal}|#{obs_domain})"
36
+ addr_spec = "#{local_part}\@#{domain}"
37
+ pattern = /^#{addr_spec}$/
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ module Validatable
2
+ class ValidatesAcceptanceOf < ValidationBase
3
+ def message(instance)
4
+ super || '%s must be accepted'.t(humanized_attribute)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Validatable
2
+ class ValidatesConfirmationOf < ValidationBase
3
+ def message(instance)
4
+ super || "%s does not match the confirmation".t(humanized_attribute)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Validatable
2
+ class ValidatesEach < ValidationBase
3
+ def message(instance)
4
+ super || '%s is invalid'.t(humanized_attribute)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/formats/email.rb'
2
+
3
+ module Validatable
4
+
5
+ class ValidatesFormatOf < ValidationBase
6
+ FORMATS = {}
7
+
8
+ include Validatable::Helpers::Formats::Email
9
+
10
+ def initialize(klass, attribute, options={})
11
+ super
12
+ if with.is_a? Symbol
13
+ self.with = if FORMATS[with].is_a? Array
14
+ @message = (FORMATS[with][1].respond_to?(:call) ? FORMATS[with][1].call(attribute) : FORMATS[with][1]) unless @message
15
+ FORMATS[with][0]
16
+ else
17
+ FORMATS[with]
18
+ end
19
+ end
20
+ end
21
+
22
+ def message(instance)
23
+ super || '%s is invalid'.t(humanized_attribute)
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,15 @@
1
+ module Validatable
2
+ class ValidatesLengthOf < ValidationBase
3
+ def message(instance)
4
+ super || unless minimum.nil?
5
+ '%s must be more than %d characters long'.t(humanized_attribute, minimum-1)
6
+ else unless maximum.nil?
7
+ '%s must be less than %d characters long'.t(humanized_attribute, maximum+1)
8
+ else unless is.nil?
9
+ '%s must be %d characters long'.t(humanized_attribute, is)
10
+ else unless within.nil?
11
+ '%s must be between %d and %d characters long'.t(humanized_attribute, within.first, within.last)
12
+ end;end;end;end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ module Validatable
2
+ class ValidatesNumericalityOf < ValidationBase
3
+ def message(instance)
4
+ super || '%s must be a number'.t(humanized_attribute)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Validatable
2
+ class ValidatesPresenceOf < ValidationBase
3
+ def message(instance)
4
+ super || "%s must not be blank".t(humanized_attribute)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Validatable
2
+ class ValidatesTrueFor < ValidationBase
3
+ def message(instance)
4
+ super || '%s must be true'.t(humanized_attribute)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ module Validatable
2
+ class ValidatesUniquenessOf < ValidationBase
3
+ option :scope, :allow_nil, :case_sensitive
4
+
5
+ def message(instance)
6
+ super || '%s has already been taken'.t(humanized_attribute)
7
+ end
8
+
9
+ def case_sensitive?
10
+ case_sensitive
11
+ end
12
+
13
+ def valid?(instance)
14
+ value = instance.send(self.attribute)
15
+ return true if allow_nil && value.nil?
16
+
17
+ finder_options = if case_sensitive?
18
+ { self.attribute => value }
19
+ else
20
+ { self.attribute.like => value }
21
+ end
22
+
23
+ if scope
24
+ scope_value = instance.send(scope)
25
+ finder_options.merge! scope => scope_value
26
+ end
27
+
28
+ finder_options.merge!({ instance.database_context.table(instance.class).key.name.not => instance.key }) unless instance.new_record?
29
+ instance.database_context.first(instance.class, finder_options).nil?
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,20 @@
1
+ Dir[File.dirname(__FILE__) + "/validatable_extensions/**/*.rb"].each do |path|
2
+ require path
3
+ end
4
+
5
+ #--
6
+ # TODO:
7
+ # implement alias_option method for all validations (so we can set :with == :as)
8
+ # make validations orthogonal with allow_nil default to true
9
+ #
10
+ module DataMapper
11
+ module Validations
12
+
13
+ def self.included(base) #:nodoc:
14
+ base.class_eval do
15
+ include Validatable
16
+ end
17
+ end
18
+
19
+ end
20
+ end
data/lib/data_mapper.rb CHANGED
@@ -20,57 +20,62 @@ require 'rubygems'
20
20
  require 'yaml'
21
21
  require 'set'
22
22
  require 'fastthread'
23
+ require 'validatable'
23
24
  require 'data_mapper/support/blank'
24
25
  require 'data_mapper/support/enumerable'
25
26
  require 'data_mapper/support/symbol'
26
27
  require 'data_mapper/support/string'
27
28
  require 'data_mapper/support/silence'
28
29
  require 'data_mapper/support/inflector'
30
+ require 'data_mapper/support/errors'
29
31
  require 'data_mapper/database'
30
32
  require 'data_mapper/base'
31
33
 
32
- # This block of code is for compatibility with Ruby On Rails' or Merb's database.yml
33
- # file, allowing you to simply require the data_mapper.rb in your
34
- # Rails application's environment.rb to configure the DataMapper.
35
- unless defined?(DM_APP_ROOT)
36
- application_root, application_environment = *if defined?(MERB_ROOT)
37
- [MERB_ROOT, MERB_ENV]
38
- elsif defined?(RAILS_ROOT)
39
- [RAILS_ROOT, RAILS_ENV]
40
- end
34
+ begin
35
+ # This block of code is for compatibility with Ruby On Rails' or Merb's database.yml
36
+ # file, allowing you to simply require the data_mapper.rb in your
37
+ # Rails application's environment.rb to configure the DataMapper.
38
+ unless defined?(DM_APP_ROOT)
39
+ application_root, application_environment = *if defined?(RAILS_ROOT)
40
+ [RAILS_ROOT, RAILS_ENV]
41
+ end
41
42
 
42
- DM_APP_ROOT = application_root || Dir::pwd
43
+ DM_APP_ROOT = application_root || Dir::pwd
43
44
 
44
- if application_root && File.exists?(application_root + '/config/database.yml')
45
+ if application_root && File.exists?(application_root + '/config/database.yml')
45
46
 
46
- database_configurations = YAML::load_file(application_root + '/config/database.yml')
47
- current_database_config = database_configurations[application_environment] || database_configurations[application_environment.to_sym]
47
+ database_configurations = YAML::load_file(application_root + '/config/database.yml')
48
+ current_database_config = database_configurations[application_environment] || database_configurations[application_environment.to_sym]
48
49
 
49
- config = lambda { |key| current_database_config[key.to_s] || current_database_config[key] }
50
+ config = lambda { |key| current_database_config[key.to_s] || current_database_config[key] }
50
51
 
51
- default_database_config = {
52
- :adapter => config[:adapter],
53
- :host => config[:host],
54
- :database => config[:database],
55
- :username => config[:username],
56
- :password => config[:password]
57
- }
52
+ default_database_config = {
53
+ :adapter => config[:adapter],
54
+ :host => config[:host],
55
+ :database => config[:database],
56
+ :username => config[:username],
57
+ :password => config[:password],
58
+ :socket => config[:socket]
59
+ }
58
60
 
59
- DataMapper::Database.setup(default_database_config)
61
+ DataMapper::Database.setup(default_database_config)
60
62
 
61
- elsif application_root && FileTest.directory?(application_root + '/config')
63
+ elsif application_root && FileTest.directory?(application_root + '/config')
62
64
 
63
- %w(development testing production).map do |environment|
64
- <<-EOS.margin
65
- #{environment}:
66
- adapter: mysql
67
- username: root
68
- password:
69
- host: localhost
70
- database: #{File.dirname(DM_APP_ROOT).split('/').last}_#{environment}
71
- EOS
72
- end
65
+ %w(development testing production).map do |environment|
66
+ <<-EOS.margin
67
+ #{environment}:
68
+ adapter: mysql
69
+ username: root
70
+ password:
71
+ host: localhost
72
+ database: #{File.dirname(DM_APP_ROOT).split('/').last}_#{environment}
73
+ EOS
74
+ end
73
75
 
74
- #File::open(application_root + '/config/database.yml')
76
+ #File::open(application_root + '/config/database.yml')
77
+ end
75
78
  end
79
+ rescue Exception
80
+ warn "Could not connect to database specified by database.yml."
76
81
  end