epugh-sequel 0.0.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.
Files changed (134) hide show
  1. data/README.rdoc +652 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/sequel +104 -0
  4. data/lib/sequel.rb +1 -0
  5. data/lib/sequel/adapters/ado.rb +85 -0
  6. data/lib/sequel/adapters/db2.rb +132 -0
  7. data/lib/sequel/adapters/dbi.rb +101 -0
  8. data/lib/sequel/adapters/do.rb +197 -0
  9. data/lib/sequel/adapters/do/mysql.rb +38 -0
  10. data/lib/sequel/adapters/do/postgres.rb +92 -0
  11. data/lib/sequel/adapters/do/sqlite.rb +31 -0
  12. data/lib/sequel/adapters/firebird.rb +307 -0
  13. data/lib/sequel/adapters/informix.rb +75 -0
  14. data/lib/sequel/adapters/jdbc.rb +485 -0
  15. data/lib/sequel/adapters/jdbc/h2.rb +62 -0
  16. data/lib/sequel/adapters/jdbc/mysql.rb +56 -0
  17. data/lib/sequel/adapters/jdbc/oracle.rb +23 -0
  18. data/lib/sequel/adapters/jdbc/postgresql.rb +101 -0
  19. data/lib/sequel/adapters/jdbc/sqlite.rb +43 -0
  20. data/lib/sequel/adapters/mysql.rb +370 -0
  21. data/lib/sequel/adapters/odbc.rb +184 -0
  22. data/lib/sequel/adapters/openbase.rb +57 -0
  23. data/lib/sequel/adapters/oracle.rb +140 -0
  24. data/lib/sequel/adapters/postgres.rb +453 -0
  25. data/lib/sequel/adapters/shared/mssql.rb +93 -0
  26. data/lib/sequel/adapters/shared/mysql.rb +341 -0
  27. data/lib/sequel/adapters/shared/oracle.rb +62 -0
  28. data/lib/sequel/adapters/shared/postgres.rb +743 -0
  29. data/lib/sequel/adapters/shared/progress.rb +34 -0
  30. data/lib/sequel/adapters/shared/sqlite.rb +263 -0
  31. data/lib/sequel/adapters/sqlite.rb +243 -0
  32. data/lib/sequel/adapters/utils/date_format.rb +21 -0
  33. data/lib/sequel/adapters/utils/stored_procedures.rb +75 -0
  34. data/lib/sequel/adapters/utils/unsupported.rb +62 -0
  35. data/lib/sequel/connection_pool.rb +258 -0
  36. data/lib/sequel/core.rb +204 -0
  37. data/lib/sequel/core_sql.rb +185 -0
  38. data/lib/sequel/database.rb +687 -0
  39. data/lib/sequel/database/schema_generator.rb +324 -0
  40. data/lib/sequel/database/schema_methods.rb +164 -0
  41. data/lib/sequel/database/schema_sql.rb +324 -0
  42. data/lib/sequel/dataset.rb +422 -0
  43. data/lib/sequel/dataset/convenience.rb +237 -0
  44. data/lib/sequel/dataset/prepared_statements.rb +220 -0
  45. data/lib/sequel/dataset/sql.rb +1105 -0
  46. data/lib/sequel/deprecated.rb +529 -0
  47. data/lib/sequel/exceptions.rb +44 -0
  48. data/lib/sequel/extensions/blank.rb +42 -0
  49. data/lib/sequel/extensions/inflector.rb +288 -0
  50. data/lib/sequel/extensions/pagination.rb +96 -0
  51. data/lib/sequel/extensions/pretty_table.rb +78 -0
  52. data/lib/sequel/extensions/query.rb +48 -0
  53. data/lib/sequel/extensions/string_date_time.rb +47 -0
  54. data/lib/sequel/metaprogramming.rb +44 -0
  55. data/lib/sequel/migration.rb +212 -0
  56. data/lib/sequel/model.rb +142 -0
  57. data/lib/sequel/model/association_reflection.rb +263 -0
  58. data/lib/sequel/model/associations.rb +1024 -0
  59. data/lib/sequel/model/base.rb +911 -0
  60. data/lib/sequel/model/deprecated.rb +188 -0
  61. data/lib/sequel/model/deprecated_hooks.rb +103 -0
  62. data/lib/sequel/model/deprecated_inflector.rb +335 -0
  63. data/lib/sequel/model/deprecated_validations.rb +384 -0
  64. data/lib/sequel/model/errors.rb +37 -0
  65. data/lib/sequel/model/exceptions.rb +7 -0
  66. data/lib/sequel/model/inflections.rb +230 -0
  67. data/lib/sequel/model/plugins.rb +74 -0
  68. data/lib/sequel/object_graph.rb +230 -0
  69. data/lib/sequel/plugins/caching.rb +122 -0
  70. data/lib/sequel/plugins/hook_class_methods.rb +122 -0
  71. data/lib/sequel/plugins/schema.rb +53 -0
  72. data/lib/sequel/plugins/single_table_inheritance.rb +63 -0
  73. data/lib/sequel/plugins/validation_class_methods.rb +373 -0
  74. data/lib/sequel/sql.rb +854 -0
  75. data/lib/sequel/version.rb +11 -0
  76. data/lib/sequel_core.rb +1 -0
  77. data/lib/sequel_model.rb +1 -0
  78. data/spec/adapters/ado_spec.rb +46 -0
  79. data/spec/adapters/firebird_spec.rb +376 -0
  80. data/spec/adapters/informix_spec.rb +96 -0
  81. data/spec/adapters/mysql_spec.rb +875 -0
  82. data/spec/adapters/oracle_spec.rb +272 -0
  83. data/spec/adapters/postgres_spec.rb +692 -0
  84. data/spec/adapters/spec_helper.rb +10 -0
  85. data/spec/adapters/sqlite_spec.rb +550 -0
  86. data/spec/core/connection_pool_spec.rb +526 -0
  87. data/spec/core/core_ext_spec.rb +156 -0
  88. data/spec/core/core_sql_spec.rb +528 -0
  89. data/spec/core/database_spec.rb +1214 -0
  90. data/spec/core/dataset_spec.rb +3513 -0
  91. data/spec/core/expression_filters_spec.rb +363 -0
  92. data/spec/core/migration_spec.rb +261 -0
  93. data/spec/core/object_graph_spec.rb +280 -0
  94. data/spec/core/pretty_table_spec.rb +58 -0
  95. data/spec/core/schema_generator_spec.rb +167 -0
  96. data/spec/core/schema_spec.rb +778 -0
  97. data/spec/core/spec_helper.rb +82 -0
  98. data/spec/core/version_spec.rb +7 -0
  99. data/spec/extensions/blank_spec.rb +67 -0
  100. data/spec/extensions/caching_spec.rb +201 -0
  101. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  102. data/spec/extensions/inflector_spec.rb +122 -0
  103. data/spec/extensions/pagination_spec.rb +99 -0
  104. data/spec/extensions/pretty_table_spec.rb +91 -0
  105. data/spec/extensions/query_spec.rb +85 -0
  106. data/spec/extensions/schema_spec.rb +111 -0
  107. data/spec/extensions/single_table_inheritance_spec.rb +53 -0
  108. data/spec/extensions/spec_helper.rb +90 -0
  109. data/spec/extensions/string_date_time_spec.rb +93 -0
  110. data/spec/extensions/validation_class_methods_spec.rb +1054 -0
  111. data/spec/integration/dataset_test.rb +160 -0
  112. data/spec/integration/eager_loader_test.rb +683 -0
  113. data/spec/integration/prepared_statement_test.rb +130 -0
  114. data/spec/integration/schema_test.rb +183 -0
  115. data/spec/integration/spec_helper.rb +75 -0
  116. data/spec/integration/type_test.rb +96 -0
  117. data/spec/model/association_reflection_spec.rb +93 -0
  118. data/spec/model/associations_spec.rb +1780 -0
  119. data/spec/model/base_spec.rb +494 -0
  120. data/spec/model/caching_spec.rb +217 -0
  121. data/spec/model/dataset_methods_spec.rb +78 -0
  122. data/spec/model/eager_loading_spec.rb +1165 -0
  123. data/spec/model/hooks_spec.rb +472 -0
  124. data/spec/model/inflector_spec.rb +126 -0
  125. data/spec/model/model_spec.rb +588 -0
  126. data/spec/model/plugins_spec.rb +142 -0
  127. data/spec/model/record_spec.rb +1243 -0
  128. data/spec/model/schema_spec.rb +92 -0
  129. data/spec/model/spec_helper.rb +124 -0
  130. data/spec/model/validations_spec.rb +1080 -0
  131. data/spec/rcov.opts +6 -0
  132. data/spec/spec.opts +0 -0
  133. data/spec/spec_config.rb.example +10 -0
  134. metadata +202 -0
