datamapper 0.2.4 → 0.2.5

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 (65) hide show
  1. data/CHANGELOG +16 -1
  2. data/README +10 -8
  3. data/environment.rb +1 -1
  4. data/example.rb +13 -5
  5. data/lib/data_mapper.rb +2 -0
  6. data/lib/data_mapper/adapters/abstract_adapter.rb +61 -0
  7. data/lib/data_mapper/adapters/data_object_adapter.rb +33 -6
  8. data/lib/data_mapper/adapters/mysql_adapter.rb +5 -0
  9. data/lib/data_mapper/adapters/postgresql_adapter.rb +12 -0
  10. data/lib/data_mapper/adapters/sql/commands/load_command.rb +6 -14
  11. data/lib/data_mapper/adapters/sql/mappings/column.rb +37 -26
  12. data/lib/data_mapper/adapters/sql/mappings/table.rb +50 -8
  13. data/lib/data_mapper/associations.rb +4 -5
  14. data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +2 -2
  15. data/lib/data_mapper/associations/has_many_association.rb +40 -13
  16. data/lib/data_mapper/associations/has_n_association.rb +1 -1
  17. data/lib/data_mapper/base.rb +5 -448
  18. data/lib/data_mapper/callbacks.rb +12 -2
  19. data/lib/data_mapper/context.rb +4 -0
  20. data/lib/data_mapper/database.rb +1 -1
  21. data/lib/data_mapper/identity_map.rb +2 -2
  22. data/lib/data_mapper/persistence.rb +538 -0
  23. data/lib/data_mapper/support/active_record_impersonation.rb +21 -3
  24. data/lib/data_mapper/support/errors.rb +2 -0
  25. data/lib/data_mapper/support/serialization.rb +7 -10
  26. data/lib/data_mapper/support/struct.rb +7 -0
  27. data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +11 -4
  28. data/performance.rb +23 -10
  29. data/rakefile.rb +1 -1
  30. data/spec/active_record_impersonation_spec.rb +2 -6
  31. data/spec/acts_as_tree_spec.rb +3 -1
  32. data/spec/associations_spec.rb +40 -160
  33. data/spec/attributes_spec.rb +1 -1
  34. data/spec/base_spec.rb +41 -13
  35. data/spec/callbacks_spec.rb +32 -0
  36. data/spec/coersion_spec.rb +1 -1
  37. data/spec/column_spec.rb +22 -12
  38. data/spec/dependency_spec.rb +5 -3
  39. data/spec/embedded_value_spec.rb +33 -17
  40. data/spec/has_many_association_spec.rb +173 -0
  41. data/spec/legacy_spec.rb +2 -2
  42. data/spec/load_command_spec.rb +59 -7
  43. data/spec/models/animal.rb +6 -2
  44. data/spec/models/animals_exhibit.rb +3 -1
  45. data/spec/models/career.rb +2 -1
  46. data/spec/models/comment.rb +3 -1
  47. data/spec/models/exhibit.rb +3 -1
  48. data/spec/models/fruit.rb +3 -1
  49. data/spec/models/person.rb +10 -1
  50. data/spec/models/post.rb +3 -1
  51. data/spec/models/project.rb +3 -1
  52. data/spec/models/section.rb +3 -1
  53. data/spec/models/serializer.rb +3 -1
  54. data/spec/models/user.rb +3 -1
  55. data/spec/models/zoo.rb +3 -1
  56. data/spec/paranoia_spec.rb +3 -1
  57. data/spec/postgres_spec.rb +54 -0
  58. data/spec/save_command_spec.rb +9 -5
  59. data/spec/schema_spec.rb +0 -91
  60. data/spec/single_table_inheritance_spec.rb +8 -0
  61. data/spec/table_spec.rb +46 -0
  62. data/spec/validates_uniqueness_of_spec.rb +19 -1
  63. metadata +8 -10
  64. data/lib/data_mapper/associations/has_one_association.rb +0 -77
  65. data/plugins/dataobjects/do_rb +0 -0
data/CHANGELOG CHANGED
@@ -123,4 +123,19 @@
123
123
  * MAJOR: Resolve issue with non-unique-hash values and #dirty?; now frozen original values are stored instead
124
124
  * Added Base#update_attributes
125
125
  * MAJOR: Queries are now passed to the database drivers in a parameterized fashion
