Permalink
Browse files

Add Dataset #set_defaults and #set_overrides, used for scoping the va…

…lues used in insert/update statements

Sequel has long been known for its chainable filters.  Before this
commit, it was not possible to chain the values used in insert or
update statements.  This commit adds two methods, with slightly
different features, to accomplish this.

set_defaults is used to set the default values used in insert or
update statements, which can be overriden by the values passed to
insert or update:

  DB[:t].set_defaults(:x=>1).insert_sql
  # => INSERT INTO t (x) VALUES (1)
  DB[:t].set_defaults(:x=>1).insert_sql(:x=>2)
  # => INSERT INTO t (x) VALUES (2)
  DB[:t].set_defaults(:x=>1).insert_sql(:y=>2)
  # => INSERT INTO t (x, y) VALUES (1, 2)

set_overrides is used to set default values that override the values
given to insert or update statements:

  DB[:t].set_overrides(:x=>1).insert_sql(:x=>2)
  # => INSERT INTO t (x) VALUES (1)

In addition, chaining calls to set_default and set_overrides operate
slightly differently:

  DB[:t].set_defaults(:x=>1).set_defaults(:x=>2).insert_sql
  # => INSERT INTO t (x) VALUES (2)
  DB[:t].set_overrides(:x=>1).set_overrides(:x=>2).insert_sql
  # => INSERT INTO t (x) VALUES (1)

As show above, with set_default, the last call takes precedence,
whereas with set_overrides, the first call takes precedence.

Note that set_defaults and set_overrides only are used when insert or
update is called with a hash.

Dataset#insert had to go through some significant refactoring for this
to work.  All the specs still pass, so hopefully nothing broke because
of it.
  • Loading branch information...
1 parent 7350715 commit ec6619ef704e0097a183e825e3c2ed2c04a1cdb1 @jeremyevans committed Aug 28, 2008
Showing with 92 additions and 31 deletions.
  1. +2 −0 CHANGELOG
  2. +12 −0 lib/sequel_core/dataset.rb
  3. +34 −31 lib/sequel_core/dataset/sql.rb
  4. +44 −0 spec/sequel_core/dataset_spec.rb
