datamapper 0.2.5 → 0.3.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 +5 -1
- data/FAQ +96 -0
- data/QUICKLINKS +12 -0
- data/README +57 -155
- data/environment.rb +61 -43
- data/example.rb +30 -12
- data/lib/data_mapper.rb +6 -1
- data/lib/data_mapper/adapters/abstract_adapter.rb +0 -57
- data/lib/data_mapper/adapters/data_object_adapter.rb +203 -97
- data/lib/data_mapper/adapters/mysql_adapter.rb +4 -0
- data/lib/data_mapper/adapters/postgresql_adapter.rb +7 -1
- data/lib/data_mapper/adapters/sql/coersion.rb +3 -2
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +29 -10
- data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +4 -0
- data/lib/data_mapper/adapters/sql/mappings/column.rb +13 -9
- data/lib/data_mapper/adapters/sql/mappings/conditions.rb +172 -0
- data/lib/data_mapper/adapters/sql/mappings/table.rb +43 -17
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +9 -2
- data/lib/data_mapper/associations.rb +75 -3
- data/lib/data_mapper/associations/belongs_to_association.rb +70 -36
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +195 -86
- data/lib/data_mapper/associations/has_many_association.rb +168 -61
- data/lib/data_mapper/associations/has_n_association.rb +23 -3
- data/lib/data_mapper/attributes.rb +73 -0
- data/lib/data_mapper/auto_migrations.rb +2 -6
- data/lib/data_mapper/base.rb +5 -9
- data/lib/data_mapper/database.rb +4 -3
- data/lib/data_mapper/embedded_value.rb +66 -30
- data/lib/data_mapper/identity_map.rb +1 -3
- data/lib/data_mapper/is/tree.rb +121 -0
- data/lib/data_mapper/migration.rb +155 -0
- data/lib/data_mapper/persistence.rb +532 -218
- data/lib/data_mapper/property.rb +306 -0
- data/lib/data_mapper/query.rb +164 -0
- data/lib/data_mapper/support/blank.rb +2 -2
- data/lib/data_mapper/support/connection_pool.rb +5 -6
- data/lib/data_mapper/support/enumerable.rb +3 -3
- data/lib/data_mapper/support/errors.rb +10 -1
- data/lib/data_mapper/support/inflector.rb +174 -238
- data/lib/data_mapper/support/object.rb +54 -0
- data/lib/data_mapper/support/serialization.rb +19 -1
- data/lib/data_mapper/support/string.rb +7 -16
- data/lib/data_mapper/support/symbol.rb +3 -15
- data/lib/data_mapper/support/typed_set.rb +68 -0
- data/lib/data_mapper/types/base.rb +44 -0
- data/lib/data_mapper/types/string.rb +34 -0
- data/lib/data_mapper/validations/number_validator.rb +40 -0
- data/lib/data_mapper/validations/string_validator.rb +20 -0
- data/lib/data_mapper/validations/validator.rb +13 -0
- data/performance.rb +26 -1
- data/profile_data_mapper.rb +1 -1
- data/rakefile.rb +42 -2
- data/spec/acts_as_tree_spec.rb +11 -3
- data/spec/adapters/data_object_adapter_spec.rb +31 -0
- data/spec/associations/belongs_to_association_spec.rb +98 -0
- data/spec/associations/has_and_belongs_to_many_association_spec.rb +377 -0
- data/spec/associations/has_many_association_spec.rb +337 -0
- data/spec/attributes_spec.rb +23 -1
- data/spec/auto_migrations_spec.rb +86 -29
- data/spec/callbacks_spec.rb +107 -0
- data/spec/column_spec.rb +5 -2
- data/spec/count_command_spec.rb +33 -1
- data/spec/database_spec.rb +18 -0
- data/spec/dependency_spec.rb +4 -2
- data/spec/embedded_value_spec.rb +8 -8
- data/spec/fixtures/people.yaml +1 -1
- data/spec/fixtures/projects.yaml +10 -1
- data/spec/fixtures/tasks.yaml +6 -0
- data/spec/fixtures/tasks_tasks.yaml +2 -0
- data/spec/fixtures/tomatoes.yaml +1 -0
- data/spec/is_a_tree_spec.rb +149 -0
- data/spec/load_command_spec.rb +71 -9
- data/spec/magic_columns_spec.rb +17 -2
- data/spec/migration_spec.rb +267 -0
- data/spec/models/animal.rb +1 -1
- data/spec/models/candidate.rb +8 -0
- data/spec/models/career.rb +1 -1
- data/spec/models/chain.rb +8 -0
- data/spec/models/comment.rb +1 -1
- data/spec/models/exhibit.rb +1 -1
- data/spec/models/fence.rb +7 -0
- data/spec/models/fruit.rb +2 -2
- data/spec/models/job.rb +8 -0
- data/spec/models/person.rb +2 -3
- data/spec/models/post.rb +1 -1
- data/spec/models/project.rb +21 -1
- data/spec/models/section.rb +1 -1
- data/spec/models/serializer.rb +1 -1
- data/spec/models/task.rb +9 -0
- data/spec/models/tomato.rb +27 -0
- data/spec/models/user.rb +8 -2
- data/spec/models/zoo.rb +2 -7
- data/spec/paranoia_spec.rb +1 -1
- data/spec/{base_spec.rb → persistence_spec.rb} +207 -18
- data/spec/postgres_spec.rb +48 -6
- data/spec/property_spec.rb +90 -9
- data/spec/query_spec.rb +71 -5
- data/spec/save_command_spec.rb +11 -0
- data/spec/spec_helper.rb +14 -11
- data/spec/support/blank_spec.rb +8 -0
- data/spec/support/inflector_spec.rb +41 -0
- data/spec/support/object_spec.rb +9 -0
- data/spec/{serialization_spec.rb → support/serialization_spec.rb} +1 -1
- data/spec/support/silence_spec.rb +15 -0
- data/spec/{support_spec.rb → support/string_spec.rb} +3 -3
- data/spec/support/struct_spec.rb +12 -0
- data/spec/support/typed_set_spec.rb +66 -0
- data/spec/table_spec.rb +3 -3
- data/spec/types/string.rb +81 -0
- data/spec/validates_uniqueness_of_spec.rb +17 -0
- data/spec/validations/number_validator.rb +59 -0
- data/spec/validations/string_validator.rb +14 -0
- metadata +59 -17
- data/do_performance.rb +0 -153
- data/lib/data_mapper/support/active_record_impersonation.rb +0 -103
- data/lib/data_mapper/support/weak_hash.rb +0 -46
- data/spec/active_record_impersonation_spec.rb +0 -129
- data/spec/associations_spec.rb +0 -232
- data/spec/conditions_spec.rb +0 -49
- data/spec/has_many_association_spec.rb +0 -173
- data/spec/models/animals_exhibit.rb +0 -8
@@ -0,0 +1,54 @@
|
|
1
|
+
module DataMapper
|
2
|
+
module Support
|
3
|
+
module Object
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
|
7
|
+
nested_constants = Hash.new do |h,k|
|
8
|
+
klass = Object
|
9
|
+
k.split('::').each do |c|
|
10
|
+
klass = klass.const_get(c)
|
11
|
+
end
|
12
|
+
h[k] = klass
|
13
|
+
end
|
14
|
+
|
15
|
+
base.instance_variable_set("@nested_constants", nested_constants)
|
16
|
+
base.send(:include, ClassMethods)
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def recursive_const_get(nested_name)
|
21
|
+
@nested_constants[nested_name]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Object #:nodoc:
|
29
|
+
include DataMapper::Support::Object
|
30
|
+
end
|
31
|
+
|
32
|
+
# require 'benchmark'
|
33
|
+
#
|
34
|
+
# N = 1_000_000
|
35
|
+
#
|
36
|
+
# puts Benchmark.measure {
|
37
|
+
# N.times { Object.recursive_const_get('DataMapper::Support::Object') }
|
38
|
+
# }
|
39
|
+
#
|
40
|
+
# puts Benchmark.measure {
|
41
|
+
# N.times {
|
42
|
+
# klass = Object
|
43
|
+
# 'DataMapper::Support::Object'.split('::').each do |c|
|
44
|
+
# klass = klass.const_get(c)
|
45
|
+
# end
|
46
|
+
# klass
|
47
|
+
# }
|
48
|
+
# }
|
49
|
+
#
|
50
|
+
# __END__
|
51
|
+
# >>> object.rb
|
52
|
+
#
|
53
|
+
# 0.910000 0.000000 0.910000 ( 0.916914)
|
54
|
+
# 6.140000 0.010000 6.150000 ( 6.151984)
|
@@ -1,5 +1,11 @@
|
|
1
1
|
require 'rexml/document'
|
2
2
|
|
3
|
+
begin
|
4
|
+
require 'faster_csv'
|
5
|
+
rescue LoadError
|
6
|
+
nil
|
7
|
+
end
|
8
|
+
|
3
9
|
begin
|
4
10
|
require 'json/ext'
|
5
11
|
rescue LoadError
|
@@ -31,11 +37,16 @@ module DataMapper
|
|
31
37
|
to_xml_document.to_s
|
32
38
|
end
|
33
39
|
|
40
|
+
def xml_element_name ## overloadable
|
41
|
+
Inflector.underscore(self.class.name)
|
42
|
+
end
|
43
|
+
|
34
44
|
def to_xml_document
|
35
45
|
doc = REXML::Document.new
|
36
46
|
|
37
47
|
table = database_context.table(self.class)
|
38
|
-
root = doc.add_element(Inflector.underscore(self.class.name))
|
48
|
+
# root = doc.add_element(Inflector.underscore(self.class.name))
|
49
|
+
root = doc.add_element(xml_element_name)
|
39
50
|
|
40
51
|
key_attribute = root.attributes << REXML::Attribute.new(table.key.to_s, key)
|
41
52
|
|
@@ -69,6 +80,13 @@ module DataMapper
|
|
69
80
|
result
|
70
81
|
end
|
71
82
|
|
83
|
+
def to_csv(writer = "")
|
84
|
+
FasterCSV.generate(writer) do |csv|
|
85
|
+
csv << database_context.table(self.class).columns.map { |column| get_value_for_column(column) }
|
86
|
+
end
|
87
|
+
return writer
|
88
|
+
end
|
89
|
+
|
72
90
|
def get_value_for_column(column)
|
73
91
|
send(column.type == :boolean ? column.name.to_s.ensure_ends_with('?') : column.name)
|
74
92
|
end
|
@@ -41,26 +41,17 @@ module DataMapper
|
|
41
41
|
split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
|
42
42
|
end
|
43
43
|
|
44
|
+
# Useful for heredocs - removes whitespace margin.
|
44
45
|
def margin(indicator = nil)
|
45
|
-
|
46
|
-
lines = target.split($/)
|
46
|
+
lines = self.dup.split($/)
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
min_margin = $1.size
|
53
|
-
end
|
48
|
+
min_margin = 0
|
49
|
+
lines.each do |line|
|
50
|
+
if line =~ /^(\s+)/ && (min_margin == 0 || $1.size < min_margin)
|
51
|
+
min_margin = $1.size
|
54
52
|
end
|
55
|
-
|
56
|
-
lines.map do |line|
|
57
|
-
line.sub(/^\s{#{min_margin}}/, '')
|
58
|
-
end.join($/)
|
59
|
-
else
|
60
|
-
lines.map do |line|
|
61
|
-
line.sub(/^.*?#{"\\" + indicator}/, '')
|
62
|
-
end.join($/)
|
63
53
|
end
|
54
|
+
lines.map { |line| line.sub(/^\s{#{min_margin}}/, '') }.join($/)
|
64
55
|
end
|
65
56
|
|
66
57
|
# Formats String for easy translation. Replaces an arbitrary number of
|
@@ -48,23 +48,11 @@ module DataMapper
|
|
48
48
|
def in
|
49
49
|
Operator.new(self, :in)
|
50
50
|
end
|
51
|
-
|
52
|
-
def select(klass = nil)
|
53
|
-
Operator.new(self, :select, { :class => klass })
|
54
|
-
end
|
55
|
-
|
56
|
-
def to_s
|
57
|
-
@string_form || (@string_form = id2name.freeze)
|
58
|
-
end
|
59
|
-
|
51
|
+
|
60
52
|
def to_proc
|
61
|
-
|
53
|
+
lambda { |value| value.send(self) }
|
62
54
|
end
|
63
55
|
|
64
|
-
def as_instance_variable_name
|
65
|
-
@instance_variable_name_form || (@instance_variable_name_form = "@#{id2name}".freeze)
|
66
|
-
end
|
67
|
-
|
68
56
|
# Calculations:
|
69
57
|
|
70
58
|
def count
|
@@ -91,4 +79,4 @@ end # module DataMapper
|
|
91
79
|
|
92
80
|
class Symbol #:nodoc:
|
93
81
|
include DataMapper::Support::Symbol
|
94
|
-
end
|
82
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
module Support
|
5
|
+
class TypedSet
|
6
|
+
|
7
|
+
include ::Enumerable
|
8
|
+
|
9
|
+
def initialize(*types)
|
10
|
+
@types = types
|
11
|
+
@set = SortedSet.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(item)
|
15
|
+
raise ArgumentError.new("#{item.inspect} must be a kind of: #{@types.inspect}") unless @types.any? { |type| type === item }
|
16
|
+
@set << item
|
17
|
+
return self
|
18
|
+
end
|
19
|
+
|
20
|
+
def concat(values)
|
21
|
+
[*values].each { |item| self << item }
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"#<DataMapper::Support::TypedSet#{@types.inspect}: {#{entries.inspect[1...-1]}}>"
|
27
|
+
end
|
28
|
+
|
29
|
+
def each
|
30
|
+
@set.each { |item| yield(item) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete?(item)
|
34
|
+
@set.delete?(item)
|
35
|
+
end
|
36
|
+
|
37
|
+
def size
|
38
|
+
@set.size
|
39
|
+
end
|
40
|
+
alias length size
|
41
|
+
|
42
|
+
def empty?
|
43
|
+
@set.empty?
|
44
|
+
end
|
45
|
+
alias blank? empty?
|
46
|
+
|
47
|
+
def clear
|
48
|
+
@set.clear
|
49
|
+
end
|
50
|
+
|
51
|
+
def +(other)
|
52
|
+
x = self.class.new(*@types)
|
53
|
+
each { |entry| x << entry }
|
54
|
+
other.each { |entry| x << entry } unless other.blank?
|
55
|
+
return x
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Class
|
62
|
+
|
63
|
+
include Comparable
|
64
|
+
|
65
|
+
def <=>(other)
|
66
|
+
name <=> other.name
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'data_mapper/validations/number_validator'
|
2
|
+
require 'data_mapper/validations/string_validator'
|
3
|
+
|
4
|
+
module DataMapper
|
5
|
+
module Types
|
6
|
+
TYPE_MAP = {}
|
7
|
+
|
8
|
+
module Base
|
9
|
+
module ClassMethods
|
10
|
+
def context(ctx)
|
11
|
+
contexts(ctx)
|
12
|
+
end
|
13
|
+
|
14
|
+
def contexts(*ctxs)
|
15
|
+
@contexts || @contexts = %w{__all__} << ctxs.map { |c| c.to_s }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.included(klass)
|
20
|
+
klass.extend(ClassMethods)
|
21
|
+
end
|
22
|
+
|
23
|
+
def do_validations
|
24
|
+
raise NotImplementedError.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid?(context = "__all__")
|
28
|
+
@errors = []
|
29
|
+
|
30
|
+
if self.class.contexts.include?(context.to_s)
|
31
|
+
do_validations
|
32
|
+
|
33
|
+
@errors.empty?
|
34
|
+
else
|
35
|
+
true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def errors
|
40
|
+
@errors || @errors = []
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'data_mapper/types/base'
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
module Types
|
5
|
+
class String < ::String
|
6
|
+
include Types::Base
|
7
|
+
|
8
|
+
TYPE_MAP[:string] = self
|
9
|
+
TYPE_MAP[::String] = self
|
10
|
+
|
11
|
+
def self.length
|
12
|
+
length_validator
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.matches(regexp)
|
16
|
+
match_validator.matches(regexp)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.length_validator
|
20
|
+
@length_validator ||
|
21
|
+
@length_validator = Validations::NumberValidator.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.match_validator
|
25
|
+
@match_validator || @match_validator = Validations::StringValidator.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def do_validations
|
29
|
+
errors.concat(self.class.length_validator.errors_for(length))
|
30
|
+
errors.concat(self.class.match_validator.errors_for(self))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'data_mapper/validations/validator'
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
module Validations
|
5
|
+
class NumberValidator < Validator
|
6
|
+
def <(max)
|
7
|
+
@max_excl = max
|
8
|
+
end
|
9
|
+
|
10
|
+
def <=(max)
|
11
|
+
@max_incl = max
|
12
|
+
end
|
13
|
+
|
14
|
+
def >(min)
|
15
|
+
@min_excl = min
|
16
|
+
end
|
17
|
+
|
18
|
+
def >=(min)
|
19
|
+
@min_incl = min
|
20
|
+
end
|
21
|
+
|
22
|
+
def between(range)
|
23
|
+
@range = range
|
24
|
+
end
|
25
|
+
|
26
|
+
def errors_for(target)
|
27
|
+
errors = []
|
28
|
+
error = nil
|
29
|
+
|
30
|
+
errors << Validator::Error.new(@max_excl, target) if @max_excl && target >= @max_excl
|
31
|
+
errors << Validator::Error.new(@max_incl, target) if @max_incl && target > @max_incl
|
32
|
+
errors << Validator::Error.new(@min_excl, target) if @min_excl && target <= @min_excl
|
33
|
+
errors << Validator::Error.new(@min_incl, target) if @min_incl && target < @min_incl
|
34
|
+
errors << Validator::Error.new(@range, target) if @range && ! @range.include?(target)
|
35
|
+
|
36
|
+
errors
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'data_mapper/validations/validator'
|
2
|
+
|
3
|
+
module DataMapper
|
4
|
+
module Validations
|
5
|
+
class StringValidator < Validator
|
6
|
+
def matches(regexp)
|
7
|
+
@regexp = regexp
|
8
|
+
end
|
9
|
+
|
10
|
+
def errors_for(target)
|
11
|
+
errors = []
|
12
|
+
|
13
|
+
errors << Validator::Error.new(@regexp, target) if @regexp && @regexp.match(target)
|
14
|
+
|
15
|
+
errors
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
data/performance.rb
CHANGED
@@ -35,7 +35,7 @@ DataMapper::Database.setup(configuration_options)
|
|
35
35
|
class DMAnimal < DataMapper::Base #:nodoc:
|
36
36
|
set_table_name 'animals'
|
37
37
|
property :name, :string
|
38
|
-
property :notes, :
|
38
|
+
property :notes, :text
|
39
39
|
end
|
40
40
|
|
41
41
|
class DMPerson < DataMapper::Base #:nodoc:
|
@@ -71,6 +71,7 @@ end
|
|
71
71
|
|
72
72
|
class Zoo < DataMapper::Base #:nodoc:
|
73
73
|
property :name, :string
|
74
|
+
property :notes, :text
|
74
75
|
has_many :exhibits
|
75
76
|
end
|
76
77
|
|
@@ -100,6 +101,18 @@ Benchmark::send(ENV['BM'] || :bmbm, 40) do |x|
|
|
100
101
|
N.times { ARAnimal.find(1).name }
|
101
102
|
end
|
102
103
|
|
104
|
+
x.report('ActiveRecord:id:raw_result-set') do
|
105
|
+
connection = ARAnimal.connection
|
106
|
+
|
107
|
+
N.times do
|
108
|
+
result = connection.execute("SELECT name FROM animals WHERE id = 1")
|
109
|
+
result.each do |row|
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
result.free if result.respond_to?(:free)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
103
116
|
x.report('DataMapper:id') do
|
104
117
|
N.times { DMAnimal[1].name }
|
105
118
|
end
|
@@ -109,6 +122,18 @@ Benchmark::send(ENV['BM'] || :bmbm, 40) do |x|
|
|
109
122
|
N.times { DMAnimal[1].name }
|
110
123
|
end
|
111
124
|
end
|
125
|
+
|
126
|
+
x.report('DataMapper:id:raw-result-set') do
|
127
|
+
database.adapter.connection do |db|
|
128
|
+
N.times do
|
129
|
+
command = db.create_command("SELECT name FROM animals WHERE id = 1")
|
130
|
+
|
131
|
+
command.execute_reader do |reader|
|
132
|
+
reader.each { reader.current_row }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
112
137
|
|
113
138
|
x.report('ActiveRecord:all') do
|
114
139
|
N.times { ARZoo.find(:all).each { |a| a.name } }
|