Skip to content

Commit

Permalink
Add Dataset #set_defaults and #set_overrides, used for scoping the va…
Browse files Browse the repository at this point in the history
…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
jeremyevans committed Aug 28, 2008
1 parent 7350715 commit ec6619e
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 31 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
12 changes: 12 additions & 0 deletions lib/sequel_core/dataset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
65 changes: 34 additions & 31 deletions lib/sequel_core/dataset/sql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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|
Expand Down
44 changes: 44 additions & 0 deletions spec/sequel_core/dataset_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.