datamapper 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|