sequel 1.3 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,351 @@
1
+ # Associations are used in order to specify relationships between model classes
2
+ # that reflect relations between tables in the database using foreign keys.
3
+ #
4
+ # Each kind of association adds a number of methods to the model class which
5
+ # are specialized according to the association type and optional parameters
6
+ # given in the definition. Example:
7
+ #
8
+ # class Project < Sequel::Model
9
+ # many_to_one :portfolio
10
+ # one_to_many :milestones
11
+ # end
12
+ #
13
+ # The project class now has the following methods:
14
+ # * Project#portfolio, Project#portfolio=
15
+ # * Project#milestones, Project#add_milestone, Project#remove_milestone,
16
+ # Project#milestones_dataset
17
+ #
18
+ # By default the classes for the associations are inferred from the association
19
+ # name, so for example the Project#portfolio will return an instance of
20
+ # Portfolio, and Project#milestones will return an array of Milestone
21
+ # instances, in similar fashion to how ActiveRecord infers class names.
22
+ #
23
+ # Association definitions are also reflected by the class, e.g.:
24
+ #
25
+ # >> Project.associations
26
+ # => [:portfolio, :milestones]
27
+ # >> Project.association_reflection(:portfolio)
28
+ # => {:type => :many_to_one, :name => :portfolio, :class_name => "Portfolio"}
29
+ #
30
+ # Associations can be defined by either using the associate method, or by
31
+ # calling one of the three methods: many_to_one, one_to_many, many_to_many.
32
+ # Sequel::Model also provides aliases for these methods that conform to
33
+ # ActiveRecord conventions: belongs_to, has_many, has_and_belongs_to_many.
34
+ # For example, the following three statements are equivalent:
35
+ #
36
+ # associate :one_to_many, :attributes
37
+ # one_to_many :attributes
38
+ # has_many :attributes
39
+ module Sequel::Model::Associations
40
+ # Array of all association reflections
41
+ def all_association_reflections
42
+ association_reflections.values
43
+ end
44
+
45
+ # Associates a related model with the current model. The following types are
46
+ # supported:
47
+ #
48
+ # * :many_to_one - Foreign key in current model's table points to
49
+ # associated model's primary key. Each associated model object can
50
+ # be associated with more than one current model objects. Each current
51
+ # model object can be associated with only one associated model object.
52
+ # Similar to ActiveRecord/DataMapper's belongs_to.
53
+ # * :one_to_many - Foreign key in associated model's table points to this
54
+ # model's primary key. Each current model object can be associated with
55
+ # more than one associated model objects. Each associated model object
56
+ # can be associated with only one current model object.
57
+ # Similar to ActiveRecord/DataMapper's has_many.
58
+ # * :many_to_many - A join table is used that has a foreign key that points
59
+ # to this model's primary key and a foreign key that points to the
60
+ # associated model's primary key. Each current model object can be
61
+ # associated with many associated model objects, and each associated
62
+ # model object can be associated with many current model objects.
63
+ # Similar to ActiveRecord/DataMapper's has_and_belongs_to_many.
64
+ #
65
+ # The following options can be supplied:
66
+ # * *ALL types*:
67
+ # - :class - The associated class or its name. If not
68
+ # given, uses the association's name, which is camelized (and
69
+ # singularized if type is :{one,many}_to_many)
70
+ # - :eager - The associations to eagerly load when loading the associated object.
71
+ # For many_to_one associations, this is ignored unless this association is
72
+ # being eagerly loaded, as it doesn't save queries unless multiple objects
73
+ # can be loaded at once.
74
+ # * :many_to_one:
75
+ # - :key - foreign_key in current model's table that references
76
+ # associated model's primary key, as a symbol. Defaults to :"#{name}_id".
77
+ # * :one_to_many:
78
+ # - :key - foreign key in associated model's table that references
79
+ # current model's primary key, as a symbol. Defaults to
80
+ # :"#{self.name.underscore}_id".
81
+ # - :reciprocal - the string name of the instance variable of the reciprocal many_to_one association,
82
+ # if it exists. By default, sequel will try to determine it by looking at the
83
+ # associated model's assocations for a many_to_one association that matches
84
+ # the current association's key. Set to nil to not use a reciprocal.
85
+ # - :order - the column(s) by which to order the association dataset. Can be a
86
+ # singular column or an array.
87
+ # * :many_to_many:
88
+ # - :join_table - name of table that includes the foreign keys to both
89
+ # the current model and the associated model, as a symbol. Defaults to the name
90
+ # of current model and name of associated model, pluralized,
91
+ # underscored, sorted, and joined with '_'.
92
+ # - :left_key - foreign key in join table that points to current model's
93
+ # primary key, as a symbol.
94
+ # - :right_key - foreign key in join table that points to associated
95
+ # model's primary key, as a symbol.
96
+ # - :select - the attributes to select. Defaults to the associated class's
97
+ # table_name.*, which means it doesn't include the attributes from the join
98
+ # join table. If you want to include the join table attributes, you can
99
+ # use this option, but beware that the join table attributes can clash with
100
+ # attributes from the model table, so you should alias any attributes that have
101
+ # the same name in both the join table and the associated table.
102
+ # - :order - the column(s) by which to order the association dataset. Can be a
103
+ # singular column or an array.
104
+ def associate(type, name, opts = {}, &block)
105
+ # check arguments
106
+ raise ArgumentError unless [:many_to_one, :one_to_many, :many_to_many].include?(type) && Symbol === name
107
+
108
+ # merge early so we don't modify opts
109
+ opts = opts.merge(:type => type, :name => name, :block => block, :cache => true)
110
+
111
+ # deprecation
112
+ if opts[:from]
113
+ STDERR << "The :from option is deprecated, please use the :class option instead.\r\n"
114
+ opts[:class] = opts[:from]
115
+ end
116
+
117
+ # find class
118
+ case opts[:class]
119
+ when String, Symbol
120
+ # Delete :class to allow late binding
121
+ opts[:class_name] ||= opts.delete(:class).to_s
122
+ when Class
123
+ opts[:class_name] ||= opts[:class].name
124
+ end
125
+
126
+ send(:"def_#{type}", name, opts)
127
+
128
+ # don't add to association_reflections until we are sure there are no errors
129
+ association_reflections[name] = opts
130
+ end
131
+
132
+ # The association reflection hash for the association of the given name.
133
+ def association_reflection(name)
134
+ association_reflections[name]
135
+ end
136
+
137
+ # Array of association name symbols
138
+ def associations
139
+ association_reflections.keys
140
+ end
141
+
142
+ # deprecated, please use many_to_one instead
143
+ def one_to_one(*args, &block)
144
+ STDERR << "one_to_one relation definitions are deprecated, please use many_to_one instead.\r\n"
145
+ many_to_one(*args, &block)
146
+ end
147
+
148
+ # Shortcut for adding a one_to_many association, see associate
149
+ def one_to_many(*args, &block)
150
+ associate(:one_to_many, *args, &block)
151
+ end
152
+ alias_method :has_many, :one_to_many
153
+
154
+ # Shortcut for adding a many_to_one association, see associate
155
+ def many_to_one(*args, &block)
156
+ associate(:many_to_one, *args, &block)
157
+ end
158
+ alias_method :belongs_to, :many_to_one
159
+
160
+ # Shortcut for adding a many_to_many association, see associate
161
+ def many_to_many(*args, &block)
162
+ associate(:many_to_many, *args, &block)
163
+ end
164
+ alias_method :has_and_belongs_to_many, :many_to_many
165
+
166
+ private
167
+ # The class related to the given association reflection
168
+ def associated_class(opts)
169
+ opts[:class] ||= opts[:class_name].constantize
170
+ end
171
+
172
+ # Name symbol for add association method
173
+ def association_add_method_name(name)
174
+ :"add_#{name.to_s.singularize}"
175
+ end
176
+
177
+ # Name symbol of association instance variable
178
+ def association_ivar(name)
179
+ :"@#{name}"
180
+ end
181
+
182
+ # Name symbol for remove_method_name
183
+ def association_remove_method_name(name)
184
+ :"remove_#{name.to_s.singularize}"
185
+ end
186
+
187
+ # Hash storing the association reflections. Keys are association name
188
+ # symbols, values are association reflection hashes.
189
+ def association_reflections
190
+ @association_reflections ||= {}
191
+ end
192
+
193
+ # Defines an association
194
+ def def_association_dataset_methods(name, opts, &block)
195
+ dataset_method = :"#{name}_dataset"
196
+ helper_method = :"#{name}_helper"
197
+ dataset_block = opts[:block]
198
+ ivar = association_ivar(name)
199
+
200
+ # define a method returning the association dataset (with optional order)
201
+ if order = opts[:order]
202
+ class_def(dataset_method) {instance_eval(&block).order(order)}
203
+ else
204
+ class_def(dataset_method, &block)
205
+ end
206
+
207
+ # If a block is given, define a helper method for it, because it takes
208
+ # an argument. This is unnecessary in Ruby 1.9, as that has instance_exec.
209
+ if dataset_block
210
+ class_def(helper_method, &dataset_block)
211
+ end
212
+
213
+ class_def(name) do |*reload|
214
+ if !reload[0] && obj = instance_variable_get(ivar)
215
+ obj
216
+ else
217
+ ds = send(dataset_method)
218
+ # if the a dataset block was specified, we need to call it and use
219
+ # the result as the dataset to fetch records from.
220
+ if dataset_block
221
+ ds = send(helper_method, ds)
222
+ end
223
+ if eager = opts[:eager]
224
+ ds = ds.eager(eager)
225
+ end
226
+ objs = ds.all
227
+ if reciprocal = self.class.send(:reciprocal_association, opts)
228
+ objs.each{|o| o.instance_variable_set(reciprocal, self)}
229
+ end
230
+ instance_variable_set(ivar, objs)
231
+ end
232
+ end
233
+ end
234
+
235
+ # Defines an association getter method, caching the block result in an
236
+ # instance variable. The defined method takes an optional reload parameter
237
+ # that can be set to true in order to bypass the cache.
238
+ def def_association_getter(name, &block)
239
+ ivar = association_ivar(name)
240
+ class_def(name) do |*reload|
241
+ if !reload[0] && obj = instance_variable_get(ivar)
242
+ obj
243
+ else
244
+ instance_variable_set(ivar, instance_eval(&block))
245
+ end
246
+ end
247
+ end
248
+
249
+ # Adds many_to_many association instance methods
250
+ def def_many_to_many(name, opts)
251
+ assoc_class = method(:associated_class) # late binding of association dataset
252
+ ivar = association_ivar(name)
253
+ left = (opts[:left_key] ||= default_remote_key)
254
+ right = (opts[:right_key] ||= :"#{name.to_s.singularize}_id")
255
+ opts[:class_name] ||= name.to_s.singularize.camelize
256
+ join_table = (opts[:join_table] ||= default_join_table_name(opts))
257
+ database = db
258
+
259
+ def_association_dataset_methods(name, opts) do
260
+ klass = assoc_class[opts]
261
+ key = (opts[:right_primary_key] ||= :"#{klass.table_name}__#{klass.primary_key}")
262
+ selection = (opts[:select] ||= klass.table_name.all)
263
+ klass.select(selection).inner_join(join_table, right => key, left => pk)
264
+ end
265
+
266
+ class_def(association_add_method_name(name)) do |o|
267
+ database[join_table].insert(left => pk, right => o.pk)
268
+ if arr = instance_variable_get(ivar)
269
+ arr.push(o)
270
+ end
271
+ o
272
+ end
273
+ class_def(association_remove_method_name(name)) do |o|
274
+ database[join_table].filter(left => pk, right => o.pk).delete
275
+ if arr = instance_variable_get(ivar)
276
+ arr.delete(o)
277
+ end
278
+ o
279
+ end
280
+ end
281
+
282
+ # Adds many_to_one association instance methods
283
+ def def_many_to_one(name, opts)
284
+ assoc_class = method(:associated_class) # late binding of association dataset
285
+ ivar = association_ivar(name)
286
+
287
+ key = (opts[:key] ||= :"#{name}_id")
288
+ opts[:class_name] ||= name.to_s.camelize
289
+
290
+ def_association_getter(name) {(fk = send(key)) ? assoc_class[opts][fk] : nil}
291
+ class_def(:"#{name}=") do |o|
292
+ instance_variable_set(ivar, o)
293
+ send(:"#{key}=", (o.pk if o))
294
+ end
295
+ end
296
+
297
+ # Adds one_to_many association instance methods
298
+ def def_one_to_many(name, opts)
299
+ assoc_class = method(:associated_class) # late binding of association dataset
300
+ ivar = association_ivar(name)
301
+ key = (opts[:key] ||= default_remote_key)
302
+ opts[:class_name] ||= name.to_s.singularize.camelize
303
+
304
+ def_association_dataset_methods(name, opts) {assoc_class[opts].filter(key => pk)}
305
+
306
+ class_def(association_add_method_name(name)) do |o|
307
+ o.send(:"#{key}=", pk)
308
+ o.save!
309
+ if arr = instance_variable_get(ivar)
310
+ arr.push(o)
311
+ end
312
+ o
313
+ end
314
+ class_def(association_remove_method_name(name)) do |o|
315
+ o.send(:"#{key}=", nil)
316
+ o.save!
317
+ if arr = instance_variable_get(ivar)
318
+ arr.delete(o)
319
+ end
320
+ o
321
+ end
322
+ end
323
+
324
+ # Name symbol for default join table
325
+ def default_join_table_name(opts)
326
+ ([opts[:class_name], self.name.demodulize]. \
327
+ map{|i| i.pluralize.underscore}.sort.join('_')).to_sym
328
+ end
329
+
330
+ # Name symbol for default foreign key
331
+ def default_remote_key
332
+ :"#{name.demodulize.underscore}_id"
333
+ end
334
+
335
+ # Sets the reciprocal association variable in the reflection, if one exists
336
+ def reciprocal_association(reflection)
337
+ if reflection[:type] != :one_to_many
338
+ nil
339
+ elsif reflection.include?(:reciprocal)
340
+ reflection[:reciprocal]
341
+ else
342
+ key = reflection[:key]
343
+ associated_class(reflection).all_association_reflections.each do |assoc_reflect|
344
+ if assoc_reflect[:type] == :many_to_one && assoc_reflect[:key] == key
345
+ return reflection[:reciprocal] = "@#{assoc_reflect[:name]}".freeze
346
+ end
347
+ end
348
+ reflection[:reciprocal] = nil
349
+ end
350
+ end
351
+ end
@@ -0,0 +1,120 @@
1
+ module Sequel
2
+ class Model
3
+ # Returns the database associated with the Model class.
4
+ def self.db
5
+ @db ||= (superclass != Object) && superclass.db or
6
+ raise Error, "No database associated with #{self}"
7
+ end
8
+
9
+ # Sets the database associated with the Model class.
10
+ def self.db=(db)
11
+ @db = db
12
+ if @dataset
13
+ set_dataset(db[table_name])
14
+ end
15
+ end
16
+
17
+ # Called when a database is opened in order to automatically associate the
18
+ # first opened database with model classes.
19
+ def self.database_opened(db)
20
+ @db = db if (self == Model) && !@db
21
+ end
22
+
23
+ # Returns the implicit table name for the model class.
24
+ def self.implicit_table_name
25
+ name.demodulize.underscore.pluralize.to_sym
26
+ end
27
+
28
+ # Returns the dataset associated with the Model class.
29
+ def self.dataset
30
+ unless @dataset
31
+ if ds = super_dataset
32
+ set_dataset(ds.clone)
33
+ elsif !name.empty?
34
+ set_dataset(db[implicit_table_name])
35
+ else
36
+ raise Error, "No dataset associated with #{self}"
37
+ end
38
+ end
39
+ @dataset
40
+ end
41
+
42
+ # def self.dataset
43
+ # @dataset ||= super_dataset ||
44
+ # (!(n = name).empty? && db[n.underscore.pluralize.to_sym]) ||
45
+ # (raise Error, "No dataset associated with #{self}")
46
+ # end
47
+
48
+ def self.super_dataset # :nodoc:
49
+ superclass.dataset if (superclass != Sequel::Model) && superclass.respond_to?(:dataset)
50
+ end
51
+
52
+ # Returns the columns in the result set in their original order.
53
+ #
54
+ # See Dataset#columns for more information.
55
+ def self.columns
56
+ @columns ||= dataset.columns or
57
+ raise Error, "Could not fetch columns for #{self}"
58
+ end
59
+
60
+ # Sets the dataset associated with the Model class.
61
+ def self.set_dataset(ds)
62
+ @db = ds.db
63
+ @dataset = ds
64
+ @dataset.set_model(self)
65
+ @dataset.extend(Associations::EagerLoading)
66
+ @dataset.transform(@transform) if @transform
67
+ end
68
+
69
+ # Returns the database assoiated with the object's Model class.
70
+ def db
71
+ @db ||= model.db
72
+ end
73
+
74
+ # Returns the dataset assoiated with the object's Model class.
75
+ #
76
+ # See Dataset for more information.
77
+ def dataset
78
+ model.dataset
79
+ end
80
+
81
+ # Returns the columns associated with the object's Model class.
82
+ def columns
83
+ model.columns
84
+ end
85
+
86
+ # Serializes column with YAML or through marshalling.
87
+ def self.serialize(*columns)
88
+ format = columns.pop[:format] if Hash === columns.last
89
+ format ||= :yaml
90
+
91
+ @transform = columns.inject({}) do |m, c|
92
+ m[c] = format
93
+ m
94
+ end
95
+ @dataset.transform(@transform) if @dataset
96
+ end
97
+ end
98
+
99
+ # Lets you create a Model class with its table name already set or reopen
100
+ # an existing Model.
101
+ #
102
+ # Makes given dataset inherited.
103
+ #
104
+ # === Example:
105
+ # class Comment < Sequel::Model(:something)
106
+ # table_name # => :something
107
+ #
108
+ # # ...
109
+ #
110
+ # end
111
+ def self.Model(source)
112
+ @models ||= {}
113
+ @models[source] ||= Class.new(Sequel::Model) do
114
+ meta_def(:inherited) do |c|
115
+ c.set_dataset(source.is_a?(Dataset) ? source : c.db[source])
116
+ end
117
+ end
118
+ end
119
+
120
+ end