126
- * Updated PostgreSQL driver and adapter to current
126
+ * Updated PostgreSQL driver and adapter to current
127
+
128
+ -- 0.2.4
129
+ * Bug fixes
130
+ * Added paranoia
131
+
132
+ -- 0.2.5
133
+ * has_one bugfixes
134
+ * Added syntax for setting CHECK-constraints directly in your properties (Postgres)
135
+ * You can now set indexes with :index => true and :index => :unique
136
+ * Support for composite indexes (thanks to Jeffrey Gelens)
137
+ * Add composite scope to validates_uniqueness
138
+ * Added private/protected properties
139
+ * Remove HasOneAssociation, Make HasManyAssociation impersonate has_one relationships
140
+ * Added #get method
141
+ * Persistence module added, inheriting from DataMapper::Base no longer necessary
data/README CHANGED
@@ -93,10 +93,10 @@ Ruby on Rails project, and have a database.yml, DataMapper should pick it up aut
93
93
 
94
94
  If you'd like to set the database connection manually before using it, do something like this:
95
95
  DataMapper::Database.setup({
96
- :adapter => 'mysql'
97
- :host => 'localhost'
98
- :username => 'root'
99
- :password => 'R00tPaswooooord'
96
+ :adapter => 'mysql',
97
+ :host => 'localhost',
98
+ :username => 'root',
99
+ :password => 'R00tPaswooooord',
100
100
  :database => 'selecta_development'
101
101
  })
102
102
 
@@ -109,7 +109,7 @@ SQLite3:: (sqlite3 adaptor)
109
109
 
110
110
  Defining your models requires a few steps.
111
111
 
112
- * Inherit your class from DataMapper::Base
112
+ * Include the module DataMapper::Persistence in your class
113
113
  * Define your properties you wish to access from the database
114
114
  * Define any relationships (optional)
115
115
 
@@ -119,7 +119,9 @@ which are too numerous to go into here.
119
119
 
120
120
  Your models might end up looking something like this:
121
121
 
122
- class Animal < DataMapper::Base
122
+ class Animal
123
+ include DataMapper::Persistence
124
+
123
125
  property :name, :string
124
126
  property :notes, :text, :lazy => true
125
127
 
@@ -187,7 +189,7 @@ This will generate the table structure for your model automatically.
187
189
 
188
190
  You can also execute the following to generate ALL of your models' tables:
189
191
 
190
- DataMapper::Base.auto_migrate!
192
+ DataMapper::Persistence.auto_migrate!
191
193
 
192
194
  === NOTE
193
195
  Both of these methods are DESTRUCTIVE. If you are working in anything other than a pre-production environment,
@@ -198,4 +200,4 @@ stay far away from these methods!
198
200
  Merb's coding conventions are a fantastic start: (http://merb.devjavu.com/#ContributingtoMerb)
199
201
 
200
202
  DataMapper could use some documentation help. If you'd like to contribute, the example you'll
201
- find on the above link is top-notch.
203
+ find on the above link is top-notch.
data/environment.rb CHANGED
@@ -41,4 +41,4 @@ DataMapper::Database.setup(:mock, :adapter => MockAdapter)
41
41
  [:default, :mock].each { |name| database(name) { load_models.call } }
42
42
 
43
43
  # Reset the test database.
44
- DataMapper::Base.auto_migrate! unless ENV['AUTO_MIGRATE'] == 'false'
44
+ DataMapper::Persistence.auto_migrate! unless ENV['AUTO_MIGRATE'] == 'false'
data/example.rb CHANGED
@@ -26,7 +26,8 @@ end
26
26
 
27
27
  require 'irb'
28
28
 
29
- database { IRB::start }
29
+ # database { IRB::start }
30
+ IRB::start
30
31
 
31
32
  if false
32
33
 
@@ -37,7 +38,9 @@ DataMapper::Database.setup({
37
38
  :username => 'root'
38
39
  })
39
40
 
40
- class Animal < DataMapper::Base #:nodoc:
41
+ class Animal #:nodoc:
42
+ include DataMapper::Persistence
43
+
41
44
  set_table_name 'animals' # Just as an example. Same inflector as Rails,
42
45
  # so this really isn't necessary.
43
46
 
@@ -47,17 +50,22 @@ class Animal < DataMapper::Base #:nodoc:
47
50
  has_and_belongs_to_many :exhibits
48
51
  end
49
52
 
50
- class Exhibit < DataMapper::Base #:nodoc:
53
+ class Exhibit #:nodoc:
54
+ include DataMapper::Persistence
55
+
51
56
  property :name, :string
