datamapper 0.2.0 → 0.2.1

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 (72) hide show
  1. data/CHANGELOG +20 -1
  2. data/environment.rb +5 -3
  3. data/lib/data_mapper.rb +42 -23
  4. data/lib/data_mapper/adapters/data_object_adapter.rb +76 -73
  5. data/lib/data_mapper/adapters/mysql_adapter.rb +15 -1
  6. data/lib/data_mapper/adapters/postgresql_adapter.rb +1 -1
  7. data/lib/data_mapper/adapters/sql/commands/load_command.rb +242 -28
  8. data/lib/data_mapper/adapters/sql/mappings/column.rb +18 -1
  9. data/lib/data_mapper/adapters/sql/mappings/table.rb +20 -32
  10. data/lib/data_mapper/adapters/sql/quoting.rb +40 -7
  11. data/lib/data_mapper/adapters/sqlite3_adapter.rb +7 -1
  12. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +6 -1
  13. data/lib/data_mapper/associations/has_many_association.rb +1 -1
  14. data/lib/data_mapper/context.rb +4 -2
  15. data/lib/data_mapper/database.rb +2 -12
  16. data/lib/data_mapper/support/active_record_impersonation.rb +1 -1
  17. data/lib/data_mapper/support/blank.rb +2 -2
  18. data/lib/data_mapper/support/serialization.rb +60 -4
  19. data/lib/data_mapper/support/string.rb +24 -2
  20. data/lib/data_mapper/validations/validation_errors.rb +6 -3
  21. data/performance.rb +20 -5
  22. data/plugins/dataobjects/Rakefile +2 -0
  23. data/plugins/dataobjects/do.rb +18 -8
  24. data/plugins/dataobjects/do_mysql.rb +57 -24
  25. data/plugins/dataobjects/do_postgres.rb +3 -7
  26. data/plugins/dataobjects/do_sqlite3.rb +18 -17
  27. data/plugins/dataobjects/swig_mysql/Makefile +146 -0
  28. data/plugins/dataobjects/swig_mysql/extconf.rb +13 -1
  29. data/plugins/dataobjects/swig_mysql/mkmf.log +24 -0
  30. data/plugins/dataobjects/swig_mysql/mysql_c.bundle +0 -0
  31. data/plugins/dataobjects/swig_mysql/mysql_c.c +303 -2501
  32. data/plugins/dataobjects/swig_mysql/mysql_c.i +63 -4
  33. data/plugins/dataobjects/swig_mysql/mysql_c.o +0 -0
  34. data/profile_data_mapper.rb +4 -4
  35. data/rakefile.rb +13 -7
  36. data/spec/acts_as_tree_spec.rb +2 -0
  37. data/spec/associations_spec.rb +12 -0
  38. data/spec/attributes_spec.rb +2 -0
  39. data/spec/base_spec.rb +2 -0
  40. data/spec/callbacks_spec.rb +2 -0
  41. data/spec/can_has_sphinx.rb +0 -1
  42. data/spec/coersion_spec.rb +10 -3
  43. data/spec/column_spec.rb +23 -0
  44. data/spec/conditions_spec.rb +18 -18
  45. data/spec/count_command_spec.rb +2 -0
  46. data/spec/dataobjects_spec.rb +26 -0
  47. data/spec/delete_command_spec.rb +2 -0
  48. data/spec/embedded_value_spec.rb +2 -0
  49. data/spec/fixtures/people.yaml +1 -1
  50. data/spec/fixtures/posts.yaml +3 -0
  51. data/spec/legacy_spec.rb +2 -0
  52. data/spec/load_command_spec.rb +28 -2
  53. data/spec/magic_columns_spec.rb +2 -0
  54. data/spec/models/person.rb +1 -1
  55. data/spec/models/post.rb +8 -0
  56. data/spec/query_spec.rb +2 -0
  57. data/spec/save_command_spec.rb +2 -0
  58. data/spec/schema_spec.rb +2 -0
  59. data/spec/serialization_spec.rb +58 -0
  60. data/spec/single_table_inheritance_spec.rb +2 -0
  61. data/spec/symbolic_operators_spec.rb +2 -0
  62. data/spec/validates_confirmation_of_spec.rb +2 -0
  63. data/spec/validates_format_of_spec.rb +2 -0
  64. data/spec/validates_length_of_spec.rb +2 -0
  65. data/spec/validates_uniqueness_of_spec.rb +2 -0
  66. data/spec/validations_spec.rb +2 -0
  67. data/tasks/fixtures.rb +15 -10
  68. metadata +10 -13
  69. data/lib/data_mapper/adapters/sql/commands/conditions.rb +0 -130
  70. data/lib/data_mapper/adapters/sql/commands/loader.rb +0 -99
  71. data/plugins/dataobjects/swig_mysql/do_mysql.bundle +0 -0
  72. data/spec/conversions_to_yaml_spec.rb +0 -17
