sequel 3.4.0 → 3.5.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 +84 -0
- data/Rakefile +1 -1
- data/doc/cheat_sheet.rdoc +5 -2
- data/doc/opening_databases.rdoc +2 -0
- data/doc/release_notes/3.5.0.txt +510 -0
- data/lib/sequel/adapters/ado.rb +3 -1
- data/lib/sequel/adapters/ado/mssql.rb +2 -2
- data/lib/sequel/adapters/do.rb +2 -11
- data/lib/sequel/adapters/do/mysql.rb +7 -0
- data/lib/sequel/adapters/do/postgres.rb +2 -2
- data/lib/sequel/adapters/firebird.rb +3 -3
- data/lib/sequel/adapters/informix.rb +3 -3
- data/lib/sequel/adapters/jdbc/h2.rb +3 -3
- data/lib/sequel/adapters/jdbc/mssql.rb +7 -0
- data/lib/sequel/adapters/mysql.rb +60 -21
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/openbase.rb +3 -3
- data/lib/sequel/adapters/oracle.rb +1 -5
- data/lib/sequel/adapters/postgres.rb +3 -3
- data/lib/sequel/adapters/shared/mssql.rb +142 -33
- data/lib/sequel/adapters/shared/mysql.rb +54 -31
- data/lib/sequel/adapters/shared/oracle.rb +17 -6
- data/lib/sequel/adapters/shared/postgres.rb +7 -7
- data/lib/sequel/adapters/shared/progress.rb +3 -3
- data/lib/sequel/adapters/shared/sqlite.rb +3 -17
- data/lib/sequel/connection_pool.rb +4 -6
- data/lib/sequel/core.rb +29 -113
- data/lib/sequel/database.rb +14 -12
- data/lib/sequel/dataset.rb +8 -21
- data/lib/sequel/dataset/convenience.rb +1 -1
- data/lib/sequel/dataset/graph.rb +9 -2
- data/lib/sequel/dataset/sql.rb +170 -104
- data/lib/sequel/exceptions.rb +3 -0
- data/lib/sequel/extensions/looser_typecasting.rb +21 -0
- data/lib/sequel/extensions/named_timezones.rb +61 -0
- data/lib/sequel/extensions/schema_dumper.rb +7 -1
- data/lib/sequel/extensions/sql_expr.rb +122 -0
- data/lib/sequel/extensions/string_date_time.rb +4 -4
- data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
- data/lib/sequel/model/associations.rb +105 -45
- data/lib/sequel/model/base.rb +37 -28
- data/lib/sequel/plugins/active_model.rb +35 -0
- data/lib/sequel/plugins/association_dependencies.rb +96 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
- data/lib/sequel/plugins/force_encoding.rb +61 -0
- data/lib/sequel/plugins/many_through_many.rb +32 -11
- data/lib/sequel/plugins/nested_attributes.rb +7 -2
- data/lib/sequel/plugins/subclasses.rb +45 -0
- data/lib/sequel/plugins/touch.rb +118 -0
- data/lib/sequel/plugins/typecast_on_load.rb +61 -0
- data/lib/sequel/sql.rb +31 -30
- data/lib/sequel/timezones.rb +161 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +262 -0
- data/spec/adapters/mysql_spec.rb +46 -8
- data/spec/adapters/postgres_spec.rb +6 -3
- data/spec/adapters/spec_helper.rb +21 -0
- data/spec/adapters/sqlite_spec.rb +1 -1
- data/spec/core/connection_pool_spec.rb +1 -1
- data/spec/core/database_spec.rb +27 -1
- data/spec/core/dataset_spec.rb +63 -1
- data/spec/core/object_graph_spec.rb +1 -1
- data/spec/core/schema_spec.rb +1 -0
- data/spec/extensions/active_model_spec.rb +47 -0
- data/spec/extensions/association_dependencies_spec.rb +108 -0
- data/spec/extensions/class_table_inheritance_spec.rb +252 -0
- data/spec/extensions/force_encoding_spec.rb +75 -0
- data/spec/extensions/looser_typecasting_spec.rb +39 -0
- data/spec/extensions/many_through_many_spec.rb +60 -2
- data/spec/extensions/named_timezones_spec.rb +72 -0
- data/spec/extensions/nested_attributes_spec.rb +29 -1
- data/spec/extensions/schema_dumper_spec.rb +10 -0
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/extensions/sql_expr_spec.rb +89 -0
- data/spec/extensions/subclasses_spec.rb +52 -0
- data/spec/extensions/thread_local_timezones_spec.rb +45 -0
- data/spec/extensions/touch_spec.rb +155 -0
- data/spec/extensions/typecast_on_load_spec.rb +60 -0
- data/spec/integration/database_test.rb +8 -0
- data/spec/integration/dataset_test.rb +9 -9
- data/spec/integration/plugin_test.rb +139 -0
- data/spec/integration/schema_test.rb +7 -7
- data/spec/integration/spec_helper.rb +32 -1
- data/spec/integration/timezone_test.rb +3 -3
- data/spec/integration/transaction_test.rb +1 -1
- data/spec/integration/type_test.rb +6 -6
- data/spec/model/association_reflection_spec.rb +18 -0
- data/spec/model/associations_spec.rb +169 -9
- data/spec/model/base_spec.rb +2 -0
- data/spec/model/eager_loading_spec.rb +82 -2
- data/spec/model/model_spec.rb +8 -1
- data/spec/model/record_spec.rb +52 -9
- metadata +33 -23
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
if RUBY_VERSION >= '1.9.0'
|
|
2
|
+
module Sequel
|
|
3
|
+
module Plugins
|
|
4
|
+
# The ForceEncoding plugin allows you force specific encodings for all
|
|
5
|
+
# strings that are used by the model. When model instances are loaded
|
|
6
|
+
# from the database, all values in the hash that are strings are
|
|
7
|
+
# forced to the given encoding. Whenever you update a model column
|
|
8
|
+
# attribute, the resulting value is forced to a given encoding if the
|
|
9
|
+
# value is a string. There are two ways to specify the encoding. You
|
|
10
|
+
# can either do so in the plugin call itself, or via the
|
|
11
|
+
# forced_encoding class accessor:
|
|
12
|
+
#
|
|
13
|
+
# class Album < Sequel::Model
|
|
14
|
+
# plugin :force_encoding, 'UTF-8'
|
|
15
|
+
# # or
|
|
16
|
+
# plugin :force_encoding
|
|
17
|
+
# self.forced_encoding = 'UTF-8'
|
|
18
|
+
# end
|
|
19
|
+
module ForceEncoding
|
|
20
|
+
# Set the forced_encoding based on the value given in the plugin call.
|
|
21
|
+
# Note that if a the plugin has been previously loaded, any previous
|
|
22
|
+
# forced encoding is overruled, even if no encoding is given when calling
|
|
23
|
+
# the plugin.
|
|
24
|
+
def self.configure(model, encoding=nil)
|
|
25
|
+
model.forced_encoding = encoding
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module ClassMethods
|
|
29
|
+
# The string encoding to force on a column string values
|
|
30
|
+
attr_accessor :forced_encoding
|
|
31
|
+
|
|
32
|
+
# Copy the forced_encoding value into the subclass
|
|
33
|
+
def inherited(subclass)
|
|
34
|
+
super
|
|
35
|
+
subclass.forced_encoding = forced_encoding
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Force the encoding of all strings for the given row to the model's
|
|
39
|
+
# forced_encoding.
|
|
40
|
+
def load(row)
|
|
41
|
+
row.values.each{|v| v.force_encoding(forced_encoding) if v.is_a?(String)} if forced_encoding
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
module InstanceMethods
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# Force the encoding of all returned strings to the model's forced_encoding.
|
|
50
|
+
def typecast_value(column, value)
|
|
51
|
+
s = super
|
|
52
|
+
s.force_encoding(model.forced_encoding) if s.is_a?(String) && model.forced_encoding
|
|
53
|
+
s
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
else
|
|
60
|
+
raise LoadError, 'ForceEncoding plugin only works on Ruby 1.9+'
|
|
61
|
+
end
|
|
@@ -37,6 +37,12 @@ module Sequel
|
|
|
37
37
|
def associated_key_table
|
|
38
38
|
self[:associated_key_table] = self[:final_reverse_edge][:alias]
|
|
39
39
|
end
|
|
40
|
+
|
|
41
|
+
# The default associated key alias(es) to use when eager loading
|
|
42
|
+
# associations via eager.
|
|
43
|
+
def default_associated_key_alias
|
|
44
|
+
self[:uses_left_composite_keys] ? (0...self[:through].first[:left].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
|
|
45
|
+
end
|
|
40
46
|
|
|
41
47
|
# The list of joins to use when eager graphing
|
|
42
48
|
def edges
|
|
@@ -103,16 +109,22 @@ module Sequel
|
|
|
103
109
|
# Must be an array, with elements that are either 3 element arrays, or hashes with keys :table, :left, and :right.
|
|
104
110
|
# The required entries in the array/hash are:
|
|
105
111
|
# * :table (first array element) - The name of the table to join.
|
|
106
|
-
# * :left (middle array element) - The key joining the table to the previous table
|
|
107
|
-
#
|
|
112
|
+
# * :left (middle array element) - The key joining the table to the previous table. Can use an
|
|
113
|
+
# array of symbols for a composite key association.
|
|
114
|
+
# * :right (last array element) - The key joining the table to the next table. Can use an
|
|
115
|
+
# array of symbols for a composite key association.
|
|
108
116
|
# If a hash is provided, the following keys are respected when using eager_graph:
|
|
109
117
|
# * :block - A proc to use as the block argument to join.
|
|
110
118
|
# * :conditions - Extra conditions to add to the JOIN ON clause. Must be a hash or array of two pairs.
|
|
111
119
|
# * :join_type - The join type to use for the join, defaults to :left_outer.
|
|
112
120
|
# * :only_conditions - Conditions to use for the join instead of the ones specified by the keys.
|
|
113
121
|
# * opts - The options for the associaion. Takes the same options as associate, and supports these additional options:
|
|
114
|
-
# * :left_primary_key - column in current table that the first :left option in
|
|
115
|
-
#
|
|
122
|
+
# * :left_primary_key - column in current table that the first :left option in
|
|
123
|
+
# through points to, as a symbol. Defaults to primary key of current table. Can use an
|
|
124
|
+
# array of symbols for a composite key association.
|
|
125
|
+
# * :right_primary_key - column in associated table that the final :right option in
|
|
126
|
+
# through points to, as a symbol. Defaults to primary key of the associated table. Can use an
|
|
127
|
+
# array of symbols for a composite key association.
|
|
116
128
|
# * :uniq - Adds a after_load callback that makes the array of objects unique.
|
|
117
129
|
def many_through_many(name, through, opts={}, &block)
|
|
118
130
|
associate(:many_through_many, name, opts.merge(:through=>through), &block)
|
|
@@ -141,12 +153,15 @@ module Sequel
|
|
|
141
153
|
end
|
|
142
154
|
|
|
143
155
|
left_key = opts[:left_key] = opts[:through].first[:left]
|
|
156
|
+
uses_lcks = opts[:uses_left_composite_keys] = left_key.is_a?(Array)
|
|
157
|
+
left_keys = Array(left_key)
|
|
144
158
|
left_pk = (opts[:left_primary_key] ||= self.primary_key)
|
|
159
|
+
left_pks = Array(left_pk)
|
|
145
160
|
opts[:dataset] ||= lambda do
|
|
146
161
|
ds = opts.associated_class
|
|
147
|
-
opts.reverse_edges.each{|t| ds = ds.join(t[:table],
|
|
162
|
+
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
148
163
|
ft = opts[:final_reverse_edge]
|
|
149
|
-
ds.join(ft[:table],
|
|
164
|
+
ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + left_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias])
|
|
150
165
|
end
|
|
151
166
|
|
|
152
167
|
left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
|
|
@@ -154,11 +169,17 @@ module Sequel
|
|
|
154
169
|
h = key_hash[left_pk]
|
|
155
170
|
records.each{|object| object.associations[name] = []}
|
|
156
171
|
ds = opts.associated_class
|
|
157
|
-
opts.reverse_edges.each{|t| ds = ds.join(t[:table],
|
|
172
|
+
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias])}
|
|
158
173
|
ft = opts[:final_reverse_edge]
|
|
159
|
-
|
|
174
|
+
conds = uses_lcks ? [[left_keys.map{|k| SQL::QualifiedIdentifier.new(ft[:table], k)}, SQL::SQLArray.new(h.keys)]] : [[left_key, h.keys]]
|
|
175
|
+
ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + conds, :table_alias=>ft[:alias])
|
|
160
176
|
model.eager_loading_dataset(opts, ds, Array(opts.select), associations).all do |assoc_record|
|
|
161
|
-
|
|
177
|
+
hash_key = if uses_lcks
|
|
178
|
+
left_key_alias.map{|k| assoc_record.values.delete(k)}
|
|
179
|
+
else
|
|
180
|
+
assoc_record.values.delete(left_key_alias)
|
|
181
|
+
end
|
|
182
|
+
next unless objects = h[hash_key]
|
|
162
183
|
objects.each{|object| object.associations[name].push(assoc_record)}
|
|
163
184
|
end
|
|
164
185
|
end
|
|
@@ -172,11 +193,11 @@ module Sequel
|
|
|
172
193
|
opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
|
|
173
194
|
iq = table_alias
|
|
174
195
|
opts.edges.each do |t|
|
|
175
|
-
ds = ds.graph(t[:table], t.include?(:only_conditions) ? t[:only_conditions] : (
|
|
196
|
+
ds = ds.graph(t[:table], t.include?(:only_conditions) ? t[:only_conditions] : (Array(t[:right]).zip(Array(t[:left])) + t[:conditions]), :select=>false, :table_alias=>ds.send(:eager_unique_table_alias, ds, t[:table]), :join_type=>t[:join_type], :implicit_qualifier=>iq, &t[:block])
|
|
176
197
|
iq = nil
|
|
177
198
|
end
|
|
178
199
|
fe = opts[:final_edge]
|
|
179
|
-
ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (
|
|
200
|
+
ds.graph(opts.associated_class, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, &graph_block)
|
|
180
201
|
end
|
|
181
202
|
|
|
182
203
|
def_association_dataset_methods(opts)
|
|
@@ -84,7 +84,9 @@ module Sequel
|
|
|
84
84
|
if reflection.returns_array?
|
|
85
85
|
after_save_hook{send(reflection.add_method, obj)}
|
|
86
86
|
else
|
|
87
|
-
|
|
87
|
+
# Don't need to validate the object twice if :validate association option is not false
|
|
88
|
+
# and don't want to validate it at all if it is false.
|
|
89
|
+
before_save_hook{send(reflection.setter_method, obj.save(:validate=>false))}
|
|
88
90
|
end
|
|
89
91
|
end
|
|
90
92
|
|
|
@@ -155,13 +157,16 @@ module Sequel
|
|
|
155
157
|
if obj = nested_attributes_find(reflection, pk)
|
|
156
158
|
obj.set(attributes)
|
|
157
159
|
after_validation_hook{validate_associated_object(reflection, obj)}
|
|
158
|
-
|
|
160
|
+
# Don't need to validate the object twice if :validate association option is not false
|
|
161
|
+
# and don't want to validate it at all if it is false.
|
|
162
|
+
after_save_hook{obj.save(:validate=>false)}
|
|
159
163
|
end
|
|
160
164
|
end
|
|
161
165
|
|
|
162
166
|
# Validate the given associated object, adding any validation error messages from the
|
|
163
167
|
# given object to the parent object.
|
|
164
168
|
def validate_associated_object(reflection, obj)
|
|
169
|
+
return if reflection[:validate] == false
|
|
165
170
|
association = reflection[:name]
|
|
166
171
|
obj.errors.full_messages.each{|m| errors.add(association, m)} unless obj.valid?
|
|
167
172
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The Subclasses plugin keeps track of all subclasses of the
|
|
4
|
+
# current model class. Direct subclasses are available via the
|
|
5
|
+
# subclasses method, and all descendent classes are available via the
|
|
6
|
+
# descendents method.
|
|
7
|
+
#
|
|
8
|
+
# c = Class.new(Sequel::Model)
|
|
9
|
+
# c.plugin :subclasses
|
|
10
|
+
# sc1 = Class.new(c)
|
|
11
|
+
# sc2 = Class.new(c)
|
|
12
|
+
# ssc1 = Class.new(sc1)
|
|
13
|
+
# c.subclasses # [sc1, sc2]
|
|
14
|
+
# sc1.subclasses # [ssc1]
|
|
15
|
+
# sc2.subclasses # []
|
|
16
|
+
# ssc1.subclasses # []
|
|
17
|
+
# c.descendents # [sc1, ssc1, sc2]
|
|
18
|
+
module Subclasses
|
|
19
|
+
# Initialize the subclasses instance variable for the model.
|
|
20
|
+
def self.apply(model, &block)
|
|
21
|
+
model.instance_variable_set(:@subclasses, [])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module ClassMethods
|
|
25
|
+
# All subclasses for the current model. Does not
|
|
26
|
+
# include the model itself.
|
|
27
|
+
attr_reader :subclasses
|
|
28
|
+
|
|
29
|
+
# All descendent classes of this model.
|
|
30
|
+
def descendents
|
|
31
|
+
subclasses.map{|x| [x] + x.descendents}.flatten
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Add the subclass to this model's current subclasses,
|
|
35
|
+
# and initialize a new subclasses instance variable
|
|
36
|
+
# in the subclass.
|
|
37
|
+
def inherited(subclass)
|
|
38
|
+
super
|
|
39
|
+
subclasses << subclass
|
|
40
|
+
subclass.instance_variable_set(:@subclasses, [])
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The touch plugin adds a touch method to model instances, which saves
|
|
4
|
+
# the object with a modified timestamp. By default, it uses the
|
|
5
|
+
# :updated_at column, but you can set which column to use.
|
|
6
|
+
# It also supports touching of associations, so that when the current
|
|
7
|
+
# model object is updated or destroyed, the associated rows in the
|
|
8
|
+
# database can have their modified timestamp updated to the current
|
|
9
|
+
# timestamp.
|
|
10
|
+
#
|
|
11
|
+
# Since the instance touch method works on model instances,
|
|
12
|
+
# it uses Time.now for the timestamp. The association touching works
|
|
13
|
+
# on datasets, so it updates all related rows in a single query, using
|
|
14
|
+
# the SQL standard CURRENT_TIMESTAMP. Both of these can be overridden
|
|
15
|
+
# easily if necessary.
|
|
16
|
+
module Touch
|
|
17
|
+
# The default column to update when touching
|
|
18
|
+
TOUCH_COLUMN_DEFAULT = :updated_at
|
|
19
|
+
|
|
20
|
+
# Set the touch_column and touched_associations variables for the model.
|
|
21
|
+
# Options:
|
|
22
|
+
# * :associations - The associations to touch when a model instance is
|
|
23
|
+
# updated or destroyed. Can be a symbol for a single association,
|
|
24
|
+
# a hash with association keys and column values, or an array of
|
|
25
|
+
# symbols and/or hashes. If a symbol is used, the column used
|
|
26
|
+
# when updating the associated objects is the model's touch_column.
|
|
27
|
+
# If a hash is used, the value is used as the column to update.
|
|
28
|
+
# * :column - The column to modify when touching a model instance.
|
|
29
|
+
def self.configure(model, opts={})
|
|
30
|
+
model.touch_column = opts[:column] || TOUCH_COLUMN_DEFAULT if opts[:column] || !model.touch_column
|
|
31
|
+
model.instance_variable_set(:@touched_associations, {})
|
|
32
|
+
model.touch_associations(opts[:associations]) if opts[:associations]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module ClassMethods
|
|
36
|
+
# The column to modify when touching a model instance, as a symbol. Also used
|
|
37
|
+
# as the default column when touching associations, if
|
|
38
|
+
# the associations don't specify a column.
|
|
39
|
+
attr_accessor :touch_column
|
|
40
|
+
|
|
41
|
+
# A hash specifying the associations to touch when instances are
|
|
42
|
+
# updated or destroyed. Keys are association dataset method name symbols and values
|
|
43
|
+
# are column name symbols.
|
|
44
|
+
attr_reader :touched_associations
|
|
45
|
+
|
|
46
|
+
# Set the touch_column for the subclass to be the same as the current class.
|
|
47
|
+
# Also, create a copy of the touched_associations in the subclass.
|
|
48
|
+
def inherited(subclass)
|
|
49
|
+
super
|
|
50
|
+
subclass.touch_column = touch_column
|
|
51
|
+
subclass.instance_variable_set(:@touched_associations, touched_associations.dup)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Add additional associations to be touched. See the :association option
|
|
55
|
+
# of the Sequel::Plugin::Touch.configure method for the format of the associations
|
|
56
|
+
# arguments.
|
|
57
|
+
def touch_associations(*associations)
|
|
58
|
+
associations.flatten.each do |a|
|
|
59
|
+
a = {a=>touch_column} if a.is_a?(Symbol)
|
|
60
|
+
a.each do |k,v|
|
|
61
|
+
raise(Error, "invalid association: #{k}") unless r = association_reflection(k)
|
|
62
|
+
touched_associations[r.dataset_method] = v
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
module InstanceMethods
|
|
69
|
+
# Touch all of the model's touched_associations when destroying the object.
|
|
70
|
+
def after_destroy
|
|
71
|
+
super
|
|
72
|
+
touch_associations
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Touch all of the model's touched_associations when updating the object.
|
|
76
|
+
def after_update
|
|
77
|
+
super
|
|
78
|
+
touch_associations
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Touch the model object. If a column is not given, use the model's touch_column
|
|
82
|
+
# as the column. If the column to use is not one of the model's columns, just
|
|
83
|
+
# save the changes to the object instead of attempting to a value that doesn't
|
|
84
|
+
# exist.
|
|
85
|
+
def touch(column=nil)
|
|
86
|
+
if column
|
|
87
|
+
set(column=>touch_instance_value)
|
|
88
|
+
else
|
|
89
|
+
column = model.touch_column
|
|
90
|
+
set(column=>touch_instance_value) if columns.include?(column)
|
|
91
|
+
end
|
|
92
|
+
save_changes
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
# The value to use when modifying the touch column for the association datasets. Uses
|
|
98
|
+
# the SQL standard CURRENT_TIMESTAMP.
|
|
99
|
+
def touch_association_value
|
|
100
|
+
Sequel::CURRENT_TIMESTAMP
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Directly update the database using the association dataset for each association.
|
|
104
|
+
def touch_associations
|
|
105
|
+
model.touched_associations.each do |meth, column|
|
|
106
|
+
send(meth).update(column=>touch_association_value)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The value to use when modifying the touch column for the model instance.
|
|
111
|
+
# Uses Time.now to work well with typecasting.
|
|
112
|
+
def touch_instance_value
|
|
113
|
+
Time.now
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Sequel
|
|
2
|
+
module Plugins
|
|
3
|
+
# The TypecastOnLoad plugin exists because most of Sequel's database adapters don't
|
|
4
|
+
# have complete control over typecasting, and may return columns that aren't
|
|
5
|
+
# typecast correctly (with correct being defined as how the model object
|
|
6
|
+
# would typecast the same column values).
|
|
7
|
+
#
|
|
8
|
+
# This plugin modifies Model.load to call the setter methods (which typecast
|
|
9
|
+
# by default) for all columns given. You can either specify the columns to
|
|
10
|
+
# typecast on load in the plugin call itself, or afterwards using
|
|
11
|
+
# add_typecast_on_load_columns:
|
|
12
|
+
#
|
|
13
|
+
# Album.plugin :typecast_on_load, :release_date, :record_date
|
|
14
|
+
# # or:
|
|
15
|
+
# Album.plugin :typecast_on_load
|
|
16
|
+
# Album.add_typecast_on_load_columns :release_date, :record_date
|
|
17
|
+
#
|
|
18
|
+
# If the database returns release_date and record_date columns as strings
|
|
19
|
+
# instead of dates, this will ensure that if you access those columns through
|
|
20
|
+
# the model object, you'll get Date objects instead of strings.
|
|
21
|
+
module TypecastOnLoad
|
|
22
|
+
# Call add_typecast_on_load_columns on the passed column arguments.
|
|
23
|
+
def self.configure(model, *columns, &block)
|
|
24
|
+
model.instance_eval do
|
|
25
|
+
@typecast_on_load_columns ||= []
|
|
26
|
+
add_typecast_on_load_columns(*columns)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module ClassMethods
|
|
31
|
+
# The columns to typecast on load for this model.
|
|
32
|
+
attr_reader :typecast_on_load_columns
|
|
33
|
+
|
|
34
|
+
# Add additional columns to typecast on load for this model.
|
|
35
|
+
def add_typecast_on_load_columns(*columns)
|
|
36
|
+
@typecast_on_load_columns.concat(columns)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Give the subclass a copy of the typecast on load columns.
|
|
40
|
+
def inherited(subclass)
|
|
41
|
+
super
|
|
42
|
+
subclass.instance_variable_set(:@typecast_on_load_columns, typecast_on_load_columns.dup)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Call the setter method for each of the typecast on load columns,
|
|
46
|
+
# ensuring the model object will have the correct typecasting even
|
|
47
|
+
# if the database doesn't typecast the columns correctly.
|
|
48
|
+
def load(values)
|
|
49
|
+
o = super
|
|
50
|
+
typecast_on_load_columns.each do |c|
|
|
51
|
+
if v = values[c]
|
|
52
|
+
o.send("#{c}=", v)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
o.changed_columns.clear
|
|
56
|
+
o
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/sequel/sql.rb
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
module Sequel
|
|
2
2
|
if RUBY_VERSION < '1.9.0'
|
|
3
|
+
# If on Ruby 1.8, create a Sequel::BasicObject class that is similar to the
|
|
4
|
+
# the Ruby 1.9 BasicObject class. This is used in a few places where proxy
|
|
5
|
+
# objects are needed that respond to any method call.
|
|
3
6
|
class BasicObject
|
|
4
7
|
(instance_methods - %w"__id__ __send__ instance_eval == equal?").each{|m| undef_method(m)}
|
|
5
8
|
end
|
|
@@ -137,9 +140,7 @@ module Sequel
|
|
|
137
140
|
ComplexExpression::BITWISE_OPERATORS.each do |o|
|
|
138
141
|
define_method(o) do |ce|
|
|
139
142
|
case ce
|
|
140
|
-
when
|
|
141
|
-
NumericExpression.new(o, self, ce)
|
|
142
|
-
when ComplexExpression
|
|
143
|
+
when BooleanExpression, StringExpression
|
|
143
144
|
raise(Sequel::Error, "cannot apply #{o} to a non-numeric expression")
|
|
144
145
|
else
|
|
145
146
|
NumericExpression.new(o, self, ce)
|
|
@@ -167,9 +168,7 @@ module Sequel
|
|
|
167
168
|
ComplexExpression::BOOLEAN_OPERATOR_METHODS.each do |m, o|
|
|
168
169
|
define_method(m) do |ce|
|
|
169
170
|
case ce
|
|
170
|
-
when
|
|
171
|
-
BooleanExpression.new(o, self, ce)
|
|
172
|
-
when ComplexExpression
|
|
171
|
+
when NumericExpression, StringExpression
|
|
173
172
|
raise(Sequel::Error, "cannot apply #{o} to a non-boolean expression")
|
|
174
173
|
else
|
|
175
174
|
BooleanExpression.new(o, self, ce)
|
|
@@ -296,9 +295,7 @@ module Sequel
|
|
|
296
295
|
ComplexExpression::MATHEMATICAL_OPERATORS.each do |o|
|
|
297
296
|
define_method(o) do |ce|
|
|
298
297
|
case ce
|
|
299
|
-
when
|
|
300
|
-
NumericExpression.new(o, self, ce)
|
|
301
|
-
when ComplexExpression
|
|
298
|
+
when BooleanExpression, StringExpression
|
|
302
299
|
raise(Sequel::Error, "cannot apply #{o} to a non-numeric expression")
|
|
303
300
|
else
|
|
304
301
|
NumericExpression.new(o, self, ce)
|
|
@@ -372,25 +369,6 @@ module Sequel
|
|
|
372
369
|
end
|
|
373
370
|
end
|
|
374
371
|
|
|
375
|
-
class ComplexExpression
|
|
376
|
-
include AliasMethods
|
|
377
|
-
include CastMethods
|
|
378
|
-
include OrderMethods
|
|
379
|
-
include SubscriptMethods
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
class GenericExpression
|
|
383
|
-
include AliasMethods
|
|
384
|
-
include CastMethods
|
|
385
|
-
include OrderMethods
|
|
386
|
-
include ComplexExpressionMethods
|
|
387
|
-
include BooleanMethods
|
|
388
|
-
include NumericMethods
|
|
389
|
-
include StringMethods
|
|
390
|
-
include SubscriptMethods
|
|
391
|
-
include InequalityMethods
|
|
392
|
-
end
|
|
393
|
-
|
|
394
372
|
### Classes ###
|
|
395
373
|
|
|
396
374
|
# Represents an aliasing of an expression/column to a given name.
|
|
@@ -475,8 +453,8 @@ module Sequel
|
|
|
475
453
|
else
|
|
476
454
|
BooleanExpression.new(OPERTATOR_INVERSIONS[op], *ce.args.dup)
|
|
477
455
|
end
|
|
478
|
-
when
|
|
479
|
-
raise(Sequel::Error, "
|
|
456
|
+
when StringExpression, NumericExpression
|
|
457
|
+
raise(Sequel::Error, "cannot invert #{ce.inspect}")
|
|
480
458
|
else
|
|
481
459
|
BooleanExpression.new(:NOT, ce)
|
|
482
460
|
end
|
|
@@ -541,6 +519,13 @@ module Sequel
|
|
|
541
519
|
to_s_method :column_all_sql
|
|
542
520
|
end
|
|
543
521
|
|
|
522
|
+
class ComplexExpression
|
|
523
|
+
include AliasMethods
|
|
524
|
+
include CastMethods
|
|
525
|
+
include OrderMethods
|
|
526
|
+
include SubscriptMethods
|
|
527
|
+
end
|
|
528
|
+
|
|
544
529
|
# Represents constants or psuedo-constants (e.g. CURRENT_DATE) in SQL
|
|
545
530
|
class Constant < GenericExpression
|
|
546
531
|
# Create an object with the given table
|
|
@@ -551,6 +536,10 @@ module Sequel
|
|
|
551
536
|
to_s_method :constant_sql, '@constant'
|
|
552
537
|
end
|
|
553
538
|
|
|
539
|
+
# Holds default generic constants that can be referenced. These
|
|
540
|
+
# are included in the Sequel top level module and are also available
|
|
541
|
+
# in this module which can be required at the top level to get
|
|
542
|
+
# direct access to the constants.
|
|
554
543
|
module Constants
|
|
555
544
|
CURRENT_DATE = Constant.new(:CURRENT_DATE)
|
|
556
545
|
CURRENT_TIME = Constant.new(:CURRENT_TIME)
|
|
@@ -579,6 +568,18 @@ module Sequel
|
|
|
579
568
|
to_s_method :function_sql
|
|
580
569
|
end
|
|
581
570
|
|
|
571
|
+
class GenericExpression
|
|
572
|
+
include AliasMethods
|
|
573
|
+
include BooleanMethods
|
|
574
|
+
include CastMethods
|
|
575
|
+
include ComplexExpressionMethods
|
|
576
|
+
include InequalityMethods
|
|
577
|
+
include NumericMethods
|
|
578
|
+
include OrderMethods
|
|
579
|
+
include StringMethods
|
|
580
|
+
include SubscriptMethods
|
|
581
|
+
end
|
|
582
|
+
|
|
582
583
|
# Represents an identifier (column or table). Can be used
|
|
583
584
|
# to specify a Symbol with multiple underscores should not be
|
|
584
585
|
# split, or for creating an identifier without using a symbol.
|