52
57
  belongs_to :zoo
53
58
  end
54
59
 
55
- class Zoo < DataMapper::Base #:nodoc:
60
+ class Zoo #:nodoc:
61
+ include DataMapper::Persistence
62
+
56
63
  property :name, :string
57
64
  has_many :exhibits
58
65
  end
59
66
 
60
- class Person < DataMapper::Base #:nodoc:
67
+ class Person #:nodoc:
68
+ include DataMapper::Persistence
61
69
 
62
70
  property :name, :string
63
71
  property :age, :integer
data/lib/data_mapper.rb CHANGED
@@ -29,8 +29,10 @@ require 'data_mapper/support/silence'
29
29
  require 'data_mapper/support/inflector'
30
30
  require 'data_mapper/support/errors'
31
31
  require 'data_mapper/database'
32
+ require 'data_mapper/persistence'
32
33
  require 'data_mapper/base'
33
34
 
35
+
34
36
  begin
35
37
  # This block of code is for compatibility with Ruby On Rails' or Merb's database.yml
36
38
  # file, allowing you to simply require the data_mapper.rb in your
@@ -29,10 +29,71 @@ module DataMapper
29
29
  raise NotImplementedError.new
30
30
  end
31
31
 
32
+ def get(database_context, klass, *keys)
33
+ raise NotImplementedError.new
34
+ end
35
+
32
36
  def logger
33
37
  @logger || @logger = @configuration.logger
34
38
  end
35
39
 
40
+ protected
41
+
42
+ def materialize(database_context, klass, values, reload, loaded_set)
43
+
44
+ table = self.table(klass)
45
+
46
+ instance_id = table.key.type_cast_value(values[table.key.name])
47
+
48
+ instance_type = if table.multi_class? && table.type_column
49
+ values[table.type_column.name].blank? ? klass :
50
+ table.type_column.type_cast_value(values[table.type_column.name])
51
+ else
52
+ klass
53
+ end
54
+
55
+ instance = create_instance(database_context, instance_type, instance_id, reload)
56
+
57
+ instance_type.callbacks.execute(:before_materialize, instance)
58
+
59
+ type_casted_values = {}
60
+
61
+ values.each_pair do |k,v|
62
+ column = table[k]
63
+ type_cast_value = column.type_cast_value(v)
64
+ type_casted_values[k] = type_cast_value
65
+ instance.instance_variable_set(column.instance_variable_name, type_cast_value)
66
+ end
67
+
68
+ instance.original_values = type_casted_values
69
+ instance.loaded_set = loaded_set
70
+
71
+ instance_type.callbacks.execute(:after_materialize, instance)
72
+
73
+ return instance
74
+
75
+ #rescue => e
76
+ # raise MaterializationError.new("Failed to materialize row: #{values.inspect}\n#{e.to_yaml}")
77
+ end
78
+
79
+ def create_instance(database_context, instance_type, instance_id, reload)
80
+ instance = database_context.identity_map.get(instance_type, instance_id)
81
+
82
+ if instance.nil? || reload
83
+ instance = instance_type.new() if instance.nil?
84
+ instance.instance_variable_set(:@__key, instance_id)
85
+ instance.instance_variable_set(:@new_record, false)
86
+ database_context.identity_map.set(instance)
87
+ elsif instance.new_record?
88
+ instance.instance_variable_set(:@__key, instance_id)
89
+ instance.instance_variable_set(:@new_record, false)
90
+ end
91
+
92
+ instance.database_context = database_context
93
+
94
+ return instance
95
+ end
96
+
36
97
  end # class AbstractAdapter
37
98
 
38
99
  end # module Adapters
@@ -138,6 +138,17 @@ module DataMapper
138
138
  db.close
139
139
  end
140
140
 
141
+ def execute(*args)
142
+ db = create_connection
143
+ command = db.create_command(args.shift)
144
+ return command.execute_non_query(*args)
145
+ rescue => e
146
+ logger.error { e }
147
+ raise e
148
+ ensure
149
+ db.close
150
+ end
151
+
141
152
  def handle_error(error)
142
153
  raise error
143
154
  end
@@ -186,14 +197,15 @@ module DataMapper
186
197
  case instance
187
198
  when Class then table(instance).create!
188
199
  when Mappings::Table then instance.create!
189
- when DataMapper::Base then
190
- return false unless instance.new_record? || instance.dirty?
191
-
200
+ when DataMapper::Persistence then
192
201
  event = instance.new_record? ? :create : :update
