datamapper 0.2.3 → 0.2.4

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