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.
- data/CHANGELOG +16 -1
- data/README +10 -8
- data/environment.rb +1 -1
- data/example.rb +13 -5
- data/lib/data_mapper.rb +2 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +61 -0
- data/lib/data_mapper/adapters/data_object_adapter.rb +33 -6
- data/lib/data_mapper/adapters/mysql_adapter.rb +5 -0
- data/lib/data_mapper/adapters/postgresql_adapter.rb +12 -0
- data/lib/data_mapper/adapters/sql/commands/load_command.rb +6 -14
- data/lib/data_mapper/adapters/sql/mappings/column.rb +37 -26
- data/lib/data_mapper/adapters/sql/mappings/table.rb +50 -8
- data/lib/data_mapper/associations.rb +4 -5
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +2 -2
- data/lib/data_mapper/associations/has_many_association.rb +40 -13
- data/lib/data_mapper/associations/has_n_association.rb +1 -1
- data/lib/data_mapper/base.rb +5 -448
- data/lib/data_mapper/callbacks.rb +12 -2
- data/lib/data_mapper/context.rb +4 -0
- data/lib/data_mapper/database.rb +1 -1
- data/lib/data_mapper/identity_map.rb +2 -2
- data/lib/data_mapper/persistence.rb +538 -0
- data/lib/data_mapper/support/active_record_impersonation.rb +21 -3
- data/lib/data_mapper/support/errors.rb +2 -0
- data/lib/data_mapper/support/serialization.rb +7 -10
- data/lib/data_mapper/support/struct.rb +7 -0
- data/lib/data_mapper/validatable_extensions/validations/validates_uniqueness_of.rb +11 -4
- data/performance.rb +23 -10
- data/rakefile.rb +1 -1
- data/spec/active_record_impersonation_spec.rb +2 -6
- data/spec/acts_as_tree_spec.rb +3 -1
- data/spec/associations_spec.rb +40 -160
- data/spec/attributes_spec.rb +1 -1
- data/spec/base_spec.rb +41 -13
- data/spec/callbacks_spec.rb +32 -0
- data/spec/coersion_spec.rb +1 -1
- data/spec/column_spec.rb +22 -12
- data/spec/dependency_spec.rb +5 -3
- data/spec/embedded_value_spec.rb +33 -17
- data/spec/has_many_association_spec.rb +173 -0
- data/spec/legacy_spec.rb +2 -2
- data/spec/load_command_spec.rb +59 -7
- data/spec/models/animal.rb +6 -2
- data/spec/models/animals_exhibit.rb +3 -1
- data/spec/models/career.rb +2 -1
- data/spec/models/comment.rb +3 -1
- data/spec/models/exhibit.rb +3 -1
- data/spec/models/fruit.rb +3 -1
- data/spec/models/person.rb +10 -1
- data/spec/models/post.rb +3 -1
- data/spec/models/project.rb +3 -1
- data/spec/models/section.rb +3 -1
- data/spec/models/serializer.rb +3 -1
- data/spec/models/user.rb +3 -1
- data/spec/models/zoo.rb +3 -1
- data/spec/paranoia_spec.rb +3 -1
- data/spec/postgres_spec.rb +54 -0
- data/spec/save_command_spec.rb +9 -5
- data/spec/schema_spec.rb +0 -91
- data/spec/single_table_inheritance_spec.rb +8 -0
- data/spec/table_spec.rb +46 -0
- data/spec/validates_uniqueness_of_spec.rb +19 -1
- metadata +8 -10
- data/lib/data_mapper/associations/has_one_association.rb +0 -77
- 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
|
-
*
|
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
|
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::
|
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::
|
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
|
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
|
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
|
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
|
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::
|
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::
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
70
|
-
|
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
|
-
|
106
|
-
|
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
|