dm-core 0.10.1 → 0.10.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +29 -0
- data/.document +5 -0
- data/.gitignore +27 -0
- data/LICENSE +20 -0
- data/{README.txt → README.rdoc} +14 -3
- data/Rakefile +23 -22
- data/VERSION +1 -0
- data/dm-core.gemspec +201 -10
- data/lib/dm-core.rb +32 -23
- data/lib/dm-core/adapters.rb +0 -1
- data/lib/dm-core/adapters/data_objects_adapter.rb +230 -151
- data/lib/dm-core/adapters/mysql_adapter.rb +7 -8
- data/lib/dm-core/adapters/oracle_adapter.rb +39 -59
- data/lib/dm-core/adapters/postgres_adapter.rb +0 -1
- data/lib/dm-core/adapters/sqlite3_adapter.rb +5 -0
- data/lib/dm-core/adapters/sqlserver_adapter.rb +114 -0
- data/lib/dm-core/adapters/yaml_adapter.rb +0 -5
- data/lib/dm-core/associations/many_to_many.rb +118 -56
- data/lib/dm-core/associations/many_to_one.rb +48 -21
- data/lib/dm-core/associations/one_to_many.rb +8 -30
- data/lib/dm-core/associations/one_to_one.rb +1 -5
- data/lib/dm-core/associations/relationship.rb +89 -97
- data/lib/dm-core/collection.rb +299 -184
- data/lib/dm-core/core_ext/enumerable.rb +28 -0
- data/lib/dm-core/core_ext/kernel.rb +0 -2
- data/lib/dm-core/migrations.rb +314 -170
- data/lib/dm-core/model.rb +97 -66
- data/lib/dm-core/model/descendant_set.rb +1 -1
- data/lib/dm-core/model/hook.rb +0 -3
- data/lib/dm-core/model/property.rb +7 -10
- data/lib/dm-core/model/relationship.rb +79 -26
- data/lib/dm-core/model/scope.rb +3 -4
- data/lib/dm-core/property.rb +152 -90
- data/lib/dm-core/property_set.rb +18 -37
- data/lib/dm-core/query.rb +452 -153
- data/lib/dm-core/query/conditions/comparison.rb +266 -173
- data/lib/dm-core/query/conditions/operation.rb +499 -57
- data/lib/dm-core/query/direction.rb +0 -3
- data/lib/dm-core/query/operator.rb +0 -4
- data/lib/dm-core/query/path.rb +10 -12
- data/lib/dm-core/query/sort.rb +4 -10
- data/lib/dm-core/repository.rb +10 -6
- data/lib/dm-core/resource.rb +343 -148
- data/lib/dm-core/spec/adapter_shared_spec.rb +17 -1
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +277 -17
- data/lib/dm-core/support/chainable.rb +0 -2
- data/lib/dm-core/support/equalizer.rb +27 -3
- data/lib/dm-core/transaction.rb +75 -75
- data/lib/dm-core/type.rb +19 -5
- data/lib/dm-core/types/discriminator.rb +4 -4
- data/lib/dm-core/types/object.rb +2 -7
- data/lib/dm-core/types/paranoid_boolean.rb +8 -2
- data/lib/dm-core/types/paranoid_datetime.rb +8 -2
- data/lib/dm-core/version.rb +1 -1
- data/script/performance.rb +7 -7
- data/script/profile.rb +6 -6
- data/spec/lib/collection_helpers.rb +2 -2
- data/spec/lib/pending_helpers.rb +22 -3
- data/spec/lib/rspec_immediate_feedback_formatter.rb +1 -0
- data/spec/public/associations/many_to_many_spec.rb +6 -4
- data/spec/public/associations/many_to_one_spec.rb +10 -1
- data/spec/public/associations/many_to_one_with_boolean_cpk_spec.rb +39 -0
- data/spec/public/associations/one_to_many_spec.rb +4 -3
- data/spec/public/associations/one_to_one_spec.rb +19 -1
- data/spec/public/associations/one_to_one_with_boolean_cpk_spec.rb +45 -0
- data/spec/public/collection_spec.rb +4 -3
- data/spec/public/migrations_spec.rb +144 -0
- data/spec/public/model/relationship_spec.rb +115 -55
- data/spec/public/model_spec.rb +13 -13
- data/spec/public/property/object_spec.rb +106 -0
- data/spec/public/property_spec.rb +18 -14
- data/spec/public/resource_spec.rb +10 -1
- data/spec/public/sel_spec.rb +16 -49
- data/spec/public/setup_spec.rb +1 -1
- data/spec/public/shared/association_collection_shared_spec.rb +6 -14
- data/spec/public/shared/collection_finder_shared_spec.rb +267 -0
- data/spec/public/shared/collection_shared_spec.rb +214 -217
- data/spec/public/shared/finder_shared_spec.rb +259 -365
- data/spec/public/shared/resource_shared_spec.rb +524 -248
- data/spec/public/transaction_spec.rb +27 -3
- data/spec/public/types/discriminator_spec.rb +1 -1
- data/spec/rcov.opts +6 -0
- data/spec/semipublic/adapters/sqlserver_adapter_spec.rb +17 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +3 -20
- data/spec/semipublic/associations_spec.rb +2 -2
- data/spec/semipublic/collection_spec.rb +0 -32
- data/spec/semipublic/model_spec.rb +96 -0
- data/spec/semipublic/property_spec.rb +3 -3
- data/spec/semipublic/query/conditions/comparison_spec.rb +1719 -0
- data/spec/semipublic/query/conditions/operation_spec.rb +1292 -0
- data/spec/semipublic/query_spec.rb +1285 -144
- data/spec/semipublic/resource_spec.rb +0 -24
- data/spec/semipublic/shared/resource_shared_spec.rb +103 -38
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +15 -6
- data/tasks/ci.rake +1 -0
- data/tasks/metrics.rake +37 -0
- data/tasks/spec.rake +41 -0
- data/tasks/yard.rake +9 -0
- data/tasks/yardstick.rake +19 -0
- metadata +99 -29
- data/CONTRIBUTING +0 -51
- data/FAQ +0 -93
- data/History.txt +0 -27
- data/MIT-LICENSE +0 -22
- data/Manifest.txt +0 -121
- data/QUICKLINKS +0 -11
- data/SPECS +0 -35
- data/TODO +0 -1
- data/spec/semipublic/query/conditions_spec.rb +0 -528
- data/tasks/ci.rb +0 -24
- data/tasks/dm.rb +0 -58
- data/tasks/doc.rb +0 -17
- data/tasks/gemspec.rb +0 -23
- data/tasks/hoe.rb +0 -45
- data/tasks/install.rb +0 -18
@@ -10,25 +10,24 @@ module DataMapper
|
|
10
10
|
|
11
11
|
private
|
12
12
|
|
13
|
-
# TODO: document
|
14
13
|
# @api private
|
15
14
|
def supports_default_values? #:nodoc:
|
16
15
|
false
|
17
16
|
end
|
18
17
|
|
19
|
-
# TODO: document
|
20
18
|
# @api private
|
21
|
-
def
|
22
|
-
|
19
|
+
def supports_subquery?(query, source_key, target_key, qualify)
|
20
|
+
# TODO: renable once query does not include target_model for deletes and updates
|
21
|
+
# query.limit.nil?
|
22
|
+
|
23
|
+
false
|
23
24
|
end
|
24
25
|
|
25
|
-
# TODO: document
|
26
26
|
# @api private
|
27
|
-
def
|
28
|
-
'
|
27
|
+
def regexp_operator(operand)
|
28
|
+
'REGEXP'
|
29
29
|
end
|
30
30
|
|
31
|
-
# TODO: document
|
32
31
|
# @api private
|
33
32
|
def quote_name(name)
|
34
33
|
"`#{name[0, self.class::IDENTIFIER_MAX_LENGTH].gsub('`', '``')}`"
|
@@ -24,10 +24,12 @@ module DataMapper
|
|
24
24
|
def insert_statement(model, properties, serial)
|
25
25
|
statement = "INSERT INTO #{quote_name(model.storage_name(name))} "
|
26
26
|
|
27
|
+
no_properties = properties.empty?
|
27
28
|
custom_sequence = serial && serial.options[:sequence]
|
29
|
+
serial_field = serial && quote_name(serial.field)
|
28
30
|
|
29
|
-
if supports_default_values? &&
|
30
|
-
statement << "(#{
|
31
|
+
if supports_default_values? && no_properties && !custom_sequence
|
32
|
+
statement << "(#{serial_field}) " if serial
|
31
33
|
statement << default_values_clause
|
32
34
|
else
|
33
35
|
# do not use custom sequence if identity field was assigned a value
|
@@ -36,14 +38,14 @@ module DataMapper
|
|
36
38
|
end
|
37
39
|
statement << "("
|
38
40
|
if custom_sequence
|
39
|
-
statement << "#{
|
40
|
-
statement << ", " unless
|
41
|
+
statement << "#{serial_field}"
|
42
|
+
statement << ", " unless no_properties
|
41
43
|
end
|
42
|
-
statement << "#{properties.map { |
|
44
|
+
statement << "#{properties.map { |property| quote_name(property.field) }.join(', ')}) "
|
43
45
|
statement << "VALUES ("
|
44
46
|
if custom_sequence
|
45
47
|
statement << "#{quote_name(custom_sequence)}.NEXTVAL"
|
46
|
-
statement << ", " unless
|
48
|
+
statement << ", " unless no_properties
|
47
49
|
end
|
48
50
|
statement << "#{(['?'] * properties.size).join(', ')})"
|
49
51
|
end
|
@@ -60,7 +62,6 @@ module DataMapper
|
|
60
62
|
'VALUES (DEFAULT)'
|
61
63
|
end
|
62
64
|
|
63
|
-
# TODO: document
|
64
65
|
# @api private
|
65
66
|
def supports_returning?
|
66
67
|
true
|
@@ -78,6 +79,7 @@ module DataMapper
|
|
78
79
|
#
|
79
80
|
# @api private
|
80
81
|
def select_statement(query)
|
82
|
+
name = self.name
|
81
83
|
model = query.model
|
82
84
|
fields = query.fields
|
83
85
|
conditions = query.conditions
|
@@ -96,14 +98,16 @@ module DataMapper
|
|
96
98
|
qualify = query.links.any?
|
97
99
|
|
98
100
|
if query.unique?
|
99
|
-
group_by = fields.select { |
|
101
|
+
group_by = fields.select { |property| property.kind_of?(Property) }
|
100
102
|
end
|
101
103
|
|
102
104
|
# create subquery to find all valid keys and then use these keys to retrive all other columns
|
103
105
|
use_subquery = qualify
|
106
|
+
no_group_by = group_by.blank?
|
107
|
+
no_order = order.blank?
|
104
108
|
|
105
109
|
# when we can include ROWNUM condition in main WHERE clause
|
106
|
-
use_simple_rownum_limit = limit && (offset||0 == 0) &&
|
110
|
+
use_simple_rownum_limit = limit && (offset||0 == 0) && no_group_by && no_order
|
107
111
|
|
108
112
|
unless (limit && limit > 1) || offset > 0 || qualify
|
109
113
|
# TODO: move this method to Query, so that it walks the conditions
|
@@ -114,25 +118,31 @@ module DataMapper
|
|
114
118
|
|
115
119
|
# if a unique property is used, and there is no OR operator, then an ORDER
|
116
120
|
# and LIMIT are unecessary because it should only return a single row
|
117
|
-
if conditions.
|
118
|
-
conditions.any? { |operand| operand.
|
119
|
-
!conditions.any? { |operand| operand.
|
121
|
+
if conditions.respond_to?(:slug) && conditions.slug == :and &&
|
122
|
+
conditions.any? { |operand| operand.respond_to?(:slug) && operand.slug == :eql && operand.subject.respond_to?(:unique?) && operand.subject.unique? } &&
|
123
|
+
!conditions.any? { |operand| operand.respond_to?(:slug) && operand.slug == :or }
|
120
124
|
order = nil
|
125
|
+
no_order = true
|
121
126
|
limit = nil
|
122
127
|
end
|
123
128
|
end
|
124
129
|
|
125
130
|
conditions_statement, bind_values = conditions_statement(conditions, qualify)
|
126
131
|
|
132
|
+
model_key_column = columns_statement(model.key(name), qualify)
|
133
|
+
from_statement = " FROM #{quote_name(model.storage_name(name))}"
|
134
|
+
|
127
135
|
statement = "SELECT #{columns_statement(fields, qualify)}"
|
128
136
|
if use_subquery
|
129
|
-
statement <<
|
130
|
-
statement << " WHERE (#{
|
131
|
-
statement << " (SELECT DISTINCT #{
|
137
|
+
statement << from_statement
|
138
|
+
statement << " WHERE (#{model_key_column}) IN"
|
139
|
+
statement << " (SELECT DISTINCT #{model_key_column}"
|
140
|
+
# do not need to do group by for uniqueness as just one row per primary key will be returned
|
141
|
+
no_group_by = true
|
132
142
|
end
|
133
|
-
statement <<
|
134
|
-
statement << join_statement(query, qualify)
|
135
|
-
statement << " WHERE (#{conditions_statement})"
|
143
|
+
statement << from_statement
|
144
|
+
statement << join_statement(query, qualify) if qualify
|
145
|
+
statement << " WHERE (#{conditions_statement})" unless conditions_statement.blank?
|
136
146
|
if use_subquery
|
137
147
|
statement << ")"
|
138
148
|
end
|
@@ -140,8 +150,8 @@ module DataMapper
|
|
140
150
|
statement << " AND rownum <= ?"
|
141
151
|
bind_values << limit
|
142
152
|
end
|
143
|
-
statement << " GROUP BY #{columns_statement(group_by, qualify)}" unless
|
144
|
-
statement << " ORDER BY #{order_statement(order, qualify)}" unless
|
153
|
+
statement << " GROUP BY #{columns_statement(group_by, qualify)}" unless no_group_by
|
154
|
+
statement << " ORDER BY #{order_statement(order, qualify)}" unless no_order
|
145
155
|
|
146
156
|
add_limit_offset!(statement, limit, offset, bind_values) unless use_simple_rownum_limit
|
147
157
|
|
@@ -152,19 +162,20 @@ module DataMapper
|
|
152
162
|
# Functionality is mimiced through the use of nested selects.
|
153
163
|
# See http://asktom.oracle.com/pls/ask/f?p=4950:8:::::F4950_P8_DISPLAYID:127412348064
|
154
164
|
def add_limit_offset!(statement, limit, offset, bind_values)
|
155
|
-
|
165
|
+
positive_offset = offset > 0
|
166
|
+
|
167
|
+
if limit && positive_offset
|
156
168
|
statement.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{statement}) raw_sql_ where rownum <= ?) where raw_rnum_ > ?"
|
157
169
|
bind_values << offset + limit << offset
|
158
170
|
elsif limit
|
159
171
|
statement.replace "select raw_sql_.* from (#{statement}) raw_sql_ where rownum <= ?"
|
160
172
|
bind_values << limit
|
161
|
-
elsif
|
173
|
+
elsif positive_offset
|
162
174
|
statement.replace "select * from (select raw_sql_.*, rownum raw_rnum_ from (#{statement}) raw_sql_) where raw_rnum_ > ?"
|
163
175
|
bind_values << offset
|
164
176
|
end
|
165
177
|
end
|
166
178
|
|
167
|
-
# TODO: document
|
168
179
|
# @api private
|
169
180
|
# Oracle does not allow " in table or column names therefore substitute them with underscore
|
170
181
|
def quote_name(name)
|
@@ -181,25 +192,15 @@ module DataMapper
|
|
181
192
|
# NOTE: just first 32767 bytes will be compared!
|
182
193
|
# @api private
|
183
194
|
def equality_operator(property, operand)
|
184
|
-
if
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
# CLOB value should be compared using DBMS_LOB.SUBSTR function
|
192
|
-
# NOTE: just first 32767 bytes will be compared!
|
193
|
-
# @api private
|
194
|
-
def inequality_operator(property, operand)
|
195
|
-
if property.type == Types::Text
|
196
|
-
operand.nil? ? 'IS NOT' : 'DBMS_LOB.SUBSTR(%s) <> ?'
|
195
|
+
if operand.nil?
|
196
|
+
'IS'
|
197
|
+
elsif property.type == Types::Text
|
198
|
+
'DBMS_LOB.SUBSTR(%s) = ?'
|
197
199
|
else
|
198
|
-
|
200
|
+
'='
|
199
201
|
end
|
200
202
|
end
|
201
203
|
|
202
|
-
# TODO: document
|
203
204
|
# @api private
|
204
205
|
def include_operator(property, operand)
|
205
206
|
operator = case operand
|
@@ -213,32 +214,11 @@ module DataMapper
|
|
213
214
|
end
|
214
215
|
end
|
215
216
|
|
216
|
-
# TODO: document
|
217
|
-
# @api private
|
218
|
-
def exclude_operator(property, operand)
|
219
|
-
operator = case operand
|
220
|
-
when Array then 'NOT IN'
|
221
|
-
when Range then 'NOT BETWEEN'
|
222
|
-
end
|
223
|
-
if property.type == Types::Text
|
224
|
-
"DBMS_LOB.SUBSTR(%s) #{operator} ?"
|
225
|
-
else
|
226
|
-
operator
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
# TODO: document
|
231
217
|
# @api private
|
232
218
|
def regexp_operator(operand)
|
233
219
|
'REGEXP_LIKE(%s, ?)'
|
234
220
|
end
|
235
221
|
|
236
|
-
# TODO: document
|
237
|
-
# @api private
|
238
|
-
def not_regexp_operator(operand)
|
239
|
-
'NOT REGEXP_LIKE(%s, ?)'
|
240
|
-
end
|
241
|
-
|
242
222
|
end #module SQL
|
243
223
|
|
244
224
|
include SQL
|
@@ -5,6 +5,11 @@ require 'do_sqlite3'
|
|
5
5
|
module DataMapper
|
6
6
|
module Adapters
|
7
7
|
class Sqlite3Adapter < DataObjectsAdapter
|
8
|
+
# @api private
|
9
|
+
def supports_subquery?(query, source_key, target_key, qualify)
|
10
|
+
# SQLite3 cannot match a subquery against more than one column
|
11
|
+
source_key.size == 1 && target_key.size == 1
|
12
|
+
end
|
8
13
|
end # class Sqlite3Adapter
|
9
14
|
|
10
15
|
const_added(:Sqlite3Adapter)
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require DataMapper.root / 'lib' / 'dm-core' / 'adapters' / 'data_objects_adapter'
|
2
|
+
|
3
|
+
require 'do_sqlserver'
|
4
|
+
|
5
|
+
DataObjects::Sqlserver = DataObjects::SqlServer
|
6
|
+
|
7
|
+
module DataMapper
|
8
|
+
module Adapters
|
9
|
+
class SqlserverAdapter < DataObjectsAdapter
|
10
|
+
module SQL #:nodoc:
|
11
|
+
private
|
12
|
+
|
13
|
+
# Constructs INSERT statement for given query,
|
14
|
+
#
|
15
|
+
# @return [String] INSERT statement as a string
|
16
|
+
#
|
17
|
+
# @api private
|
18
|
+
def insert_statement(model, properties, serial)
|
19
|
+
statement = ""
|
20
|
+
# Check if there is a serial property being set directly
|
21
|
+
require_identity_insert = !properties.empty? && properties.any? { |property| property.serial? }
|
22
|
+
set_identity_insert(model, statement, true) if require_identity_insert
|
23
|
+
statement << super
|
24
|
+
set_identity_insert(model, statement, false) if require_identity_insert
|
25
|
+
statement
|
26
|
+
end
|
27
|
+
|
28
|
+
def set_identity_insert(model, statement, enable = true)
|
29
|
+
statement << " SET IDENTITY_INSERT #{quote_name(model.storage_name(name))} #{enable ? 'ON' : 'OFF'} "
|
30
|
+
end
|
31
|
+
|
32
|
+
def select_statement(query)
|
33
|
+
name = self.name
|
34
|
+
qualify = query.links.any?
|
35
|
+
fields = query.fields
|
36
|
+
offset = query.offset
|
37
|
+
limit = query.limit
|
38
|
+
order_by = query.order
|
39
|
+
group_by = if qualify || query.unique?
|
40
|
+
fields.select { |property| property.kind_of?(Property) }
|
41
|
+
end
|
42
|
+
|
43
|
+
conditions_statement, bind_values = conditions_statement(query.conditions, qualify)
|
44
|
+
|
45
|
+
use_limit_offset_subquery = limit && offset > 0
|
46
|
+
|
47
|
+
columns_statement = columns_statement(fields, qualify)
|
48
|
+
from_statement = " FROM #{quote_name(query.model.storage_name(name))}"
|
49
|
+
where_statement = " WHERE #{conditions_statement}" unless conditions_statement.blank?
|
50
|
+
join_statement = join_statement(query, qualify)
|
51
|
+
order_statement = order_statement(order_by, qualify)
|
52
|
+
no_group_by = group_by ? group_by.empty? : true
|
53
|
+
no_order_by = order_by ? order_by.empty? : true
|
54
|
+
|
55
|
+
if use_limit_offset_subquery
|
56
|
+
# If using qualifiers, we must qualify elements outside the subquery
|
57
|
+
# with 'RowResults' -- this is a different scope to the subquery.
|
58
|
+
# Otherwise, we hit upon "multi-part identifier cannot be bound"
|
59
|
+
# error from SQL Server.
|
60
|
+
statement = "SELECT #{columns_statement(fields, qualify, 'RowResults')}"
|
61
|
+
statement << " FROM ( SELECT Row_Number() OVER (ORDER BY #{order_statement}) AS RowID,"
|
62
|
+
statement << " #{columns_statement}"
|
63
|
+
statement << from_statement
|
64
|
+
statement << join_statement if qualify
|
65
|
+
statement << where_statement if where_statement
|
66
|
+
statement << ") AS RowResults"
|
67
|
+
statement << " WHERE RowId > #{offset} AND RowId <= #{offset + limit}"
|
68
|
+
statement << " GROUP BY #{columns_statement(group_by, qualify, 'RowResults')}" unless no_group_by
|
69
|
+
statement << " ORDER BY #{order_statement(order_by, qualify, 'RowResults')}" unless no_order_by
|
70
|
+
else
|
71
|
+
statement = "SELECT #{columns_statement}"
|
72
|
+
statement << from_statement
|
73
|
+
statement << join_statement if qualify
|
74
|
+
statement << where_statement if where_statement
|
75
|
+
statement << " GROUP BY #{columns_statement(group_by, qualify)}" unless no_group_by
|
76
|
+
statement << " ORDER BY #{order_statement}" unless no_order_by
|
77
|
+
end
|
78
|
+
|
79
|
+
add_limit_offset!(statement, limit, offset, bind_values) unless use_limit_offset_subquery
|
80
|
+
|
81
|
+
return statement, bind_values
|
82
|
+
end
|
83
|
+
|
84
|
+
# SQL Server does not support LIMIT and OFFSET
|
85
|
+
# Functionality therefore must be mimicked through the use of nested selects.
|
86
|
+
# See also:
|
87
|
+
# - http://stackoverflow.com/questions/2840/paging-sql-server-2005-results
|
88
|
+
# - http://stackoverflow.com/questions/216673/emulate-mysql-limit-clause-in-microsoft-sql-server-2000
|
89
|
+
#
|
90
|
+
def add_limit_offset!(statement, limit, offset, bind_values)
|
91
|
+
# Limit and offset is handled by subqueries (see #select_statement).
|
92
|
+
if limit
|
93
|
+
# If there is just a limit on rows to return, but no offset, then we
|
94
|
+
# can use TOP clause.
|
95
|
+
statement.sub!(/^\s*SELECT(\s+DISTINCT)?/i) { "SELECT#{$1} TOP #{limit}" }
|
96
|
+
# bind_values << limit
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# @api private
|
101
|
+
# TODO: Not actually supported out of the box. Is theoretically possible
|
102
|
+
# via CLR integration, custom functions.
|
103
|
+
def regexp_operator(operand)
|
104
|
+
'REGEXP'
|
105
|
+
end
|
106
|
+
|
107
|
+
end #module SQL
|
108
|
+
|
109
|
+
include SQL
|
110
|
+
end # class SqlserverAdapter
|
111
|
+
|
112
|
+
const_added(:SqlserverAdapter)
|
113
|
+
end # module Adapters
|
114
|
+
end # module DataMapper
|
@@ -4,7 +4,6 @@ require 'yaml'
|
|
4
4
|
module DataMapper
|
5
5
|
module Adapters
|
6
6
|
class YamlAdapter < AbstractAdapter
|
7
|
-
# TODO: document
|
8
7
|
# @api semipublic
|
9
8
|
def create(resources)
|
10
9
|
update_records(resources.first.model) do |records|
|
@@ -15,13 +14,11 @@ module DataMapper
|
|
15
14
|
end
|
16
15
|
end
|
17
16
|
|
18
|
-
# TODO: document
|
19
17
|
# @api semipublic
|
20
18
|
def read(query)
|
21
19
|
query.filter_records(records_for(query.model).dup)
|
22
20
|
end
|
23
21
|
|
24
|
-
# TODO: document
|
25
22
|
# @api semipublic
|
26
23
|
def update(attributes, collection)
|
27
24
|
attributes = attributes_as_fields(attributes)
|
@@ -32,7 +29,6 @@ module DataMapper
|
|
32
29
|
end
|
33
30
|
end
|
34
31
|
|
35
|
-
# TODO: document
|
36
32
|
# @api semipublic
|
37
33
|
def delete(collection)
|
38
34
|
update_records(collection.model) do |records|
|
@@ -44,7 +40,6 @@ module DataMapper
|
|
44
40
|
|
45
41
|
private
|
46
42
|
|
47
|
-
# TODO: document
|
48
43
|
# @api semipublic
|
49
44
|
def initialize(name, options = {})
|
50
45
|
super
|
@@ -26,7 +26,6 @@ module DataMapper
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
-
# TODO: document
|
30
29
|
# @api semipublic
|
31
30
|
alias target_key child_key
|
32
31
|
|
@@ -52,17 +51,20 @@ module DataMapper
|
|
52
51
|
def through
|
53
52
|
return @through if defined?(@through)
|
54
53
|
|
55
|
-
|
56
|
-
|
54
|
+
@through = options[:through]
|
55
|
+
|
56
|
+
if @through.kind_of?(Associations::Relationship)
|
57
|
+
return @through
|
57
58
|
end
|
58
59
|
|
60
|
+
model = source_model
|
59
61
|
repository_name = source_repository_name
|
60
|
-
relationships =
|
62
|
+
relationships = model.relationships(repository_name)
|
61
63
|
name = through_relationship_name
|
62
64
|
|
63
65
|
@through = relationships[name] ||
|
64
66
|
DataMapper.repository(repository_name) do
|
65
|
-
|
67
|
+
model.has(min..max, name, through_model, one_to_many_options)
|
66
68
|
end
|
67
69
|
|
68
70
|
@through.child_key
|
@@ -70,22 +72,25 @@ module DataMapper
|
|
70
72
|
@through
|
71
73
|
end
|
72
74
|
|
73
|
-
# TODO: document
|
74
75
|
# @api semipublic
|
75
76
|
def via
|
76
77
|
return @via if defined?(@via)
|
77
78
|
|
78
|
-
|
79
|
-
|
79
|
+
@via = options[:via]
|
80
|
+
|
81
|
+
if @via.kind_of?(Associations::Relationship)
|
82
|
+
return @via
|
80
83
|
end
|
81
84
|
|
85
|
+
name = self.name
|
86
|
+
through = self.through
|
82
87
|
repository_name = through.relative_target_repository_name
|
83
88
|
through_model = through.target_model
|
84
89
|
relationships = through_model.relationships(repository_name)
|
85
90
|
singular_name = name.to_s.singularize.to_sym
|
86
91
|
|
87
|
-
@via = relationships[
|
88
|
-
relationships[name]
|
92
|
+
@via = relationships[@via] ||
|
93
|
+
relationships[name] ||
|
89
94
|
relationships[singular_name]
|
90
95
|
|
91
96
|
@via ||= if anonymous_through_model?
|
@@ -101,7 +106,6 @@ module DataMapper
|
|
101
106
|
@via
|
102
107
|
end
|
103
108
|
|
104
|
-
# TODO: document
|
105
109
|
# @api semipublic
|
106
110
|
def links
|
107
111
|
return @links if defined?(@links)
|
@@ -120,13 +124,11 @@ module DataMapper
|
|
120
124
|
@links.freeze
|
121
125
|
end
|
122
126
|
|
123
|
-
# TODO: document
|
124
127
|
# @api private
|
125
128
|
def source_scope(source)
|
126
129
|
{ through.inverse => source }
|
127
130
|
end
|
128
131
|
|
129
|
-
# TODO: document
|
130
132
|
# @api private
|
131
133
|
def query
|
132
134
|
# TODO: consider making this a query_for method, so that ManyToMany::Relationship#query only
|
@@ -152,7 +154,6 @@ module DataMapper
|
|
152
154
|
|
153
155
|
private
|
154
156
|
|
155
|
-
# TODO: document
|
156
157
|
# @api private
|
157
158
|
def through_model
|
158
159
|
namespace, name = through_model_namespace_name
|
@@ -173,7 +174,6 @@ module DataMapper
|
|
173
174
|
end
|
174
175
|
end
|
175
176
|
|
176
|
-
# TODO: document
|
177
177
|
# @api private
|
178
178
|
def through_model_namespace_name
|
179
179
|
target_parts = target_model.base_model.name.split('::')
|
@@ -192,7 +192,6 @@ module DataMapper
|
|
192
192
|
return namespace, name
|
193
193
|
end
|
194
194
|
|
195
|
-
# TODO: document
|
196
195
|
# @api private
|
197
196
|
def through_relationship_name
|
198
197
|
if anonymous_through_model?
|
@@ -218,7 +217,39 @@ module DataMapper
|
|
218
217
|
options[:through] == Resource
|
219
218
|
end
|
220
219
|
|
221
|
-
#
|
220
|
+
# @api private
|
221
|
+
def nearest_relationship
|
222
|
+
return @nearest_relationship if defined?(@nearest_relationship)
|
223
|
+
|
224
|
+
nearest_relationship = self
|
225
|
+
|
226
|
+
while nearest_relationship.respond_to?(:through)
|
227
|
+
nearest_relationship = nearest_relationship.through
|
228
|
+
end
|
229
|
+
|
230
|
+
@nearest_relationship = nearest_relationship
|
231
|
+
end
|
232
|
+
|
233
|
+
# @api private
|
234
|
+
def valid_target?(target)
|
235
|
+
relationship = via
|
236
|
+
source_key = relationship.source_key
|
237
|
+
target_key = relationship.target_key
|
238
|
+
|
239
|
+
target.kind_of?(target_model) &&
|
240
|
+
source_key.valid?(target_key.get(target))
|
241
|
+
end
|
242
|
+
|
243
|
+
# @api private
|
244
|
+
def valid_source?(source)
|
245
|
+
relationship = nearest_relationship
|
246
|
+
source_key = relationship.source_key
|
247
|
+
target_key = relationship.target_key
|
248
|
+
|
249
|
+
source.kind_of?(source_model) &&
|
250
|
+
target_key.valid?(source_key.get(source))
|
251
|
+
end
|
252
|
+
|
222
253
|
# @api semipublic
|
223
254
|
chainable do
|
224
255
|
def many_to_one_options
|
@@ -226,7 +257,6 @@ module DataMapper
|
|
226
257
|
end
|
227
258
|
end
|
228
259
|
|
229
|
-
# TODO: document
|
230
260
|
# @api semipublic
|
231
261
|
chainable do
|
232
262
|
def one_to_many_options
|
@@ -241,13 +271,11 @@ module DataMapper
|
|
241
271
|
self.class
|
242
272
|
end
|
243
273
|
|
244
|
-
# TODO: document
|
245
274
|
# @api private
|
246
275
|
def invert
|
247
276
|
inverse_class.new(inverse_name, parent_model, child_model, inverted_options)
|
248
277
|
end
|
249
278
|
|
250
|
-
# TODO: document
|
251
279
|
# @api private
|
252
280
|
def inverted_options
|
253
281
|
links = self.links.dup
|
@@ -264,6 +292,8 @@ module DataMapper
|
|
264
292
|
)
|
265
293
|
end
|
266
294
|
|
295
|
+
options = self.options
|
296
|
+
|
267
297
|
options.only(*OPTIONS - [ :min, :max ]).update(
|
268
298
|
:through => through,
|
269
299
|
:child_key => options[:parent_key],
|
@@ -312,7 +342,7 @@ module DataMapper
|
|
312
342
|
# the intermediaries are removed
|
313
343
|
lazy_load
|
314
344
|
|
315
|
-
unless intermediaries.destroy
|
345
|
+
unless intermediaries.all(via => self).destroy
|
316
346
|
return false
|
317
347
|
end
|
318
348
|
|
@@ -332,45 +362,71 @@ module DataMapper
|
|
332
362
|
def destroy!
|
333
363
|
assert_source_saved 'The source must be saved before mass-deleting the collection'
|
334
364
|
|
335
|
-
|
336
|
-
|
337
|
-
|
365
|
+
model = self.model
|
366
|
+
key = model.key(repository_name)
|
367
|
+
conditions = Query.target_conditions(self, key, key)
|
338
368
|
|
339
|
-
unless intermediaries.destroy!
|
369
|
+
unless intermediaries.all(via => self).destroy!
|
340
370
|
return false
|
341
371
|
end
|
342
372
|
|
343
|
-
|
373
|
+
unless model.all(:repository => repository, :conditions => conditions).destroy!
|
374
|
+
return false
|
375
|
+
end
|
376
|
+
|
377
|
+
each { |resource| resource.reset }
|
378
|
+
clear
|
379
|
+
|
380
|
+
true
|
344
381
|
end
|
345
382
|
|
346
|
-
# Return the intermediaries
|
383
|
+
# Return the intermediaries linking the source to the targets
|
347
384
|
#
|
348
385
|
# @return [Collection]
|
349
386
|
# the intermediary collection
|
350
387
|
#
|
351
388
|
# @api public
|
352
389
|
def intermediaries
|
353
|
-
|
390
|
+
through = self.through
|
391
|
+
source = self.source
|
354
392
|
|
355
|
-
intermediaries
|
393
|
+
@intermediaries ||= if through.loaded?(source)
|
356
394
|
through.get!(source)
|
357
395
|
else
|
358
|
-
|
396
|
+
reset_intermediaries
|
359
397
|
end
|
398
|
+
end
|
360
399
|
|
361
|
-
|
400
|
+
protected
|
362
401
|
|
363
|
-
|
402
|
+
# Map the resources in the collection to the intermediaries
|
403
|
+
#
|
404
|
+
# @return [Hash]
|
405
|
+
# the map of resources to their intermediaries
|
406
|
+
#
|
407
|
+
# @api private
|
408
|
+
def intermediary_for
|
409
|
+
@intermediary_for ||= {}
|
410
|
+
end
|
411
|
+
|
412
|
+
# @api private
|
413
|
+
def through
|
414
|
+
relationship.through
|
415
|
+
end
|
416
|
+
|
417
|
+
# @api private
|
418
|
+
def via
|
419
|
+
relationship.via
|
364
420
|
end
|
365
421
|
|
366
422
|
private
|
367
423
|
|
368
|
-
# TODO: document
|
369
424
|
# @api private
|
370
425
|
def _create(safe, attributes)
|
426
|
+
via = self.via
|
371
427
|
if via.respond_to?(:resource_for)
|
372
428
|
resource = super
|
373
|
-
if create_intermediary(safe,
|
429
|
+
if create_intermediary(safe, resource)
|
374
430
|
resource
|
375
431
|
end
|
376
432
|
else
|
@@ -380,21 +436,23 @@ module DataMapper
|
|
380
436
|
end
|
381
437
|
end
|
382
438
|
|
383
|
-
# TODO: document
|
384
439
|
# @api private
|
385
440
|
def _save(safe)
|
441
|
+
via = self.via
|
442
|
+
|
386
443
|
if @removed.any?
|
387
444
|
# delete only intermediaries linked to the removed targets
|
388
|
-
|
389
|
-
intermediaries.delete(resource)
|
390
|
-
end
|
445
|
+
return false unless intermediaries.all(via => @removed).send(safe ? :destroy : :destroy!)
|
391
446
|
|
392
|
-
|
447
|
+
# reset the intermediaries so that it reflects the current state of the datastore
|
448
|
+
reset_intermediaries
|
393
449
|
end
|
394
450
|
|
451
|
+
loaded_entries = self.loaded_entries
|
452
|
+
|
395
453
|
if via.respond_to?(:resource_for)
|
396
454
|
super
|
397
|
-
loaded_entries.all? { |resource| create_intermediary(safe,
|
455
|
+
loaded_entries.all? { |resource| create_intermediary(safe, resource) }
|
398
456
|
else
|
399
457
|
if intermediary = create_intermediary(safe)
|
400
458
|
inverse = via.inverse
|
@@ -405,32 +463,36 @@ module DataMapper
|
|
405
463
|
end
|
406
464
|
end
|
407
465
|
|
408
|
-
# TODO: document
|
409
466
|
# @api private
|
410
|
-
def create_intermediary(safe,
|
411
|
-
|
467
|
+
def create_intermediary(safe, resource = nil)
|
468
|
+
intermediary_for = self.intermediary_for
|
412
469
|
|
413
|
-
|
470
|
+
intermediary_resource = intermediary_for[resource]
|
471
|
+
return intermediary_resource if intermediary_resource
|
414
472
|
|
415
|
-
|
416
|
-
|
473
|
+
intermediaries = self.intermediaries
|
474
|
+
method = safe ? :save : :save!
|
417
475
|
|
418
|
-
return
|
419
|
-
end
|
476
|
+
return unless intermediaries.send(method)
|
420
477
|
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
478
|
+
attributes = {}
|
479
|
+
attributes[via] = resource if resource
|
480
|
+
|
481
|
+
intermediary = intermediaries.first_or_new(attributes)
|
482
|
+
return unless intermediary.__send__(method)
|
483
|
+
|
484
|
+
# map the resource, even if it is nil, to the intermediary
|
485
|
+
intermediary_for[resource] = intermediary
|
425
486
|
end
|
426
487
|
|
427
|
-
# TODO: document
|
428
488
|
# @api private
|
429
|
-
def
|
430
|
-
|
489
|
+
def reset_intermediaries
|
490
|
+
through = self.through
|
491
|
+
source = self.source
|
492
|
+
|
493
|
+
through.set!(source, through.collection_for(source))
|
431
494
|
end
|
432
495
|
|
433
|
-
# TODO: document
|
434
496
|
# @api private
|
435
497
|
def inverse_set(*)
|
436
498
|
# do nothing
|