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,523 @@
1
+ module Sequel
2
+ class Model
3
+ @allowed_columns = nil
4
+ @association_reflections = {}
5
+ @cache_store = nil
6
+ @cache_ttl = nil
7
+ @db = nil
8
+ @db_schema = nil
9
+ @dataset_methods = {}
10
+ @hooks = {}
11
+ @overridable_methods_module = nil
12
+ @primary_key = :id
13
+ @raise_on_save_failure = true
14
+ @raise_on_typecast_failure = true
15
+ @restrict_primary_key = true
16
+ @restricted_columns = nil
17
+ @skip_superclass_validations = nil
18
+ @sti_dataset = nil
19
+ @sti_key = nil
20
+ @strict_param_setting = true
21
+ @transform = nil
22
+ @typecast_empty_string_to_nil = true
23
+ @typecast_on_assignment = true
24
+
25
+ # Which columns should be the only columns allowed in a call to set
26
+ # (default: all columns).
27
+ metaattr_reader :allowed_columns
28
+
29
+ # All association reflections defined for this model (default: none).
30
+ metaattr_reader :association_reflections
31
+
32
+ # Hash of dataset methods to add to this class and subclasses when
33
+ # set_dataset is called.
34
+ metaattr_reader :dataset_methods
35
+
36
+ # The default primary key for classes (default: :id)
37
+ metaattr_reader :primary_key
38
+
39
+ # Whether to raise an error instead of returning nil on a failure
40
+ # to save/create/save_changes/etc.
41
+ metaattr_accessor :raise_on_save_failure
42
+
43
+ # Whether to raise an error when unable to typecast data for a column
44
+ # (default: true)
45
+ metaattr_accessor :raise_on_typecast_failure
46
+
47
+ # Which columns should not be update in a call to set
48
+ # (default: no columns).
49
+ metaattr_reader :restricted_columns
50
+
51
+ # The base dataset for STI, to which filters are added to get
52
+ # only the models for the specific STI subclass.
53
+ metaattr_reader :sti_dataset
54
+
55
+ # The column name holding the STI key for this model
56
+ metaattr_reader :sti_key
57
+
58
+ # Whether new/set/update and their variants should raise an error
59
+ # if an invalid key is used (either that doesn't exist or that
60
+ # access is restricted to it).
61
+ metaattr_accessor :strict_param_setting
62
+
63
+ # Whether to typecast the empty string ('') to nil for columns that
64
+ # are not string or blob.
65
+ metaattr_accessor :typecast_empty_string_to_nil
66
+
67
+ # Whether to typecast attribute values on assignment (default: true)
68
+ metaattr_accessor :typecast_on_assignment
69
+
70
+ # Dataset methods to proxy via metaprogramming
71
+ DATASET_METHODS = %w'<< all avg count delete distinct eager eager_graph each each_page
72
+ empty? except exclude filter first from from_self full_outer_join get graph
73
+ group group_and_count group_by having import inner_join insert
74
+ insert_multiple intersect interval invert_order join join_table last
75
+ left_outer_join limit map multi_insert naked order order_by order_more
76
+ paginate print query range reverse_order right_outer_join select
77
+ select_all select_more server set set_graph_aliases single_value size to_csv to_hash
78
+ transform union uniq unfiltered unordered update where'.map{|x| x.to_sym}
79
+
80
+ # Instance variables that are inherited in subclasses
81
+ INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@cache_store=>nil,
82
+ :@cache_ttl=>nil, :@dataset_methods=>:dup, :@primary_key=>nil,
83
+ :@raise_on_save_failure=>nil, :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
84
+ :@sti_dataset=>nil, :@sti_key=>nil, :@strict_param_setting=>nil,
85
+ :@typecast_empty_string_to_nil=>nil, :@typecast_on_assignment=>nil,
86
+ :@raise_on_typecast_failure=>nil, :@association_reflections=>:dup}
87
+
88
+ # Empty instance variables, for -w compliance
89
+ EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@transform, :@db, :@skip_superclass_validations]
90
+
91
+ # Returns the first record from the database matching the conditions.
92
+ # If a hash is given, it is used as the conditions. If another
93
+ # object is given, it finds the first record whose primary key(s) match
94
+ # the given argument(s). If caching is used, the cache is checked
95
+ # first before a dataset lookup is attempted unless a hash is supplied.
96
+ def self.[](*args)
97
+ args = args.first if (args.size == 1)
98
+
99
+ if Hash === args
100
+ dataset[args]
101
+ else
102
+ @cache_store ? cache_lookup(args) : dataset[primary_key_hash(args)]
103
+ end
104
+ end
105
+
106
+ # Returns the columns in the result set in their original order.
107
+ # Generally, this will used the columns determined via the database
108
+ # schema, but in certain cases (e.g. models that are based on a joined
109
+ # dataset) it will use Dataset#columns to find the columns, which
110
+ # may be empty if the Dataset has no records.
111
+ def self.columns
112
+ @columns || set_columns(dataset.naked.columns)
113
+ end
114
+
115
+ # Creates new instance with values set to passed-in Hash, saves it
116
+ # (running any callbacks), and returns the instance if the object
117
+ # was saved correctly. If there was an error saving the object,
118
+ # returns false.
119
+ def self.create(values = {}, &block)
120
+ obj = new(values, &block)
121
+ return unless obj.save
122
+ obj
123
+ end
124
+
125
+ # Returns the dataset associated with the Model class.
126
+ def self.dataset
127
+ @dataset || raise(Error, "No dataset associated with #{self}")
128
+ end
129
+
130
+ # Returns the database associated with the Model class.
131
+ def self.db
132
+ return @db if @db
133
+ @db = self == Model ? DATABASES.first : superclass.db
134
+ raise(Error, "No database associated with #{self}") unless @db
135
+ @db
136
+ end
137
+
138
+ # Sets the database associated with the Model class.
139
+ def self.db=(db)
140
+ @db = db
141
+ if @dataset
142
+ set_dataset(db[table_name])
143
+ end
144
+ end
145
+
146
+ # Returns the cached schema information if available or gets it
147
+ # from the database.
148
+ def self.db_schema
149
+ @db_schema ||= get_db_schema
150
+ end
151
+
152
+ # If a block is given, define a method on the dataset with the given argument name using
153
+ # the given block as well as a method on the model that calls the
154
+ # dataset method.
155
+ #
156
+ # If a block is not given, define a method on the model for each argument
157
+ # that calls the dataset method of the same argument name.
158
+ def self.def_dataset_method(*args, &block)
159
+ raise(Error, "No arguments given") if args.empty?
160
+ if block_given?
161
+ raise(Error, "Defining a dataset method using a block requires only one argument") if args.length > 1
162
+ meth = args.first
163
+ @dataset_methods[meth] = block
164
+ dataset.meta_def(meth, &block)
165
+ end
166
+ args.each{|arg| instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__)}
167
+ end
168
+
169
+ # Deletes all records in the model's table.
170
+ def self.delete_all
171
+ dataset.delete
172
+ end
173
+
174
+ # Like delete_all, but invokes before_destroy and after_destroy hooks if used.
175
+ def self.destroy_all
176
+ dataset.destroy
177
+ end
178
+
179
+ # Returns a dataset with custom SQL that yields model objects.
180
+ def self.fetch(*args)
181
+ db.fetch(*args).set_model(self)
182
+ end
183
+
184
+ # Modify and return eager loading dataset based on association options
185
+ def self.eager_loading_dataset(opts, ds, select, associations)
186
+ ds = ds.select(*select) if select
187
+ ds = ds.filter(opts[:conditions]) if opts[:conditions]
188
+ ds = ds.order(*opts[:order]) if opts[:order]
189
+ ds = ds.eager(opts[:eager]) if opts[:eager]
190
+ ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph]
191
+ ds = ds.eager(associations) unless associations.blank?
192
+ ds = opts[:eager_block].call(ds) if opts[:eager_block]
193
+ ds
194
+ end
195
+
196
+ # Finds a single record according to the supplied filter, e.g.:
197
+ #
198
+ # Ticket.find :author => 'Sharon' # => record
199
+ def self.find(*args, &block)
200
+ dataset.filter(*args, &block).first
201
+ end
202
+
203
+ # Like find but invokes create with given conditions when record does not
204
+ # exists.
205
+ def self.find_or_create(cond)
206
+ find(cond) || create(cond)
207
+ end
208
+
209
+ # If possible, set the dataset for the model subclass as soon as it
210
+ # is created. Also, inherit the INHERITED_INSTANCE_VARIABLES
211
+ # from the parent class.
212
+ def self.inherited(subclass)
213
+ sup_class = subclass.superclass
214
+ ivs = subclass.instance_variables.collect{|x| x.to_s}
215
+ EMPTY_INSTANCE_VARIABLES.each{|iv| subclass.instance_variable_set(iv, nil) unless ivs.include?(iv.to_s)}
216
+ INHERITED_INSTANCE_VARIABLES.each do |iv, dup|
217
+ next if ivs.include?(iv.to_s)
218
+ sup_class_value = sup_class.instance_variable_get(iv)
219
+ sup_class_value = sup_class_value.dup if dup == :dup && sup_class_value
220
+ subclass.instance_variable_set(iv, sup_class_value)
221
+ end
222
+ unless ivs.include?("@dataset")
223
+ db
224
+ begin
225
+ if sup_class == Model
226
+ subclass.set_dataset(Model.db[subclass.implicit_table_name]) unless subclass.name.blank?
227
+ elsif ds = sup_class.instance_variable_get(:@dataset)
228
+ subclass.set_dataset(sup_class.sti_key ? sup_class.sti_dataset.filter(sup_class.sti_key=>subclass.name.to_s) : ds.clone, :inherited=>true)
229
+ end
230
+ rescue
231
+ nil
232
+ end
233
+ end
234
+ hooks = subclass.instance_variable_set(:@hooks, {})
235
+ sup_class.instance_variable_get(:@hooks).each{|k,v| hooks[k] = v.dup}
236
+ end
237
+
238
+ # Returns the implicit table name for the model class.
239
+ def self.implicit_table_name
240
+ name.demodulize.underscore.pluralize.to_sym
241
+ end
242
+
243
+ # Initializes a model instance as an existing record. This constructor is
244
+ # used by Sequel to initialize model instances when fetching records.
245
+ # #load requires that values be a hash where all keys are symbols. It
246
+ # probably should not be used by external code.
247
+ def self.load(values)
248
+ new(values, true)
249
+ end
250
+
251
+ # Mark the model as not having a primary key. Not having a primary key
252
+ # can cause issues, among which is that you won't be able to update records.
253
+ def self.no_primary_key
254
+ @primary_key = nil
255
+ end
256
+
257
+ # Returns primary key attribute hash. If using a composite primary key
258
+ # value such be an array with values for each primary key in the correct
259
+ # order. For a standard primary key, value should be an object with a
260
+ # compatible type for the key. If the model does not have a primary key,
261
+ # raises an Error.
262
+ def self.primary_key_hash(value)
263
+ raise(Error, "#{self} does not have a primary key") unless key = @primary_key
264
+ case key
265
+ when Array
266
+ hash = {}
267
+ key.each_with_index{|k,i| hash[k] = value[i]}
268
+ hash
269
+ else
270
+ {key => value}
271
+ end
272
+ end
273
+
274
+ # Restrict the setting of the primary key(s) inside new/set/update. Because
275
+ # this is the default, this only make sense to use in a subclass where the
276
+ # parent class has used unrestrict_primary_key.
277
+ def self.restrict_primary_key
278
+ @restrict_primary_key = true
279
+ end
280
+
281
+ # Whether or not setting the primary key inside new/set/update is
282
+ # restricted, true by default.
283
+ def self.restrict_primary_key?
284
+ @restrict_primary_key
285
+ end
286
+
287
+ # Serializes column with YAML or through marshalling. Arguments should be
288
+ # column symbols, with an optional trailing hash with a :format key
289
+ # set to :yaml or :marshal (:yaml is the default). Setting this adds
290
+ # a transform to the model and dataset so that columns values will be serialized
291
+ # when saved and deserialized when returned from the database.
292
+ def self.serialize(*columns)
293
+ format = columns.extract_options![:format] || :yaml
294
+ @transform = columns.inject({}) do |m, c|
295
+ m[c] = format
296
+ m
297
+ end
298
+ @dataset.transform(@transform) if @dataset
299
+ end
300
+
301
+ # Whether or not the given column is serialized for this model.
302
+ def self.serialized?(column)
303
+ @transform ? @transform.include?(column) : false
304
+ end
305
+
306
+ # Set the columns to allow in new/set/update. Using this means that
307
+ # any columns not listed here will not be modified. If you have any virtual
308
+ # setter methods (methods that end in =) that you want to be used in
309
+ # new/set/update, they need to be listed here as well (without the =).
310
+ #
311
+ # It may be better to use (set|update)_only instead of this in places where
312
+ # only certain columns may be allowed.
313
+ def self.set_allowed_columns(*cols)
314
+ @allowed_columns = cols
315
+ end
316
+
317
+ # Sets the dataset associated with the Model class. ds can be a Symbol
318
+ # (specifying a table name in the current database), or a Dataset.
319
+ # If a dataset is used, the model's database is changed to the given
320
+ # dataset. If a symbol is used, a dataset is created from the current
321
+ # database with the table name given. Other arguments raise an Error.
322
+ #
323
+ # This sets the model of the the given/created dataset to the current model
324
+ # and adds a destroy method to it. It also extends the dataset with
325
+ # the Associations::EagerLoading methods, and assigns a transform to it
326
+ # if there is one associated with the model. Finally, it attempts to
327
+ # determine the database schema based on the given/created dataset.
328
+ def self.set_dataset(ds, opts={})
329
+ inherited = opts[:inherited]
330
+ @dataset = case ds
331
+ when Symbol
332
+ db[ds]
333
+ when Dataset
334
+ @db = ds.db
335
+ ds
336
+ else
337
+ raise(Error, "Model.set_dataset takes a Symbol or a Sequel::Dataset")
338
+ end
339
+ @dataset.set_model(self)
340
+ @dataset.transform(@transform) if @transform
341
+ if inherited
342
+ @columns = @dataset.columns rescue nil
343
+ else
344
+ @dataset.extend(DatasetMethods)
345
+ @dataset.extend(Associations::EagerLoading)
346
+ @dataset_methods.each{|meth, block| @dataset.meta_def(meth, &block)} if @dataset_methods
347
+ end
348
+ @db_schema = (inherited ? superclass.db_schema : get_db_schema) rescue nil
349
+ self
350
+ end
351
+ metaalias :dataset=, :set_dataset
352
+
353
+ # Sets primary key, regular and composite are possible.
354
+ #
355
+ # Example:
356
+ # class Tagging < Sequel::Model
357
+ # # composite key
358
+ # set_primary_key :taggable_id, :tag_id
359
+ # end
360
+ #
361
+ # class Person < Sequel::Model
362
+ # # regular key
363
+ # set_primary_key :person_id
364
+ # end
365
+ #
366
+ # You can set it to nil to not have a primary key, but that
367
+ # cause certain things not to work, see #no_primary_key.
368
+ def self.set_primary_key(*key)
369
+ @primary_key = (key.length == 1) ? key[0] : key.flatten
370
+ end
371
+
372
+ # Set the columns to restrict in new/set/update. Using this means that
373
+ # any columns listed here will not be modified. If you have any virtual
374
+ # setter methods (methods that end in =) that you want not to be used in
375
+ # new/set/update, they need to be listed here as well (without the =).
376
+ #
377
+ # It may be better to use (set|update)_except instead of this in places where
378
+ # only certain columns may be allowed.
379
+ def self.set_restricted_columns(*cols)
380
+ @restricted_columns = cols
381
+ end
382
+
383
+ # Makes this model a polymorphic model with the given key being a string
384
+ # field in the database holding the name of the class to use. If the
385
+ # key given has a NULL value or there are any problems looking up the
386
+ # class, uses the current class.
387
+ #
388
+ # This should be used to set up single table inheritance for the model,
389
+ # and it only makes sense to use this in the parent class.
390
+ #
391
+ # You should call sti_key after any calls to set_dataset in the model,
392
+ # otherwise subclasses might not have the filters set up correctly.
393
+ #
394
+ # The filters that sti_key sets up in subclasses will not work if
395
+ # those subclasses have further subclasses. For those middle subclasses,
396
+ # you will need to call set_dataset manually with the correct filter set.
397
+ def self.set_sti_key(key)
398
+ m = self
399
+ @sti_key = key
400
+ @sti_dataset = dataset
401
+ dataset.set_model(key, Hash.new{|h,k| h[k] = (k.constantize rescue m)})
402
+ before_create(:set_sti_key){send("#{key}=", model.name.to_s)}
403
+ end
404
+
405
+ # Returns the columns as a list of frozen strings instead
406
+ # of a list of symbols. This makes it possible to check
407
+ # whether a column exists without creating a symbol, which
408
+ # would be a memory leak if called with user input.
409
+ def self.str_columns
410
+ @str_columns ||= columns.map{|c| c.to_s.freeze}
411
+ end
412
+
413
+ # Defines a method that returns a filtered dataset. Subsets
414
+ # create dataset methods, so they can be chained for scoping.
415
+ # For example:
416
+ #
417
+ # Topic.subset(:popular, :num_posts.sql_number > 100)
418
+ # Topic.subset(:recent, :created_on + 7 > Date.today)
419
+ #
420
+ # Allows you to do:
421
+ #
422
+ # Topic.filter(:username.like('%joe%')).popular.recent
423
+ #
424
+ # to get topics with a username that includes joe that
425
+ # have more than 100 posts and were created less than
426
+ # 7 days ago.
427
+ def self.subset(name, *args, &block)
428
+ def_dataset_method(name){filter(*args, &block)}
429
+ end
430
+
431
+ # Returns name of primary table for the dataset.
432
+ def self.table_name
433
+ dataset.opts[:from].first
434
+ end
435
+
436
+ # Allow the setting of the primary key(s) inside new/set/update.
437
+ def self.unrestrict_primary_key
438
+ @restrict_primary_key = false
439
+ end
440
+
441
+ # Add model methods that call dataset methods
442
+ def_dataset_method(*DATASET_METHODS)
443
+
444
+ ### Private Class Methods ###
445
+
446
+ # Create the column accessors
447
+ def self.def_column_accessor(*columns) # :nodoc:
448
+ columns.each do |column|
449
+ im = instance_methods.collect{|x| x.to_s}
450
+ meth = "#{column}="
451
+ overridable_methods_module.module_eval do
452
+ define_method(column){self[column]} unless im.include?(column.to_s)
453
+ unless im.include?(meth)
454
+ define_method(meth) do |*v|
455
+ len = v.length
456
+ raise(ArgumentError, "wrong number of arguments (#{len} for 1)") unless len == 1
457
+ self[column] = v.first
458
+ end
459
+ end
460
+ end
461
+ end
462
+ end
463
+
464
+ # Get the schema from the database, fall back on checking the columns
465
+ # via the database if that will return inaccurate results or if
466
+ # it raises an error.
467
+ def self.get_db_schema(reload = false) # :nodoc:
468
+ set_columns(nil)
469
+ return nil unless @dataset
470
+ schema_hash = {}
471
+ ds_opts = dataset.opts
472
+ single_table = ds_opts[:from] && (ds_opts[:from].length == 1) \
473
+ && !ds_opts.include?(:join) && !ds_opts.include?(:sql)
474
+ get_columns = proc{columns rescue []}
475
+ if single_table && (schema_array = (db.schema(table_name, :reload=>reload) rescue nil))
476
+ schema_array.each{|k,v| schema_hash[k] = v}
477
+ if ds_opts.include?(:select)
478
+ # Dataset only selects certain columns, delete the other
479
+ # columns from the schema
480
+ cols = get_columns.call
481
+ schema_hash.delete_if{|k,v| !cols.include?(k)}
482
+ cols.each{|c| schema_hash[c] ||= {}}
483
+ else
484
+ # Dataset is for a single table with all columns,
485
+ # so set the columns based on the order they were
486
+ # returned by the schema.
487
+ cols = schema_array.collect{|k,v| k}
488
+ set_columns(cols)
489
+ # Set the primary key(s) based on the schema information
490
+ pks = schema_array.collect{|k,v| k if v[:primary_key]}.compact
491
+ pks.length > 0 ? set_primary_key(*pks) : no_primary_key
492
+ # Also set the columns for the dataset, so the dataset
493
+ # doesn't have to do a query to get them.
494
+ dataset.instance_variable_set(:@columns, cols)
495
+ end
496
+ else
497
+ # If the dataset uses multiple tables or custom sql or getting
498
+ # the schema raised an error, just get the columns and
499
+ # create an empty schema hash for it.
500
+ get_columns.call.each{|c| schema_hash[c] = {}}
501
+ end
502
+ schema_hash
503
+ end
504
+
505
+ # Module that the class includes that holds methods the class adds for column accessors and
506
+ # associations so that the methods can be overridden with super
507
+ def self.overridable_methods_module # :nodoc:
508
+ include(@overridable_methods_module = Module.new) unless @overridable_methods_module
509
+ @overridable_methods_module
510
+ end
511
+
512
+ # Set the columns for this model, reset the str_columns,
513
+ # and create accessor methods for each column.
514
+ def self.set_columns(new_columns) # :nodoc:
515
+ @columns = new_columns
516
+ def_column_accessor(*new_columns) if new_columns
517
+ @str_columns = nil
518
+ @columns
519
+ end
520
+
521
+ private_class_method :def_column_accessor, :get_db_schema, :overridable_methods_module, :set_columns
522
+ end
523
+ end
@@ -0,0 +1,82 @@
1
+ module Sequel
2
+ class Model
3
+ metaattr_reader :cache_store, :cache_ttl
4
+
5
+ ### Public Class Methods ###
6
+
7
+ # Set the cache store for the model, as well as the caching before_* hooks.
8
+ #
9
+ # The cache store should implement the following API:
10
+ #
11
+ # cache_store.set(key, obj, time) # Associate the obj with the given key
12
+ # # in the cache for the time (specified
13
+ # # in seconds)
14
+ # cache_store.get(key) => obj # Returns object set with same key
15
+ # cache_store.get(key2) => nil # nil returned if there isn't an object
16
+ # # currently in the cache with that key
17
+ def self.set_cache(store, opts = {})
18
+ @cache_store = store
19
+ @cache_ttl = opts[:ttl] || 3600
20
+ before_update :cache_delete
21
+ before_update_values :cache_delete
22
+ before_delete :cache_delete
23
+ end
24
+
25
+ # Set the time to live for the cache store, in seconds (default is 3600,
26
+ # so 1 hour).
27
+ def self.set_cache_ttl(ttl)
28
+ @cache_ttl = ttl
29
+ end
30
+
31
+ ### Private Class Methods ###
32
+
33
+ # Delete the entry with the matching key from the cache
34
+ def self.cache_delete(key) # :nodoc:
35
+ @cache_store.delete(key)
36
+ nil
37
+ end
38
+
39
+ # Return a key string for the pk
40
+ def self.cache_key(pk) # :nodoc:
41
+ "#{self}:#{Array(pk).join(',')}"
42
+ end
43
+
44
+ # Lookup the primary key in the cache.
45
+ # If found, return the matching object.
46
+ # Otherwise, get the matching object from the database and
47
+ # update the cache with it.
48
+ def self.cache_lookup(pk) # :nodoc:
49
+ ck = cache_key(pk)
50
+ unless obj = @cache_store.get(ck)
51
+ obj = dataset[primary_key_hash(pk)]
52
+ @cache_store.set(ck, obj, @cache_ttl)
53
+ end
54
+ obj
55
+ end
56
+
57
+ private_class_method :cache_delete, :cache_key, :cache_lookup
58
+
59
+ ### Instance Methods ###
60
+
61
+ # Return a key unique to the underlying record for caching, based on the
62
+ # primary key value(s) for the object. If the model does not have a primary
63
+ # key, raise an Error.
64
+ def cache_key
65
+ raise(Error, "No primary key is associated with this model") unless key = primary_key
66
+ pk = case key
67
+ when Array
68
+ key.collect{|k| @values[k]}
69
+ else
70
+ @values[key] || (raise Error, 'no primary key for this record')
71
+ end
72
+ model.send(:cache_key, pk)
73
+ end
74
+
75
+ private
76
+
77
+ # Delete this object from the cache
78
+ def cache_delete
79
+ model.send(:cache_delete, cache_key)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,26 @@
1
+ # Dataset methods are methods that the model class extends its dataset with in
2
+ # the call to set_dataset.
3
+ module Sequel::Model::DatasetMethods
4
+ # Destroy each row in the dataset by instantiating it and then calling
5
+ # destroy on the resulting model object. This isn't as fast as deleting
6
+ # the object, which does a single SQL call, but this runs any destroy
7
+ # hooks.
8
+ def destroy
9
+ raise(Error, "No model associated with this dataset") unless @opts[:models]
10
+ count = 0
11
+ @db.transaction{all{|r| count += 1; r.destroy}}
12
+ count
13
+ end
14
+
15
+ # This allows you to call to_hash without any arguments, which will
16
+ # result in a hash with the primary key value being the key and the
17
+ # model object being the value.
18
+ def to_hash(key_column=nil, value_column=nil)
19
+ if key_column
20
+ super
21
+ else
22
+ raise(Sequel::Error, "No primary key for model") unless pk = @opts[:models][nil].primary_key
23
+ super(pk, value_column)
24
+ end
25
+ end
26
+ end