datamapper 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|