@@ -9,10 +9,11 @@ module DataMapper
9
9
 
10
10
  attr_accessor :table, :name, :type, :options
11
11
 
12
- def initialize(adapter, table, name, type, options = {})
12
+ def initialize(adapter, table, name, type, ordinal, options = {})
13
13
  @adapter = adapter
14
14
  @table = table
15
15
  @name, @type, @options = name.to_sym, type, options
16
+ @ordinal = ordinal
16
17
 
17
18
  @key = (@options[:key] == true)
18
19
  @nullable = @options.has_key?(:nullable) ? @options[:nullable] : !@key
@@ -26,6 +27,10 @@ module DataMapper
26
27
  end
27
28
  EOS
28
29
  end
30
+
31
+ def ordinal
32
+ @ordinal
33
+ end
29
34
 
30
35
  def lazy=(value)
31
36
  @lazy = value
@@ -121,6 +126,18 @@ module DataMapper
121
126
  end
122
127
  end
123
128
 
129
+ def <=>(other)
130
+ ordinal <=> other.ordinal
131
+ end
132
+
133
+ def hash
134
+ name.hash
135
+ end
136
+
137
+ def eql?(other)
138
+ name == other.name
139
+ end
140
+
124
141
  private
125
142
 
126
143
  def primary_key_declaration
@@ -17,9 +17,8 @@ module DataMapper
17
17
  @klass_or_name = klass_or_name
18
18
 
19
19
  @adapter = adapter
20
- @columns = []
21
- @columns_hash = Hash.new { |h,k| h[k] = @columns.find { |c| c.name == k } }
22
- @columns_by_column_name = Hash.new { |h,k| h[k.to_s] = @columns.find { |c| c.column_name == k.to_s } }
20
+ @columns = SortedSet.new
21
+ @columns_hash = Hash.new { |h,k| h[k] = columns.find { |c| c.name == k } }
23
22
 
24
23
  @associations = AssociationsSet.new
25
24
 
@@ -65,40 +64,36 @@ module DataMapper
65
64
  end
66
65
 
67
66
  def key
68
- if @key.nil?
69
- key_column = @columns.find { |c| c.key? }
70
- @key = if key_column.nil?
71
- column = add_column(:id, :integer, :key => true)
67
+ @key || begin
68
+ @key = @columns.find { |column| column.key? }
69
+
70
+ if @key.nil?
71
+ @key = add_column(:id, :integer, :key => true, :ordinal => -1)
72
72
  @klass.send(:attr_reader, :id) unless @klass.methods.include?(:id)
73
- column
74
- else
75
- key_column
76
73
  end
74
+
75
+ @key
77
76
  end
78
-
79
- @key
80
77
  end
81
78
 
82
79
  def add_column(column_name, type, options)
83
- column = @columns.find { |c| c.name == column_name.to_sym }
84
-
85
- if column.nil?
86
- reset_derived_columns!
87
- column = @adapter.class::Mappings::Column.new(@adapter, self, column_name, type, options)
88
- @columns.send(column_name == :id ? :unshift : :push, column)
89
- @multi_class = true if column_name == :type
80
+
81
+ column_ordinal = if options.is_a?(Hash) && options.has_key?(:ordinal)
82
+ options.delete(:ordinal)
83
+ else
84
+ @columns.size
90
85
  end
86
+
87
+ column = @adapter.class::Mappings::Column.new(@adapter, self, column_name, type, column_ordinal, options)
88
+ @columns << column
89
+
90
+ @multi_class = true if column_name == :type
91
91
 
92
92
  return column
93
93
  end
94
94
 
95
95
  def [](column_name)
96
- return key if column_name == :id
97
- @columns_hash[column_name.kind_of?(Symbol) ? column_name : column_name.to_sym]
98
- end
99
-
100
- def find_by_column_name(column_name)
101
- @columns_by_column_name[column_name.kind_of?(String) ? column_name : column_name.to_s]
96
+ @columns_hash[column_name.to_sym]
102
97
  end
103
98
 
104
99
  def name
@@ -157,13 +152,6 @@ module DataMapper
157
152
  ]
