sequel 3.4.0 → 3.5.0

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