sequel 3.4.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|