158
153
  end
159
154
 
160
- private
161
- def reset_derived_columns!
162
- @columns_hash.clear
163
- @columns_by_column_name.clear
164
- @key = nil
165
- end
166
-
167
155
  end
168
156
 
169
157
  end
@@ -22,13 +22,14 @@ module DataMapper
22
22
  return 'NULL' if value.nil?
23
23
 
24
24
  case value
25
- when Numeric then value.to_s
26
- when String then "'#{value.gsub("'", "''")}'"
27
- when Class then "'#{value.name}'"
28
- when Date then "'#{value.to_s}'"
29
- when Time, DateTime then "'#{value.utc.strftime("%Y-%m-%d %H:%M:%S")}'"
30
- when TrueClass, FalseClass then value.to_s.upcase
31
- when Array then "(#{value.map { |entry| quote_value(entry) }.join(', ')})"
25
+ when Numeric then quote_numeric(value)
26
+ when String then quote_string(value)
27
+ when Class then quote_class(value)
28
+ when Time then quote_time(value)
29
+ when DateTime then quote_datetime(value)
30
+ when Date then quote_date(value)
31
+ when TrueClass, FalseClass then quote_boolean(value)
32
+ when Array then quote_array(value)
32
33
  else
33
34
  if value.respond_to?(:to_sql)
34
35
  value.to_sql
@@ -37,6 +38,38 @@ module DataMapper
37
38
  end
38
39
  end
39
40
  end
41
+
42
+ def quote_numeric(value)
43
+ value.to_s
44
+ end
45
+
46
+ def quote_string(value)
47
+ "'#{value.gsub("'", "''")}'"
48
+ end
49
+
50
+ def quote_class(value)
51
+ "'#{value.name}'"
52
+ end
53
+
54
+ def quote_time(value)
55
+ "'#{value.xmlschema}'"
56
+ end
57
+
58
+ def quote_datetime(value)
59
+ "'#{value}'"
60
+ end
61
+
62
+ def quote_date(value)
63
+ "'#{value.strftime("%Y-%m-%d")}'"
64
+ end
65
+
66
+ def quote_boolean(value)
67
+ value.to_s.upcase
68
+ end
69
+
70
+ def quote_array(value)
71
+ "(#{value.map { |entry| quote_value(entry) }.join(', ')})"
72
+ end
40
73
 
41
74
  end # module Quoting
42
75
  end
@@ -1,7 +1,7 @@
1
1
  require 'data_mapper/adapters/data_object_adapter'
2
2
  begin
3
3
  require 'do_sqlite3'
4
- rescue
4
+ rescue LoadError
5
5
  STDERR.puts <<-EOS
6
6
  You must install the DataObjects::SQLite3 driver.
7
7
  rake dm:install:sqlite3
@@ -29,6 +29,12 @@ module DataMapper
29
29
  conn.open
30
30
  return conn
31
31
  end
32
+
33
+ def truncate(session, name)
34
+ result = execute("DELETE FROM #{table(name).to_sql}")
35
+ session.identity_map.clear!(name)
36
+ result.to_i > 0
37
+ end
32
38
 
33
39
  module Mappings
34
40
  class Table
@@ -150,7 +150,12 @@ module DataMapper
150
150
 
151
151
  # Locate the column for the left-key.
152
152
  unless left_key_index
153
- left_key_index = columns.index(association.left_foreign_key)
153
+ columns.each_with_index do |column, index|
154
+ if column.name == association.left_foreign_key.name
155
+ left_key_index = index
156
+ break
157
+ end
158
+ end
154
159
  end
155
160
 
156
161
  if instance.kind_of?(association_constant)
@@ -109,7 +109,7 @@ module DataMapper
109
109
  end # def items
110
110
 
111
111
  def inspect
112
- @entries.inspect
112
+ entries.inspect
113
113
  end
114
114
  end
115
115
 
@@ -35,9 +35,11 @@ module DataMapper
35
35
  raise ArgumentError.new('Session#first takes a class, and optional type_or_id and/or options arguments')
36
36
  end
37
37
 
38
- options.merge!(b.to_hash) if block_given?
38
+ # Account for undesired behaviour in MySQL that returns the
39
+ # last inserted row when the WHERE clause contains a "#{primary_key} IS NULL".
40
+ return nil if options.has_key?(:id) && options[:id] == nil
39
41
 
40
- @adapter.load(self, klass, options)
42
+ @adapter.load(self, klass, options).first
41
43
  end
42
44
 