@@ -0,0 +1,911 @@
1
+ module Sequel
2
+ class Model
3
+ extend Enumerable
4
+ extend Inflections
5
+ extend Metaprogramming
6
+ include Metaprogramming
7
+
8
+ module ClassMethods
9
+ # Which columns should be the only columns allowed in a call to set
10
+ # (default: all columns).
11
+ attr_reader :allowed_columns
12
+
13
+ # Array of modules that extend this model's dataset.
14
+ attr_reader :dataset_method_modules
15
+
16
+ # Hash of dataset methods to add to this class and subclasses when
17
+ # set_dataset is called.
18
+ attr_reader :dataset_methods
19
+
20
+ # The default primary key for classes (default: :id)
21
+ attr_reader :primary_key
22
+
23
+ # Whether to raise an error instead of returning nil on a failure
24
+ # to save/create/save_changes/etc.
25
+ attr_accessor :raise_on_save_failure
26
+
27
+ # Whether to raise an error when unable to typecast data for a column
28
+ # (default: true)
29
+ attr_accessor :raise_on_typecast_failure
30
+
31
+ # Which columns should not be update in a call to set
32
+ # (default: no columns).
33
+ attr_reader :restricted_columns
34
+
35
+ # Should be the literal primary key column name if this Model's table has a simple primary key, or
36
+ # nil if the model has a compound primary key or no primary key.
37
+ attr_reader :simple_pk
38
+
39
+ # Should be the literal table name if this Model's dataset is a simple table (no select, order, join, etc.),
40
+ # or nil otherwise.
41
+ attr_reader :simple_table
42
+
43
+ # Whether new/set/update and their variants should raise an error
44
+ # if an invalid key is used (either that doesn't exist or that
45
+ # access is restricted to it).
46
+ attr_accessor :strict_param_setting
47
+
48
+ # Whether to typecast the empty string ('') to nil for columns that
49
+ # are not string or blob.
50
+ attr_accessor :typecast_empty_string_to_nil
51
+
52
+ # Whether to typecast attribute values on assignment (default: true)
53
+ attr_accessor :typecast_on_assignment
54
+
55
+ # Whether to use a transaction by default when saving/deleting records
56
+ attr_accessor :use_transactions
57
+
58
+ # Returns the first record from the database matching the conditions.
59
+ # If a hash is given, it is used as the conditions. If another
60
+ # object is given, it finds the first record whose primary key(s) match
61
+ # the given argument(s).
62
+ def [](*args)
63
+ args = args.first if (args.size == 1)
64
+ return dataset[args] if args.is_a?(Hash)
65
+ if t = simple_table and p = simple_pk
66
+ with_sql("SELECT * FROM #{t} WHERE #{p} = #{dataset.literal(args)} LIMIT 1").first
67
+ else
68
+ dataset[primary_key_hash(args)]
69
+ end
70
+ end
71
+
72
+ # Returns the columns in the result set in their original order.
73
+ # Generally, this will used the columns determined via the database
74
+ # schema, but in certain cases (e.g. models that are based on a joined
75
+ # dataset) it will use Dataset#columns to find the columns, which
76
+ # may be empty if the Dataset has no records.
77
+ def columns
78
+ @columns || set_columns(dataset.naked.columns)
79
+ end
80
+
81
+ # Creates new instance with values set to passed-in Hash, saves it
82
+ # (running any callbacks), and returns the instance if the object
83
+ # was saved correctly. If there was an error saving the object,
84
+ # returns false.
85
+ def create(values = {}, &block)
86
+ obj = new(values, &block)
87
+ return unless obj.save
88
+ obj
89
+ end
90
+
91
+ # Returns the dataset associated with the Model class.
92
+ def dataset
93
+ @dataset || raise(Error, "No dataset associated with #{self}")
94
+ end
95
+
96
+ # Returns the database associated with the Model class.
97
+ def db
98
+ return @db if @db
99
+ @db = self == Model ? DATABASES.first : superclass.db
100
+ raise(Error, "No database associated with #{self}") unless @db
101
+ @db
102
+ end
103
+
104
+ # Sets the database associated with the Model class.
105
+ def db=(db)
106
+ @db = db
107
+ set_dataset(db.dataset(@dataset.opts)) if @dataset
108
+ end
109
+
110
+ # Returns the cached schema information if available or gets it
111
+ # from the database.
112
+ def db_schema
113
+ @db_schema ||= get_db_schema
114
+ end
115
+
116
+ # If a block is given, define a method on the dataset with the given argument name using
117
+ # the given block as well as a method on the model that calls the
118
+ # dataset method.
119
+ #
120
+ # If a block is not given, define a method on the model for each argument
121
+ # that calls the dataset method of the same argument name.
122
+ def def_dataset_method(*args, &block)
123
+ raise(Error, "No arguments given") if args.empty?
124
+ if block_given?
125
+ raise(Error, "Defining a dataset method using a block requires only one argument") if args.length > 1
126
+ meth = args.first
127
+ @dataset_methods[meth] = block
128
+ dataset.meta_def(meth, &block)
129
+ end
130
+ args.each{|arg| instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__) unless method_defined?(arg)}
131
+ end
132
+
133
+ # Finds a single record according to the supplied filter, e.g.:
134
+ #
135
+ # Ticket.find :author => 'Sharon' # => record
136
+ def find(*args, &block)
137
+ filter(*args, &block).first
138
+ end
139
+
140
+ # Like find but invokes create with given conditions when record does not
141
+ # exists.
142
+ def find_or_create(cond)
143
+ find(cond) || create(cond)
144
+ end
145
+
146
+ # If possible, set the dataset for the model subclass as soon as it
147
+ # is created. Also, inherit the INHERITED_INSTANCE_VARIABLES
148
+ # from the parent class.
149
+ def inherited(subclass)
150
+ ivs = subclass.instance_variables.collect{|x| x.to_s}
151
+ EMPTY_INSTANCE_VARIABLES.each{|iv| subclass.instance_variable_set(iv, nil) unless ivs.include?(iv.to_s)}
152
+ INHERITED_INSTANCE_VARIABLES.each do |iv, dup|
153
+ next if ivs.include?(iv.to_s)
154
+ sup_class_value = instance_variable_get(iv)
155
+ sup_class_value = sup_class_value.dup if dup == :dup && sup_class_value
156
+ subclass.instance_variable_set(iv, sup_class_value)
157
+ end
158
+ unless ivs.include?("@dataset")
159
+ db
160
+ begin
161
+ if self == Model
162
+ subclass.set_dataset(subclass.implicit_table_name) unless subclass.name.empty?
163
+ elsif ds = instance_variable_get(:@dataset)
164
+ subclass.set_dataset(ds.clone, :inherited=>true)
165
+ end
166
+ rescue
167
+ nil
168
+ end
169
+ end
170
+ end
171
+
172
+ # Returns the implicit table name for the model class.
173
+ def implicit_table_name
174
+ pluralize(underscore(demodulize(name))).to_sym
175
+ end
176
+
177
+ # Initializes a model instance as an existing record. This constructor is
178
+ # used by Sequel to initialize model instances when fetching records.
179
+ # #load requires that values be a hash where all keys are symbols. It
180
+ # probably should not be used by external code.
181
+ def load(values)
182
+ new(values, true)
183
+ end
184
+
185
+ # Mark the model as not having a primary key. Not having a primary key
186
+ # can cause issues, among which is that you won't be able to update records.
187
+ def no_primary_key
188
+ @simple_pk = @primary_key = nil
189
+ end
190
+
191
+ # Returns primary key attribute hash. If using a composite primary key
192
+ # value such be an array with values for each primary key in the correct
193
+ # order. For a standard primary key, value should be an object with a
194
+ # compatible type for the key. If the model does not have a primary key,
195
+ # raises an Error.
196
+ def primary_key_hash(value)
197
+ raise(Error, "#{self} does not have a primary key") unless key = @primary_key
198
+ case key
199
+ when Array
200
+ hash = {}
201
+ key.each_with_index{|k,i| hash[k] = value[i]}
202
+ hash
203
+ else
204
+ {key => value}
205
+ end
206
+ end
207
+
208
+ # Restrict the setting of the primary key(s) inside new/set/update. Because
209
+ # this is the default, this only make sense to use in a subclass where the
210
+ # parent class has used unrestrict_primary_key.
211
+ def restrict_primary_key
212
+ @restrict_primary_key = true
213
+ end
214
+
215
+ # Whether or not setting the primary key inside new/set/update is
216
+ # restricted, true by default.
217
+ def restrict_primary_key?
218
+ @restrict_primary_key
219
+ end
220
+
221
+ # Serializes column with YAML or through marshalling. Arguments should be
222
+ # column symbols, with an optional trailing hash with a :format key
223
+ # set to :yaml or :marshal (:yaml is the default). Setting this adds
224
+ # a transform to the model and dataset so that columns values will be serialized
225
+ # when saved and deserialized when returned from the database.
226
+ def serialize(*columns)
227
+ format = extract_options!(columns)[:format] || :yaml
228
+ @transform = columns.inject({}) do |m, c|
229
+ m[c] = format
230
+ m
231
+ end
232
+ @dataset.transform(@transform) if @dataset
233
+ end
234
+
235
+ # Whether or not the given column is serialized for this model.
236
+ def serialized?(column)
237
+ @transform ? @transform.include?(column) : false
238
+ end
239
+
240
+ # Set the columns to allow in new/set/update. Using this means that
241
+ # any columns not listed here will not be modified. If you have any virtual
242
+ # setter methods (methods that end in =) that you want to be used in
243
+ # new/set/update, they need to be listed here as well (without the =).
244
+ #
245
+ # It may be better to use (set|update)_only instead of this in places where
246
+ # only certain columns may be allowed.
247
+ def set_allowed_columns(*cols)
248
+ @allowed_columns = cols
249
+ end
250
+
251
+ # Sets the dataset associated with the Model class. ds can be a Symbol
252
+ # (specifying a table name in the current database), or a Dataset.
253
+ # If a dataset is used, the model's database is changed to the given
254
+ # dataset. If a symbol is used, a dataset is created from the current
255
+ # database with the table name given. Other arguments raise an Error.
256
+ #
257
+ # This sets the model of the the given/created dataset to the current model
258
+ # and adds a destroy method to it. It also extends the dataset with
259
+ # the Associations::EagerLoading methods, and assigns a transform to it
260
+ # if there is one associated with the model. Finally, it attempts to
261
+ # determine the database schema based on the given/created dataset.
262
+ def set_dataset(ds, opts={})
263
+ inherited = opts[:inherited]
264
+ @dataset = case ds
265
+ when Symbol
266
+ @simple_table = db.literal(ds)
267
+ db[ds]
268
+ when Dataset
269
+ @simple_table = nil
270
+ @db = ds.db
271
+ ds
272
+ else
273
+ raise(Error, "Model.set_dataset takes a Symbol or a Sequel::Dataset")
274
+ end
275
+ @dataset.row_proc = Proc.new{|r| load(r)}
276
+ @dataset.transform(@transform) if @transform
277
+ if inherited
278
+ @simple_table = superclass.simple_table
279
+ @columns = @dataset.columns rescue nil
280
+ else
281
+ @dataset_method_modules.each{|m| @dataset.extend(m)} if @dataset_method_modules
282
+ @dataset_methods.each{|meth, block| @dataset.meta_def(meth, &block)} if @dataset_methods
283
+ end
284
+ @dataset.model = self if @dataset.respond_to?(:model=)
285
+ @db_schema = (inherited ? superclass.db_schema : get_db_schema) rescue nil
286
+ self
287
+ end
288
+ alias dataset= set_dataset
289
+
290
+ # Sets primary key, regular and composite are possible.
291
+ #
292
+ # Example:
293
+ # class Tagging < Sequel::Model
294
+ # # composite key
295
+ # set_primary_key :taggable_id, :tag_id
296
+ # end
297
+ #
298
+ # class Person < Sequel::Model
299
+ # # regular key
300
+ # set_primary_key :person_id
301
+ # end
302
+ #
303
+ # You can set it to nil to not have a primary key, but that
304
+ # cause certain things not to work, see #no_primary_key.
305
+ def set_primary_key(*key)
306
+ @simple_pk = key.length == 1 ? db.literal(key.first) : nil
307
+ @primary_key = (key.length == 1) ? key[0] : key.flatten
308
+ end
309
+
310
+ # Set the columns to restrict in new/set/update. Using this means that
311
+ # any columns listed here will not be modified. If you have any virtual
312
+ # setter methods (methods that end in =) that you want not to be used in
313
+ # new/set/update, they need to be listed here as well (without the =).
314
+ #
315
+ # It may be better to use (set|update)_except instead of this in places where
316
+ # only certain columns may be allowed.
317
+ def set_restricted_columns(*cols)
318
+ @restricted_columns = cols
319
+ end
320
+
321
+ # Defines a method that returns a filtered dataset. Subsets
322
+ # create dataset methods, so they can be chained for scoping.
323
+ # For example:
324
+ #
325
+ # Topic.subset(:popular){|o| o.num_posts > 100}
326
+ # Topic.subset(:recent){|o| o.created_on > Date.today - 7}
327
+ #
328
+ # Allows you to do:
329
+ #
330
+ # Topic.filter(:username.like('%joe%')).popular.recent
331
+ #
332
+ # to get topics with a username that includes joe that
333
+ # have more than 100 posts and were created less than
334
+ # 7 days ago.
335
+ def subset(name, *args, &block)
336
+ def_dataset_method(name){filter(*args, &block)}
337
+ end
338
+
339
+ # Returns name of primary table for the dataset.
340
+ def table_name
341
+ dataset.opts[:from].first
342
+ end
343
+
344
+ # Allow the setting of the primary key(s) inside new/set/update.
345
+ def unrestrict_primary_key
346
+ @restrict_primary_key = false
347
+ end
348
+
349
+ private
350
+
351
+ # Create the column accessors. For columns that can be used as method names directly in ruby code,
352
+ # use a string to define the method for speed. For other columns names, use a block.
353
+ def def_column_accessor(*columns)
354
+ columns, bad_columns = columns.partition{|x| %r{\A[_A-Za-z][0-9A-Za-z_]*\z}io.match(x.to_s)}
355
+ bad_columns.each{|x| def_bad_column_accessor(x)}
356
+ im = instance_methods.collect{|x| x.to_s}
357
+ columns.each do |column|
358
+ meth = "#{column}="
359
+ overridable_methods_module.module_eval("def #{column}; self[:#{column}] end") unless im.include?(column.to_s)
360
+ overridable_methods_module.module_eval("def #{meth}(v); self[:#{column}] = v end") unless im.include?(meth)
361
+ end
362
+ end
363
+
364
+ # Create a column accessor for a column with a method name that is hard to use in ruby code.
365
+ def def_bad_column_accessor(column)
366
+ overridable_methods_module.module_eval do
367
+ define_method(column){self[column]}
368
+ define_method("#{column}="){|v| self[column] = v}
369
+ end
370
+ end
371
+
372
+ # Removes and returns the last member of the array if it is a hash. Otherwise,
373
+ # an empty hash is returned This method is useful when writing methods that
374
+ # take an options hash as the last parameter.
375
+ def extract_options!(array)
376
+ array.last.is_a?(Hash) ? array.pop : {}
377
+ end
378
+
379
+ # Get the schema from the database, fall back on checking the columns
380
+ # via the database if that will return inaccurate results or if
381
+ # it raises an error.
382
+ def get_db_schema(reload = false)
383
+ set_columns(nil)
384
+ return nil unless @dataset
385
+ schema_hash = {}
386
+ ds_opts = dataset.opts
387
+ single_table = ds_opts[:from] && (ds_opts[:from].length == 1) \
388
+ && !ds_opts.include?(:join) && !ds_opts.include?(:sql)
389
+ get_columns = proc{columns rescue []}
390
+ if single_table && (schema_array = (db.schema(table_name, :reload=>reload) rescue nil))
391
+ schema_array.each{|k,v| schema_hash[k] = v}
392
+ if ds_opts.include?(:select)
393
+ # Dataset only selects certain columns, delete the other
394
+ # columns from the schema
395
+ cols = get_columns.call
396
+ schema_hash.delete_if{|k,v| !cols.include?(k)}
397
+ cols.each{|c| schema_hash[c] ||= {}}
398
+ else
399
+ # Dataset is for a single table with all columns,
400
+ # so set the columns based on the order they were
401
+ # returned by the schema.
402
+ cols = schema_array.collect{|k,v| k}
403
+ set_columns(cols)
404
+ # Set the primary key(s) based on the schema information
405
+ pks = schema_array.collect{|k,v| k if v[:primary_key]}.compact
406
+ pks.length > 0 ? set_primary_key(*pks) : no_primary_key
407
+ # Also set the columns for the dataset, so the dataset
408
+ # doesn't have to do a query to get them.
409
+ dataset.instance_variable_set(:@columns, cols)
410
+ end
411
+ else
412
+ # If the dataset uses multiple tables or custom sql or getting
413
+ # the schema raised an error, just get the columns and
414
+ # create an empty schema hash for it.
415
+ get_columns.call.each{|c| schema_hash[c] = {}}
416
+ end
417
+ schema_hash
418
+ end
419
+
420
+ # Module that the class includes that holds methods the class adds for column accessors and
421
+ # associations so that the methods can be overridden with super
422
+ def overridable_methods_module
423
+ include(@overridable_methods_module = Module.new) unless @overridable_methods_module
424
+ @overridable_methods_module
425
+ end
426
+
427
+ # Set the columns for this model, reset the str_columns,
428
+ # and create accessor methods for each column.
429
+ def set_columns(new_columns)
430
+ @columns = new_columns
431
+ def_column_accessor(*new_columns) if new_columns
432
+ @str_columns = nil
433
+ @columns
434
+ end
435
+
436
+ # Add model methods that call dataset methods
437
+ DATASET_METHODS.each{|arg| class_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__)}
438
+
439
+ # Returns a copy of the model's dataset with custom SQL
440
+ alias fetch with_sql
441
+ end
442
+
443
+ module InstanceMethods
444
+ HOOKS.each{|h| class_eval("def #{h}; end", __FILE__, __LINE__)}
445
+
446
+ # Define instance method(s) that calls class method(s) of the
447
+ # same name, caching the result in an instance variable. Define
448
+ # standard attr_writer method for modifying that instance variable
449
+ def self.class_attr_overridable(*meths) # :nodoc:
450
+ meths.each{|meth| class_eval("def #{meth}; !defined?(@#{meth}) ? (@#{meth} = self.class.#{meth}) : @#{meth} end")}
451
+ attr_writer(*meths)
452
+ end
453
+
454
+ # Define instance method(s) that calls class method(s) of the
455
+ # same name. Replaces the construct:
456
+ #
457
+ # define_method(meth){self.class.send(meth)}
458
+ def self.class_attr_reader(*meths) # :nodoc:
459
+ meths.each{|meth| class_eval("def #{meth}; model.#{meth} end")}
460
+ end
461
+
462
+ private_class_method :class_attr_overridable, :class_attr_reader
463
+
464
+ class_attr_reader :columns, :db, :primary_key, :db_schema
465
+ class_attr_overridable :raise_on_save_failure, :raise_on_typecast_failure, :strict_param_setting, :typecast_empty_string_to_nil, :typecast_on_assignment, :use_transactions
466
+
467
+ # The hash of attribute values. Keys are symbols with the names of the
468
+ # underlying database columns.
469
+ attr_reader :values
470
+
471
+ # Creates new instance with values set to passed-in Hash.
472
+ # If a block is given, yield the instance to the block unless
473
+ # from_db is true.
474
+ # This method runs the after_initialize hook after
475
+ # it has optionally yielded itself to the block.
476
+ #
477
+ # Arguments:
478
+ # * values - should be a hash with symbol keys, though
479
+ # string keys will work if from_db is false.
480
+ # * from_db - should only be set by Model.load, forget it
481
+ # exists.
482
+ def initialize(values = {}, from_db = false)
483
+ if from_db
484
+ @new = false
485
+ @values = values
486
+ else
487
+ @values = {}
488
+ @new = true
489
+ set(values)
490
+ changed_columns.clear
491
+ yield self if block_given?
492
+ end
493
+ after_initialize
494
+ end
495
+
496
+ # Returns value of the column's attribute.
497
+ def [](column)
498
+ @values[column]
499
+ end
500
+
501
+ # Sets value of the column's attribute and marks the column as changed.
502
+ # If the column already has the same value, this is a no-op.
503
+ def []=(column, value)
504
+ # If it is new, it doesn't have a value yet, so we should
505
+ # definitely set the new value.
506
+ # If the column isn't in @values, we can't assume it is
507
+ # NULL in the database, so assume it has changed.
508
+ if new? || !@values.include?(column) || value != @values[column]
509
+ changed_columns << column unless changed_columns.include?(column)
510
+ @values[column] = typecast_value(column, value)
511
+ end
512
+ end
513
+
514
+ # Compares model instances by values.
515
+ def ==(obj)
516
+ (obj.class == model) && (obj.values == @values)
517
+ end
518
+ alias eql? ==
519
+
520
+ # If pk is not nil, true only if the objects have the same class and pk.
521
+ # If pk is nil, false.
522
+ def ===(obj)
523
+ pk.nil? ? false : (obj.class == model) && (obj.pk == pk)
524
+ end
525
+
526
+ # class is defined in Object, but it is also a keyword,
527
+ # and since a lot of instance methods call class methods,
528
+ # the model makes it so you can use model instead of
529
+ # self.class.
530
+ alias_method :model, :class
531
+
532
+ # The current cached associations. A hash with the keys being the
533
+ # association name symbols and the values being the associated object
534
+ # or nil (many_to_one), or the array of associated objects (*_to_many).
535
+ def associations
536
+ @associations ||= {}
537
+ end
538
+
539
+ # The columns that have been updated. This isn't completely accurate,
540
+ # see Model#[]=.
541
+ def changed_columns
542
+ @changed_columns ||= []
543
+ end
544
+
545
+ # Deletes and returns self. Does not run destroy hooks.
546
+ # Look into using destroy instead.
547
+ def delete
548
+ this.delete
549
+ self
550
+ end
551
+
552
+ # Like delete but runs hooks before and after delete.
553
+ # If before_destroy returns false, returns false without
554
+ # deleting the object the the database. Otherwise, deletes
555
+ # the item from the database and returns self.
556
+ def destroy
557
+ use_transactions ? db.transaction{_destroy} : _destroy
558
+ end
559
+
560
+ # Enumerates through all attributes.
561
+ #
562
+ # Example:
563
+ # Ticket.find(7).each { |k, v| puts "#{k} => #{v}" }
564
+ def each(&block)
565
+ @values.each(&block)
566
+ end
567
+
568
+ # Returns the validation errors associated with the object.
569
+ def errors
570
+ @errors ||= Errors.new
571
+ end
572
+
573
+ # Returns true when current instance exists, false otherwise.
574
+ def exists?
575
+ this.count > 0
576
+ end
577
+
578
+ # Unique for objects with the same class and pk (if pk is not nil), or
579
+ # the same class and values (if pk is nil).
580
+ def hash
581
+ [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
582
+ end
583
+
584
+ # Returns value for the :id attribute, even if the primary key is
585
+ # not id. To get the primary key value, use #pk.
586
+ def id
587
+ @values[:id]
588
+ end
589
+
590
+ # Returns a string representation of the model instance including
591
+ # the class name and values.
592
+ def inspect
593
+ "#<#{model.name} @values=#{inspect_values}>"
594
+ end
595
+
596
+ # Returns attribute names as an array of symbols.
597
+ def keys
598
+ @values.keys
599
+ end
600
+
601
+ # Returns true if the current instance represents a new record.
602
+ def new?
603
+ @new
604
+ end
605
+
606
+ # Returns the primary key value identifying the model instance.
607
+ # Raises an error if this model does not have a primary key.
608
+ # If the model has a composite primary key, returns an array of values.
609
+ def pk
610
+ raise(Error, "No primary key is associated with this model") unless key = primary_key
611
+ case key
612
+ when Array
613
+ key.collect{|k| @values[k]}
614
+ else
615
+ @values[key]
616
+ end
617
+ end
618
+
619
+ # Returns a hash identifying the model instance. It should be true that:
620
+ #
621
+ # Model[model_instance.pk_hash] === model_instance
622
+ def pk_hash
623
+ model.primary_key_hash(pk)
624
+ end
625
+
626
+ # Reloads attributes from database and returns self. Also clears all
627
+ # cached association information. Raises an Error if the record no longer
628
+ # exists in the database.
629
+ def refresh
630
+ @values = this.first || raise(Error, "Record not found")
631
+ changed_columns.clear
632
+ associations.clear
633
+ self
634
+ end
635
+ alias reload refresh
636
+
637
+ # Creates or updates the record, after making sure the record
638
+ # is valid. If the record is not valid, or before_save,
639
+ # before_create (if new?), or before_update (if !new?) return
640
+ # false, returns nil unless raise_on_save_failure is true (if it
641
+ # is true, it raises an error).
642
+ # Otherwise, returns self. You can provide an optional list of
643
+ # columns to update, in which case it only updates those columns.
644
+ #
645
+ # Takes the following options:
646
+ #
647
+ # * :changed - save all changed columns, instead of all columns or the columns
648
+ # * :transaction - set to false not to use a transaction
649
+ # * :validate - set to false not to validate the model before saving
650
+ def save(*columns)
651
+ opts = columns.last.is_a?(Hash) ? columns.pop : {}
652
+ return save_failure(:invalid) if opts[:validate] != false and !valid?
653
+ use_transaction = if opts.include?(:transaction)
654
+ opts[:transaction]
655
+ else
656
+ use_transactions
657
+ end
658
+ use_transaction ? db.transaction(opts){_save(columns, opts)} : _save(columns, opts)
659
+ end
660
+
661
+ # Saves only changed columns or does nothing if no columns are marked as
662
+ # chanaged. If no columns have been changed, returns nil. If unable to
663
+ # save, returns false unless raise_on_save_failure is true.
664
+ def save_changes
665
+ save(:changed=>true) || false unless changed_columns.empty?
666
+ end
667
+
668
+ # Updates the instance with the supplied values with support for virtual
669
+ # attributes, raising an exception if a value is used that doesn't have
670
+ # a setter method (or ignoring it if strict_param_setting = false).
671
+ # Does not save the record.
672
+ #
673
+ # If no columns have been set for this model (very unlikely), assume symbol
674
+ # keys are valid column names, and assign the column value based on that.
675
+ def set(hash)
676
+ set_restricted(hash, nil, nil)
677
+ end
678
+
679
+ # Set all values using the entries in the hash, ignoring any setting of
680
+ # allowed_columns or restricted columns in the model.
681
+ def set_all(hash)
682
+ set_restricted(hash, false, false)
683
+ end
684
+
685
+ # Set all values using the entries in the hash, except for the keys
686
+ # given in except.
687
+ def set_except(hash, *except)
688
+ set_restricted(hash, false, except.flatten)
689
+ end
690
+
691
+ # Set the values using the entries in the hash, only if the key
692
+ # is included in only.
693
+ def set_only(hash, *only)
694
+ set_restricted(hash, only.flatten, false)
695
+ end
696
+
697
+ # Returns (naked) dataset that should return only this instance.
698
+ def this
699
+ @this ||= model.dataset.filter(pk_hash).limit(1).naked
700
+ end
701
+
702
+ # Runs set with the passed hash and runs save_changes (which runs any callback methods).
703
+ def update(hash)
704
+ update_restricted(hash, nil, nil)
705
+ end
706
+
707
+ # Update all values using the entries in the hash, ignoring any setting of
708
+ # allowed_columns or restricted columns in the model.
709
+ def update_all(hash)
710
+ update_restricted(hash, false, false)
711
+ end
712
+
713
+ # Update all values using the entries in the hash, except for the keys
714
+ # given in except.
715
+ def update_except(hash, *except)
716
+ update_restricted(hash, false, except.flatten)
717
+ end
718
+
719
+ # Update the values using the entries in the hash, only if the key
720
+ # is included in only.
721
+ def update_only(hash, *only)
722
+ update_restricted(hash, only.flatten, false)
723
+ end
724
+
725
+ # Validates the object. If the object is invalid, errors should be added
726
+ # to the errors attribute. By default, does nothing, as all models
727
+ # are valid by default.
728
+ def validate
729
+ end
730
+
731
+ # Validates the object and returns true if no errors are reported.
732
+ def valid?
733
+ errors.clear
734
+ if before_validation == false
735
+ save_failure(:validation)
736
+ return false
737
+ end
738
+ validate
739
+ after_validation
740
+ errors.empty?
741
+ end
742
+
743
+ private
744
+
745
+ # Internal destroy method, separted from destroy to
746
+ # allow running inside a transaction
747
+ def _destroy
748
+ return save_failure(:destroy) if before_destroy == false
749
+ delete
750
+ after_destroy
751
+ self
752
+ end
753
+
754
+ # Internal version of save, split from save to allow running inside
755
+ # it's own transaction.
756
+ def _save(columns, opts)
757
+ return save_failure(:save) if before_save == false
758
+ if new?
759
+ return save_failure(:create) if before_create == false
760
+ ds = model.dataset
761
+ if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
762
+ @values = h
763
+ @this = nil
764
+ else
765
+ iid = ds.insert(@values)
766
+ # if we have a regular primary key and it's not set in @values,
767
+ # we assume it's the last inserted id
768
+ if (pk = primary_key) && !(Array === pk) && !@values[pk]
769
+ @values[pk] = iid
770
+ end
771
+ @this = nil if pk
772
+ end
773
+ @new = false
774
+ @was_new = true
775
+ after_create
776
+ after_save
777
+ @was_new = nil
778
+ refresh if pk
779
+ else
780
+ return save_failure(:update) if before_update == false
781
+ if columns.empty?
782
+ @columns_updated = opts[:changed] ? @values.reject{|k,v| !changed_columns.include?(k)} : @values
783
+ changed_columns.clear
784
+ else # update only the specified columns
785
+ @columns_updated = @values.reject{|k, v| !columns.include?(k)}
786
+ changed_columns.reject!{|c| columns.include?(c)}
787
+ end
788
+ this.update(@columns_updated)
789
+ after_update
790
+ after_save
791
+ @columns_updated = nil
792
+ end
793
+ self
794
+ end
795
+
796
+ # Default inspection output for a record, overwrite to change the way #inspect prints the @values hash
797
+ def inspect_values
798
+ @values.inspect
799
+ end
800
+
801
+ # Raise an error if raise_on_save_failure is true
802
+ def save_failure(type)
803
+ if raise_on_save_failure
804
+ if type == :invalid
805
+ raise ValidationFailed, errors.full_messages.join(', ')
806
+ else
807
+ raise BeforeHookFailed, "one of the before_#{type} hooks returned false"
808
+ end
809
+ end
810
+ end
811
+
812
+ # Set the columns, filtered by the only and except arrays.
813
+ def set_restricted(hash, only, except)
814
+ columns_not_set = [nil, false, "", [], {}].include?(model.instance_variable_get(:@columns))
815
+ meths = setter_methods(only, except)
816
+ strict = strict_param_setting
817
+ hash.each do |k,v|
818
+ m = "#{k}="
819
+ if meths.include?(m)
820
+ send(m, v)
821
+ elsif columns_not_set && (Symbol === k)
822
+ Deprecation.deprecate('Calling Model#set_restricted for a column without a setter method when the model class does not have any columns', 'Use Model#[] for these columns')
823
+ self[k] = v
824
+ elsif strict
825
+ raise Error, "method #{m} doesn't exist or access is restricted to it"
826
+ end
827
+ end
828
+ self
829
+ end
830
+
831
+ # Returns all methods that can be used for attribute
832
+ # assignment (those that end with =), modified by the only
833
+ # and except arguments:
834
+ #
835
+ # * only
836
+ # * false - Don't modify the results
837
+ # * nil - if the model has allowed_columns, use only these, otherwise, don't modify
838
+ # * Array - allow only the given methods to be used
839
+ # * except
840
+ # * false - Don't modify the results
841
+ # * nil - if the model has restricted_columns, remove these, otherwise, don't modify
842
+ # * Array - remove the given methods
843
+ #
844
+ # only takes precedence over except, and if only is not used, certain methods are always
845
+ # restricted (RESTRICTED_SETTER_METHODS). The primary key is restricted by default as
846
+ # well, see Model.unrestrict_primary_key to change this.
847
+ def setter_methods(only, except)
848
+ only = only.nil? ? model.allowed_columns : only
849
+ except = except.nil? ? model.restricted_columns : except
850
+ if only
851
+ only.map{|x| "#{x}="}
852
+ else
853
+ meths = methods.collect{|x| x.to_s}.grep(/=\z/) - RESTRICTED_SETTER_METHODS
854
+ meths -= Array(primary_key).map{|x| "#{x}="} if primary_key && model.restrict_primary_key?
855
+ meths -= except.map{|x| "#{x}="} if except
856
+ meths
857
+ end
858
+ end
859
+
860
+ # Typecast the value to the column's type if typecasting. Calls the database's
861
+ # typecast_value method, so database adapters can override/augment the handling
862
+ # for database specific column types.
863
+ def typecast_value(column, value)
864
+ return value unless typecast_on_assignment && db_schema && (col_schema = db_schema[column]) && !model.serialized?(column)
865
+ value = nil if value == '' and typecast_empty_string_to_nil and col_schema[:type] and ![:string, :blob].include?(col_schema[:type])
866
+ raise(Error::InvalidValue, "nil/NULL is not allowed for the #{column} column") if raise_on_typecast_failure && value.nil? && (col_schema[:allow_null] == false)
867
+ begin
868
+ model.db.typecast_value(col_schema[:type], value)
869
+ rescue Error::InvalidValue
870
+ raise_on_typecast_failure ? raise : value
871
+ end
872
+ end
873
+
874
+ # Set the columns, filtered by the only and except arrays.
875
+ def update_restricted(hash, only, except)
876
+ set_restricted(hash, only, except)
877
+ save_changes
878
+ end
879
+ end
880
+
881
+ # Dataset methods are methods that the model class extends its dataset with in
882
+ # the call to set_dataset.
883
+ module DatasetMethods
884
+ attr_accessor :model
885
+
886
+ # Destroy each row in the dataset by instantiating it and then calling
887
+ # destroy on the resulting model object. This isn't as fast as deleting
888
+ # the object, which does a single SQL call, but this runs any destroy
889
+ # hooks.
890
+ def destroy
891
+ count = 0
892
+ @db.transaction{all{|r| count += 1; r.destroy}}
893
+ count
894
+ end
895
+
896
+ # This allows you to call to_hash without any arguments, which will
897
+ # result in a hash with the primary key value being the key and the
898
+ # model object being the value.
899
+ def to_hash(key_column=nil, value_column=nil)
900
+ if key_column
901
+ super
902
+ else
903
+ raise(Sequel::Error, "No primary key for model") unless model and pk = model.primary_key
904
+ super(pk, value_column)
905
+ end
906
+ end
907
+ end
908
+
909
+ plugin self
910
+ end
911
+ end