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.
- data/CHANGELOG +2 -0
- data/MIT-LICENSE +22 -0
- data/README +1 -0
- data/example.rb +25 -0
- data/lib/data_mapper.rb +30 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +229 -0
- data/lib/data_mapper/adapters/mysql_adapter.rb +171 -0
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +189 -0
- data/lib/data_mapper/associations.rb +19 -0
- data/lib/data_mapper/associations/belongs_to_association.rb +111 -0
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +100 -0
- data/lib/data_mapper/associations/has_many_association.rb +101 -0
- data/lib/data_mapper/associations/has_one_association.rb +107 -0
- data/lib/data_mapper/base.rb +160 -0
- data/lib/data_mapper/callbacks.rb +47 -0
- data/lib/data_mapper/database.rb +134 -0
- data/lib/data_mapper/extensions/active_record_impersonation.rb +69 -0
- data/lib/data_mapper/extensions/callback_helpers.rb +35 -0
- data/lib/data_mapper/identity_map.rb +21 -0
- data/lib/data_mapper/loaded_set.rb +45 -0
- data/lib/data_mapper/mappings/column.rb +78 -0
- data/lib/data_mapper/mappings/schema.rb +28 -0
- data/lib/data_mapper/mappings/table.rb +99 -0
- data/lib/data_mapper/queries/conditions.rb +141 -0
- data/lib/data_mapper/queries/connection.rb +34 -0
- data/lib/data_mapper/queries/create_table_statement.rb +38 -0
- data/lib/data_mapper/queries/delete_statement.rb +17 -0
- data/lib/data_mapper/queries/drop_table_statement.rb +17 -0
- data/lib/data_mapper/queries/insert_statement.rb +29 -0
- data/lib/data_mapper/queries/reader.rb +42 -0
- data/lib/data_mapper/queries/result.rb +19 -0
- data/lib/data_mapper/queries/select_statement.rb +103 -0
- data/lib/data_mapper/queries/table_exists_statement.rb +17 -0
- data/lib/data_mapper/queries/truncate_table_statement.rb +17 -0
- data/lib/data_mapper/queries/update_statement.rb +25 -0
- data/lib/data_mapper/session.rb +240 -0
- data/lib/data_mapper/support/blank_slate.rb +3 -0
- data/lib/data_mapper/support/connection_pool.rb +117 -0
- data/lib/data_mapper/support/enumerable.rb +27 -0
- data/lib/data_mapper/support/inflector.rb +329 -0
- data/lib/data_mapper/support/proc.rb +69 -0
- data/lib/data_mapper/support/string.rb +23 -0
- data/lib/data_mapper/support/symbol.rb +91 -0
- data/lib/data_mapper/support/weak_hash.rb +46 -0
- data/lib/data_mapper/unit_of_work.rb +38 -0
- data/lib/data_mapper/validations/confirmation_validator.rb +55 -0
- data/lib/data_mapper/validations/contextual_validations.rb +50 -0
- data/lib/data_mapper/validations/format_validator.rb +85 -0
- data/lib/data_mapper/validations/formats/email.rb +78 -0
- data/lib/data_mapper/validations/generic_validator.rb +27 -0
- data/lib/data_mapper/validations/length_validator.rb +75 -0
- data/lib/data_mapper/validations/required_field_validator.rb +47 -0
- data/lib/data_mapper/validations/unique_validator.rb +65 -0
- data/lib/data_mapper/validations/validation_errors.rb +34 -0
- data/lib/data_mapper/validations/validation_helper.rb +60 -0
- data/performance.rb +156 -0
- data/profile_data_mapper.rb +18 -0
- data/rakefile.rb +80 -0
- data/spec/basic_finder.rb +67 -0
- data/spec/belongs_to.rb +47 -0
- data/spec/fixtures/animals.yaml +32 -0
- data/spec/fixtures/exhibits.yaml +90 -0
- data/spec/fixtures/fruit.yaml +6 -0
- data/spec/fixtures/people.yaml +15 -0
- data/spec/fixtures/zoos.yaml +20 -0
- data/spec/has_and_belongs_to_many.rb +25 -0
- data/spec/has_many.rb +34 -0
- data/spec/legacy.rb +14 -0
- data/spec/models/animal.rb +7 -0
- data/spec/models/exhibit.rb +6 -0
- data/spec/models/fruit.rb +6 -0
- data/spec/models/person.rb +7 -0
- data/spec/models/post.rb +4 -0
- data/spec/models/sales_person.rb +4 -0
- data/spec/models/zoo.rb +5 -0
- data/spec/new_record.rb +24 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/sub_select.rb +16 -0
- data/spec/symbolic_operators.rb +21 -0
- data/spec/validates_confirmation_of.rb +36 -0
- data/spec/validates_format_of.rb +61 -0
- data/spec/validates_length_of.rb +101 -0
- data/spec/validates_uniqueness_of.rb +45 -0
- data/spec/validations.rb +63 -0
- 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
|