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.
- data/CHANGELOG +3134 -0
- data/COPYING +19 -0
- data/README.rdoc +723 -0
- data/Rakefile +193 -0
- data/bin/sequel +196 -0
- data/doc/advanced_associations.rdoc +644 -0
- data/doc/cheat_sheet.rdoc +218 -0
- data/doc/dataset_basics.rdoc +106 -0
- data/doc/dataset_filtering.rdoc +158 -0
- data/doc/opening_databases.rdoc +296 -0
- data/doc/prepared_statements.rdoc +104 -0
- data/doc/reflection.rdoc +84 -0
- data/doc/release_notes/1.0.txt +38 -0
- data/doc/release_notes/1.1.txt +143 -0
- data/doc/release_notes/1.3.txt +101 -0
- data/doc/release_notes/1.4.0.txt +53 -0
- data/doc/release_notes/1.5.0.txt +155 -0
- data/doc/release_notes/2.0.0.txt +298 -0
- data/doc/release_notes/2.1.0.txt +271 -0
- data/doc/release_notes/2.10.0.txt +328 -0
- data/doc/release_notes/2.11.0.txt +215 -0
- data/doc/release_notes/2.12.0.txt +534 -0
- data/doc/release_notes/2.2.0.txt +253 -0
- data/doc/release_notes/2.3.0.txt +88 -0
- data/doc/release_notes/2.4.0.txt +106 -0
- data/doc/release_notes/2.5.0.txt +137 -0
- data/doc/release_notes/2.6.0.txt +157 -0
- data/doc/release_notes/2.7.0.txt +166 -0
- data/doc/release_notes/2.8.0.txt +171 -0
- data/doc/release_notes/2.9.0.txt +97 -0
- data/doc/release_notes/3.0.0.txt +221 -0
- data/doc/release_notes/3.1.0.txt +406 -0
- data/doc/release_notes/3.10.0.txt +286 -0
- data/doc/release_notes/3.2.0.txt +268 -0
- data/doc/release_notes/3.3.0.txt +192 -0
- data/doc/release_notes/3.4.0.txt +325 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/doc/release_notes/3.6.0.txt +366 -0
- data/doc/release_notes/3.7.0.txt +179 -0
- data/doc/release_notes/3.8.0.txt +151 -0
- data/doc/release_notes/3.9.0.txt +233 -0
- data/doc/schema.rdoc +36 -0
- data/doc/sharding.rdoc +113 -0
- data/doc/virtual_rows.rdoc +205 -0
- data/lib/sequel.rb +1 -0
- data/lib/sequel/adapters/ado.rb +90 -0
- data/lib/sequel/adapters/ado/mssql.rb +30 -0
- data/lib/sequel/adapters/amalgalite.rb +176 -0
- data/lib/sequel/adapters/db2.rb +139 -0
- data/lib/sequel/adapters/dbi.rb +113 -0
- data/lib/sequel/adapters/do.rb +188 -0
- data/lib/sequel/adapters/do/mysql.rb +49 -0
- data/lib/sequel/adapters/do/postgres.rb +91 -0
- data/lib/sequel/adapters/do/sqlite.rb +40 -0
- data/lib/sequel/adapters/firebird.rb +283 -0
- data/lib/sequel/adapters/informix.rb +77 -0
- data/lib/sequel/adapters/jdbc.rb +587 -0
- data/lib/sequel/adapters/jdbc/as400.rb +58 -0
- data/lib/sequel/adapters/jdbc/h2.rb +133 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +57 -0
- data/lib/sequel/adapters/jdbc/mysql.rb +78 -0
- data/lib/sequel/adapters/jdbc/oracle.rb +50 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +108 -0
- data/lib/sequel/adapters/jdbc/sqlite.rb +55 -0
- data/lib/sequel/adapters/mysql.rb +421 -0
- data/lib/sequel/adapters/odbc.rb +143 -0
- data/lib/sequel/adapters/odbc/mssql.rb +42 -0
- data/lib/sequel/adapters/openbase.rb +64 -0
- data/lib/sequel/adapters/oracle.rb +131 -0
- data/lib/sequel/adapters/postgres.rb +504 -0
- data/lib/sequel/adapters/shared/mssql.rb +490 -0
- data/lib/sequel/adapters/shared/mysql.rb +498 -0
- data/lib/sequel/adapters/shared/oracle.rb +195 -0
- data/lib/sequel/adapters/shared/postgres.rb +830 -0
- data/lib/sequel/adapters/shared/progress.rb +44 -0
- data/lib/sequel/adapters/shared/sqlite.rb +389 -0
- data/lib/sequel/adapters/sqlite.rb +224 -0
- data/lib/sequel/adapters/utils/stored_procedures.rb +84 -0
- data/lib/sequel/connection_pool.rb +99 -0
- data/lib/sequel/connection_pool/sharded_single.rb +84 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
- data/lib/sequel/connection_pool/single.rb +29 -0
- data/lib/sequel/connection_pool/threaded.rb +150 -0
- data/lib/sequel/core.rb +293 -0
- data/lib/sequel/core_sql.rb +241 -0
- data/lib/sequel/database.rb +1079 -0
- data/lib/sequel/database/schema_generator.rb +327 -0
- data/lib/sequel/database/schema_methods.rb +203 -0
- data/lib/sequel/database/schema_sql.rb +320 -0
- data/lib/sequel/dataset.rb +32 -0
- data/lib/sequel/dataset/actions.rb +441 -0
- data/lib/sequel/dataset/features.rb +86 -0
- data/lib/sequel/dataset/graph.rb +254 -0
- data/lib/sequel/dataset/misc.rb +119 -0
- data/lib/sequel/dataset/mutation.rb +64 -0
- data/lib/sequel/dataset/prepared_statements.rb +227 -0
- data/lib/sequel/dataset/query.rb +709 -0
- data/lib/sequel/dataset/sql.rb +996 -0
- data/lib/sequel/exceptions.rb +51 -0
- data/lib/sequel/extensions/blank.rb +43 -0
- data/lib/sequel/extensions/inflector.rb +242 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/migration.rb +239 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/pagination.rb +100 -0
- data/lib/sequel/extensions/pretty_table.rb +82 -0
- data/lib/sequel/extensions/query.rb +52 -0
- data/lib/sequel/extensions/schema_dumper.rb +271 -0
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +46 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/metaprogramming.rb +9 -0
- data/lib/sequel/model.rb +120 -0
- data/lib/sequel/model/associations.rb +1514 -0
- data/lib/sequel/model/base.rb +1069 -0
- data/lib/sequel/model/default_inflections.rb +45 -0
- data/lib/sequel/model/errors.rb +39 -0
- data/lib/sequel/model/exceptions.rb +21 -0
- data/lib/sequel/model/inflections.rb +162 -0
- data/lib/sequel/model/plugins.rb +70 -0
- data/lib/sequel/plugins/active_model.rb +59 -0
- data/lib/sequel/plugins/association_dependencies.rb +103 -0
- data/lib/sequel/plugins/association_proxies.rb +41 -0
- data/lib/sequel/plugins/boolean_readers.rb +53 -0
- data/lib/sequel/plugins/caching.rb +141 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/composition.rb +138 -0
- data/lib/sequel/plugins/force_encoding.rb +72 -0
- data/lib/sequel/plugins/hook_class_methods.rb +126 -0
- data/lib/sequel/plugins/identity_map.rb +116 -0
- data/lib/sequel/plugins/instance_filters.rb +98 -0
- data/lib/sequel/plugins/instance_hooks.rb +57 -0
- data/lib/sequel/plugins/lazy_attributes.rb +77 -0
- data/lib/sequel/plugins/many_through_many.rb +208 -0
- data/lib/sequel/plugins/nested_attributes.rb +206 -0
- data/lib/sequel/plugins/optimistic_locking.rb +81 -0
- data/lib/sequel/plugins/rcte_tree.rb +281 -0
- data/lib/sequel/plugins/schema.rb +66 -0
- data/lib/sequel/plugins/serialization.rb +166 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +74 -0
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +61 -0
- data/lib/sequel/plugins/timestamps.rb +87 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +72 -0
- data/lib/sequel/plugins/validation_class_methods.rb +405 -0
- data/lib/sequel/plugins/validation_helpers.rb +223 -0
- data/lib/sequel/sql.rb +1020 -0
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +12 -0
- data/lib/sequel_core.rb +1 -0
- data/lib/sequel_model.rb +1 -0
- data/spec/adapters/firebird_spec.rb +407 -0
- data/spec/adapters/informix_spec.rb +97 -0
- data/spec/adapters/mssql_spec.rb +403 -0
- data/spec/adapters/mysql_spec.rb +1019 -0
- data/spec/adapters/oracle_spec.rb +286 -0
- data/spec/adapters/postgres_spec.rb +969 -0
- data/spec/adapters/spec_helper.rb +51 -0
- data/spec/adapters/sqlite_spec.rb +432 -0
- data/spec/core/connection_pool_spec.rb +808 -0
- data/spec/core/core_sql_spec.rb +417 -0
- data/spec/core/database_spec.rb +1662 -0
- data/spec/core/dataset_spec.rb +3827 -0
- data/spec/core/expression_filters_spec.rb +595 -0
- data/spec/core/object_graph_spec.rb +296 -0
- data/spec/core/schema_generator_spec.rb +159 -0
- data/spec/core/schema_spec.rb +830 -0
- data/spec/core/spec_helper.rb +56 -0
- data/spec/core/version_spec.rb +7 -0
- data/spec/extensions/active_model_spec.rb +76 -0
- data/spec/extensions/association_dependencies_spec.rb +127 -0
- data/spec/extensions/association_proxies_spec.rb +50 -0
- data/spec/extensions/blank_spec.rb +67 -0
- data/spec/extensions/boolean_readers_spec.rb +92 -0
- data/spec/extensions/caching_spec.rb +250 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/composition_spec.rb +194 -0
- data/spec/extensions/force_encoding_spec.rb +117 -0
- data/spec/extensions/hook_class_methods_spec.rb +470 -0
- data/spec/extensions/identity_map_spec.rb +202 -0
- data/spec/extensions/inflector_spec.rb +181 -0
- data/spec/extensions/instance_filters_spec.rb +55 -0
- data/spec/extensions/instance_hooks_spec.rb +133 -0
- data/spec/extensions/lazy_attributes_spec.rb +153 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +884 -0
- data/spec/extensions/migration_spec.rb +332 -0
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +396 -0
- data/spec/extensions/optimistic_locking_spec.rb +100 -0
- 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/extensions/rcte_tree_spec.rb +205 -0
- data/spec/extensions/schema_dumper_spec.rb +357 -0
- data/spec/extensions/schema_spec.rb +127 -0
- data/spec/extensions/serialization_spec.rb +209 -0
- data/spec/extensions/single_table_inheritance_spec.rb +96 -0
- data/spec/extensions/spec_helper.rb +91 -0
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/string_date_time_spec.rb +93 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/tactical_eager_loading_spec.rb +65 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/timestamps_spec.rb +150 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +69 -0
- data/spec/extensions/validation_class_methods_spec.rb +984 -0
- data/spec/extensions/validation_helpers_spec.rb +438 -0
- data/spec/integration/associations_test.rb +281 -0
- data/spec/integration/database_test.rb +26 -0
- data/spec/integration/dataset_test.rb +963 -0
- data/spec/integration/eager_loader_test.rb +734 -0
- data/spec/integration/model_test.rb +130 -0
- data/spec/integration/plugin_test.rb +814 -0
- data/spec/integration/prepared_statement_test.rb +213 -0
- data/spec/integration/schema_test.rb +361 -0
- data/spec/integration/spec_helper.rb +73 -0
- data/spec/integration/timezone_test.rb +55 -0
- data/spec/integration/transaction_test.rb +122 -0
- data/spec/integration/type_test.rb +96 -0
- data/spec/model/association_reflection_spec.rb +175 -0
- data/spec/model/associations_spec.rb +2633 -0
- data/spec/model/base_spec.rb +418 -0
- data/spec/model/dataset_methods_spec.rb +78 -0
- data/spec/model/eager_loading_spec.rb +1391 -0
- data/spec/model/hooks_spec.rb +240 -0
- data/spec/model/inflector_spec.rb +26 -0
- data/spec/model/model_spec.rb +593 -0
- data/spec/model/plugins_spec.rb +236 -0
- data/spec/model/record_spec.rb +1500 -0
- data/spec/model/spec_helper.rb +97 -0
- data/spec/model/validations_spec.rb +153 -0
- data/spec/rcov.opts +6 -0
- data/spec/spec_config.rb.example +10 -0
- 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
|