43
45
  def all(klass, options = {})
@@ -87,7 +87,6 @@ module DataMapper
87
87
  class Database
88
88
 
89
89
  @databases = {}
90
- @context = []
91
90
 
92
91
  # Allows you to access any of the named databases you have already setup.
93
92
  #
@@ -101,7 +100,7 @@ module DataMapper
101
100
  #
102
101
  # This is what gives us thread safety, boys and girls
103
102
  def self.context
104
- @context
103
+ Thread::current[:context] || Thread::current[:context] = []
105
104
  end
106
105
 
107
106
  # Setup creates a database and sets all of your properties for that database.
@@ -157,11 +156,10 @@ module DataMapper
157
156
  # Creates a new database object with the name you specify, and a default set of options.
158
157
  #
159
158
  # The default options are as follows:
160
- # {:single_threaded => true, :host => 'localhost', :database => nil, :username => 'root', :password => '', :adapter = nil }
159
+ # { :host => 'localhost', :database => nil, :username => 'root', :password => '', :adapter = nil }
161
160
  def initialize(name)
162
161
  @name = name
163
162
 
164
- @single_threaded = true
165
163
  @adapter = nil
166
164
  @host = 'localhost'
167
165
  @database = nil
@@ -175,16 +173,8 @@ module DataMapper
175
173
  end
176
174
 
177
175
  attr_reader :name, :adapter
178
- attr_writer :single_threaded
179
176
  attr_accessor :host, :database, :schema_search_path, :username, :password, :log_stream, :log_level, :index_path, :socket
180
177
 
181
- # Returns true or false
182
- #
183
- # NOTE: single_threaded is true unless explicitly set to false.
184
- def single_threaded?
185
- @single_threaded
186
- end
187
-
188
178
  # Allows us to set the adapter for this database object. It can only be set once, and expects two types of values.
189
179
  #
190
180
  # You may pass in either a class inheriting from DataMapper::Adapters::AbstractAdapter
@@ -12,7 +12,7 @@ module DataMapper
12
12
  end
13
13
 
14
14
  def reload!
15
- session.first(self.class, key, :select => session.mappings[self.class].columns.map(&:name), :reload => true)
15
+ session.first(self.class, key, :select => original_hashes.keys, :reload => true)
16
16
  end
17
17
 
18
18
  def reload
@@ -30,6 +30,6 @@ end
30
30
 
31
31
  class String
32
32
  def blank?
33
- empty? || self =~ /^\s*$/
33
+ empty? || self =~ /\A\s*\Z/
34
34
  end
35
- end
35
+ end
@@ -1,13 +1,69 @@
1
+ require 'rexml/document'
2
+
3
+ begin
4
+ require 'json/ext'
5
+ rescue LoadError
6
+ require 'json/pure'
7
+ end
8
+
1
9
  module DataMapper
2
10
  module Support
3
11
  module Serialization
4
12
 
5
- def to_yaml
6
- document = {}
7
- attributes.each_pair { |k,v| document[k.to_s] = v }
8
- document.to_yaml
13
+ def to_yaml(opts = {})
14
+
15
+ YAML::quick_emit( object_id, opts ) do |out|
16
+ out.map(nil, to_yaml_style ) do |map|
17
+ session.table(self).columns.each do |column|
18
+ lazy_load!(column.name) if column.lazy?
19
+ value = instance_variable_get(column.instance_variable_name)
20
+ map.add(column.to_s, value.is_a?(Class) ? value.to_s : value)
21
+ end
22
+ (self.instance_variable_get("@yaml_added") || []).each do |k,v|
23
+ map.add(k.to_s, v)
24
+ end
25
+ end
26
+ end
27
+
9
28
  end
10
29
 
