colincasey-sequel 2.10.0 → 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (137) hide show
  1. data/CHANGELOG +7 -1
  2. data/doc/advanced_associations.rdoc +614 -0
  3. data/doc/cheat_sheet.rdoc +223 -0
  4. data/doc/dataset_filtering.rdoc +158 -0
  5. data/doc/prepared_statements.rdoc +104 -0
  6. data/doc/release_notes/1.0.txt +38 -0
  7. data/doc/release_notes/1.1.txt +143 -0
  8. data/doc/release_notes/1.3.txt +101 -0
  9. data/doc/release_notes/1.4.0.txt +53 -0
  10. data/doc/release_notes/1.5.0.txt +155 -0
  11. data/doc/release_notes/2.0.0.txt +298 -0
  12. data/doc/release_notes/2.1.0.txt +271 -0
  13. data/doc/release_notes/2.10.0.txt +328 -0
  14. data/doc/release_notes/2.2.0.txt +253 -0
  15. data/doc/release_notes/2.3.0.txt +88 -0
  16. data/doc/release_notes/2.4.0.txt +106 -0
  17. data/doc/release_notes/2.5.0.txt +137 -0
  18. data/doc/release_notes/2.6.0.txt +157 -0
  19. data/doc/release_notes/2.7.0.txt +166 -0
  20. data/doc/release_notes/2.8.0.txt +171 -0
  21. data/doc/release_notes/2.9.0.txt +97 -0
  22. data/doc/schema.rdoc +29 -0
  23. data/doc/sharding.rdoc +113 -0
  24. data/lib/sequel.rb +1 -0
  25. data/lib/sequel_core/adapters/ado.rb +89 -0
  26. data/lib/sequel_core/adapters/db2.rb +143 -0
  27. data/lib/sequel_core/adapters/dbi.rb +112 -0
  28. data/lib/sequel_core/adapters/do/mysql.rb +38 -0
  29. data/lib/sequel_core/adapters/do/postgres.rb +92 -0
  30. data/lib/sequel_core/adapters/do/sqlite.rb +31 -0
  31. data/lib/sequel_core/adapters/do.rb +205 -0
  32. data/lib/sequel_core/adapters/firebird.rb +298 -0
  33. data/lib/sequel_core/adapters/informix.rb +85 -0
  34. data/lib/sequel_core/adapters/jdbc/h2.rb +69 -0
  35. data/lib/sequel_core/adapters/jdbc/mysql.rb +66 -0
  36. data/lib/sequel_core/adapters/jdbc/oracle.rb +23 -0
  37. data/lib/sequel_core/adapters/jdbc/postgresql.rb +113 -0
  38. data/lib/sequel_core/adapters/jdbc/sqlite.rb +43 -0
  39. data/lib/sequel_core/adapters/jdbc.rb +491 -0
  40. data/lib/sequel_core/adapters/mysql.rb +369 -0
  41. data/lib/sequel_core/adapters/odbc.rb +174 -0
  42. data/lib/sequel_core/adapters/openbase.rb +68 -0
  43. data/lib/sequel_core/adapters/oracle.rb +107 -0
  44. data/lib/sequel_core/adapters/postgres.rb +456 -0
  45. data/lib/sequel_core/adapters/shared/ms_access.rb +110 -0
  46. data/lib/sequel_core/adapters/shared/mssql.rb +102 -0
  47. data/lib/sequel_core/adapters/shared/mysql.rb +325 -0
  48. data/lib/sequel_core/adapters/shared/oracle.rb +61 -0
  49. data/lib/sequel_core/adapters/shared/postgres.rb +715 -0
  50. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  51. data/lib/sequel_core/adapters/shared/sqlite.rb +265 -0
  52. data/lib/sequel_core/adapters/sqlite.rb +248 -0
  53. data/lib/sequel_core/connection_pool.rb +258 -0
  54. data/lib/sequel_core/core_ext.rb +217 -0
  55. data/lib/sequel_core/core_sql.rb +202 -0
  56. data/lib/sequel_core/database/schema.rb +164 -0
  57. data/lib/sequel_core/database.rb +691 -0
  58. data/lib/sequel_core/dataset/callback.rb +13 -0
  59. data/lib/sequel_core/dataset/convenience.rb +237 -0
  60. data/lib/sequel_core/dataset/pagination.rb +96 -0
  61. data/lib/sequel_core/dataset/prepared_statements.rb +220 -0
  62. data/lib/sequel_core/dataset/query.rb +41 -0
  63. data/lib/sequel_core/dataset/schema.rb +15 -0
  64. data/lib/sequel_core/dataset/sql.rb +1010 -0
  65. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  66. data/lib/sequel_core/dataset/unsupported.rb +43 -0
  67. data/lib/sequel_core/dataset.rb +511 -0
  68. data/lib/sequel_core/deprecated.rb +26 -0
  69. data/lib/sequel_core/exceptions.rb +44 -0
  70. data/lib/sequel_core/migration.rb +212 -0
  71. data/lib/sequel_core/object_graph.rb +230 -0
  72. data/lib/sequel_core/pretty_table.rb +71 -0
  73. data/lib/sequel_core/schema/generator.rb +320 -0
  74. data/lib/sequel_core/schema/sql.rb +325 -0
  75. data/lib/sequel_core/schema.rb +2 -0
  76. data/lib/sequel_core/sql.rb +887 -0
  77. data/lib/sequel_core/version.rb +11 -0
  78. data/lib/sequel_core.rb +172 -0
  79. data/lib/sequel_model/association_reflection.rb +267 -0
  80. data/lib/sequel_model/associations.rb +499 -0
  81. data/lib/sequel_model/base.rb +523 -0
  82. data/lib/sequel_model/caching.rb +82 -0
  83. data/lib/sequel_model/dataset_methods.rb +26 -0
  84. data/lib/sequel_model/eager_loading.rb +370 -0
  85. data/lib/sequel_model/exceptions.rb +7 -0
  86. data/lib/sequel_model/hooks.rb +101 -0
  87. data/lib/sequel_model/inflector.rb +281 -0
  88. data/lib/sequel_model/plugins.rb +62 -0
  89. data/lib/sequel_model/record.rb +568 -0
  90. data/lib/sequel_model/schema.rb +49 -0
  91. data/lib/sequel_model/validations.rb +429 -0
  92. data/lib/sequel_model.rb +91 -0
  93. data/spec/adapters/ado_spec.rb +46 -0
  94. data/spec/adapters/firebird_spec.rb +376 -0
  95. data/spec/adapters/informix_spec.rb +96 -0
  96. data/spec/adapters/mysql_spec.rb +881 -0
  97. data/spec/adapters/oracle_spec.rb +244 -0
  98. data/spec/adapters/postgres_spec.rb +687 -0
  99. data/spec/adapters/spec_helper.rb +10 -0
  100. data/spec/adapters/sqlite_spec.rb +555 -0
  101. data/spec/integration/dataset_test.rb +134 -0
  102. data/spec/integration/eager_loader_test.rb +696 -0
  103. data/spec/integration/prepared_statement_test.rb +130 -0
  104. data/spec/integration/schema_test.rb +180 -0
  105. data/spec/integration/spec_helper.rb +58 -0
  106. data/spec/integration/type_test.rb +96 -0
  107. data/spec/rcov.opts +6 -0
  108. data/spec/sequel_core/connection_pool_spec.rb +526 -0
  109. data/spec/sequel_core/core_ext_spec.rb +156 -0
  110. data/spec/sequel_core/core_sql_spec.rb +522 -0
  111. data/spec/sequel_core/database_spec.rb +1188 -0
  112. data/spec/sequel_core/dataset_spec.rb +3481 -0
  113. data/spec/sequel_core/expression_filters_spec.rb +363 -0
  114. data/spec/sequel_core/migration_spec.rb +261 -0
  115. data/spec/sequel_core/object_graph_spec.rb +272 -0
  116. data/spec/sequel_core/pretty_table_spec.rb +58 -0
  117. data/spec/sequel_core/schema_generator_spec.rb +167 -0
  118. data/spec/sequel_core/schema_spec.rb +780 -0
  119. data/spec/sequel_core/spec_helper.rb +55 -0
  120. data/spec/sequel_core/version_spec.rb +7 -0
  121. data/spec/sequel_model/association_reflection_spec.rb +93 -0
  122. data/spec/sequel_model/associations_spec.rb +1767 -0
  123. data/spec/sequel_model/base_spec.rb +419 -0
  124. data/spec/sequel_model/caching_spec.rb +215 -0
  125. data/spec/sequel_model/dataset_methods_spec.rb +78 -0
  126. data/spec/sequel_model/eager_loading_spec.rb +1165 -0
  127. data/spec/sequel_model/hooks_spec.rb +485 -0
  128. data/spec/sequel_model/inflector_spec.rb +119 -0
  129. data/spec/sequel_model/model_spec.rb +588 -0
  130. data/spec/sequel_model/plugins_spec.rb +80 -0
  131. data/spec/sequel_model/record_spec.rb +1184 -0
  132. data/spec/sequel_model/schema_spec.rb +90 -0
  133. data/spec/sequel_model/spec_helper.rb +78 -0
  134. data/spec/sequel_model/validations_spec.rb +1067 -0
  135. data/spec/spec.opts +0 -0
  136. data/spec/spec_config.rb.example +10 -0
  137. metadata +177 -3
