sequel 1.3 → 1.4.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.
@@ -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