datamapper-dm-core 0.9.11 → 0.10.0
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/.autotest +17 -14
- data/.gitignore +3 -1
- data/FAQ +6 -5
- data/History.txt +5 -39
- data/Manifest.txt +67 -76
- data/QUICKLINKS +1 -1
- data/README.txt +21 -15
- data/Rakefile +16 -15
- data/SPECS +2 -29
- data/TODO +1 -1
- data/dm-core.gemspec +11 -15
- data/lib/dm-core/adapters/abstract_adapter.rb +182 -185
- data/lib/dm-core/adapters/data_objects_adapter.rb +482 -534
- data/lib/dm-core/adapters/in_memory_adapter.rb +90 -69
- data/lib/dm-core/adapters/mysql_adapter.rb +22 -115
- data/lib/dm-core/adapters/oracle_adapter.rb +249 -0
- data/lib/dm-core/adapters/postgres_adapter.rb +7 -173
- data/lib/dm-core/adapters/sqlite3_adapter.rb +4 -97
- data/lib/dm-core/adapters/yaml_adapter.rb +116 -0
- data/lib/dm-core/adapters.rb +135 -16
- data/lib/dm-core/associations/many_to_many.rb +372 -90
- data/lib/dm-core/associations/many_to_one.rb +220 -73
- data/lib/dm-core/associations/one_to_many.rb +319 -255
- data/lib/dm-core/associations/one_to_one.rb +66 -53
- data/lib/dm-core/associations/relationship.rb +560 -158
- data/lib/dm-core/collection.rb +1104 -381
- data/lib/dm-core/core_ext/kernel.rb +12 -0
- data/lib/dm-core/core_ext/symbol.rb +10 -0
- data/lib/dm-core/identity_map.rb +4 -34
- data/lib/dm-core/migrations.rb +1283 -0
- data/lib/dm-core/model/descendant_set.rb +81 -0
- data/lib/dm-core/model/hook.rb +45 -0
- data/lib/dm-core/model/is.rb +32 -0
- data/lib/dm-core/model/property.rb +248 -0
- data/lib/dm-core/model/relationship.rb +335 -0
- data/lib/dm-core/model/scope.rb +90 -0
- data/lib/dm-core/model.rb +570 -369
- data/lib/dm-core/property.rb +753 -280
- data/lib/dm-core/property_set.rb +141 -98
- data/lib/dm-core/query/conditions/comparison.rb +814 -0
- data/lib/dm-core/query/conditions/operation.rb +247 -0
- data/lib/dm-core/query/direction.rb +43 -0
- data/lib/dm-core/query/operator.rb +42 -0
- data/lib/dm-core/query/path.rb +102 -0
- data/lib/dm-core/query/sort.rb +45 -0
- data/lib/dm-core/query.rb +974 -492
- data/lib/dm-core/repository.rb +147 -107
- data/lib/dm-core/resource.rb +644 -429
- data/lib/dm-core/spec/adapter_shared_spec.rb +294 -0
- data/lib/dm-core/spec/data_objects_adapter_shared_spec.rb +106 -0
- data/lib/dm-core/support/chainable.rb +20 -0
- data/lib/dm-core/support/deprecate.rb +12 -0
- data/lib/dm-core/support/equalizer.rb +23 -0
- data/lib/dm-core/support/logger.rb +13 -0
- data/lib/dm-core/{naming_conventions.rb → support/naming_conventions.rb} +6 -6
- data/lib/dm-core/transaction.rb +333 -92
- data/lib/dm-core/type.rb +98 -60
- data/lib/dm-core/types/boolean.rb +1 -1
- data/lib/dm-core/types/discriminator.rb +34 -20
- data/lib/dm-core/types/object.rb +7 -4
- data/lib/dm-core/types/paranoid_boolean.rb +11 -9
- data/lib/dm-core/types/paranoid_datetime.rb +11 -9
- data/lib/dm-core/types/serial.rb +3 -3
- data/lib/dm-core/types/text.rb +3 -4
- data/lib/dm-core/version.rb +1 -1
- data/lib/dm-core.rb +106 -110
- data/script/performance.rb +102 -109
- data/script/profile.rb +169 -38
- data/spec/lib/adapter_helpers.rb +105 -0
- data/spec/lib/collection_helpers.rb +18 -0
- data/spec/lib/counter_adapter.rb +34 -0
- data/spec/lib/pending_helpers.rb +27 -0
- data/spec/lib/rspec_immediate_feedback_formatter.rb +53 -0
- data/spec/public/associations/many_to_many_spec.rb +193 -0
- data/spec/public/associations/many_to_one_spec.rb +73 -0
- data/spec/public/associations/one_to_many_spec.rb +77 -0
- data/spec/public/associations/one_to_one_spec.rb +156 -0
- data/spec/public/collection_spec.rb +65 -0
- data/spec/public/model/relationship_spec.rb +924 -0
- data/spec/public/model_spec.rb +159 -0
- data/spec/public/property_spec.rb +829 -0
- data/spec/public/resource_spec.rb +71 -0
- data/spec/public/sel_spec.rb +44 -0
- data/spec/public/setup_spec.rb +145 -0
- data/spec/public/shared/association_collection_shared_spec.rb +317 -0
- data/spec/public/shared/collection_shared_spec.rb +1723 -0
- data/spec/public/shared/finder_shared_spec.rb +1619 -0
- data/spec/public/shared/resource_shared_spec.rb +924 -0
- data/spec/public/shared/sel_shared_spec.rb +112 -0
- data/spec/public/transaction_spec.rb +129 -0
- data/spec/public/types/discriminator_spec.rb +130 -0
- data/spec/semipublic/adapters/abstract_adapter_spec.rb +30 -0
- data/spec/semipublic/adapters/in_memory_adapter_spec.rb +12 -0
- data/spec/semipublic/adapters/mysql_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/oracle_adapter_spec.rb +194 -0
- data/spec/semipublic/adapters/postgres_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/sqlite3_adapter_spec.rb +17 -0
- data/spec/semipublic/adapters/yaml_adapter_spec.rb +12 -0
- data/spec/semipublic/associations/many_to_one_spec.rb +53 -0
- data/spec/semipublic/associations/relationship_spec.rb +194 -0
- data/spec/semipublic/associations_spec.rb +177 -0
- data/spec/semipublic/collection_spec.rb +142 -0
- data/spec/semipublic/property_spec.rb +61 -0
- data/spec/semipublic/query/conditions_spec.rb +528 -0
- data/spec/semipublic/query/path_spec.rb +443 -0
- data/spec/semipublic/query_spec.rb +2626 -0
- data/spec/semipublic/resource_spec.rb +47 -0
- data/spec/semipublic/shared/resource_shared_spec.rb +126 -0
- data/spec/spec.opts +3 -1
- data/spec/spec_helper.rb +80 -57
- data/tasks/ci.rb +19 -31
- data/tasks/dm.rb +43 -48
- data/tasks/doc.rb +8 -11
- data/tasks/gemspec.rb +5 -5
- data/tasks/hoe.rb +15 -16
- data/tasks/install.rb +8 -10
- metadata +72 -93
- data/lib/dm-core/associations/relationship_chain.rb +0 -81
- data/lib/dm-core/associations.rb +0 -207
- data/lib/dm-core/auto_migrations.rb +0 -105
- data/lib/dm-core/dependency_queue.rb +0 -32
- data/lib/dm-core/hook.rb +0 -11
- data/lib/dm-core/is.rb +0 -16
- data/lib/dm-core/logger.rb +0 -232
- data/lib/dm-core/migrations/destructive_migrations.rb +0 -17
- data/lib/dm-core/migrator.rb +0 -29
- data/lib/dm-core/scope.rb +0 -58
- data/lib/dm-core/support/array.rb +0 -13
- data/lib/dm-core/support/assertions.rb +0 -8
- data/lib/dm-core/support/errors.rb +0 -23
- data/lib/dm-core/support/kernel.rb +0 -11
- data/lib/dm-core/support/symbol.rb +0 -41
- data/lib/dm-core/support.rb +0 -7
- data/lib/dm-core/type_map.rb +0 -80
- data/lib/dm-core/types.rb +0 -19
- data/script/all +0 -4
- data/spec/integration/association_spec.rb +0 -1382
- data/spec/integration/association_through_spec.rb +0 -203
- data/spec/integration/associations/many_to_many_spec.rb +0 -449
- data/spec/integration/associations/many_to_one_spec.rb +0 -163
- data/spec/integration/associations/one_to_many_spec.rb +0 -188
- data/spec/integration/auto_migrations_spec.rb +0 -413
- data/spec/integration/collection_spec.rb +0 -1073
- data/spec/integration/data_objects_adapter_spec.rb +0 -32
- data/spec/integration/dependency_queue_spec.rb +0 -46
- data/spec/integration/model_spec.rb +0 -197
- data/spec/integration/mysql_adapter_spec.rb +0 -85
- data/spec/integration/postgres_adapter_spec.rb +0 -731
- data/spec/integration/property_spec.rb +0 -253
- data/spec/integration/query_spec.rb +0 -514
- data/spec/integration/repository_spec.rb +0 -61
- data/spec/integration/resource_spec.rb +0 -513
- data/spec/integration/sqlite3_adapter_spec.rb +0 -352
- data/spec/integration/sti_spec.rb +0 -273
- data/spec/integration/strategic_eager_loading_spec.rb +0 -156
- data/spec/integration/transaction_spec.rb +0 -75
- data/spec/integration/type_spec.rb +0 -275
- data/spec/lib/logging_helper.rb +0 -18
- data/spec/lib/mock_adapter.rb +0 -27
- data/spec/lib/model_loader.rb +0 -100
- data/spec/lib/publicize_methods.rb +0 -28
- data/spec/models/content.rb +0 -16
- data/spec/models/vehicles.rb +0 -34
- data/spec/models/zoo.rb +0 -48
- data/spec/unit/adapters/abstract_adapter_spec.rb +0 -133
- data/spec/unit/adapters/adapter_shared_spec.rb +0 -15
- data/spec/unit/adapters/data_objects_adapter_spec.rb +0 -632
- data/spec/unit/adapters/in_memory_adapter_spec.rb +0 -98
- data/spec/unit/adapters/postgres_adapter_spec.rb +0 -133
- data/spec/unit/associations/many_to_many_spec.rb +0 -32
- data/spec/unit/associations/many_to_one_spec.rb +0 -159
- data/spec/unit/associations/one_to_many_spec.rb +0 -393
- data/spec/unit/associations/one_to_one_spec.rb +0 -7
- data/spec/unit/associations/relationship_spec.rb +0 -71
- data/spec/unit/associations_spec.rb +0 -242
- data/spec/unit/auto_migrations_spec.rb +0 -111
- data/spec/unit/collection_spec.rb +0 -182
- data/spec/unit/data_mapper_spec.rb +0 -35
- data/spec/unit/identity_map_spec.rb +0 -126
- data/spec/unit/is_spec.rb +0 -80
- data/spec/unit/migrator_spec.rb +0 -33
- data/spec/unit/model_spec.rb +0 -321
- data/spec/unit/naming_conventions_spec.rb +0 -36
- data/spec/unit/property_set_spec.rb +0 -90
- data/spec/unit/property_spec.rb +0 -753
- data/spec/unit/query_spec.rb +0 -571
- data/spec/unit/repository_spec.rb +0 -93
- data/spec/unit/resource_spec.rb +0 -649
- data/spec/unit/scope_spec.rb +0 -142
- data/spec/unit/transaction_spec.rb +0 -493
- data/spec/unit/type_map_spec.rb +0 -114
- data/spec/unit/type_spec.rb +0 -119
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# TODO: update Model#respond_to? to return true if method_method missing
|
|
2
|
+
# would handle the message
|
|
3
|
+
|
|
4
|
+
module DataMapper
|
|
5
|
+
module Model
|
|
6
|
+
module Relationship
|
|
7
|
+
Model.append_extensions self
|
|
8
|
+
|
|
9
|
+
include Extlib::Assertions
|
|
10
|
+
extend Chainable
|
|
11
|
+
|
|
12
|
+
# Initializes relationships hash for extended model
|
|
13
|
+
# class.
|
|
14
|
+
#
|
|
15
|
+
# When model calls has n, has 1 or belongs_to, relationships
|
|
16
|
+
# are stored in that hash: keys are repository names and
|
|
17
|
+
# values are relationship sets.
|
|
18
|
+
#
|
|
19
|
+
# @api private
|
|
20
|
+
def self.extended(model)
|
|
21
|
+
model.instance_variable_set(:@relationships, {})
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
chainable do
|
|
25
|
+
# When DataMapper model is inherited, relationships
|
|
26
|
+
# of parent are duplicated and copied to subclass model
|
|
27
|
+
#
|
|
28
|
+
# @api private
|
|
29
|
+
def inherited(model)
|
|
30
|
+
# TODO: Create a RelationshipSet class, and then add a method that allows copying the relationships to the supplied repository and model
|
|
31
|
+
model.instance_variable_set(:@relationships, duped_relationships = {})
|
|
32
|
+
|
|
33
|
+
@relationships.each do |repository_name, relationships|
|
|
34
|
+
dup = duped_relationships[repository_name] ||= Mash.new
|
|
35
|
+
|
|
36
|
+
relationships.each do |name, relationship|
|
|
37
|
+
dup[name] = relationship.inherited_by(model)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
super
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns copy of relationships set in given repository.
|
|
46
|
+
#
|
|
47
|
+
# @param [Symbol] repository_name
|
|
48
|
+
# Name of the repository for which relationships set is returned
|
|
49
|
+
# @return [Mash] relationships set for given repository
|
|
50
|
+
#
|
|
51
|
+
# @api semipublic
|
|
52
|
+
def relationships(repository_name = default_repository_name)
|
|
53
|
+
# TODO: create RelationshipSet#copy that will copy the relationships, but assign the
|
|
54
|
+
# new Relationship objects to a supplied repository and model. dup does not really
|
|
55
|
+
# do what is needed
|
|
56
|
+
|
|
57
|
+
@relationships[repository_name] ||= if repository_name == default_repository_name
|
|
58
|
+
Mash.new
|
|
59
|
+
else
|
|
60
|
+
relationships(default_repository_name).dup
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Used to express unlimited cardinality of association,
|
|
65
|
+
# see +has+
|
|
66
|
+
#
|
|
67
|
+
# @api public
|
|
68
|
+
def n
|
|
69
|
+
1.0/0
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# A shorthand, clear syntax for defining one-to-one, one-to-many and
|
|
73
|
+
# many-to-many resource relationships.
|
|
74
|
+
#
|
|
75
|
+
# * has 1, :friend # one friend
|
|
76
|
+
# * has n, :friends # many friends
|
|
77
|
+
# * has 1..3, :friends # many friends (at least 1, at most 3)
|
|
78
|
+
# * has 3, :friends # many friends (exactly 3)
|
|
79
|
+
# * has 1, :friend, 'User' # one friend with the class User
|
|
80
|
+
# * has 3, :friends, :through => :friendships # many friends through the friendships relationship
|
|
81
|
+
#
|
|
82
|
+
# @param cardinality [Integer, Range, Infinity]
|
|
83
|
+
# cardinality that defines the association type and constraints
|
|
84
|
+
# @param name [Symbol]
|
|
85
|
+
# the name that the association will be referenced by
|
|
86
|
+
# @param model [Model, #to_str]
|
|
87
|
+
# the target model of the relationship
|
|
88
|
+
# @param opts [Hash]
|
|
89
|
+
# an options hash
|
|
90
|
+
#
|
|
91
|
+
# @option :through[Symbol] A association that this join should go through to form
|
|
92
|
+
# a many-to-many association
|
|
93
|
+
# @option :model[Model, String] The name of the class to associate with, if omitted
|
|
94
|
+
# then the association name is assumed to match the class name
|
|
95
|
+
# @option :repository[Symbol]
|
|
96
|
+
# name of child model repository
|
|
97
|
+
#
|
|
98
|
+
# @return [Association::Relationship] the relationship that was
|
|
99
|
+
# created to reflect either a one-to-one, one-to-many or many-to-many
|
|
100
|
+
# relationship
|
|
101
|
+
# @raise [ArgumentError] if the cardinality was not understood. Should be a
|
|
102
|
+
# Integer, Range or Infinity(n)
|
|
103
|
+
#
|
|
104
|
+
# @api public
|
|
105
|
+
def has(cardinality, name, *args)
|
|
106
|
+
assert_kind_of 'cardinality', cardinality, Integer, Range, n.class
|
|
107
|
+
assert_kind_of 'name', name, Symbol
|
|
108
|
+
|
|
109
|
+
model = extract_model(args)
|
|
110
|
+
options = extract_options(args)
|
|
111
|
+
|
|
112
|
+
min, max = extract_min_max(cardinality)
|
|
113
|
+
options.update(:min => min, :max => max)
|
|
114
|
+
|
|
115
|
+
assert_valid_options(options)
|
|
116
|
+
|
|
117
|
+
if options.key?(:model) && model
|
|
118
|
+
raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
model ||= options.delete(:model)
|
|
122
|
+
|
|
123
|
+
# TODO: change to :target_respository_name and :source_repository_name
|
|
124
|
+
options[:child_repository_name] = options.delete(:repository)
|
|
125
|
+
options[:parent_repository_name] = repository.name
|
|
126
|
+
|
|
127
|
+
klass = if options[:max] > 1
|
|
128
|
+
options.key?(:through) ? Associations::ManyToMany::Relationship : Associations::OneToMany::Relationship
|
|
129
|
+
else
|
|
130
|
+
Associations::OneToOne::Relationship
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
relationship = relationships(repository.name)[name] = klass.new(name, model, self, options)
|
|
134
|
+
|
|
135
|
+
descendants.each do |descendant|
|
|
136
|
+
descendant.relationships(repository.name)[name] ||= relationship.inherited_by(descendant)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
relationship
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# A shorthand, clear syntax for defining many-to-one resource relationships.
|
|
143
|
+
#
|
|
144
|
+
# * belongs_to :user # many to one user
|
|
145
|
+
# * belongs_to :friend, :model => 'User' # many to one friend
|
|
146
|
+
# * belongs_to :reference, :repository => :pubmed # association for repository other than default
|
|
147
|
+
#
|
|
148
|
+
# @param name [Symbol]
|
|
149
|
+
# the name that the association will be referenced by
|
|
150
|
+
# @param model [Model, #to_str]
|
|
151
|
+
# the target model of the relationship
|
|
152
|
+
# @param opts [Hash]
|
|
153
|
+
# an options hash
|
|
154
|
+
#
|
|
155
|
+
# @option :model[Model, String] The name of the class to associate with, if omitted
|
|
156
|
+
# then the association name is assumed to match the class name
|
|
157
|
+
# @option :repository[Symbol]
|
|
158
|
+
# name of child model repository
|
|
159
|
+
#
|
|
160
|
+
# @return [Association::Relationship] The association created
|
|
161
|
+
# should not be accessed directly
|
|
162
|
+
#
|
|
163
|
+
# @api public
|
|
164
|
+
def belongs_to(name, *args)
|
|
165
|
+
assert_kind_of 'name', name, Symbol
|
|
166
|
+
|
|
167
|
+
model = extract_model(args)
|
|
168
|
+
options = extract_options(args)
|
|
169
|
+
|
|
170
|
+
if options.key?(:through)
|
|
171
|
+
warn "#{self.name}#belongs_to with :through is deprecated, use 'has 1, :#{name}, #{options.inspect}' in #{self.name} instead (#{caller[0]})"
|
|
172
|
+
return has(1, name, model, options)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
assert_valid_options(options)
|
|
176
|
+
|
|
177
|
+
if options.key?(:model) && model
|
|
178
|
+
raise ArgumentError, 'should not specify options[:model] if passing the model in the third argument'
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
model ||= options.delete(:model)
|
|
182
|
+
|
|
183
|
+
repository_name = repository.name
|
|
184
|
+
|
|
185
|
+
# TODO: change to source_repository_name and target_respository_name
|
|
186
|
+
options[:child_repository_name] = repository_name
|
|
187
|
+
options[:parent_repository_name] = options.delete(:repository)
|
|
188
|
+
|
|
189
|
+
relationship = relationships(repository.name)[name] = Associations::ManyToOne::Relationship.new(name, self, model, options)
|
|
190
|
+
|
|
191
|
+
descendants.each do |descendant|
|
|
192
|
+
descendant.relationships(repository.name)[name] ||= relationship.inherited_by(descendant)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
relationship
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
# Extract the model from an Array of arguments
|
|
201
|
+
#
|
|
202
|
+
# @param [Array(Model, String, Hash)]
|
|
203
|
+
# The arguments passed to an relationship declaration
|
|
204
|
+
#
|
|
205
|
+
# @return [Model, #to_str]
|
|
206
|
+
# target model for the association
|
|
207
|
+
#
|
|
208
|
+
# @api private
|
|
209
|
+
def extract_model(args)
|
|
210
|
+
model = args.first
|
|
211
|
+
|
|
212
|
+
if model.kind_of?(Model)
|
|
213
|
+
model
|
|
214
|
+
elsif model.respond_to?(:to_str)
|
|
215
|
+
model.to_str
|
|
216
|
+
else
|
|
217
|
+
nil
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Extract the model from an Array of arguments
|
|
222
|
+
#
|
|
223
|
+
# @param [Array(Model, String, Hash)]
|
|
224
|
+
# The arguments passed to an relationship declaration
|
|
225
|
+
#
|
|
226
|
+
# @return [Hash]
|
|
227
|
+
# options for the association
|
|
228
|
+
#
|
|
229
|
+
# @api private
|
|
230
|
+
def extract_options(args)
|
|
231
|
+
options = args.last
|
|
232
|
+
|
|
233
|
+
if options.kind_of?(Hash)
|
|
234
|
+
options.dup
|
|
235
|
+
else
|
|
236
|
+
{}
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# A support method for converting Integer, Range or Infinity values into two
|
|
241
|
+
# values representing the minimum and maximum cardinality of the association
|
|
242
|
+
#
|
|
243
|
+
# @return [Array] A pair of integers, min and max
|
|
244
|
+
#
|
|
245
|
+
# @api private
|
|
246
|
+
def extract_min_max(cardinality)
|
|
247
|
+
case cardinality
|
|
248
|
+
when Integer then [ cardinality, cardinality ]
|
|
249
|
+
when Range then [ cardinality.first, cardinality.last ]
|
|
250
|
+
when n then [ 0, n ]
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Validates options of association method like belongs_to or has:
|
|
255
|
+
# verifies types of cardinality bounds, repository, association class,
|
|
256
|
+
# keys and possible values of :through option.
|
|
257
|
+
#
|
|
258
|
+
# @api private
|
|
259
|
+
def assert_valid_options(options)
|
|
260
|
+
# TODO: update to match Query#assert_valid_options
|
|
261
|
+
# - perform options normalization elsewhere
|
|
262
|
+
|
|
263
|
+
if options.key?(:min) && options.key?(:max)
|
|
264
|
+
assert_kind_of 'options[:min]', options[:min], Integer
|
|
265
|
+
assert_kind_of 'options[:max]', options[:max], Integer, n.class
|
|
266
|
+
|
|
267
|
+
if options[:min] == n && options[:max] == n
|
|
268
|
+
raise ArgumentError, 'Cardinality may not be n..n. The cardinality specifies the min/max number of results from the association'
|
|
269
|
+
elsif options[:min] > options[:max]
|
|
270
|
+
raise ArgumentError, "Cardinality min (#{options[:min]}) cannot be larger than the max (#{options[:max]})"
|
|
271
|
+
elsif options[:min] < 0
|
|
272
|
+
raise ArgumentError, "Cardinality min much be greater than or equal to 0, but was #{options[:min]}"
|
|
273
|
+
elsif options[:max] < 1
|
|
274
|
+
raise ArgumentError, "Cardinality max much be greater than or equal to 1, but was #{options[:max]}"
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
if options.key?(:repository)
|
|
279
|
+
assert_kind_of 'options[:repository]', options[:repository], Repository, Symbol
|
|
280
|
+
|
|
281
|
+
if options[:repository].kind_of?(Repository)
|
|
282
|
+
options[:repository] = options[:repository].name
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
if options.key?(:class_name)
|
|
287
|
+
assert_kind_of 'options[:class_name]', options[:class_name], String
|
|
288
|
+
warn "+options[:class_name]+ is deprecated, use :model instead (#{caller[1]})"
|
|
289
|
+
options[:model] = options.delete(:class_name)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
if options.key?(:remote_name)
|
|
293
|
+
assert_kind_of 'options[:remote_name]', options[:remote_name], Symbol
|
|
294
|
+
warn "+options[:remote_name]+ is deprecated, use :via instead (#{caller[1]})"
|
|
295
|
+
options[:via] = options.delete(:remote_name)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
if options.key?(:through)
|
|
299
|
+
assert_kind_of 'options[:through]', options[:through], Symbol, Module
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
[ :via, :inverse ].each do |key|
|
|
303
|
+
if options.key?(key)
|
|
304
|
+
assert_kind_of "options[#{key.inspect}]", options[key], Symbol, Associations::Relationship
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# TODO: deprecate :child_key and :parent_key in favor of :source_key and
|
|
309
|
+
# :target_key (will mean something different for each relationship)
|
|
310
|
+
|
|
311
|
+
[ :child_key, :parent_key ].each do |key|
|
|
312
|
+
if options.key?(key)
|
|
313
|
+
assert_kind_of "options[#{key.inspect}]", options[key], Enumerable
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
if options.key?(:limit)
|
|
318
|
+
raise ArgumentError, '+options[:limit]+ should not be specified on a relationship'
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
chainable do
|
|
323
|
+
# TODO: document
|
|
324
|
+
# @api public
|
|
325
|
+
def method_missing(method, *args, &block)
|
|
326
|
+
if relationship = relationships(repository_name)[method]
|
|
327
|
+
return Query::Path.new([ relationship ])
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
super
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end # module Relationship
|
|
334
|
+
end # module Model
|
|
335
|
+
end # module DataMapper
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module DataMapper
|
|
2
|
+
module Model
|
|
3
|
+
# Module with query scoping functionality.
|
|
4
|
+
#
|
|
5
|
+
# Scopes are implemented using simple array based
|
|
6
|
+
# stack that is thread local. Default scope can be set
|
|
7
|
+
# on a per repository basis.
|
|
8
|
+
#
|
|
9
|
+
# Scopes are merged as new queries are nested.
|
|
10
|
+
# It is also possible to get exclusive scope access
|
|
11
|
+
# using +with_exclusive_scope+
|
|
12
|
+
module Scope
|
|
13
|
+
# TODO: document
|
|
14
|
+
# @api private
|
|
15
|
+
def default_scope(repository_name = default_repository_name)
|
|
16
|
+
@default_scope ||= {}
|
|
17
|
+
|
|
18
|
+
@default_scope[repository_name] ||= if repository_name == default_repository_name
|
|
19
|
+
{}
|
|
20
|
+
else
|
|
21
|
+
default_scope(default_repository_name).dup
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Returns query on top of scope stack
|
|
26
|
+
#
|
|
27
|
+
# @api private
|
|
28
|
+
def query
|
|
29
|
+
Query.new(repository, self, current_scope).freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# TODO: document
|
|
33
|
+
# @api private
|
|
34
|
+
def current_scope
|
|
35
|
+
scope_stack.last || default_scope(repository.name)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
protected
|
|
39
|
+
|
|
40
|
+
# Pushes given query on top of the stack
|
|
41
|
+
#
|
|
42
|
+
# @param [Hash, Query] Query to add to current scope nesting
|
|
43
|
+
#
|
|
44
|
+
# @api private
|
|
45
|
+
def with_scope(query)
|
|
46
|
+
options = if query.kind_of?(Hash)
|
|
47
|
+
query
|
|
48
|
+
else
|
|
49
|
+
query.options
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# merge the current scope with the passed in query
|
|
53
|
+
with_exclusive_scope(self.query.merge(options)) { |*block_args| yield(*block_args) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Pushes given query on top of scope stack and yields
|
|
57
|
+
# given block, then pops the stack. During block execution
|
|
58
|
+
# queries previously pushed onto the stack
|
|
59
|
+
# have no effect.
|
|
60
|
+
#
|
|
61
|
+
# @api private
|
|
62
|
+
def with_exclusive_scope(query)
|
|
63
|
+
query = if query.kind_of?(Hash)
|
|
64
|
+
Query.new(repository, self, query)
|
|
65
|
+
else
|
|
66
|
+
query.dup
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
scope_stack << query.options
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
yield query.freeze
|
|
73
|
+
ensure
|
|
74
|
+
scope_stack.pop
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# Initializes (if necessary) and returns current scope stack
|
|
81
|
+
# @api private
|
|
82
|
+
def scope_stack
|
|
83
|
+
scope_stack_for = Thread.current[:dm_scope_stack] ||= {}
|
|
84
|
+
scope_stack_for[self] ||= []
|
|
85
|
+
end
|
|
86
|
+
end # module Scope
|
|
87
|
+
|
|
88
|
+
include Scope
|
|
89
|
+
end # module Model
|
|
90
|
+
end # module DataMapper
|