viking-sequel 3.10.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 (237) hide show
  1. data/CHANGELOG +3134 -0
  2. data/COPYING +19 -0
  3. data/README.rdoc +723 -0
  4. data/Rakefile +193 -0
  5. data/bin/sequel +196 -0
  6. data/doc/advanced_associations.rdoc +644 -0
  7. data/doc/cheat_sheet.rdoc +218 -0
  8. data/doc/dataset_basics.rdoc +106 -0
  9. data/doc/dataset_filtering.rdoc +158 -0
  10. data/doc/opening_databases.rdoc +296 -0
  11. data/doc/prepared_statements.rdoc +104 -0
  12. data/doc/reflection.rdoc +84 -0
  13. data/doc/release_notes/1.0.txt +38 -0
  14. data/doc/release_notes/1.1.txt +143 -0
  15. data/doc/release_notes/1.3.txt +101 -0
  16. data/doc/release_notes/1.4.0.txt +53 -0
  17. data/doc/release_notes/1.5.0.txt +155 -0
  18. data/doc/release_notes/2.0.0.txt +298 -0
  19. data/doc/release_notes/2.1.0.txt +271 -0
  20. data/doc/release_notes/2.10.0.txt +328 -0
  21. data/doc/release_notes/2.11.0.txt +215 -0
  22. data/doc/release_notes/2.12.0.txt +534 -0
  23. data/doc/release_notes/2.2.0.txt +253 -0
  24. data/doc/release_notes/2.3.0.txt +88 -0
  25. data/doc/release_notes/2.4.0.txt +106 -0
  26. data/doc/release_notes/2.5.0.txt +137 -0
  27. data/doc/release_notes/2.6.0.txt +157 -0
  28. data/doc/release_notes/2.7.0.txt +166 -0
  29. data/doc/release_notes/2.8.0.txt +171 -0
  30. data/doc/release_notes/2.9.0.txt +97 -0
  31. data/doc/release_notes/3.0.0.txt +221 -0
  32. data/doc/release_notes/3.1.0.txt +406 -0
  33. data/doc/release_notes/3.10.0.txt +286 -0
  34. data/doc/release_notes/3.2.0.txt +268 -0
  35. data/doc/release_notes/3.3.0.txt +192 -0
  36. data/doc/release_notes/3.4.0.txt +325 -0
  37. data/doc/release_notes/3.5.0.txt +510 -0
  38. data/doc/release_notes/3.6.0.txt +366 -0
  39. data/doc/release_notes/3.7.0.txt +179 -0
  40. data/doc/release_notes/3.8.0.txt +151 -0
  41. data/doc/release_notes/3.9.0.txt +233 -0
  42. data/doc/schema.rdoc +36 -0
  43. data/doc/sharding.rdoc +113 -0
  44. data/doc/virtual_rows.rdoc +205 -0
  45. data/lib/sequel.rb +1 -0
  46. data/lib/sequel/adapters/ado.rb +90 -0
  47. data/lib/sequel/adapters/ado/mssql.rb +30 -0
  48. data/lib/sequel/adapters/amalgalite.rb +176 -0
  49. data/lib/sequel/adapters/db2.rb +139 -0
  50. data/lib/sequel/adapters/dbi.rb +113 -0
  51. data/lib/sequel/adapters/do.rb +188 -0
  52. data/lib/sequel/adapters/do/mysql.rb +49 -0
  53. data/lib/sequel/adapters/do/postgres.rb +91 -0
  54. data/lib/sequel/adapters/do/sqlite.rb +40 -0
  55. data/lib/sequel/adapters/firebird.rb +283 -0
  56. data/lib/sequel/adapters/informix.rb +77 -0
  57. data/lib/sequel/adapters/jdbc.rb +587 -0
  58. data/lib/sequel/adapters/jdbc/as400.rb +58 -0
  59. data/lib/sequel/adapters/jdbc/h2.rb +133 -0
  60. data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
  61. data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
  62. data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
  64. data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
  65. data/lib/sequel/adapters/mysql.rb +421 -0
  66. data/lib/sequel/adapters/odbc.rb +143 -0
  67. data/lib/sequel/adapters/odbc/mssql.rb +42 -0
  68. data/lib/sequel/adapters/openbase.rb +64 -0
  69. data/lib/sequel/adapters/oracle.rb +131 -0
  70. data/lib/sequel/adapters/postgres.rb +504 -0
  71. data/lib/sequel/adapters/shared/mssql.rb +490 -0
  72. data/lib/sequel/adapters/shared/mysql.rb +498 -0
  73. data/lib/sequel/adapters/shared/oracle.rb +195 -0
  74. data/lib/sequel/adapters/shared/postgres.rb +830 -0
  75. data/lib/sequel/adapters/shared/progress.rb +44 -0
  76. data/lib/sequel/adapters/shared/sqlite.rb +389 -0
  77. data/lib/sequel/adapters/sqlite.rb +224 -0
  78. data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
  79. data/lib/sequel/connection_pool.rb +99 -0
  80. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  81. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  82. data/lib/sequel/connection_pool/single.rb +29 -0
  83. data/lib/sequel/connection_pool/threaded.rb +150 -0
  84. data/lib/sequel/core.rb +293 -0
  85. data/lib/sequel/core_sql.rb +241 -0
  86. data/lib/sequel/database.rb +1079 -0
  87. data/lib/sequel/database/schema_generator.rb +327 -0
  88. data/lib/sequel/database/schema_methods.rb +203 -0
  89. data/lib/sequel/database/schema_sql.rb +320 -0
  90. data/lib/sequel/dataset.rb +32 -0
  91. data/lib/sequel/dataset/actions.rb +441 -0
  92. data/lib/sequel/dataset/features.rb +86 -0
  93. data/lib/sequel/dataset/graph.rb +254 -0
  94. data/lib/sequel/dataset/misc.rb +119 -0
  95. data/lib/sequel/dataset/mutation.rb +64 -0
  96. data/lib/sequel/dataset/prepared_statements.rb +227 -0
  97. data/lib/sequel/dataset/query.rb +709 -0
  98. data/lib/sequel/dataset/sql.rb +996 -0
  99. data/lib/sequel/exceptions.rb +51 -0
  100. data/lib/sequel/extensions/blank.rb +43 -0
  101. data/lib/sequel/extensions/inflector.rb +242 -0
  102. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  103. data/lib/sequel/extensions/migration.rb +239 -0
  104. data/lib/sequel/extensions/named_timezones.rb +61 -0
  105. data/lib/sequel/extensions/pagination.rb +100 -0
  106. data/lib/sequel/extensions/pretty_table.rb +82 -0
  107. data/lib/sequel/extensions/query.rb +52 -0
  108. data/lib/sequel/extensions/schema_dumper.rb +271 -0
  109. data/lib/sequel/extensions/sql_expr.rb +122 -0
  110. data/lib/sequel/extensions/string_date_time.rb +46 -0
  111. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  112. data/lib/sequel/metaprogramming.rb +9 -0
  113. data/lib/sequel/model.rb +120 -0
  114. data/lib/sequel/model/associations.rb +1514 -0
  115. data/lib/sequel/model/base.rb +1069 -0
  116. data/lib/sequel/model/default_inflections.rb +45 -0
  117. data/lib/sequel/model/errors.rb +39 -0
  118. data/lib/sequel/model/exceptions.rb +21 -0
  119. data/lib/sequel/model/inflections.rb +162 -0
  120. data/lib/sequel/model/plugins.rb +70 -0
  121. data/lib/sequel/plugins/active_model.rb +59 -0
  122. data/lib/sequel/plugins/association_dependencies.rb +103 -0
  123. data/lib/sequel/plugins/association_proxies.rb +41 -0
  124. data/lib/sequel/plugins/boolean_readers.rb +53 -0
  125. data/lib/sequel/plugins/caching.rb +141 -0
  126. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  127. data/lib/sequel/plugins/composition.rb +138 -0
  128. data/lib/sequel/plugins/force_encoding.rb +72 -0
  129. data/lib/sequel/plugins/hook_class_methods.rb +126 -0
  130. data/lib/sequel/plugins/identity_map.rb +116 -0
  131. data/lib/sequel/plugins/instance_filters.rb +98 -0
  132. data/lib/sequel/plugins/instance_hooks.rb +57 -0
  133. data/lib/sequel/plugins/lazy_attributes.rb +77 -0
  134. data/lib/sequel/plugins/many_through_many.rb +208 -0
  135. data/lib/sequel/plugins/nested_attributes.rb +206 -0
  136. data/lib/sequel/plugins/optimistic_locking.rb +81 -0
  137. data/lib/sequel/plugins/rcte_tree.rb +281 -0
  138. data/lib/sequel/plugins/schema.rb +66 -0
  139. data/lib/sequel/plugins/serialization.rb +166 -0
  140. data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
  141. data/lib/sequel/plugins/subclasses.rb +45 -0
  142. data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
  143. data/lib/sequel/plugins/timestamps.rb +87 -0
  144. data/lib/sequel/plugins/touch.rb +118 -0
  145. data/lib/sequel/plugins/typecast_on_load.rb +72 -0
  146. data/lib/sequel/plugins/validation_class_methods.rb +405 -0
  147. data/lib/sequel/plugins/validation_helpers.rb +223 -0
  148. data/lib/sequel/sql.rb +1020 -0
  149. data/lib/sequel/timezones.rb +161 -0
  150. data/lib/sequel/version.rb +12 -0
  151. data/lib/sequel_core.rb +1 -0
  152. data/lib/sequel_model.rb +1 -0
  153. data/spec/adapters/firebird_spec.rb +407 -0
  154. data/spec/adapters/informix_spec.rb +97 -0
  155. data/spec/adapters/mssql_spec.rb +403 -0
  156. data/spec/adapters/mysql_spec.rb +1019 -0
  157. data/spec/adapters/oracle_spec.rb +286 -0
  158. data/spec/adapters/postgres_spec.rb +969 -0
  159. data/spec/adapters/spec_helper.rb +51 -0
  160. data/spec/adapters/sqlite_spec.rb +432 -0
  161. data/spec/core/connection_pool_spec.rb +808 -0
  162. data/spec/core/core_sql_spec.rb +417 -0
  163. data/spec/core/database_spec.rb +1662 -0
  164. data/spec/core/dataset_spec.rb +3827 -0
  165. data/spec/core/expression_filters_spec.rb +595 -0
  166. data/spec/core/object_graph_spec.rb +296 -0
  167. data/spec/core/schema_generator_spec.rb +159 -0
  168. data/spec/core/schema_spec.rb +830 -0
  169. data/spec/core/spec_helper.rb +56 -0
  170. data/spec/core/version_spec.rb +7 -0
  171. data/spec/extensions/active_model_spec.rb +76 -0
  172. data/spec/extensions/association_dependencies_spec.rb +127 -0
  173. data/spec/extensions/association_proxies_spec.rb +50 -0
  174. data/spec/extensions/blank_spec.rb +67 -0
  175. data/spec/extensions/boolean_readers_spec.rb +92 -0
  176. data/spec/extensions/caching_spec.rb +250 -0
  177. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  178. data/spec/extensions/composition_spec.rb +194 -0
  179. data/spec/extensions/force_encoding_spec.rb +117 -0
  180. data/spec/extensions/hook_class_methods_spec.rb +470 -0
  181. data/spec/extensions/identity_map_spec.rb +202 -0
  182. data/spec/extensions/inflector_spec.rb +181 -0
  183. data/spec/extensions/instance_filters_spec.rb +55 -0
  184. data/spec/extensions/instance_hooks_spec.rb +133 -0
  185. data/spec/extensions/lazy_attributes_spec.rb +153 -0
  186. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  187. data/spec/extensions/many_through_many_spec.rb +884 -0
  188. data/spec/extensions/migration_spec.rb +332 -0
  189. data/spec/extensions/named_timezones_spec.rb +72 -0
  190. data/spec/extensions/nested_attributes_spec.rb +396 -0
  191. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  192. data/spec/extensions/pagination_spec.rb +99 -0
  193. data/spec/extensions/pretty_table_spec.rb +91 -0
  194. data/spec/extensions/query_spec.rb +85 -0
  195. data/spec/extensions/rcte_tree_spec.rb +205 -0
  196. data/spec/extensions/schema_dumper_spec.rb +357 -0
  197. data/spec/extensions/schema_spec.rb +127 -0
  198. data/spec/extensions/serialization_spec.rb +209 -0
  199. data/spec/extensions/single_table_inheritance_spec.rb +96 -0
  200. data/spec/extensions/spec_helper.rb +91 -0
  201. data/spec/extensions/sql_expr_spec.rb +89 -0
  202. data/spec/extensions/string_date_time_spec.rb +93 -0
  203. data/spec/extensions/subclasses_spec.rb +52 -0
  204. data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
  205. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  206. data/spec/extensions/timestamps_spec.rb +150 -0
  207. data/spec/extensions/touch_spec.rb +155 -0
  208. data/spec/extensions/typecast_on_load_spec.rb +69 -0
  209. data/spec/extensions/validation_class_methods_spec.rb +984 -0
  210. data/spec/extensions/validation_helpers_spec.rb +438 -0
  211. data/spec/integration/associations_test.rb +281 -0
  212. data/spec/integration/database_test.rb +26 -0
  213. data/spec/integration/dataset_test.rb +963 -0
  214. data/spec/integration/eager_loader_test.rb +734 -0
  215. data/spec/integration/model_test.rb +130 -0
  216. data/spec/integration/plugin_test.rb +814 -0
  217. data/spec/integration/prepared_statement_test.rb +213 -0
  218. data/spec/integration/schema_test.rb +361 -0
  219. data/spec/integration/spec_helper.rb +73 -0
  220. data/spec/integration/timezone_test.rb +55 -0
  221. data/spec/integration/transaction_test.rb +122 -0
  222. data/spec/integration/type_test.rb +96 -0
  223. data/spec/model/association_reflection_spec.rb +175 -0
  224. data/spec/model/associations_spec.rb +2633 -0
  225. data/spec/model/base_spec.rb +418 -0
  226. data/spec/model/dataset_methods_spec.rb +78 -0
  227. data/spec/model/eager_loading_spec.rb +1391 -0
  228. data/spec/model/hooks_spec.rb +240 -0
  229. data/spec/model/inflector_spec.rb +26 -0
  230. data/spec/model/model_spec.rb +593 -0
  231. data/spec/model/plugins_spec.rb +236 -0
  232. data/spec/model/record_spec.rb +1500 -0
  233. data/spec/model/spec_helper.rb +97 -0
  234. data/spec/model/validations_spec.rb +153 -0
  235. data/spec/rcov.opts +6 -0
  236. data/spec/spec_config.rb.example +10 -0
  237. metadata +346 -0
