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.
Files changed (93) hide show
  1. data/CHANGELOG +84 -0
  2. data/Rakefile +1 -1
  3. data/doc/cheat_sheet.rdoc +5 -2
  4. data/doc/opening_databases.rdoc +2 -0
  5. data/doc/release_notes/3.5.0.txt +510 -0
  6. data/lib/sequel/adapters/ado.rb +3 -1
  7. data/lib/sequel/adapters/ado/mssql.rb +2 -2
  8. data/lib/sequel/adapters/do.rb +2 -11
  9. data/lib/sequel/adapters/do/mysql.rb +7 -0
  10. data/lib/sequel/adapters/do/postgres.rb +2 -2
  11. data/lib/sequel/adapters/firebird.rb +3 -3
  12. data/lib/sequel/adapters/informix.rb +3 -3
  13. data/lib/sequel/adapters/jdbc/h2.rb +3 -3
  14. data/lib/sequel/adapters/jdbc/mssql.rb +7 -0
  15. data/lib/sequel/adapters/mysql.rb +60 -21
  16. data/lib/sequel/adapters/odbc.rb +1 -1
  17. data/lib/sequel/adapters/openbase.rb +3 -3
  18. data/lib/sequel/adapters/oracle.rb +1 -5
  19. data/lib/sequel/adapters/postgres.rb +3 -3
  20. data/lib/sequel/adapters/shared/mssql.rb +142 -33
  21. data/lib/sequel/adapters/shared/mysql.rb +54 -31
  22. data/lib/sequel/adapters/shared/oracle.rb +17 -6
  23. data/lib/sequel/adapters/shared/postgres.rb +7 -7
  24. data/lib/sequel/adapters/shared/progress.rb +3 -3
  25. data/lib/sequel/adapters/shared/sqlite.rb +3 -17
  26. data/lib/sequel/connection_pool.rb +4 -6
  27. data/lib/sequel/core.rb +29 -113
  28. data/lib/sequel/database.rb +14 -12
  29. data/lib/sequel/dataset.rb +8 -21
  30. data/lib/sequel/dataset/convenience.rb +1 -1
  31. data/lib/sequel/dataset/graph.rb +9 -2
  32. data/lib/sequel/dataset/sql.rb +170 -104
  33. data/lib/sequel/exceptions.rb +3 -0
  34. data/lib/sequel/extensions/looser_typecasting.rb +21 -0
  35. data/lib/sequel/extensions/named_timezones.rb +61 -0
  36. data/lib/sequel/extensions/schema_dumper.rb +7 -1
  37. data/lib/sequel/extensions/sql_expr.rb +122 -0
  38. data/lib/sequel/extensions/string_date_time.rb +4 -4
  39. data/lib/sequel/extensions/thread_local_timezones.rb +48 -0
  40. data/lib/sequel/model/associations.rb +105 -45
  41. data/lib/sequel/model/base.rb +37 -28
  42. data/lib/sequel/plugins/active_model.rb +35 -0
  43. data/lib/sequel/plugins/association_dependencies.rb +96 -0
  44. data/lib/sequel/plugins/class_table_inheritance.rb +214 -0
  45. data/lib/sequel/plugins/force_encoding.rb +61 -0
  46. data/lib/sequel/plugins/many_through_many.rb +32 -11
  47. data/lib/sequel/plugins/nested_attributes.rb +7 -2
  48. data/lib/sequel/plugins/subclasses.rb +45 -0
  49. data/lib/sequel/plugins/touch.rb +118 -0
  50. data/lib/sequel/plugins/typecast_on_load.rb +61 -0
  51. data/lib/sequel/sql.rb +31 -30
  52. data/lib/sequel/timezones.rb +161 -0
  53. data/lib/sequel/version.rb +1 -1
  54. data/spec/adapters/mssql_spec.rb +262 -0
  55. data/spec/adapters/mysql_spec.rb +46 -8
  56. data/spec/adapters/postgres_spec.rb +6 -3
  57. data/spec/adapters/spec_helper.rb +21 -0
  58. data/spec/adapters/sqlite_spec.rb +1 -1
  59. data/spec/core/connection_pool_spec.rb +1 -1
  60. data/spec/core/database_spec.rb +27 -1
  61. data/spec/core/dataset_spec.rb +63 -1
  62. data/spec/core/object_graph_spec.rb +1 -1
  63. data/spec/core/schema_spec.rb +1 -0
  64. data/spec/extensions/active_model_spec.rb +47 -0
  65. data/spec/extensions/association_dependencies_spec.rb +108 -0
  66. data/spec/extensions/class_table_inheritance_spec.rb +252 -0
  67. data/spec/extensions/force_encoding_spec.rb +75 -0
  68. data/spec/extensions/looser_typecasting_spec.rb +39 -0
  69. data/spec/extensions/many_through_many_spec.rb +60 -2
  70. data/spec/extensions/named_timezones_spec.rb +72 -0
  71. data/spec/extensions/nested_attributes_spec.rb +29 -1
  72. data/spec/extensions/schema_dumper_spec.rb +10 -0
  73. data/spec/extensions/spec_helper.rb +1 -1
  74. data/spec/extensions/sql_expr_spec.rb +89 -0
  75. data/spec/extensions/subclasses_spec.rb +52 -0
  76. data/spec/extensions/thread_local_timezones_spec.rb +45 -0
  77. data/spec/extensions/touch_spec.rb +155 -0
  78. data/spec/extensions/typecast_on_load_spec.rb +60 -0
  79. data/spec/integration/database_test.rb +8 -0
  80. data/spec/integration/dataset_test.rb +9 -9
  81. data/spec/integration/plugin_test.rb +139 -0
  82. data/spec/integration/schema_test.rb +7 -7
  83. data/spec/integration/spec_helper.rb +32 -1
  84. data/spec/integration/timezone_test.rb +3 -3
  85. data/spec/integration/transaction_test.rb +1 -1
  86. data/spec/integration/type_test.rb +6 -6
  87. data/spec/model/association_reflection_spec.rb +18 -0
  88. data/spec/model/associations_spec.rb +169 -9
  89. data/spec/model/base_spec.rb +2 -0
  90. data/spec/model/eager_loading_spec.rb +82 -2
  91. data/spec/model/model_spec.rb +8 -1
  92. data/spec/model/record_spec.rb +52 -9
  93. 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
- # * :right (last array element) - The key joining the table to the next table
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 through points to, as a symbol. Defaults to primary key of current table.
115
- # * :right_primary_key - column in associated table that the final :right option in through points to, as a symbol. Defaults to primary key of the associated table.
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], [[t[:left], t[:right]]], :table_alias=>t[:alias])}
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], [[ft[:left], ft[:right]], [left_key, send(left_pk)]], :table_alias=>ft[:alias])
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], [[t[:left], t[:right]]], :table_alias=>t[:alias])}
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
- ds = ds.join(ft[:table], [[ft[:left], ft[:right]], [left_key, h.keys]], :table_alias=>ft[:alias])
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
- next unless objects = h[assoc_record.values.delete(left_key_alias)]
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] : ([[t[:right], 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])
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 : ([[opts.right_primary_key, fe[:left]]] + conditions), :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, &graph_block)
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
- before_save_hook{send(reflection.setter_method, obj.save)}
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
- after_save_hook{obj.save}
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 NumericExpression
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 BooleanExpression
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 NumericExpression
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 ComplexExpression
479
- raise(Sequel::Error, "operator #{ce.op} cannot be inverted")
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.