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