datamapper 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/CHANGELOG +5 -1
  2. data/FAQ +96 -0
  3. data/QUICKLINKS +12 -0
  4. data/README +57 -155
  5. data/environment.rb +61 -43
  6. data/example.rb +30 -12
  7. data/lib/data_mapper.rb +6 -1
  8. data/lib/data_mapper/adapters/abstract_adapter.rb +0 -57
  9. data/lib/data_mapper/adapters/data_object_adapter.rb +203 -97
  10. data/lib/data_mapper/adapters/mysql_adapter.rb +4 -0
  11. data/lib/data_mapper/adapters/postgresql_adapter.rb +7 -1
  12. data/lib/data_mapper/adapters/sql/coersion.rb +3 -2
  13. data/lib/data_mapper/adapters/sql/commands/load_command.rb +29 -10
  14. data/lib/data_mapper/adapters/sql/mappings/associations_set.rb +4 -0
  15. data/lib/data_mapper/adapters/sql/mappings/column.rb +13 -9
  16. data/lib/data_mapper/adapters/sql/mappings/conditions.rb +172 -0
  17. data/lib/data_mapper/adapters/sql/mappings/table.rb +43 -17
  18. data/lib/data_mapper/adapters/sqlite3_adapter.rb +9 -2
  19. data/lib/data_mapper/associations.rb +75 -3
  20. data/lib/data_mapper/associations/belongs_to_association.rb +70 -36
  21. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +195 -86
  22. data/lib/data_mapper/associations/has_many_association.rb +168 -61
  23. data/lib/data_mapper/associations/has_n_association.rb +23 -3
  24. data/lib/data_mapper/attributes.rb +73 -0
  25. data/lib/data_mapper/auto_migrations.rb +2 -6
  26. data/lib/data_mapper/base.rb +5 -9
  27. data/lib/data_mapper/database.rb +4 -3
  28. data/lib/data_mapper/embedded_value.rb +66 -30
  29. data/lib/data_mapper/identity_map.rb +1 -3
  30. data/lib/data_mapper/is/tree.rb +121 -0
  31. data/lib/data_mapper/migration.rb +155 -0
  32. data/lib/data_mapper/persistence.rb +532 -218
  33. data/lib/data_mapper/property.rb +306 -0
  34. data/lib/data_mapper/query.rb +164 -0
  35. data/lib/data_mapper/support/blank.rb +2 -2
  36. data/lib/data_mapper/support/connection_pool.rb +5 -6
  37. data/lib/data_mapper/support/enumerable.rb +3 -3
  38. data/lib/data_mapper/support/errors.rb +10 -1
  39. data/lib/data_mapper/support/inflector.rb +174 -238
  40. data/lib/data_mapper/support/object.rb +54 -0
  41. data/lib/data_mapper/support/serialization.rb +19 -1
  42. data/lib/data_mapper/support/string.rb +7 -16
  43. data/lib/data_mapper/support/symbol.rb +3 -15
  44. data/lib/data_mapper/support/typed_set.rb +68 -0
  45. data/lib/data_mapper/types/base.rb +44 -0
  46. data/lib/data_mapper/types/string.rb +34 -0
  47. data/lib/data_mapper/validations/number_validator.rb +40 -0
  48. data/lib/data_mapper/validations/string_validator.rb +20 -0
  49. data/lib/data_mapper/validations/validator.rb +13 -0
  50. data/performance.rb +26 -1
  51. data/profile_data_mapper.rb +1 -1
  52. data/rakefile.rb +42 -2
  53. data/spec/acts_as_tree_spec.rb +11 -3
  54. data/spec/adapters/data_object_adapter_spec.rb +31 -0
  55. data/spec/associations/belongs_to_association_spec.rb +98 -0
  56. data/spec/associations/has_and_belongs_to_many_association_spec.rb +377 -0
  57. data/spec/associations/has_many_association_spec.rb +337 -0
  58. data/spec/attributes_spec.rb +23 -1
  59. data/spec/auto_migrations_spec.rb +86 -29
  60. data/spec/callbacks_spec.rb +107 -0
  61. data/spec/column_spec.rb +5 -2
  62. data/spec/count_command_spec.rb +33 -1
  63. data/spec/database_spec.rb +18 -0
  64. data/spec/dependency_spec.rb +4 -2
  65. data/spec/embedded_value_spec.rb +8 -8
  66. data/spec/fixtures/people.yaml +1 -1
  67. data/spec/fixtures/projects.yaml +10 -1
  68. data/spec/fixtures/tasks.yaml +6 -0
  69. data/spec/fixtures/tasks_tasks.yaml +2 -0
  70. data/spec/fixtures/tomatoes.yaml +1 -0
  71. data/spec/is_a_tree_spec.rb +149 -0
  72. data/spec/load_command_spec.rb +71 -9
  73. data/spec/magic_columns_spec.rb +17 -2
  74. data/spec/migration_spec.rb +267 -0
  75. data/spec/models/animal.rb +1 -1
  76. data/spec/models/candidate.rb +8 -0
  77. data/spec/models/career.rb +1 -1
  78. data/spec/models/chain.rb +8 -0
  79. data/spec/models/comment.rb +1 -1
  80. data/spec/models/exhibit.rb +1 -1
  81. data/spec/models/fence.rb +7 -0
  82. data/spec/models/fruit.rb +2 -2
  83. data/spec/models/job.rb +8 -0
  84. data/spec/models/person.rb +2 -3
  85. data/spec/models/post.rb +1 -1
  86. data/spec/models/project.rb +21 -1
  87. data/spec/models/section.rb +1 -1
  88. data/spec/models/serializer.rb +1 -1
  89. data/spec/models/task.rb +9 -0
  90. data/spec/models/tomato.rb +27 -0
  91. data/spec/models/user.rb +8 -2
  92. data/spec/models/zoo.rb +2 -7
  93. data/spec/paranoia_spec.rb +1 -1
  94. data/spec/{base_spec.rb → persistence_spec.rb} +207 -18
  95. data/spec/postgres_spec.rb +48 -6
  96. data/spec/property_spec.rb +90 -9
  97. data/spec/query_spec.rb +71 -5
  98. data/spec/save_command_spec.rb +11 -0
  99. data/spec/spec_helper.rb +14 -11
  100. data/spec/support/blank_spec.rb +8 -0
  101. data/spec/support/inflector_spec.rb +41 -0
  102. data/spec/support/object_spec.rb +9 -0
  103. data/spec/{serialization_spec.rb → support/serialization_spec.rb} +1 -1
  104. data/spec/support/silence_spec.rb +15 -0
  105. data/spec/{support_spec.rb → support/string_spec.rb} +3 -3
  106. data/spec/support/struct_spec.rb +12 -0
  107. data/spec/support/typed_set_spec.rb +66 -0
  108. data/spec/table_spec.rb +3 -3
  109. data/spec/types/string.rb +81 -0
  110. data/spec/validates_uniqueness_of_spec.rb +17 -0
  111. data/spec/validations/number_validator.rb +59 -0
  112. data/spec/validations/string_validator.rb +14 -0
  113. metadata +59 -17
  114. data/do_performance.rb +0 -153
  115. data/lib/data_mapper/support/active_record_impersonation.rb +0 -103
  116. data/lib/data_mapper/support/weak_hash.rb +0 -46
  117. data/spec/active_record_impersonation_spec.rb +0 -129
  118. data/spec/associations_spec.rb +0 -232
  119. data/spec/conditions_spec.rb +0 -49
  120. data/spec/has_many_association_spec.rb +0 -173
  121. 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
- target = dup
46
- lines = target.split($/)
46
+ lines = self.dup.split($/)
47
47
 
48
- if indicator.nil?
49
- min_margin = nil
50
- lines.each do |line|
51
- if line =~ /(\s+)/ && (min_margin.nil? || $1.size < min_margin)
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
- lambda { |value| value.send(self) }
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
+
@@ -0,0 +1,13 @@
1
+ module DataMapper
2
+ module Validations
3
+ class Validator
4
+ Error = Struct.new(:expected, :got)
5
+
6
+ def errors_for(target)
7
+ raise NotImplementedError.new
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+
@@ -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, :string
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 } }
@@ -15,7 +15,7 @@ end
15
15
 
16
16
  profile do
17
17
  1000.times do
18
- database.query("SELECT * FROM zoos")
18
+ Zoo.all.each { |zoo| zoo.name; zoo.exhibits.entries }
19
19
  end
20
20
  end
21
21