193
202
 
194
203
  return false if validate && !instance.validate_recursively(event, Set.new)
195
204
 
196
205
  callback(instance, :before_save)
206
+
207
+ return false unless instance.new_record? || instance.dirty?
208
+
197
209
  result = send(event, database_context, instance)
198
210
 
199
211
  instance.database_context = database_context
@@ -249,7 +261,7 @@ module DataMapper
249
261
 
250
262
  table = self.table(instance)
251
263
  attributes = instance.dirty_attributes
252
-
264
+
253
265
  if table.multi_class?
254
266
  instance.instance_variable_set(
255
267
  table[:type].instance_variable_name,
@@ -263,7 +275,7 @@ module DataMapper
263
275
  keys << table[key].to_sql
264
276
  values << value
265
277
  end
266
-
278
+
267
279
  sql = if keys.size > 0
268
280
  "INSERT INTO #{table.to_sql} (#{keys.join(', ')}) VALUES ?"
269
281
  else
@@ -283,10 +295,25 @@ module DataMapper
283
295
  self.class::Commands::LoadCommand.new(self, database_context, klass, options).call
284
296
  end
285
297
 
298
+ def get(database_context, klass, keys)
299
+ table = self.table(klass)
300
+ sql = "SELECT #{table.columns.map { |column| column.to_sql }.join(', ')} FROM #{table.to_sql} WHERE #{table.keys.map { |key| "#{key.to_sql} = ?" }.join(' AND ')}"
301
+
302
+ connection do |db|
303
+ db.create_command(sql).execute_reader(*keys) do |reader|
304
+ values = {}
305
+ table.columns.each_with_index do |column, i|
306
+ values[column.name] = reader.item(i)
307
+ end
308
+ materialize(database_context, klass, values, false, [])
309
+ end
310
+ end
311
+ end
312
+
286
313
  def table(instance)
287
314
  case instance
288
315
  when DataMapper::Adapters::Sql::Mappings::Table then instance
289
- when DataMapper::Base then schema[instance.class]
316
+ when DataMapper::Persistence then schema[instance.class]
290
317
  when Class, String then schema[instance]
291
318
  else raise "Don't know how to map #{instance.inspect} to a table."
292
319
  end
@@ -13,6 +13,11 @@ module DataMapper
13
13
  module Adapters
14
14
 
15
15
  class MysqlAdapter < DataObjectAdapter
16
+
17
+ TRUE_ALIASES << "T".freeze << "\004\bT".freeze
18
+ FALSE_ALIASES << "F".freeze << "\004\bF".freeze
19
+
20
+
16
21
  def create_connection
17
22
 
18
23
  connection_string = ""
@@ -149,6 +149,10 @@ module DataMapper
149
149
  "SERIAL"
150
150
  end
151
151
 
152
+ def check_declaration
153
+ "CHECK (" << check << ")"
154
+ end
155
+
152
156
  def to_alter_sql
153
157
  "ALTER TABLE " << table.to_sql << " ALTER COLUMN " << to_alter_form
154
158
  end
@@ -221,6 +225,14 @@ module DataMapper
221
225
  if default && !default_declaration.blank?
222
226
  @to_long_form << " #{default_declaration}"
223
227
  end
228
+
229
+ if unique? && !unique_declaration.blank?
230
+ @to_long_form << " #{unique_declaration}"
231
+ end
232
+
233
+ if check && !check_declaration.blank?
234
+ @to_long_form << " #{check_declaration}"
235
+ end
224
236
  end
225
237
 
226
238
  @to_long_form
@@ -38,7 +38,6 @@ module DataMapper
38
38
  end
39
39
 
40
40
  def materialize(values)
41
-
42
41
  instance_id = @key.type_cast_value(values[@key_index])
43
42
  instance = create_instance(instance_id,
44
43
  if @type_override_present
@@ -47,28 +46,23 @@ module DataMapper
47
46
  @klass
48
47
  end
49
48
  )
50
-
49
+
51
50
  @klass.callbacks.execute(:before_materialize, instance)
52
51
 
53
- original_values = instance.original_values
54
-
52
+ type_casted_values = {}
53
+
55
54
  @columns.each_pair do |index, column|
56
55
  # This may be a little confusing, but we're
57
56
  # setting both the original_value, and the
58
57
  # instance-variable through method chaining to avoid
59
58
  # lots of extra short-lived local variables.
60
- type_cast_value = instance.instance_variable_set(
59
+ type_casted_values[column.name] = instance.instance_variable_set(
61
60
  column.instance_variable_name,
62
61
  column.type_cast_value(values[index])
63
62
  )
64
-
65
- original_values[column.name] = case type_cast_value
66
- when String, Date, Time then type_cast_value.dup
67
- when column.type == :object then Marshal.dump(type_cast_value)
68
- else type_cast_value
69
- end
70
63
  end
71
-
64
+
65
+ instance.original_values = type_casted_values
72
66
  instance.instance_variable_set(:@loaded_set, @set)
73
67
  @set << instance
74
68
 
@@ -85,8 +79,6 @@ module DataMapper
85
79
  end
86
80
 
87
81
  private
88
-
89
- class MaterializationError < StandardError; end
90
82
 
91
83
  def create_instance(instance_id, instance_type)
92
84
  instance = @database_context.identity_map.get(@klass, instance_id)
@@ -6,8 +6,8 @@ module DataMapper
6
6
  # TODO: There are of course many more options to add here.
7
7
  # Ordinal, Length/Size, Nullability are just a few.
8
8
  class Column
9
-
10
- attr_reader :type, :name
9
+ attr_reader :type, :name, :ordinal, :size, :default, :check
10
+ attr_writer :lazy, :index
11
11
  attr_accessor :table, :options
12
12
 
13
13
  def initialize(adapter, table, name, type, ordinal, options = {})
@@ -21,6 +21,21 @@ module DataMapper
21
21
  @lazy = @options.has_key?(:lazy) ? @options[:lazy] : (@type == :text && !@key)
22
22
  @serial = @options[:serial] == true
23
23
  @default = @options[:default]
24
+ @unique = @options.has_value?(:unique)
25
+ @index = @options[:index]
26
+ @check = @options[:check] # only for postgresql
27
+
28
+ @size = if @options.has_key?(:size)
29
+ @options[:size]
30
+ elsif @options.has_key?(:length)
31
+ @options[:length]
32
+ else
33
+ case type
34
+ when :integer then 11
35
+ when :string, :class then 50
36
+ else nil
37
+ end
38
+ end
24
39
  end
25
40
 
26
41
  def type=(value)
@@ -38,15 +53,7 @@ module DataMapper
38
53
  flush_sql_caches!
39
54
  @name = value
40
55
  end
41
-
42
- def ordinal
43
- @ordinal
44
- end
45
-
46
- def lazy=(value)
47
- @lazy = value
48
- end
49
-
56
+
50
57
  # Determines if the field should be lazy loaded.
51
58
  # You can set this explicitly, or accept the default,
52
59
  # which is false for all but text fields.
@@ -66,10 +73,14 @@ module DataMapper
66
73
  @serial
67
74
  end
68
75
 
69
- def default
70
- @default
76
+ def unique?
77
+ @unique
71
78
  end
72
79
 
80
+ def index?
81
+ @index
82
+ end
83
+
73
84
  def default=(value)
74
85
  self.flush_sql_caches!
75
86
  @default = value
@@ -100,18 +111,10 @@ module DataMapper
100
111
  @to_sql || (@to_sql = @adapter.quote_column_name(column_name).freeze)
101
112
  end
102
113
  end
103
-
104
- def size
105
- @size || begin
106
- return @size = @options[:size] if @options.has_key?(:size)
107
- return @size = @options[:length] if @options.has_key?(:length)
108
-
109
- @size = case type
110
- when :integer then 11
111
- when :string, :class then 50
112
- else nil
113
- end
114
- end
114
+
115
+ def size=(val)
116
+ self.flush_sql_caches!
117
+ @size = val
115
118
  end
116
119
 
117
120
  def inspect
@@ -207,7 +210,11 @@ module DataMapper
207
210
  unless default.nil? || (value = default_declaration).blank?
208
211
  @to_long_form << " #{value}"
209
212
  end
210
-
213
+
214
+ if unique? && !unique_declaration.blank?
215
+ @to_long_form << " #{unique_declaration}"
216
+ end
217
+
211
218
  @to_long_form
212
219
  end
213
220
  end
@@ -244,6 +251,10 @@ module DataMapper
244
251
  "AUTO_INCREMENT"
245
252
  end
246
253
 
254
+ def unique_declaration
255
+ "UNIQUE"
256
+ end
257
+
247
258
  def default_declaration
248
259
  @adapter.connection { |db| db.create_command("DEFAULT ?").escape_sql([default]) }
249
260
  end