30
+ def to_xml
31
+ doc = REXML::Document.new
32
+
33
+ table = session.table(self.class)
34
+ root = doc.add_element(Inflector.underscore(self.class.name))
35
+
36
+ key_attribute = root.attributes << REXML::Attribute.new(table.key.to_s, key)
37
+
38
+ # Single-quoted attributes are ugly. :p
39
+ # NOTE: I don't want to break existing REXML specs for everyone, so I'm
40
+ # overwriting REXML::Attribute#to_string just for this instance.
41
+ def key_attribute.to_string
42
+ %Q[#@expanded_name="#{to_s().gsub(/"/, '&quot;')}"]
43
+ end
44
+
45
+ table.columns.each do |column|
46
+ next if column.key?
47
+ value = send(column.name)
48
+ node = root.add_element(column.to_s)
49
+ node << REXML::Text.new(value.to_s) unless value.nil?
50
+ end
51
+
52
+ doc.to_s
53
+ end
54
+
55
+ def to_json(*a)
56
+ table = session.table(self.class)
57
+
58
+ result = '{ '
59
+
60
+ result << table.columns.map do |column|
61
+ "#{column.name.to_json}: #{send(column.name).to_json(*a)}"
62
+ end.join(', ')
63
+
64
+ result << ' }'
65
+ result
66
+ end
11
67
  end
12
68
  end # module Support
13
69
  end # module DataMapper
@@ -26,8 +26,30 @@ module DataMapper
26
26
  # FROM users
27
27
  # QUERY
28
28
  # => "SELECT name FROM users"
29
- def compress_lines
30
- gsub(/\s+/, ' ').strip
29
+ def compress_lines(spaced = true)
30
+ split($/).map { |line| line.strip }.join(spaced ? ' ' : '')
31
+ end
32
+
33
+ def margin(indicator = nil)
34
+ target = dup
35
+ lines = target.split($/)
36
+
37
+ if indicator.nil?
38
+ min_margin = nil
39
+ lines.each do |line|
40
+ if line =~ /(\s+)/ && (min_margin.nil? || $1.size < min_margin)
41
+ min_margin = $1.size
42
+ end
43
+ end
44
+
45
+ lines.map do |line|
46
+ line.sub(/^\s{#{min_margin}}/, '')
47
+ end.join($/)
48
+ else
49
+ lines.map do |line|
50
+ line.sub(/^.*?#{"\\" + indicator}/, '')
51
+ end.join($/)
52
+ end
31
53
  end
32
54
 
33
55
  end # module String
@@ -24,9 +24,12 @@ module DataMapper
24
24
  end
25
25
  end
26
26
 
27
- # Are any errors present?
28
- def empty?
29
- @errors.empty?
27
+ def on(attribute)
28
+ @errors[attribute]
29
+ end
30
+
31
+ def method_missing(meth, *args)
32
+ @errors.send(meth, *args)
30
33
  end
31
34
 
32
35
  end
@@ -4,7 +4,8 @@ require 'active_record'
4
4
  ActiveRecord::Base.establish_connection :adapter => 'mysql',
5
5
  :username => 'root',
6
6
  :password => '',
7
- :database => 'data_mapper_1'
7
+ :database => 'data_mapper_1',
8
+ :socker => "/tmp/mysql.sock"
8
9
 
9
10
  ActiveRecord::Base.find_by_sql('SELECT 1')
10
11
 
@@ -21,7 +22,8 @@ require 'lib/data_mapper'
21
22
  DataMapper::Database.setup({
22
23
  :adapter => 'mysql',
23
24
  :database => 'data_mapper_1',
24
- :username => 'root'
25
+ :username => 'root',
26
+ :socket => "/tmp/mysql.sock"
25
27
  })
26
28
 
27
29
  class DMAnimal < DataMapper::Base
@@ -89,16 +91,16 @@ Benchmark::send(ENV['BM'] || :bmbm, 40) do |x|
89
91
  end
90
92
 
91
93
  x.report('ActiveRecord:all') do
92
- N.times { ARAnimal.find(:all).each { |a| a.name } }
94
+ N.times { ARZoo.find(:all).each { |a| a.name } }
93
95
  end
94
96
 
95
97
  x.report('DataMapper:all') do
96
- N.times { DMAnimal.all.each { |a| a.name } }
98
+ N.times { Zoo.all.each { |a| a.name } }
97
99
  end
98
100
 
99
101
  x.report('DataMapper:all:in-session') do
100
102
  database do
101
- N.times { DMAnimal.all.each { |a| a.name } }
103
+ N.times { Zoo.all.each { |a| a.name } }
102
104
  end
103
105
  end
104
106
 
@@ -212,6 +214,19 @@ Benchmark::send(ENV['BM'] || :bmbm, 40) do |x|
212
214
  end
213
215
  end
214
216
 
217
+ x.report('DataMapper:raw-query') do
218
+ N.times do
219
+ database.adapter.connection do |db|
220
+
221
+ command = db.create_command("SELECT * FROM zoos")
222
+
223
+ command.execute_reader do |reader|
224
+ reader.each { reader.current_row }
225
+ end
226
+ end
227
+ end
228
+ end
229
+
215
230
  x.report('ActiveRecord:accessors') do
216
231
  person = ARPerson.find(:first)
217
232