@@ -0,0 +1,1069 @@
1
+ module Sequel
2
+ class Model
3
+ extend Enumerable
4
+ extend Inflections
5
+ extend Metaprogramming
6
+ include Metaprogramming
7
+
8
+ # Class methods for Sequel::Model that implement basic model functionality.
9
+ #
10
+ # * All of the method names in Model::DATASET_METHODS have class methods created that call
11
+ # the Model's dataset with the method of the same name with the given arguments.
12
+ module ClassMethods
13
+ # Which columns should be the only columns allowed in a call to set
14
+ # (default: not set, so all columns not otherwise restricted).
15
+ attr_reader :allowed_columns
16
+
17
+ # Array of modules that extend this model's dataset. Stored
18
+ # so that if the model's dataset is changed, it will be extended
19
+ # with all of these modules.
20
+ attr_reader :dataset_method_modules
21
+
22
+ # Hash of dataset methods with method name keys and proc values that are
23
+ # stored so when the dataset changes, methods defined with def_dataset_method
24
+ # will be applied to the new dataset.
25
+ attr_reader :dataset_methods
26
+
27
+ # The primary key for the class. Sequel can determine this automatically for
28
+ # many databases, but not all, so you may need to set it manually. If not
29
+ # determined automatically, the default is :id.
30
+ attr_reader :primary_key
31
+
32
+ # Whether to raise an error instead of returning nil on a failure
33
+ # to save/create/save_changes/etc due to a validation failure or
34
+ # a before_* hook returning false.
35
+ attr_accessor :raise_on_save_failure
36
+
37
+ # Whether to raise an error when unable to typecast data for a column
38
+ # (default: true). This should be set to false if you want to use
39
+ # validations to display nice error messages to the user (e.g. most
40
+ # web applications). You can use the validates_not_string validations
41
+ # (from either the validation_helpers or validation_class_methods standard
42
+ # plugins) in connection with option to check for typecast failures for
43
+ # columns that aren't blobs or strings.
44
+ attr_accessor :raise_on_typecast_failure
45
+
46
+ # Whether to raise an error if an UPDATE or DELETE query related to
47
+ # a model instance does not modify exactly 1 row. If set to false,
48
+ # Sequel will not check the number of rows modified (default: true).
49
+ attr_accessor :require_modification
50
+
51
+ # Which columns are specifically restricted in a call to set/update/new/etc.
52
+ # (default: not set). Some columns are restricted regardless of
53
+ # this setting, such as the primary key column and columns in Model::RESTRICTED_SETTER_METHODS.
54
+ attr_reader :restricted_columns
55
+
56
+ # Should be the literal primary key column name if this Model's table has a simple primary key, or
57
+ # nil if the model has a compound primary key or no primary key.
58
+ attr_reader :simple_pk
59
+
60
+ # Should be the literal table name if this Model's dataset is a simple table (no select, order, join, etc.),
61
+ # or nil otherwise. This and simple_pk are used for an optimization in Model.[].
62
+ attr_reader :simple_table
63
+
64
+ # Whether new/set/update and their variants should raise an error
65
+ # if an invalid key is used. A key is invalid if no setter method exists
66
+ # for that key or the access to the setter method is restricted (e.g. due to it
67
+ # being a primary key field). If set to false, silently skip
68
+ # any key where the setter method doesn't exist or access to it is restricted.
69
+ attr_accessor :strict_param_setting
70
+
71
+ # Whether to typecast the empty string ('') to nil for columns that
72
+ # are not string or blob. In most cases the empty string would be the
73
+ # way to specify a NULL SQL value in string form (nil.to_s == ''),
74
+ # and an empty string would not usually be typecast correctly for other
75
+ # types, so the default is true.
76
+ attr_accessor :typecast_empty_string_to_nil
77
+
78
+ # Whether to typecast attribute values on assignment (default: true).
79
+ # If set to false, no typecasting is done, so it will be left up to the
80
+ # database to typecast the value correctly.
81
+ attr_accessor :typecast_on_assignment
82
+
83
+ # Whether to use a transaction by default when saving/deleting records (default: true).
84
+ # If you are sending database queries in before_* or after_* hooks, you shouldn't change
85
+ # the default setting without a good reason.
86
+ attr_accessor :use_transactions
87
+
88
+ # Returns the first record from the database matching the conditions.
89
+ # If a hash is given, it is used as the conditions. If another
90
+ # object is given, it finds the first record whose primary key(s) match
91
+ # the given argument(s).
92
+ def [](*args)
93
+ args = args.first if (args.size == 1)
94
+ args.is_a?(Hash) ? dataset[args] : primary_key_lookup(args)
95
+ end
96
+
97
+ # Returns the columns in the result set in their original order.
98
+ # Generally, this will use the columns determined via the database
99
+ # schema, but in certain cases (e.g. models that are based on a joined
100
+ # dataset) it will use Dataset#columns to find the columns, which
101
+ # may be empty if the Dataset has no records.
102
+ def columns
103
+ @columns || set_columns(dataset.naked.columns)
104
+ end
105
+
106
+ # Creates instance using new with the given values and block, and saves it.
107
+ def create(values = {}, &block)
108
+ new(values, &block).save
109
+ end
110
+
111
+ # Returns the dataset associated with the Model class. Raises
112
+ # an error if there is no associated dataset for this class.
113
+ def dataset
114
+ @dataset || raise(Error, "No dataset associated with #{self}")
115
+ end
116
+
117
+ # Returns the database associated with the Model class.
118
+ # If this model doesn't have a database associated with it,
119
+ # assumes the superclass's database, or the first object in
120
+ # Sequel::DATABASES. If no Sequel::Database object has
121
+ # been created, raises an error.
122
+ def db
123
+ return @db if @db
124
+ @db = self == Model ? DATABASES.first : superclass.db
125
+ raise(Error, "No database associated with #{self}") unless @db
126
+ @db
127
+ end
128
+
129
+ # Sets the database associated with the Model class. If the
130
+ # model has an associated dataset, sets the model's dataset
131
+ # to a dataset on the new database with the same options
132
+ # used by the current dataset.
133
+ def db=(db)
134
+ @db = db
135
+ set_dataset(db.dataset(@dataset.opts)) if @dataset
136
+ end
137
+
138
+ # Returns the cached schema information if available or gets it
139
+ # from the database.
140
+ def db_schema
141
+ @db_schema ||= get_db_schema
142
+ end
143
+
144
+ # If a block is given, define a method on the dataset (if the model has an associated dataset) with the given argument name using
145
+ # the given block as well as a method on the model that calls the
146
+ # dataset method. Stores the method name and block so that it can be reapplied if the model's
147
+ # dataset changes.
148
+ #
149
+ # If a block is not given, define a method on the model for each argument
150
+ # that calls the dataset method of the same argument name.
151
+ def def_dataset_method(*args, &block)
152
+ raise(Error, "No arguments given") if args.empty?
153
+ if block_given?
154
+ raise(Error, "Defining a dataset method using a block requires only one argument") if args.length > 1
155
+ meth = args.first
156
+ @dataset_methods[meth] = block
157
+ dataset.meta_def(meth, &block) if @dataset
158
+ end
159
+ args.each{|arg| instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__) unless respond_to?(arg)}
160
+ end
161
+
162
+ # Finds a single record according to the supplied filter, e.g.:
163
+ #
164
+ # Ticket.find :author => 'Sharon' # => record
165
+ #
166
+ # You are encouraged to use Model.[] or Model.first instead of this method.
167
+ def find(*args, &block)
168
+ filter(*args, &block).first
169
+ end
170
+
171
+ # Like find but invokes create with given conditions when record does not
172
+ # exist.
173
+ def find_or_create(cond)
174
+ find(cond) || create(cond)
175
+ end
176
+
177
+ # If possible, set the dataset for the model subclass as soon as it
178
+ # is created. Also, make sure the inherited class instance variables
179
+ # are copied into the subclass.
180
+ def inherited(subclass)
181
+ super
182
+ ivs = subclass.instance_variables.collect{|x| x.to_s}
183
+ EMPTY_INSTANCE_VARIABLES.each{|iv| subclass.instance_variable_set(iv, nil) unless ivs.include?(iv.to_s)}
184
+ INHERITED_INSTANCE_VARIABLES.each do |iv, dup|
185
+ next if ivs.include?(iv.to_s)
186
+ sup_class_value = instance_variable_get(iv)
187
+ sup_class_value = sup_class_value.dup if dup == :dup && sup_class_value
188
+ subclass.instance_variable_set(iv, sup_class_value)
189
+ end
190
+ unless ivs.include?("@dataset")
191
+ db
192
+ begin
193
+ if self == Model || !@dataset
194
+ subclass.set_dataset(subclass.implicit_table_name) unless subclass.name.empty?
195
+ elsif @dataset
196
+ subclass.set_dataset(@dataset.clone, :inherited=>true)
197
+ end
198
+ rescue
199
+ nil
200
+ end
201
+ end
202
+ end
203
+
204
+ # Returns the implicit table name for the model class.
205
+ def implicit_table_name
206
+ pluralize(underscore(demodulize(name))).to_sym
207
+ end
208
+
209
+ # Initializes a model instance as an existing record. This constructor is
210
+ # used by Sequel to initialize model instances when fetching records.
211
+ # load requires that values be a hash where all keys are symbols. It
212
+ # probably should not be used by external code.
213
+ def load(values)
214
+ new(values, true)
215
+ end
216
+
217
+ # Mark the model as not having a primary key. Not having a primary key
218
+ # can cause issues, among which is that you won't be able to update records.
219
+ def no_primary_key
220
+ @simple_pk = @primary_key = nil
221
+ end
222
+
223
+ # Returns primary key attribute hash. If using a composite primary key
224
+ # value such be an array with values for each primary key in the correct
225
+ # order. For a standard primary key, value should be an object with a
226
+ # compatible type for the key. If the model does not have a primary key,
227
+ # raises an Error.
228
+ def primary_key_hash(value)
229
+ raise(Error, "#{self} does not have a primary key") unless key = @primary_key
230
+ case key
231
+ when Array
232
+ hash = {}
233
+ key.each_with_index{|k,i| hash[k] = value[i]}
234
+ hash
235
+ else
236
+ {key => value}
237
+ end
238
+ end
239
+
240
+ # Restrict the setting of the primary key(s) inside new/set/update. Because
241
+ # this is the default, this only make sense to use in a subclass where the
242
+ # parent class has used unrestrict_primary_key.
243
+ def restrict_primary_key
244
+ @restrict_primary_key = true
245
+ end
246
+
247
+ # Whether or not setting the primary key inside new/set/update is
248
+ # restricted, true by default.
249
+ def restrict_primary_key?
250
+ @restrict_primary_key
251
+ end
252
+
253
+ # Set the columns to allow in new/set/update. Using this means that
254
+ # any columns not listed here will not be modified. If you have any virtual
255
+ # setter methods (methods that end in =) that you want to be used in
256
+ # new/set/update, they need to be listed here as well (without the =).
257
+ #
258
+ # It may be better to use (set|update)_only instead of this in places where
259
+ # only certain columns may be allowed.
260
+ def set_allowed_columns(*cols)
261
+ @allowed_columns = cols
262
+ end
263
+
264
+ # Sets the dataset associated with the Model class. ds can be a Symbol
265
+ # (specifying a table name in the current database), or a Dataset.
266
+ # If a dataset is used, the model's database is changed to the given
267
+ # dataset. If a symbol is used, a dataset is created from the current
268
+ # database with the table name given. Other arguments raise an Error.
269
+ # Returns self.
270
+ #
271
+ # This changes the row_proc of the given dataset to return
272
+ # model objects, extends the dataset with the dataset_method_modules,
273
+ # and defines methods on the dataset using the dataset_methods.
274
+ # It also attempts to determine the database schema for the model,
275
+ # based on the given dataset.
276
+ def set_dataset(ds, opts={})
277
+ inherited = opts[:inherited]
278
+ @dataset = case ds
279
+ when Symbol
280
+ @simple_table = db.literal(ds)
281
+ db[ds]
282
+ when Dataset
283
+ @simple_table = nil
284
+ @db = ds.db
285
+ ds
286
+ else
287
+ raise(Error, "Model.set_dataset takes a Symbol or a Sequel::Dataset")
288
+ end
289
+ @dataset.row_proc = Proc.new{|r| load(r)}
290
+ @require_modification = Sequel::Model.require_modification.nil? ? @dataset.provides_accurate_rows_matched? : Sequel::Model.require_modification
291
+ if inherited
292
+ @simple_table = superclass.simple_table
293
+ @columns = @dataset.columns rescue nil
294
+ else
295
+ @dataset_method_modules.each{|m| @dataset.extend(m)} if @dataset_method_modules
296
+ @dataset_methods.each{|meth, block| @dataset.meta_def(meth, &block)} if @dataset_methods
297
+ end
298
+ @dataset.model = self if @dataset.respond_to?(:model=)
299
+ check_non_connection_error{@db_schema = (inherited ? superclass.db_schema : get_db_schema)}
300
+ self
301
+ end
302
+ alias dataset= set_dataset
303
+
304
+ # Sets the primary key for this model. You can use either a regular
305
+ # or a composite primary key.
306
+ #
307
+ # Example:
308
+ # class Tagging < Sequel::Model
309
+ # # composite key
310
+ # set_primary_key [:taggable_id, :tag_id]
311
+ # end
312
+ #
313
+ # class Person < Sequel::Model
314
+ # # regular key
315
+ # set_primary_key :person_id
316
+ # end
317
+ #
318
+ # You can set it to nil to not have a primary key, but that
319
+ # cause certain things not to work, see no_primary_key.
320
+ def set_primary_key(*key)
321
+ key = key.flatten
322
+ @simple_pk = key.length == 1 ? db.literal(key.first) : nil
323
+ @primary_key = (key.length == 1) ? key[0] : key
324
+ end
325
+
326
+ # Set the columns to restrict in new/set/update. Using this means that
327
+ # attempts to call setter methods for the columns listed here will cause an
328
+ # exception or be silently skipped (based on the strict_param_setting setting.
329
+ # If you have any virtual # setter methods (methods that end in =) that you
330
+ # want not to be used in new/set/update, they need to be listed here as well (without the =).
331
+ #
332
+ # It may be better to use (set|update)_except instead of this in places where
333
+ # only certain columns may be allowed.
334
+ def set_restricted_columns(*cols)
335
+ @restricted_columns = cols
336
+ end
337
+
338
+ # Defines a method that returns a filtered dataset. Subsets
339
+ # create dataset methods, so they can be chained for scoping.
340
+ # For example:
341
+ #
342
+ # Topic.subset(:joes, :username.like('%joe%'))
343
+ # Topic.subset(:popular){|o| o.num_posts > 100}
344
+ # Topic.subset(:recent){|o| o.created_on > Date.today - 7}
345
+ #
346
+ # Allows you to do:
347
+ #
348
+ # Topic.joes.recent.popular
349
+ #
350
+ # to get topics with a username that includes joe that
351
+ # have more than 100 posts and were created less than
352
+ # 7 days ago.
353
+ #
354
+ # Both the args given and the block are passed to Dataset#filter.
355
+ def subset(name, *args, &block)
356
+ def_dataset_method(name){filter(*args, &block)}
357
+ end
358
+
359
+ # Returns name of primary table for the dataset.
360
+ def table_name
361
+ dataset.first_source_alias
362
+ end
363
+
364
+ # Allow the setting of the primary key(s) inside new/set/update.
365
+ def unrestrict_primary_key
366
+ @restrict_primary_key = false
367
+ end
368
+
369
+ private
370
+
371
+ # Yield to the passed block and swallow all errors other than DatabaseConnectionErrors.
372
+ def check_non_connection_error
373
+ begin
374
+ yield
375
+ rescue Sequel::DatabaseConnectionError
376
+ raise
377
+ rescue
378
+ nil
379
+ end
380
+ end
381
+
382
+ # Create a column accessor for a column with a method name that is hard to use in ruby code.
383
+ def def_bad_column_accessor(column)
384
+ overridable_methods_module.module_eval do
385
+ define_method(column){self[column]}
386
+ define_method("#{column}="){|v| self[column] = v}
387
+ end
388
+ end
389
+
390
+ # Create the column accessors. For columns that can be used as method names directly in ruby code,
391
+ # use a string to define the method for speed. For other columns names, use a block.
392
+ def def_column_accessor(*columns)
393
+ columns, bad_columns = columns.partition{|x| NORMAL_METHOD_NAME_REGEXP.match(x.to_s)}
394
+ bad_columns.each{|x| def_bad_column_accessor(x)}
395
+ im = instance_methods.collect{|x| x.to_s}
396
+ columns.each do |column|
397
+ meth = "#{column}="
398
+ overridable_methods_module.module_eval("def #{column}; self[:#{column}] end", __FILE__, __LINE__) unless im.include?(column.to_s)
399
+ overridable_methods_module.module_eval("def #{meth}(v); self[:#{column}] = v end", __FILE__, __LINE__) unless im.include?(meth)
400
+ end
401
+ end
402
+
403
+ # Get the schema from the database, fall back on checking the columns
404
+ # via the database if that will return inaccurate results or if
405
+ # it raises an error.
406
+ def get_db_schema(reload = false)
407
+ set_columns(nil)
408
+ return nil unless @dataset
409
+ schema_hash = {}
410
+ ds_opts = dataset.opts
411
+ single_table = ds_opts[:from] && (ds_opts[:from].length == 1) \
412
+ && !ds_opts.include?(:join) && !ds_opts.include?(:sql)
413
+ get_columns = proc{check_non_connection_error{columns} || []}
414
+ if single_table && (schema_array = (db.schema(table_name, :reload=>reload) rescue nil))
415
+ schema_array.each{|k,v| schema_hash[k] = v}
416
+ if ds_opts.include?(:select)
417
+ # Dataset only selects certain columns, delete the other
418
+ # columns from the schema
419
+ cols = get_columns.call
420
+ schema_hash.delete_if{|k,v| !cols.include?(k)}
421
+ cols.each{|c| schema_hash[c] ||= {}}
422
+ else
423
+ # Dataset is for a single table with all columns,
424
+ # so set the columns based on the order they were
425
+ # returned by the schema.
426
+ cols = schema_array.collect{|k,v| k}
427
+ set_columns(cols)
428
+ # Set the primary key(s) based on the schema information,
429
+ # if the schema information includes primary key information
430
+ if schema_array.all?{|k,v| v.has_key?(:primary_key)}
431
+ pks = schema_array.collect{|k,v| k if v[:primary_key]}.compact
432
+ pks.length > 0 ? set_primary_key(*pks) : no_primary_key
433
+ end
434
+ # Also set the columns for the dataset, so the dataset
435
+ # doesn't have to do a query to get them.
436
+ dataset.instance_variable_set(:@columns, cols)
437
+ end
438
+ else
439
+ # If the dataset uses multiple tables or custom sql or getting
440
+ # the schema raised an error, just get the columns and
441
+ # create an empty schema hash for it.
442
+ get_columns.call.each{|c| schema_hash[c] = {}}
443
+ end
444
+ schema_hash
445
+ end
446
+
447
+ # For the given opts hash and default name or :class option, add a
448
+ # :class_name option unless already present which contains the name
449
+ # of the class to use as a string. The purpose is to allow late
450
+ # binding to the class later using constantize.
451
+ def late_binding_class_option(opts, default)
452
+ case opts[:class]
453
+ when String, Symbol
454
+ # Delete :class to allow late binding
455
+ opts[:class_name] ||= opts.delete(:class).to_s
456
+ when Class
457
+ opts[:class_name] ||= opts[:class].name
458
+ end
459
+ opts[:class_name] ||= ((name || '').split("::")[0..-2] + [camelize(default)]).join('::')
460
+ end
461
+
462
+ # Module that the class includes that holds methods the class adds for column accessors and
463
+ # associations so that the methods can be overridden with super
464
+ def overridable_methods_module
465
+ include(@overridable_methods_module = Module.new) unless @overridable_methods_module
466
+ @overridable_methods_module
467
+ end
468
+
469
+ # Find the row in the dataset that matches the primary key. Uses
470
+ # an static SQL optimization if the table and primary key are simple.
471
+ def primary_key_lookup(pk)
472
+ if t = simple_table and p = simple_pk
473
+ with_sql("SELECT * FROM #{t} WHERE #{p} = #{dataset.literal(pk)}").first
474
+ else
475
+ dataset[primary_key_hash(pk)]
476
+ end
477
+ end
478
+
479
+ # Set the columns for this model and create accessor methods for each column.
480
+ def set_columns(new_columns)
481
+ @columns = new_columns
482
+ def_column_accessor(*new_columns) if new_columns
483
+ @columns
484
+ end
485
+
486
+ # Add model methods that call dataset methods
487
+ DATASET_METHODS.each{|arg| class_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__)}
488
+
489
+ # Returns a copy of the model's dataset with custom SQL
490
+ alias fetch with_sql
491
+ end
492
+
493
+ # Sequel::Model instance methods that implement basic model functionality.
494
+ #
495
+ # * All of the methods in HOOKS create instance methods that are called
496
+ # by Sequel when the appropriate action occurs. For example, when destroying
497
+ # a model object, Sequel will call before_destroy, do the destroy,
498
+ # and then call after_destroy.
499
+ # * The following instance_methods all call the class method of the same
500
+ # name: columns, dataset, db, primary_key, db_schema.
501
+ # * The following instance methods allow boolean flags to be set on a per-object
502
+ # basis: raise_on_save_failure, raise_on_typecast_failure, require_modification, strict_param_setting,
503
+ # typecast_empty_string_to_nil, typecast_on_assignment, use_transactions.
504
+ # If they are not used, the object will default to whatever the model setting is.
505
+ module InstanceMethods
506
+ HOOKS.each{|h| class_eval("def #{h}; end", __FILE__, __LINE__)}
507
+
508
+ # Define instance method(s) that calls class method(s) of the
509
+ # same name, caching the result in an instance variable. Define
510
+ # standard attr_writer method for modifying that instance variable
511
+ def self.class_attr_overridable(*meths) # :nodoc:
512
+ meths.each{|meth| class_eval("def #{meth}; !defined?(@#{meth}) ? (@#{meth} = self.class.#{meth}) : @#{meth} end", __FILE__, __LINE__)}
513
+ attr_writer(*meths)
514
+ end
515
+
516
+ # Define instance method(s) that calls class method(s) of the
517
+ # same name. Replaces the construct:
518
+ #
519
+ # define_method(meth){self.class.send(meth)}
520
+ def self.class_attr_reader(*meths) # :nodoc:
521
+ meths.each{|meth| class_eval("def #{meth}; model.#{meth} end", __FILE__, __LINE__)}
522
+ end
523
+
524
+ private_class_method :class_attr_overridable, :class_attr_reader
525
+
526
+ class_attr_reader :columns, :db, :primary_key, :db_schema
527
+ class_attr_overridable :raise_on_save_failure, :raise_on_typecast_failure, :require_modification, :strict_param_setting, :typecast_empty_string_to_nil, :typecast_on_assignment, :use_transactions
528
+
529
+ # The hash of attribute values. Keys are symbols with the names of the
530
+ # underlying database columns.
531
+ attr_reader :values
532
+
533
+ # Creates new instance and passes the given values to set.
534
+ # If a block is given, yield the instance to the block unless
535
+ # from_db is true.
536
+ # This method runs the after_initialize hook after
537
+ # it has optionally yielded itself to the block.
538
+ #
539
+ # Arguments:
540
+ # * values - should be a hash to pass to set.
541
+ # * from_db - should only be set by Model.load, forget it
542
+ # exists.
543
+ def initialize(values = {}, from_db = false)
544
+ if from_db
545
+ @new = false
546
+ set_values(values)
547
+ else
548
+ @values = {}
549
+ @new = true
550
+ @modified = true
551
+ set(values)
552
+ changed_columns.clear
553
+ yield self if block_given?
554
+ end
555
+ after_initialize
556
+ end
557
+
558
+ # Returns value of the column's attribute.
559
+ def [](column)
560
+ @values[column]
561
+ end
562
+
563
+ # Sets the value for the given column. If typecasting is enabled for
564
+ # this object, typecast the value based on the column's type.
565
+ # If this a a new record or the typecasted value isn't the same
566
+ # as the current value for the column, mark the column as changed.
567
+ def []=(column, value)
568
+ # If it is new, it doesn't have a value yet, so we should
569
+ # definitely set the new value.
570
+ # If the column isn't in @values, we can't assume it is
571
+ # NULL in the database, so assume it has changed.
572
+ v = typecast_value(column, value)
573
+ if new? || !@values.include?(column) || v != @values[column]
574
+ changed_columns << column unless changed_columns.include?(column)
575
+ @values[column] = v
576
+ end
577
+ end
578
+
579
+ # Compares model instances by values.
580
+ def ==(obj)
581
+ (obj.class == model) && (obj.values == @values)
582
+ end
583
+ alias eql? ==
584
+
585
+ # If pk is not nil, true only if the objects have the same class and pk.
586
+ # If pk is nil, false.
587
+ def ===(obj)
588
+ pk.nil? ? false : (obj.class == model) && (obj.pk == pk)
589
+ end
590
+
591
+ # class is defined in Object, but it is also a keyword,
592
+ # and since a lot of instance methods call class methods,
593
+ # this alias makes it so you can use model instead of
594
+ # self.class.
595
+ alias_method :model, :class
596
+
597
+ # The autoincrementing primary key for this model object. Should be
598
+ # overridden if you have a composite primary key with one part of it
599
+ # being autoincrementing.
600
+ def autoincrementing_primary_key
601
+ primary_key
602
+ end
603
+
604
+ # The columns that have been updated. This isn't completely accurate,
605
+ # see Model#[]=.
606
+ def changed_columns
607
+ @changed_columns ||= []
608
+ end
609
+
610
+ # Deletes and returns self. Does not run destroy hooks.
611
+ # Look into using destroy instead.
612
+ def delete
613
+ _delete
614
+ self
615
+ end
616
+
617
+ # Like delete but runs hooks before and after delete.
618
+ # If before_destroy returns false, returns false without
619
+ # deleting the object the the database. Otherwise, deletes
620
+ # the item from the database and returns self. Uses a transaction
621
+ # if use_transactions is true or if the :transaction option is given and
622
+ # true.
623
+ def destroy(opts = {})
624
+ checked_save_failure{checked_transaction(opts){_destroy(opts)}}
625
+ end
626
+
627
+ # Iterates through all of the current values using each.
628
+ #
629
+ # Example:
630
+ # Ticket.find(7).each { |k, v| puts "#{k} => #{v}" }
631
+ def each(&block)
632
+ @values.each(&block)
633
+ end
634
+
635
+ # Returns the validation errors associated with this object.
636
+ def errors
637
+ @errors ||= Errors.new
638
+ end
639
+
640
+ # Returns true when current instance exists, false otherwise.
641
+ # Generally an object that isn't new will exist unless it has
642
+ # been deleted.
643
+ def exists?
644
+ this.count > 0
645
+ end
646
+
647
+ # Value that should be unique for objects with the same class and pk (if pk is not nil), or
648
+ # the same class and values (if pk is nil).
649
+ def hash
650
+ [model, pk.nil? ? @values.sort_by{|k,v| k.to_s} : pk].hash
651
+ end
652
+
653
+ # Returns value for the :id attribute, even if the primary key is
654
+ # not id. To get the primary key value, use #pk.
655
+ def id
656
+ @values[:id]
657
+ end
658
+
659
+ # Returns a string representation of the model instance including
660
+ # the class name and values.
661
+ def inspect
662
+ "#<#{model.name} @values=#{inspect_values}>"
663
+ end
664
+
665
+ # Returns the keys in values. May not include all column names.
666
+ def keys
667
+ @values.keys
668
+ end
669
+
670
+ # Refresh this record using for_update unless this is a new record. Returns self.
671
+ def lock!
672
+ new? ? self : _refresh(this.for_update)
673
+ end
674
+
675
+ # Remove elements of the model object that make marshalling fail. Returns self.
676
+ def marshallable!
677
+ @this = nil
678
+ self
679
+ end
680
+
681
+ # Explicitly mark the object as modified, so save_changes/update will
682
+ # run callbacks even if no columns have changed.
683
+ def modified!
684
+ @modified = true
685
+ end
686
+
687
+ # Whether this object has been modified since last saved, used by
688
+ # save_changes to determine whether changes should be saved. New
689
+ # values are always considered modified.
690
+ def modified?
691
+ @modified || !changed_columns.empty?
692
+ end
693
+
694
+ # Returns true if the current instance represents a new record.
695
+ def new?
696
+ @new
697
+ end
698
+
699
+ # Returns the primary key value identifying the model instance.
700
+ # Raises an error if this model does not have a primary key.
701
+ # If the model has a composite primary key, returns an array of values.
702
+ def pk
703
+ raise(Error, "No primary key is associated with this model") unless key = primary_key
704
+ key.is_a?(Array) ? key.map{|k| @values[k]} : @values[key]
705
+ end
706
+
707
+ # Returns a hash identifying the model instance. It should be true that:
708
+ #
709
+ # Model[model_instance.pk_hash] === model_instance
710
+ def pk_hash
711
+ model.primary_key_hash(pk)
712
+ end
713
+
714
+ # Reloads attributes from database and returns self. Also clears all
715
+ # cached association and changed_columns information. Raises an Error if the record no longer
716
+ # exists in the database.
717
+ def refresh
718
+ _refresh(this)
719
+ end
720
+
721
+ # Alias of refresh, but not aliased directly to make overriding in a plugin easier.
722
+ def reload
723
+ refresh
724
+ end
725
+
726
+ # Creates or updates the record, after making sure the record
727
+ # is valid. If the record is not valid, or before_save,
728
+ # before_create (if new?), or before_update (if !new?) return
729
+ # false, returns nil unless raise_on_save_failure is true (if it
730
+ # is true, it raises an error).
731
+ # Otherwise, returns self. You can provide an optional list of
732
+ # columns to update, in which case it only updates those columns.
733
+ #
734
+ # Takes the following options:
735
+ #
736
+ # * :changed - save all changed columns, instead of all columns or the columns given
737
+ # * :transaction - set to false not to use a transaction
738
+ # * :validate - set to false not to validate the model before saving
739
+ def save(*columns)
740
+ opts = columns.last.is_a?(Hash) ? columns.pop : {}
741
+ if opts[:validate] != false and !valid?
742
+ raise(ValidationFailed.new(errors)) if raise_on_save_failure
743
+ return
744
+ end
745
+ checked_save_failure{checked_transaction(opts){_save(columns, opts)}}
746
+ end
747
+
748
+ # Saves only changed columns if the object has been modified.
749
+ # If the object has not been modified, returns nil. If unable to
750
+ # save, returns false unless raise_on_save_failure is true.
751
+ def save_changes(opts={})
752
+ save(opts.merge(:changed=>true)) || false if modified?
753
+ end
754
+
755
+ # Updates the instance with the supplied values with support for virtual
756
+ # attributes, raising an exception if a value is used that doesn't have
757
+ # a setter method (or ignoring it if strict_param_setting = false).
758
+ # Does not save the record.
759
+ def set(hash)
760
+ set_restricted(hash, nil, nil)
761
+ end
762
+
763
+ # Set all values using the entries in the hash, ignoring any setting of
764
+ # allowed_columns or restricted columns in the model.
765
+ def set_all(hash)
766
+ set_restricted(hash, false, false)
767
+ end
768
+
769
+ # Set all values using the entries in the hash, except for the keys
770
+ # given in except.
771
+ def set_except(hash, *except)
772
+ set_restricted(hash, false, except.flatten)
773
+ end
774
+
775
+ # Set the values using the entries in the hash, only if the key
776
+ # is included in only.
777
+ def set_only(hash, *only)
778
+ set_restricted(hash, only.flatten, false)
779
+ end
780
+
781
+ # Returns (naked) dataset that should return only this instance.
782
+ def this
783
+ @this ||= model.dataset.filter(pk_hash).limit(1).naked
784
+ end
785
+
786
+ # Runs set with the passed hash and then runs save_changes.
787
+ def update(hash)
788
+ update_restricted(hash, nil, nil)
789
+ end
790
+
791
+ # Update all values using the entries in the hash, ignoring any setting of
792
+ # allowed_columns or restricted columns in the model.
793
+ def update_all(hash)
794
+ update_restricted(hash, false, false)
795
+ end
796
+
797
+ # Update all values using the entries in the hash, except for the keys
798
+ # given in except.
799
+ def update_except(hash, *except)
800
+ update_restricted(hash, false, except.flatten)
801
+ end
802
+
803
+ # Update the values using the entries in the hash, only if the key
804
+ # is included in only.
805
+ def update_only(hash, *only)
806
+ update_restricted(hash, only.flatten, false)
807
+ end
808
+
809
+ # Validates the object. If the object is invalid, errors should be added
810
+ # to the errors attribute. By default, does nothing, as all models
811
+ # are valid by default.
812
+ def validate
813
+ end
814
+
815
+ # Validates the object and returns true if no errors are reported.
816
+ def valid?
817
+ errors.clear
818
+ if before_validation == false
819
+ save_failure(:validation) if raise_on_save_failure
820
+ return false
821
+ end
822
+ validate
823
+ after_validation
824
+ errors.empty?
825
+ end
826
+
827
+ private
828
+
829
+ # Actually do the deletion of the object's dataset.
830
+ def _delete
831
+ n = _delete_dataset.delete
832
+ raise(NoExistingObject, "Attempt to delete object did not result in a single row modification (Rows Deleted: #{n}, SQL: #{_delete_dataset.delete_sql})") if require_modification && n != 1
833
+ n
834
+ end
835
+
836
+ # The dataset to use when deleting the object. The same as the object's
837
+ # dataset by default.
838
+ def _delete_dataset
839
+ this
840
+ end
841
+
842
+ # Internal destroy method, separted from destroy to
843
+ # allow running inside a transaction
844
+ def _destroy(opts)
845
+ return save_failure(:destroy) if before_destroy == false
846
+ _destroy_delete
847
+ after_destroy
848
+ self
849
+ end
850
+
851
+ # Internal delete method to call when destroying an object,
852
+ # separated from delete to allow you to override destroy's version
853
+ # without affecting delete.
854
+ def _destroy_delete
855
+ delete
856
+ end
857
+
858
+ def _insert
859
+ ds = model.dataset
860
+ if ds.respond_to?(:insert_select) and h = ds.insert_select(@values)
861
+ @values = h
862
+ nil
863
+ else
864
+ iid = ds.insert(@values)
865
+ # if we have a regular primary key and it's not set in @values,
866
+ # we assume it's the last inserted id
867
+ if (pk = autoincrementing_primary_key) && pk.is_a?(Symbol) && !@values[pk]
868
+ @values[pk] = iid
869
+ end
870
+ pk
871
+ end
872
+ end
873
+
874
+ # Refresh using a particular dataset, used inside save to make sure the same server
875
+ # is used for reading newly inserted values from the database
876
+ def _refresh(dataset)
877
+ set_values(dataset.first || raise(Error, "Record not found"))
878
+ changed_columns.clear
879
+ self
880
+ end
881
+
882
+ # Internal version of save, split from save to allow running inside
883
+ # it's own transaction.
884
+ def _save(columns, opts)
885
+ return save_failure(:save) if before_save == false
886
+ if new?
887
+ return save_failure(:create) if before_create == false
888
+ pk = _insert
889
+ @this = nil if pk
890
+ @new = false
891
+ @was_new = true
892
+ after_create
893
+ after_save
894
+ @was_new = nil
895
+ if pk
896
+ ds = this
897
+ ds = ds.server(:default) unless ds.opts[:server]
898
+ _refresh(ds)
899
+ else
900
+ changed_columns.clear
901
+ end
902
+ else
903
+ return save_failure(:update) if before_update == false
904
+ if columns.empty?
905
+ @columns_updated = opts[:changed] ? @values.reject{|k,v| !changed_columns.include?(k)} : @values.dup
906
+ changed_columns.clear
907
+ else # update only the specified columns
908
+ @columns_updated = @values.reject{|k, v| !columns.include?(k)}
909
+ changed_columns.reject!{|c| columns.include?(c)}
910
+ end
911
+ Array(primary_key).each{|x| @columns_updated.delete(x)}
912
+ _update(@columns_updated) unless @columns_updated.empty?
913
+ after_update
914
+ after_save
915
+ @columns_updated = nil
916
+ end
917
+ @modified = false
918
+ self
919
+ end
920
+
921
+ # Update this instance's dataset with the supplied column hash.
922
+ def _update(columns)
923
+ n = _update_dataset.update(columns)
924
+ raise(NoExistingObject, "Attempt to update object did not result in a single row modification (SQL: #{_update_dataset.update_sql(columns)})") if require_modification && n != 1
925
+ n
926
+ end
927
+
928
+ # The dataset to use when updating an object. The same as the object's
929
+ # dataset by default.
930
+ def _update_dataset
931
+ this
932
+ end
933
+
934
+ # If raise_on_save_failure is false, check for BeforeHookFailed
935
+ # beind raised by yielding and swallow it.
936
+ def checked_save_failure
937
+ if raise_on_save_failure
938
+ yield
939
+ else
940
+ begin
941
+ yield
942
+ rescue BeforeHookFailed
943
+ nil
944
+ end
945
+ end
946
+ end
947
+
948
+ # If transactions should be used, wrap the yield in a transaction block.
949
+ def checked_transaction(opts)
950
+ use_transaction?(opts) ? db.transaction(opts){yield} : yield
951
+ end
952
+
953
+ # Default inspection output for the values hash, overwrite to change what #inspect displays.
954
+ def inspect_values
955
+ @values.inspect
956
+ end
957
+
958
+ # Raise an error if raise_on_save_failure is true, return nil otherwise.
959
+ def save_failure(type)
960
+ raise BeforeHookFailed, "one of the before_#{type} hooks returned false"
961
+ end
962
+
963
+ # Set the columns, filtered by the only and except arrays.
964
+ def set_restricted(hash, only, except)
965
+ meths = setter_methods(only, except)
966
+ strict = strict_param_setting
967
+ hash.each do |k,v|
968
+ m = "#{k}="
969
+ if meths.include?(m)
970
+ send(m, v)
971
+ elsif strict
972
+ raise Error, "method #{m} doesn't exist or access is restricted to it"
973
+ end
974
+ end
975
+ self
976
+ end
977
+
978
+ # Replace the current values with hash.
979
+ def set_values(hash)
980
+ @values = hash
981
+ end
982
+
983
+ # Returns all methods that can be used for attribute
984
+ # assignment (those that end with =), modified by the only
985
+ # and except arguments:
986
+ #
987
+ # * only
988
+ # * false - Don't modify the results
989
+ # * nil - if the model has allowed_columns, use only these, otherwise, don't modify
990
+ # * Array - allow only the given methods to be used
991
+ # * except
992
+ # * false - Don't modify the results
993
+ # * nil - if the model has restricted_columns, remove these, otherwise, don't modify
994
+ # * Array - remove the given methods
995
+ #
996
+ # only takes precedence over except, and if only is not used, certain methods are always
997
+ # restricted (RESTRICTED_SETTER_METHODS). The primary key is restricted by default as
998
+ # well, see Model.unrestrict_primary_key to change this.
999
+ def setter_methods(only, except)
1000
+ only = only.nil? ? model.allowed_columns : only
1001
+ except = except.nil? ? model.restricted_columns : except
1002
+ if only
1003
+ only.map{|x| "#{x}="}
1004
+ else
1005
+ meths = methods.collect{|x| x.to_s}.grep(SETTER_METHOD_REGEXP) - RESTRICTED_SETTER_METHODS
1006
+ meths -= Array(primary_key).map{|x| "#{x}="} if primary_key && model.restrict_primary_key?
1007
+ meths -= except.map{|x| "#{x}="} if except
1008
+ meths
1009
+ end
1010
+ end
1011
+
1012
+ # Typecast the value to the column's type if typecasting. Calls the database's
1013
+ # typecast_value method, so database adapters can override/augment the handling
1014
+ # for database specific column types.
1015
+ def typecast_value(column, value)
1016
+ return value unless typecast_on_assignment && db_schema && (col_schema = db_schema[column])
1017
+ value = nil if value == '' and typecast_empty_string_to_nil and col_schema[:type] and ![:string, :blob].include?(col_schema[:type])
1018
+ raise(InvalidValue, "nil/NULL is not allowed for the #{column} column") if raise_on_typecast_failure && value.nil? && (col_schema[:allow_null] == false)
1019
+ begin
1020
+ model.db.typecast_value(col_schema[:type], value)
1021
+ rescue InvalidValue
1022
+ raise_on_typecast_failure ? raise : value
1023
+ end
1024
+ end
1025
+
1026
+ # Set the columns, filtered by the only and except arrays.
1027
+ def update_restricted(hash, only, except)
1028
+ set_restricted(hash, only, except)
1029
+ save_changes
1030
+ end
1031
+
1032
+ # Whether to use a transaction for this action. If the :transaction
1033
+ # option is present in the hash, use that, otherwise, fallback to the
1034
+ # object's default (if set), or class's default (if not).
1035
+ def use_transaction?(opts = {})
1036
+ opts.fetch(:transaction, use_transactions)
1037
+ end
1038
+ end
1039
+
1040
+ # Dataset methods are methods that the model class extends its dataset with in
1041
+ # the call to set_dataset.
1042
+ module DatasetMethods
1043
+ # The model class associated with this dataset
1044
+ attr_accessor :model
1045
+
1046
+ # Destroy each row in the dataset by instantiating it and then calling
1047
+ # destroy on the resulting model object. This isn't as fast as deleting
1048
+ # the dataset, which does a single SQL call, but this runs any destroy
1049
+ # hooks on each object in the dataset.
1050
+ def destroy
1051
+ @db.transaction{all{|r| r.destroy}.length}
1052
+ end
1053
+
1054
+ # This allows you to call to_hash without any arguments, which will
1055
+ # result in a hash with the primary key value being the key and the
1056
+ # model object being the value.
1057
+ def to_hash(key_column=nil, value_column=nil)
1058
+ if key_column
1059
+ super
1060
+ else
1061
+ raise(Sequel::Error, "No primary key for model") unless model and pk = model.primary_key
1062
+ super(pk, value_column)
1063
+ end
1064
+ end
1065
+ end
1066
+
1067
+ plugin self
1068
+ end
1069
+ end