@@ -0,0 +1,568 @@
1
+ module Sequel
2
+ class Model
3
+ # The setter methods (methods ending with =) that are never allowed
4
+ # to be called automatically via set.
5
+ RESTRICTED_SETTER_METHODS = %w"== === []= taguri= typecast_empty_string_to_nil= typecast_on_assignment= strict_param_setting= raise_on_save_failure= raise_on_typecast_failure="
6
+
7
+ # The hash of attribute values. Keys are symbols with the names of the
8
+ # underlying database columns.
9
+ attr_reader :values
10
+
11
+ class_attr_reader :columns, :dataset, :db, :primary_key, :str_columns
12
+ class_attr_overridable :db_schema, :raise_on_save_failure, :raise_on_typecast_failure, :strict_param_setting, :typecast_empty_string_to_nil, :typecast_on_assignment
13
+ remove_method :db_schema=
14
+
15
+ # Creates new instance with values set to passed-in Hash.
16
+ # If a block is given, yield the instance to the block unless
17
+ # from_db is true.
18
+ # This method runs the after_initialize hook after
19
+ # it has optionally yielded itself to the block.
20
+ #
21
+ # Arguments:
22
+ # * values - should be a hash with symbol keys, though
23
+ # string keys will work if from_db is false.
24
+ # * from_db - should only be set by Model.load, forget it
25
+ # exists.
26
+ def initialize(values = {}, from_db = false)
27
+ if from_db
28
+ @new = false
29
+ @values = values
30
+ else
31
+ @values = {}
32
+ @new = true
33
+ set(values)
34
+ changed_columns.clear
35
+ yield self if block_given?
36
+ end
37
+ after_initialize
38
+ end
39
+
40
+ # Returns value of the column's attribute.
41
+ def [](column)
42
+ @values[column]
43
+ end
44
+
45
+ # Sets value of the column's attribute and marks the column as changed.
46
+ # If the column already has the same value, this is a no-op.
47
+ def []=(column, value)
48
+ # If it is new, it doesn't have a value yet, so we should
49
+ # definitely set the new value.
50
+ # If the column isn't in @values, we can't assume it is
51
+ # NULL in the database, so assume it has changed.
52
+ if new? || !@values.include?(column) || value != @values[column]
53
+ changed_columns << column unless changed_columns.include?(column)
54
+ @values[column] = typecast_value(column, value)
55
+ end
56
+ end
57
+
58
+ # Compares model instances by values.
59
+ def ==(obj)
60
+ (obj.class == model) && (obj.values == @values)
61
+ end
62
+ alias_method :eql?, :"=="
63
+
64
+ # If pk is not nil, true only if the objects have the same class and pk.
65
+ # If pk is nil, false.
66
+ def ===(obj)
67
+ pk.nil? ? false : (obj.class == model) && (obj.pk == pk)
68
+ end
69
+
70
+ # class is defined in Object, but it is also a keyword,
71
+ # and since a lot of instance methods call class methods,
72
+ # the model makes it so you can use model instead of
73
+ # self.class.
74
+ alias_method :model, :class
75
+
76
+ # The current cached associations. A hash with the keys being the
77
+ # association name symbols and the values being the associated object
78
+ # or nil (many_to_one), or the array of associated objects (*_to_many).
79
+ def associations
80
+ @associations ||= {}
81
+ end
82
+
83
+ # The columns that have been updated. This isn't completely accurate,
84
+ # see Model#[]=.
85
+ def changed_columns
86
+ @changed_columns ||= []
87
+ end
88
+
89
+ # Deletes and returns self. Does not run destroy hooks.
90
+ # Look into using destroy instead.
91
+ def delete
92
+ before_delete
93
+ this.delete
94
+ self
95
+ end
96
+
97
+ # Like delete but runs hooks before and after delete.
98
+ # If before_destroy returns false, returns false without
99
+ # deleting the object the the database. Otherwise, deletes
100
+ # the item from the database and returns self.
101
+ def destroy
102
+ db.transaction do
103
+ return save_failure(:destroy) if before_destroy == false
104
+ delete
105
+ after_destroy
106
+ end
107
+ self
108
+ end
109
+
110
+ # Enumerates through all attributes.
111
+ #
112
+ # Example:
113
+ # Ticket.find(7).each { |k, v| puts "#{k} => #{v}" }
114
+ def each(&block)
115
+ @values.each(&block)
116
+ end
117
+
118
+ # Returns true when current instance exists, false otherwise.
119
+ def exists?
120
+ this.count > 0
121
+ end
122
+
123
+ # Unique for objects with the same class and pk (if pk is not nil), or
124
+ # the same class and values (if pk is nil).
125
+ def hash
126
+ [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
127
+ end
128
+
129
+ # Returns value for the :id attribute, even if the primary key is
130
+ # not id. To get the primary key value, use #pk.
131
+ def id
132
+ @values[:id]
133
+ end
134
+
135
+ # Returns a string representation of the model instance including
136
+ # the class name and values.
137
+ def inspect
138
+ "#<#{model.name} @values=#{inspect_values}>"
139
+ end
140
+
141
+ # Returns attribute names as an array of symbols.
142
+ def keys
143
+ @values.keys
144
+ end
145
+
146
+ # Returns true if the current instance represents a new record.
147
+ def new?
148
+ @new
149
+ end
150
+
151
+ # Returns the primary key value identifying the model instance.
152
+ # Raises an error if this model does not have a primary key.
153
+ # If the model has a composite primary key, returns an array of values.
154
+ def pk
155
+ raise(Error, "No primary key is associated with this model") unless key = primary_key
156
+ case key
157
+ when Array
158
+ key.collect{|k| @values[k]}
159
+ else
160
+ @values[key]
161
+ end
162
+ end
163
+
164
+ # Returns a hash identifying the model instance. It should be true that:
165
+ #
166
+ # Model[model_instance.pk_hash] === model_instance
167
+ def pk_hash
168
+ model.primary_key_hash(pk)
169
+ end
170
+
171
+ # Reloads attributes from database and returns self. Also clears all
172
+ # cached association information. Raises an Error if the record no longer
173
+ # exists in the database.
174
+ def refresh
175
+ @values = this.first || raise(Error, "Record not found")
176
+ changed_columns.clear
177
+ associations.clear
178
+ self
179
+ end
180
+ alias_method :reload, :refresh
181
+
182
+ # Creates or updates the record, after making sure the record
183
+ # is valid. If the record is not valid, or before_save,
184
+ # before_create (if new?), or before_update (if !new?) return
185
+ # false, returns nil unless raise_on_save_failure is true (if it
186
+ # is true, it raises an error).
187
+ # Otherwise, returns self. You can provide an optional list of
188
+ # columns to update, in which case it only updates those columns.
189
+ def save(*columns)
190
+ valid? ? save!(*columns) : save_failure(:invalid)
191
+ end
192
+
193
+ # Creates or updates the record, without attempting to validate
194
+ # it first. You can provide an optional list of columns to update,
195
+ # in which case it only updates those columns.
196
+ # If before_save, before_create (if new?), or before_update
197
+ # (if !new?) return false, returns nil unless raise_on_save_failure
198
+ # is true (if it is true, it raises an error). Otherwise, returns self.
199
+ def save!(*columns)
200
+ opts = columns.extract_options!
201
+ return save_failure(:save) if before_save == false
202
+ if new?
203
+ return save_failure(:create) if before_create == false
204
+ ds = model.dataset
205
+ if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
206
+ @values = h
207
+ @this = nil
208
+ else
209
+ iid = ds.insert(@values)
210
+ # if we have a regular primary key and it's not set in @values,
211
+ # we assume it's the last inserted id
212
+ if (pk = primary_key) && !(Array === pk) && !@values[pk]
213
+ @values[pk] = iid
214
+ end
215
+ @this = nil if pk
216
+ end
217
+ after_create
218
+ after_save
219
+ @new = false
220
+ refresh if pk
221
+ else
222
+ return save_failure(:update) if before_update == false
223
+ if columns.empty?
224
+ vals = opts[:changed] ? @values.reject{|k,v| !changed_columns.include?(k)} : @values
225
+ this.update(vals)
226
+ else # update only the specified columns
227
+ this.update(@values.reject{|k, v| !columns.include?(k)})
228
+ end
229
+ after_update
230
+ after_save
231
+ if columns.empty?
232
+ changed_columns.clear
233
+ else
234
+ changed_columns.reject!{|c| columns.include?(c)}
235
+ end
236
+ end
237
+ self
238
+ end
239
+
240
+ # Saves only changed columns or does nothing if no columns are marked as
241
+ # chanaged. If no columns have been changed, returns nil. If unable to
242
+ # save, returns false unless raise_on_save_failure is true.
243
+ def save_changes
244
+ save(:changed=>true) || false unless changed_columns.empty?
245
+ end
246
+
247
+ # Updates the instance with the supplied values with support for virtual
248
+ # attributes, raising an exception if a value is used that doesn't have
249
+ # a setter method (or ignoring it if strict_param_setting = false).
250
+ # Does not save the record.
251
+ #
252
+ # If no columns have been set for this model (very unlikely), assume symbol
253
+ # keys are valid column names, and assign the column value based on that.
254
+ def set(hash)
255
+ set_restricted(hash, nil, nil)
256
+ end
257
+ alias_method :set_with_params, :set
258
+
259
+ # Set all values using the entries in the hash, ignoring any setting of
260
+ # allowed_columns or restricted columns in the model.
261
+ def set_all(hash)
262
+ set_restricted(hash, false, false)
263
+ end
264
+
265
+ # Set all values using the entries in the hash, except for the keys
266
+ # given in except.
267
+ def set_except(hash, *except)
268
+ set_restricted(hash, false, except.flatten)
269
+ end
270
+
271
+ # Set the values using the entries in the hash, only if the key
272
+ # is included in only.
273
+ def set_only(hash, *only)
274
+ set_restricted(hash, only.flatten, false)
275
+ end
276
+
277
+ # Sets the value attributes without saving the record. Returns
278
+ # the values changed. Raises an error if the keys are not symbols
279
+ # or strings or a string key was passed that was not a valid column.
280
+ # This is a low level method that does not respect virtual attributes. It
281
+ # should probably be avoided. Look into using set instead.
282
+ def set_values(values)
283
+ s = str_columns
284
+ vals = values.inject({}) do |m, kv|
285
+ k, v = kv
286
+ k = case k
287
+ when Symbol
288
+ k
289
+ when String
290
+ # Prevent denial of service via memory exhaustion by only
291
+ # calling to_sym if the symbol already exists.
292
+ raise(Error, "all string keys must be a valid columns") unless s.include?(k)
293
+ k.to_sym
294
+ else
295
+ raise(Error, "Only symbols and strings allows as keys")
296
+ end
297
+ m[k] = v
298
+ m
299
+ end
300
+ vals.each {|k, v| @values[k] = v}
301
+ vals
302
+ end
303
+
304
+ # Returns (naked) dataset that should return only this instance.
305
+ def this
306
+ @this ||= dataset.filter(pk_hash).limit(1).naked
307
+ end
308
+
309
+ # Runs set with the passed hash and runs save_changes (which runs any callback methods).
310
+ def update(hash)
311
+ update_restricted(hash, nil, nil)
312
+ end
313
+ alias_method :update_with_params, :update
314
+
315
+ # Update all values using the entries in the hash, ignoring any setting of
316
+ # allowed_columns or restricted columns in the model.
317
+ def update_all(hash)
318
+ update_restricted(hash, false, false)
319
+ end
320
+
321
+ # Update all values using the entries in the hash, except for the keys
322
+ # given in except.
323
+ def update_except(hash, *except)
324
+ update_restricted(hash, false, except.flatten)
325
+ end
326
+
327
+ # Update the values using the entries in the hash, only if the key
328
+ # is included in only.
329
+ def update_only(hash, *only)
330
+ update_restricted(hash, only.flatten, false)
331
+ end
332
+
333
+ # Sets the values attributes with set_values and then updates
334
+ # the record in the database using those values. This is a
335
+ # low level method that does not run the usual save callbacks.
336
+ # It should probably be avoided. Look into using update_with_params instead.
337
+ def update_values(values)
338
+ before_update_values
339
+ this.update(set_values(values))
340
+ end
341
+
342
+ private
343
+
344
+ # Backbone behind association_dataset
345
+ def _dataset(opts)
346
+ raise(Sequel::Error, "model object #{model} does not have a primary key") if opts.dataset_need_primary_key? && !pk
347
+ ds = send(opts._dataset_method)
348
+ ds.extend(Associations::DatasetMethods)
349
+ ds.model_object = self
350
+ ds.association_reflection = opts
351
+ opts[:extend].each{|m| ds.extend(m)}
352
+ ds = ds.select(*opts.select) if opts.select
353
+ ds = ds.filter(opts[:conditions]) if opts[:conditions]
354
+ ds = ds.order(*opts[:order]) if opts[:order]
355
+ ds = ds.limit(*opts[:limit]) if opts[:limit]
356
+ ds = ds.eager(*opts[:eager]) if opts[:eager]
357
+ ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
358
+ ds = send(opts.dataset_helper_method, ds) if opts[:block]
359
+ ds
360
+ end
361
+
362
+ # Add the given associated object to the given association
363
+ def add_associated_object(opts, o)
364
+ raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
365
+ raise(Sequel::Error, "associated object #{o.model} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
366
+ return if run_association_callbacks(opts, :before_add, o) == false
367
+ send(opts._add_method, o)
368
+ associations[opts[:name]].push(o) if associations.include?(opts[:name])
369
+ add_reciprocal_object(opts, o)
370
+ run_association_callbacks(opts, :after_add, o)
371
+ o
372
+ end
373
+
374
+ # Add/Set the current object to/as the given object's reciprocal association.
375
+ def add_reciprocal_object(opts, o)
376
+ return unless reciprocal = opts.reciprocal
377
+ if opts.reciprocal_array?
378
+ if array = o.associations[reciprocal] and !array.include?(self)
379
+ array.push(self)
380
+ end
381
+ else
382
+ o.associations[reciprocal] = self
383
+ end
384
+ end
385
+
386
+ # Default inspection output for a record, overwrite to change the way #inspect prints the @values hash
387
+ def inspect_values
388
+ @values.inspect
389
+ end
390
+
391
+ # Load the associated objects using the dataset
392
+ def load_associated_objects(opts, reload=false)
393
+ name = opts[:name]
394
+ if associations.include?(name) and !reload
395
+ associations[name]
396
+ else
397
+ objs = if opts.returns_array?
398
+ send(opts.dataset_method).all
399
+ else
400
+ if !opts[:key]
401
+ send(opts.dataset_method).all.first
402
+ elsif send(opts[:key])
403
+ send(opts.dataset_method).first
404
+ end
405
+ end
406
+ run_association_callbacks(opts, :after_load, objs)
407
+ # Only one_to_many associations should set the reciprocal object
408
+ objs.each{|o| add_reciprocal_object(opts, o)} if opts.set_reciprocal_to_self?
409
+ associations[name] = objs
410
+ end
411
+ end
412
+
413
+ # Remove all associated objects from the given association
414
+ def remove_all_associated_objects(opts)
415
+ raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
416
+ send(opts._remove_all_method)
417
+ ret = associations[opts[:name]].each{|o| remove_reciprocal_object(opts, o)} if associations.include?(opts[:name])
418
+ associations[opts[:name]] = []
419
+ ret
420
+ end
421
+
422
+ # Remove the given associated object from the given association
423
+ def remove_associated_object(opts, o)
424
+ raise(Sequel::Error, "model object #{model} does not have a primary key") unless pk
425
+ raise(Sequel::Error, "associated object #{o.model} does not have a primary key") if opts.need_associated_primary_key? && !o.pk
426
+ return if run_association_callbacks(opts, :before_remove, o) == false
427
+ send(opts._remove_method, o)
428
+ associations[opts[:name]].delete_if{|x| o === x} if associations.include?(opts[:name])
429
+ remove_reciprocal_object(opts, o)
430
+ run_association_callbacks(opts, :after_remove, o)
431
+ o
432
+ end
433
+
434
+ # Remove/unset the current object from/as the given object's reciprocal association.
435
+ def remove_reciprocal_object(opts, o)
436
+ return unless reciprocal = opts.reciprocal
437
+ if opts.reciprocal_array?
438
+ if array = o.associations[reciprocal]
439
+ array.delete_if{|x| self === x}
440
+ end
441
+ else
442
+ o.associations[reciprocal] = nil
443
+ end
444
+ end
445
+
446
+ # Run the callback for the association with the object.
447
+ def run_association_callbacks(reflection, callback_type, object)
448
+ raise_error = raise_on_save_failure || !reflection.returns_array?
449
+ stop_on_false = [:before_add, :before_remove].include?(callback_type)
450
+ reflection[callback_type].each do |cb|
451
+ res = case cb
452
+ when Symbol
453
+ send(cb, object)
454
+ when Proc
455
+ cb.call(self, object)
456
+ else
457
+ raise Error, "callbacks should either be Procs or Symbols"
458
+ end
459
+ if res == false and stop_on_false
460
+ raise(BeforeHookFailed, "Unable to modify association for record: one of the #{callback_type} hooks returned false") if raise_error
461
+ return false
462
+ end
463
+ end
464
+ end
465
+
466
+ # Raise an error if raise_on_save_failure is true
467
+ def save_failure(type)
468
+ if raise_on_save_failure
469
+ if type == :invalid
470
+ raise ValidationFailed, errors.full_messages.join(', ')
471
+ else
472
+ raise BeforeHookFailed, "one of the before_#{type} hooks returned false"
473
+ end
474
+ end
475
+ end
476
+
477
+ # Set the given object as the associated object for the given association
478
+ def set_associated_object(opts, o)
479
+ raise(Sequel::Error, "model object #{model} does not have a primary key") if o && !o.pk
480
+ old_val = send(opts.association_method)
481
+ return o if old_val == o
482
+ return if old_val and run_association_callbacks(opts, :before_remove, old_val) == false
483
+ return if o and run_association_callbacks(opts, :before_add, o) == false
484
+ send(opts._setter_method, o)
485
+ associations[opts[:name]] = o
486
+ remove_reciprocal_object(opts, old_val) if old_val
487
+ if o
488
+ add_reciprocal_object(opts, o)
489
+ run_association_callbacks(opts, :after_add, o)
490
+ end
491
+ run_association_callbacks(opts, :after_remove, old_val) if old_val
492
+ o
493
+ end
494
+
495
+ # Set the columns, filtered by the only and except arrays.
496
+ def set_restricted(hash, only, except)
497
+ columns_not_set = model.instance_variable_get(:@columns).blank?
498
+ meths = setter_methods(only, except)
499
+ strict = strict_param_setting
500
+ hash.each do |k,v|
501
+ m = "#{k}="
502
+ if meths.include?(m)
503
+ send(m, v)
504
+ elsif columns_not_set && (Symbol === k)
505
+ self[k] = v
506
+ elsif strict
507
+ raise Error, "method #{m} doesn't exist or access is restricted to it"
508
+ end
509
+ end
510
+ self
511
+ end
512
+
513
+ # Returns all methods that can be used for attribute
514
+ # assignment (those that end with =), modified by the only
515
+ # and except arguments:
516
+ #
517
+ # * only
518
+ # * false - Don't modify the results
519
+ # * nil - if the model has allowed_columns, use only these, otherwise, don't modify
520
+ # * Array - allow only the given methods to be used
521
+ # * except
522
+ # * false - Don't modify the results
523
+ # * nil - if the model has restricted_columns, remove these, otherwise, don't modify
524
+ # * Array - remove the given methods
525
+ #
526
+ # only takes precedence over except, and if only is not used, certain methods are always
527
+ # restricted (RESTRICTED_SETTER_METHODS). The primary key is restricted by default as
528
+ # well, see Model.unrestrict_primary_key to change this.
529
+ def setter_methods(only, except)
530
+ only = only.nil? ? model.allowed_columns : only
531
+ except = except.nil? ? model.restricted_columns : except
532
+ if only
533
+ only.map{|x| "#{x}="}
534
+ else
535
+ meths = methods.collect{|x| x.to_s}.grep(/=\z/) - RESTRICTED_SETTER_METHODS
536
+ meths -= Array(primary_key).map{|x| "#{x}="} if primary_key && model.restrict_primary_key?
537
+ meths -= except.map{|x| "#{x}="} if except
538
+ meths
539
+ end
540
+ end
541
+
542
+ # Typecast the value to the column's type if typecasting. Calls the database's
543
+ # typecast_value method, so database adapters can override/augment the handling
544
+ # for database specific column types.
545
+ def typecast_value(column, value)
546
+ return value unless typecast_on_assignment && db_schema && (col_schema = db_schema[column]) && !model.serialized?(column)
547
+ value = nil if value == '' and typecast_empty_string_to_nil and col_schema[:type] and ![:string, :blob].include?(col_schema[:type])
548
+ raise(Error::InvalidValue, "nil/NULL is not allowed for the #{column} column") if raise_on_typecast_failure && value.nil? && (col_schema[:allow_null] == false)
549
+ begin
550
+ model.db.typecast_value(col_schema[:type], value)
551
+ rescue Error::InvalidValue
552
+ raise_on_typecast_failure ? raise : value
553
+ end
554
+ end
555
+
556
+ # Call uniq! on the given array. This is used by the :uniq option,
557
+ # and is an actual method for memory reasons.
558
+ def array_uniq!(a)
559
+ a.uniq!
560
+ end
561
+
562
+ # Set the columns, filtered by the only and except arrays.
563
+ def update_restricted(hash, only, except)
564
+ set_restricted(hash, only, except)
565
+ save_changes
566
+ end
567
+ end
568
+ end
@@ -0,0 +1,49 @@
1
+ module Sequel
2
+ class Model
3
+ # Creates table, using the column information from set_schema.
4
+ def self.create_table
5
+ db.create_table(table_name, :generator=>@schema)
6
+ @db_schema = get_db_schema(true)
7
+ columns
8
+ end
9
+
10
+ # Drops the table if it exists and then runs create_table. Should probably
11
+ # not be used except in testing.
12
+ def self.create_table!
13
+ drop_table rescue nil
14
+ create_table
15
+ end
16
+
17
+ # Drops table.
18
+ def self.drop_table
19
+ db.drop_table(table_name)
20
+ end
21
+
22
+ # Returns table schema created with set_schema for direct descendant of Model.
23
+ # Does not retreive schema information from the database, see db_schema if you
24
+ # want that.
25
+ def self.schema
26
+ @schema || (superclass.schema unless superclass == Model)
27
+ end
28
+
29
+ # Defines a table schema (see Schema::Generator for more information).
30
+ #
31
+ # This is only needed if you want to use the create_table/create_table! methods.
32
+ # Will also set the dataset if you provide a name, as well as setting
33
+ # the primary key if you defined one in the passed block.
34
+ #
35
+ # In general, it is a better idea to use migrations for production code, as
36
+ # migrations allow changes to existing schema. set_schema is mostly useful for
37
+ # test code or simple examples.
38
+ def self.set_schema(name = nil, &block)
39
+ set_dataset(db[name]) if name
40
+ @schema = Schema::Generator.new(db, &block)
41
+ set_primary_key(@schema.primary_key_name) if @schema.primary_key_name
42
+ end
43
+
44
+ # Returns true if table exists, false otherwise.
45
+ def self.table_exists?
46
+ db.table_exists?(table_name)
47
+ end
48
+ end
49
+ end