View
@@ -1,5 +1,7 @@
=== HEAD
+* Add Dataset #set_defaults and #set_overrides, used for scoping the values used in insert/update statements (jeremyevans)
+
* Allow Models to use the RETURNING clause when inserting records on PostgreSQL (jeremyevans)
* Raise Sequel::DatabaseError instead of generic Sequel::Error for database errors, don't swallow tracebacks (jeremyevans)
View
@@ -262,6 +262,12 @@ def set(*args)
update(*args)
end
+ # Set the default values for insert and update statements. The values passed
+ # to insert or update are merged into this hash.
+ def set_defaults(hash)
+ clone(:defaults=>(@opts[:defaults]||{}).merge(hash))
+ end
+
# Associates or disassociates the dataset with a model(s). If
# nil is specified, the dataset is turned into a naked dataset and returns
# records as hashes. If a model class specified, the dataset is modified
@@ -344,6 +350,12 @@ def set_model(key, *args)
self
end
+ # Set values that override hash arguments given to insert and update statements.
+ # This hash is merged into the hash provided to insert or update.
+ def set_overrides(hash)
+ clone(:overrides=>hash.merge(@opts[:overrides]||{}))
+ end
+
# Sets a value transform which is used to convert values loaded and saved
# to/from the database. The transform should be supplied as a hash. Each
# value in the hash should be an array containing two proc objects - one
@@ -264,41 +264,42 @@ def insert_sql(*values)
return sql
end
- if values.empty?
- insert_default_values_sql
- else
- values = values[0] if values.size == 1
-
- # if hash or array with keys we need to transform the values
- if @transform && (values.is_a?(Hash) || (values.is_a?(Array) && values.keys))
- values = transform_save(values)
+ from = source_list(@opts[:from])
+ case values.size
+ when 0
+ values = {}
+ when 1
+ vals = values.at(0)
+ if vals.is_one_of?(Hash, Dataset, Array)
+ values = vals
+ elsif vals.respond_to?(:values)
+ values = vals.values
end
- from = source_list(@opts[:from])
-
- case values
- when Array
- if values.empty?
- insert_default_values_sql
- else
- "INSERT INTO #{from} VALUES #{literal(values)}"
- end
- when Hash
- if values.empty?
- insert_default_values_sql
- else
- fl, vl = [], []
- values.each {|k, v| fl << literal(k.is_a?(String) ? k.to_sym : k); vl << literal(v)}
- "INSERT INTO #{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
- end
- when Dataset
- "INSERT INTO #{from} #{literal(values)}"
+ end
+
+ case values
+ when Array
+ if values.empty?
+ insert_default_values_sql
+ else
+ "INSERT INTO #{from} VALUES #{literal(values)}"
+ end
+ when Hash
+ values = @opts[:defaults].merge(values) if @opts[:defaults]
+ values = values.merge(@opts[:overrides]) if @opts[:overrides]
+ values = transform_save(values) if @transform
+ if values.empty?
+ insert_default_values_sql
else
- if values.respond_to?(:values)
- insert_sql(values.values)
- else
- "INSERT INTO #{from} VALUES (#{literal(values)})"
+ fl, vl = [], []
+ values.each do |k, v|
+ fl << literal(String === k ? k.to_sym : k)
+ vl << literal(v)
end
+ "INSERT INTO #{from} (#{fl.join(COMMA_SEPARATOR)}) VALUES (#{vl.join(COMMA_SEPARATOR)})"
end
+ when Dataset
+ "INSERT INTO #{from} #{literal(values)}"
end
end
@@ -718,6 +719,8 @@ def update_sql(values = {}, opts = nil)
sql = "UPDATE #{source_list(@opts[:from])} SET "
set = if values.is_a?(Hash)
+ values = opts[:defaults].merge(values) if opts[:defaults]
+ values = values.merge(opts[:overrides]) if opts[:overrides]
# get values from hash
values = transform_save(values) if @transform
values.map do |k, v|
@@ -3106,3 +3106,47 @@ def @ds.fetch_rows(sql, &block)
['UPDATE items SET a = (a + 1)', :u]]
end
end
+
+context "Sequel::Dataset #set_defaults" do
+ before do
+ @ds = Sequel::Dataset.new(nil).from(:items).set_defaults(:x=>1)
+ end
+
+ specify "should set the default values for inserts" do
+ @ds.insert_sql.should == "INSERT INTO items (x) VALUES (1)"
+ @ds.insert_sql(:x=>2).should == "INSERT INTO items (x) VALUES (2)"
+ @ds.insert_sql(:y=>2).should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
+ @ds.set_defaults(:y=>2).insert_sql.should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
+ @ds.set_defaults(:x=>2).insert_sql.should == "INSERT INTO items (x) VALUES (2)"
+ end
+
+ specify "should set the default values for updates" do
+ @ds.update_sql.should == "UPDATE items SET x = 1"
+ @ds.update_sql(:x=>2).should == "UPDATE items SET x = 2"
+ @ds.update_sql(:y=>2).should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
+ @ds.set_defaults(:y=>2).update_sql.should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
+ @ds.set_defaults(:x=>2).update_sql.should == "UPDATE items SET x = 2"
+ end
+end
+
+context "Sequel::Dataset #set_overrides" do
+ before do
+ @ds = Sequel::Dataset.new(nil).from(:items).set_overrides(:x=>1)
+ end
+
+ specify "should override the given values for inserts" do
+ @ds.insert_sql.should == "INSERT INTO items (x) VALUES (1)"
+ @ds.insert_sql(:x=>2).should == "INSERT INTO items (x) VALUES (1)"
+ @ds.insert_sql(:y=>2).should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
+ @ds.set_overrides(:y=>2).insert_sql.should =~ /INSERT INTO items \([xy], [xy]\) VALUES \([21], [21]\)/
+ @ds.set_overrides(:x=>2).insert_sql.should == "INSERT INTO items (x) VALUES (1)"
+ end
+
+ specify "should override the given values for updates" do
+ @ds.update_sql.should == "UPDATE items SET x = 1"
+ @ds.update_sql(:x=>2).should == "UPDATE items SET x = 1"
+ @ds.update_sql(:y=>2).should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
+ @ds.set_overrides(:y=>2).update_sql.should =~ /UPDATE items SET (x = 1|y = 2), (x = 1|y = 2)/
+ @ds.set_overrides(:x=>2).update_sql.should == "UPDATE items SET x = 1"
+ end
+end

0 comments on commit ec6619e

Please sign in to comment.