upsert 0.3.4 → 0.4.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.
- data/CHANGELOG +12 -0
- data/README.md +6 -9
- data/Rakefile +9 -14
- data/lib/upsert.rb +40 -71
- data/lib/upsert/buffer.rb +36 -0
- data/lib/upsert/buffer/mysql2_client.rb +67 -0
- data/lib/upsert/buffer/pg_connection.rb +54 -0
- data/lib/upsert/buffer/pg_connection/merge_function.rb +138 -0
- data/lib/upsert/buffer/sqlite3_database.rb +13 -0
- data/lib/upsert/connection.rb +41 -0
- data/lib/upsert/connection/mysql2_client.rb +53 -0
- data/lib/upsert/connection/pg_connection.rb +39 -0
- data/lib/upsert/connection/sqlite3_database.rb +36 -0
- data/lib/upsert/row.rb +28 -24
- data/lib/upsert/version.rb +1 -1
- data/spec/active_record_upsert_spec.rb +16 -0
- data/spec/binary_spec.rb +21 -0
- data/spec/correctness_spec.rb +73 -0
- data/spec/database_functions_spec.rb +36 -0
- data/spec/database_spec.rb +97 -0
- data/spec/logger_spec.rb +37 -0
- data/{test → spec}/misc/get_postgres_reserved_words.rb +0 -0
- data/{test → spec}/misc/mysql_reserved.txt +0 -0
- data/{test → spec}/misc/pg_reserved.txt +0 -0
- data/spec/multibyte_spec.rb +27 -0
- data/spec/precision_spec.rb +11 -0
- data/spec/reserved_words_spec.rb +46 -0
- data/{test/helper.rb → spec/spec_helper.rb} +43 -43
- data/spec/speed_spec.rb +73 -0
- data/spec/threaded_spec.rb +34 -0
- data/spec/timezones_spec.rb +28 -0
- data/upsert.gemspec +6 -2
- metadata +99 -50
- data/lib/upsert/mysql2_client.rb +0 -104
- data/lib/upsert/pg_connection.rb +0 -92
- data/lib/upsert/pg_connection/column_definition.rb +0 -35
- data/lib/upsert/sqlite3_database.rb +0 -39
- data/test/shared/binary.rb +0 -18
- data/test/shared/correctness.rb +0 -72
- data/test/shared/database.rb +0 -94
- data/test/shared/multibyte.rb +0 -37
- data/test/shared/precision.rb +0 -8
- data/test/shared/reserved_words.rb +0 -45
- data/test/shared/speed.rb +0 -72
- data/test/shared/threaded.rb +0 -31
- data/test/shared/timezones.rb +0 -25
- data/test/test_active_record_connection_adapter.rb +0 -36
- data/test/test_active_record_upsert.rb +0 -23
- data/test/test_mysql2.rb +0 -43
- data/test/test_pg.rb +0 -45
- data/test/test_sqlite.rb +0 -47
@@ -0,0 +1,13 @@
|
|
1
|
+
class Upsert
|
2
|
+
class Buffer
|
3
|
+
# @private
|
4
|
+
class SQLite3_Database < Buffer
|
5
|
+
def ready
|
6
|
+
return if rows.empty?
|
7
|
+
row = rows.shift
|
8
|
+
c = parent.connection
|
9
|
+
c.execute %{INSERT OR IGNORE INTO #{parent.quoted_table_name} (#{row.columns_sql}) VALUES (#{row.values_sql}); UPDATE #{parent.quoted_table_name} SET #{row.set_sql} WHERE #{row.where_sql}}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'upsert/connection/mysql2_client'
|
2
|
+
require 'upsert/connection/pg_connection'
|
3
|
+
require 'upsert/connection/sqlite3_database'
|
4
|
+
|
5
|
+
class Upsert
|
6
|
+
# @private
|
7
|
+
class Connection
|
8
|
+
attr_reader :parent
|
9
|
+
attr_reader :raw_connection
|
10
|
+
|
11
|
+
def initialize(parent, raw_connection)
|
12
|
+
@parent = parent
|
13
|
+
@raw_connection = raw_connection
|
14
|
+
end
|
15
|
+
|
16
|
+
def quote_value(v)
|
17
|
+
case v
|
18
|
+
when NilClass
|
19
|
+
NULL_WORD
|
20
|
+
when Upsert::Binary
|
21
|
+
quote_binary v # must be defined by base
|
22
|
+
when String
|
23
|
+
quote_string v # must be defined by base
|
24
|
+
when TrueClass, FalseClass
|
25
|
+
quote_boolean v
|
26
|
+
when BigDecimal
|
27
|
+
quote_big_decimal v
|
28
|
+
when Numeric
|
29
|
+
v
|
30
|
+
when Symbol
|
31
|
+
quote_string v.to_s
|
32
|
+
when Time, DateTime
|
33
|
+
quote_time v # must be defined by base
|
34
|
+
when Date
|
35
|
+
quote_string v.strftime(ISO8601_DATE)
|
36
|
+
else
|
37
|
+
raise "not sure how to quote #{v.class}: #{v.inspect}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Upsert
|
2
|
+
class Connection
|
3
|
+
# @private
|
4
|
+
class Mysql2_Client < Connection
|
5
|
+
def execute(sql)
|
6
|
+
Upsert.logger.debug { %{[upsert] #{sql}} }
|
7
|
+
raw_connection.query sql
|
8
|
+
end
|
9
|
+
|
10
|
+
def quote_boolean(v)
|
11
|
+
v ? 'TRUE' : 'FALSE'
|
12
|
+
end
|
13
|
+
|
14
|
+
def quote_string(v)
|
15
|
+
SINGLE_QUOTE + raw_connection.escape(v) + SINGLE_QUOTE
|
16
|
+
end
|
17
|
+
|
18
|
+
# This doubles the size of the representation.
|
19
|
+
def quote_binary(v)
|
20
|
+
X_AND_SINGLE_QUOTE + v.unpack("H*")[0] + SINGLE_QUOTE
|
21
|
+
end
|
22
|
+
|
23
|
+
# put raw binary straight into sql
|
24
|
+
# might work if we could get the encoding issues fixed when joining together the values for the sql
|
25
|
+
# alias_method :quote_binary, :quote_string
|
26
|
+
|
27
|
+
def quote_time(v)
|
28
|
+
quote_string v.strftime(ISO8601_DATETIME)
|
29
|
+
end
|
30
|
+
|
31
|
+
def quote_ident(k)
|
32
|
+
BACKTICK + raw_connection.escape(k.to_s) + BACKTICK
|
33
|
+
end
|
34
|
+
|
35
|
+
def quote_big_decimal(v)
|
36
|
+
v.to_s('F')
|
37
|
+
end
|
38
|
+
|
39
|
+
def database_variable_get(k)
|
40
|
+
sql = "SHOW VARIABLES LIKE '#{k}'"
|
41
|
+
row = execute(sql).first
|
42
|
+
case row
|
43
|
+
when Array
|
44
|
+
row[1]
|
45
|
+
when Hash
|
46
|
+
row['Value']
|
47
|
+
else
|
48
|
+
raise "Don't know what to do if connection.query returns a #{row.class}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Upsert
|
2
|
+
class Connection
|
3
|
+
# @private
|
4
|
+
class PG_Connection < Connection
|
5
|
+
def execute(sql)
|
6
|
+
Upsert.logger.debug { %{[upsert] #{sql}} }
|
7
|
+
raw_connection.exec sql
|
8
|
+
end
|
9
|
+
|
10
|
+
def quote_string(v)
|
11
|
+
SINGLE_QUOTE + raw_connection.escape_string(v) + SINGLE_QUOTE
|
12
|
+
end
|
13
|
+
|
14
|
+
def quote_binary(v)
|
15
|
+
E_AND_SINGLE_QUOTE + raw_connection.escape_bytea(v) + SINGLE_QUOTE
|
16
|
+
end
|
17
|
+
|
18
|
+
def quote_time(v)
|
19
|
+
quote_string [v.strftime(ISO8601_DATETIME), sprintf(USEC_SPRINTF, v.usec)].join('.')
|
20
|
+
end
|
21
|
+
|
22
|
+
def quote_big_decimal(v)
|
23
|
+
v.to_s('F')
|
24
|
+
end
|
25
|
+
|
26
|
+
def quote_boolean(v)
|
27
|
+
v ? 'TRUE' : 'FALSE'
|
28
|
+
end
|
29
|
+
|
30
|
+
def quote_ident(k)
|
31
|
+
raw_connection.quote_ident k.to_s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @private
|
36
|
+
# backwards compatibility - https://github.com/seamusabshere/upsert/issues/2
|
37
|
+
PGconn = PG_Connection
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Upsert
|
2
|
+
class Connection
|
3
|
+
# @private
|
4
|
+
class SQLite3_Database < Connection
|
5
|
+
def execute(sql)
|
6
|
+
Upsert.logger.debug { %{[upsert] #{sql}} }
|
7
|
+
raw_connection.execute_batch sql
|
8
|
+
end
|
9
|
+
|
10
|
+
def quote_string(v)
|
11
|
+
SINGLE_QUOTE + SQLite3::Database.quote(v) + SINGLE_QUOTE
|
12
|
+
end
|
13
|
+
|
14
|
+
def quote_binary(v)
|
15
|
+
X_AND_SINGLE_QUOTE + v.unpack("H*")[0] + SINGLE_QUOTE
|
16
|
+
end
|
17
|
+
|
18
|
+
def quote_time(v)
|
19
|
+
quote_string [v.strftime(ISO8601_DATETIME), sprintf(USEC_SPRINTF, v.usec)].join('.')
|
20
|
+
end
|
21
|
+
|
22
|
+
def quote_ident(k)
|
23
|
+
DOUBLE_QUOTE + SQLite3::Database.quote(k.to_s) + DOUBLE_QUOTE
|
24
|
+
end
|
25
|
+
|
26
|
+
def quote_boolean(v)
|
27
|
+
s = v ? 't' : 'f'
|
28
|
+
quote_string s
|
29
|
+
end
|
30
|
+
|
31
|
+
def quote_big_decimal(v)
|
32
|
+
v.to_f
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/upsert/row.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
1
|
class Upsert
|
2
2
|
# @private
|
3
3
|
class Row
|
4
|
-
|
5
|
-
|
4
|
+
Cell = Struct.new(:quoted_key, :quoted_value)
|
5
|
+
|
6
6
|
attr_reader :selector
|
7
7
|
attr_reader :document
|
8
8
|
|
9
9
|
def initialize(parent, raw_selector, raw_document)
|
10
|
-
|
11
|
-
@raw_selector = raw_selector
|
10
|
+
c = parent.connection
|
12
11
|
@selector = raw_selector.inject({}) do |memo, (k, v)|
|
13
|
-
memo[
|
12
|
+
memo[k.to_s] = Cell.new(c.quote_ident(k), c.quote_value(v))
|
14
13
|
memo
|
15
14
|
end
|
16
15
|
@document = raw_document.inject({}) do |memo, (k, v)|
|
17
|
-
memo[
|
16
|
+
memo[k.to_s] = Cell.new(c.quote_ident(k), c.quote_value(v))
|
18
17
|
memo
|
19
18
|
end
|
20
19
|
end
|
@@ -24,41 +23,46 @@ class Upsert
|
|
24
23
|
end
|
25
24
|
|
26
25
|
def values_sql_bytesize
|
27
|
-
@values_sql_bytesize ||=
|
26
|
+
@values_sql_bytesize ||= quoted_pairs.inject(0) { |sum, (_, v)| sum + v.to_s.bytesize } + columns.length - 1
|
28
27
|
end
|
29
28
|
|
30
29
|
def values_sql
|
31
|
-
|
30
|
+
quoted_pairs.map { |_, v| v }.join(',')
|
32
31
|
end
|
33
32
|
|
34
33
|
def columns_sql
|
35
|
-
|
34
|
+
quoted_pairs.map { |k, _| k }.join(',')
|
36
35
|
end
|
37
36
|
|
38
37
|
def where_sql
|
39
|
-
selector.map { |
|
38
|
+
selector.map { |_, cell| [cell.quoted_key, cell.quoted_value].join('=') }.join(' AND ')
|
40
39
|
end
|
41
40
|
|
42
41
|
def set_sql
|
43
|
-
|
42
|
+
quoted_pairs.map { |k, v| [k, v].join('=') }.join(',')
|
44
43
|
end
|
45
44
|
|
46
|
-
def
|
47
|
-
|
48
|
-
|
49
|
-
# prefer the document so that you can change rows
|
50
|
-
document[k]
|
51
|
-
else
|
52
|
-
selector[k]
|
53
|
-
end
|
54
|
-
[ k, v ]
|
45
|
+
def quoted_value(k)
|
46
|
+
if c = cell(k)
|
47
|
+
c.quoted_value
|
55
48
|
end
|
56
49
|
end
|
57
50
|
|
58
|
-
def
|
59
|
-
@
|
60
|
-
|
61
|
-
|
51
|
+
def quoted_pairs
|
52
|
+
@quoted_pairs ||= columns.map do |k|
|
53
|
+
c = cell k
|
54
|
+
[ c.quoted_key, c.quoted_value ]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def cell(k)
|
61
|
+
if document.has_key?(k)
|
62
|
+
# prefer the document so that you can change rows
|
63
|
+
document[k]
|
64
|
+
else
|
65
|
+
selector[k]
|
62
66
|
end
|
63
67
|
end
|
64
68
|
end
|
data/lib/upsert/version.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'upsert/active_record_upsert'
|
4
|
+
|
5
|
+
describe Upsert do
|
6
|
+
describe 'the optional active_record extension' do
|
7
|
+
describe :upsert do
|
8
|
+
it "is easy to use" do
|
9
|
+
assert_creates(Pet,[{:name => 'Jerry', :good => true}]) do
|
10
|
+
Pet.upsert({:name => 'Jerry'}, :good => false)
|
11
|
+
Pet.upsert({:name => 'Jerry'}, :good => true)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/spec/binary_spec.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Upsert do
|
3
|
+
describe "supports binary upserts" do
|
4
|
+
before do
|
5
|
+
@fakes = []
|
6
|
+
10.times do
|
7
|
+
@fakes << [Faker::Name.name, Faker::Lorem.paragraphs(10).join("\n\n")]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
it "saves binary one by one" do
|
11
|
+
@fakes.each do |name, biography|
|
12
|
+
zipped_biography = Zlib::Deflate.deflate biography
|
13
|
+
upsert = Upsert.new $conn, :pets
|
14
|
+
assert_creates(Pet, [{:name => name, :zipped_biography => zipped_biography}]) do
|
15
|
+
upsert.row({:name => name}, {:zipped_biography => Upsert.binary(zipped_biography)})
|
16
|
+
end
|
17
|
+
Zlib::Inflate.inflate(Pet.find_by_name(name).zipped_biography).should == biography
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Upsert do
|
3
|
+
describe "is just as correct as other ways" do
|
4
|
+
describe 'compared to native ActiveRecord' do
|
5
|
+
it "is as correct as than new/set/save" do
|
6
|
+
assert_same_result lotsa_records do |records|
|
7
|
+
records.each do |selector, document|
|
8
|
+
if pet = Pet.where(selector).first
|
9
|
+
pet.update_attributes document, :without_protection => true
|
10
|
+
else
|
11
|
+
pet = Pet.new
|
12
|
+
selector.each do |k, v|
|
13
|
+
pet.send "#{k}=", v
|
14
|
+
end
|
15
|
+
document.each do |k, v|
|
16
|
+
pet.send "#{k}=", v
|
17
|
+
end
|
18
|
+
pet.save!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
it "is as correct as than find_or_create + update_attributes" do
|
24
|
+
assert_same_result lotsa_records do |records|
|
25
|
+
dynamic_method = nil
|
26
|
+
records.each do |selector, document|
|
27
|
+
dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
|
28
|
+
pet = Pet.send(dynamic_method, *selector.values)
|
29
|
+
pet.update_attributes document, :without_protection => true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
it "is as correct as than create + rescue/find/update" do
|
34
|
+
assert_same_result lotsa_records do |records|
|
35
|
+
dynamic_method = nil
|
36
|
+
records.each do |selector, document|
|
37
|
+
dynamic_method ||= "find_or_create_by_#{selector.keys.join('_or_')}"
|
38
|
+
begin
|
39
|
+
Pet.create selector.merge(document), :without_protection => true
|
40
|
+
rescue
|
41
|
+
pet = Pet.send(dynamic_method, *selector.values)
|
42
|
+
pet.update_attributes document, :without_protection => true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if ENV['ADAPTER'] == 'mysql2'
|
50
|
+
describe 'compared to activerecord-import' do
|
51
|
+
it "is as correct as faking upserts with activerecord-import" do
|
52
|
+
assert_same_result lotsa_records do |records|
|
53
|
+
columns = nil
|
54
|
+
all_values = []
|
55
|
+
records.each do |selector, document|
|
56
|
+
columns ||= (selector.keys + document.keys).uniq
|
57
|
+
all_values << columns.map do |k|
|
58
|
+
if document.has_key?(k)
|
59
|
+
# prefer the document so that you can change rows
|
60
|
+
document[k]
|
61
|
+
else
|
62
|
+
selector[k]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
Pet.import columns, all_values, :timestamps => false, :on_duplicate_key_update => columns
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
describe Upsert do
|
4
|
+
if ENV['ADAPTER'] == 'postgresql'
|
5
|
+
describe 'PostgreSQL database functions' do
|
6
|
+
it "re-uses merge functions across connections" do
|
7
|
+
begin
|
8
|
+
io = StringIO.new
|
9
|
+
old_logger = Upsert.logger
|
10
|
+
Upsert.logger = Logger.new io, Logger::INFO
|
11
|
+
|
12
|
+
# clear
|
13
|
+
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).buffer.clear_database_functions
|
14
|
+
|
15
|
+
# create
|
16
|
+
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
|
17
|
+
|
18
|
+
# clear
|
19
|
+
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).buffer.clear_database_functions
|
20
|
+
|
21
|
+
# create (#2)
|
22
|
+
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
|
23
|
+
|
24
|
+
# no create!
|
25
|
+
Upsert.new(PGconn.new(:dbname => 'upsert_test'), :pets).row :name => 'hello'
|
26
|
+
|
27
|
+
io.rewind
|
28
|
+
hits = io.read.split("\n").grep(/Creating or replacing/)
|
29
|
+
hits.length.should == 2
|
30
|
+
ensure
|
31
|
+
Upsert.logger = old_logger
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
describe Upsert do
|
3
|
+
describe "is a database with an upsert trick" do
|
4
|
+
describe :row do
|
5
|
+
it "works for a single row (base case)" do
|
6
|
+
upsert = Upsert.new $conn, :pets
|
7
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
8
|
+
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
it "works for complex selectors" do
|
12
|
+
upsert = Upsert.new $conn, :pets
|
13
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male', :tag_number => 4}]) do
|
14
|
+
upsert.row({:name => 'Jerry', :gender => 'male'}, {:tag_number => 1})
|
15
|
+
upsert.row({:name => 'Jerry', :gender => 'male'}, {:tag_number => 4})
|
16
|
+
end
|
17
|
+
end
|
18
|
+
it "doesn't nullify columns that are not included in the selector or document" do
|
19
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male', :tag_number => 4}]) do
|
20
|
+
one = Upsert.new $conn, :pets
|
21
|
+
one.row({:name => 'Jerry'}, {:gender => 'male'})
|
22
|
+
two = Upsert.new $conn, :pets
|
23
|
+
two.row({:name => 'Jerry'}, {:tag_number => 4})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
it "works for a single row (not changing anything)" do
|
27
|
+
upsert = Upsert.new $conn, :pets
|
28
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
29
|
+
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
30
|
+
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
it "works for a single row (changing something)" do
|
34
|
+
upsert = Upsert.new $conn, :pets
|
35
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'neutered'}]) do
|
36
|
+
upsert.row({:name => 'Jerry'}, {:gender => 'male'})
|
37
|
+
upsert.row({:name => 'Jerry'}, {:gender => 'neutered'})
|
38
|
+
end
|
39
|
+
Pet.where(:gender => 'male').count.should == 0
|
40
|
+
end
|
41
|
+
it "works for a single row with implicit nulls" do
|
42
|
+
upsert = Upsert.new $conn, :pets
|
43
|
+
assert_creates(Pet, [{:name => 'Inky', :gender => nil}]) do
|
44
|
+
upsert.row({:name => 'Inky'}, {})
|
45
|
+
upsert.row({:name => 'Inky'}, {})
|
46
|
+
end
|
47
|
+
end
|
48
|
+
it "works for a single row with empty document" do
|
49
|
+
upsert = Upsert.new $conn, :pets
|
50
|
+
assert_creates(Pet, [{:name => 'Inky', :gender => nil}]) do
|
51
|
+
upsert.row(:name => 'Inky')
|
52
|
+
upsert.row(:name => 'Inky')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
it "works for a single row with explicit nulls" do
|
56
|
+
upsert = Upsert.new $conn, :pets
|
57
|
+
assert_creates(Pet, [{:name => 'Inky', :gender => nil}]) do
|
58
|
+
upsert.row({:name => 'Inky'}, {:gender => nil})
|
59
|
+
upsert.row({:name => 'Inky'}, {:gender => nil})
|
60
|
+
end
|
61
|
+
end
|
62
|
+
it "works with ids" do
|
63
|
+
jerry = Pet.create :name => 'Jerry', :lovability => 1.0
|
64
|
+
upsert = Upsert.new $conn, :pets
|
65
|
+
assert_creates(Pet, [{:name => 'Jerry', :lovability => 2.0}]) do
|
66
|
+
upsert.row({:id => jerry.id}, :lovability => 2.0)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
describe :batch do
|
71
|
+
it "works for multiple rows (base case)" do
|
72
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
73
|
+
Upsert.batch($conn, :pets) do |upsert|
|
74
|
+
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
it "works for multiple rows (not changing anything)" do
|
79
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'male'}]) do
|
80
|
+
Upsert.batch($conn, :pets) do |upsert|
|
81
|
+
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
82
|
+
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
it "works for multiple rows (changing something)" do
|
87
|
+
assert_creates(Pet, [{:name => 'Jerry', :gender => 'neutered'}]) do
|
88
|
+
Upsert.batch($conn, :pets) do |upsert|
|
89
|
+
upsert.row({:name => 'Jerry'}, :gender => 'male')
|
90
|
+
upsert.row({:name => 'Jerry'}, :gender => 'neutered')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
Pet.where(:gender => 'male').count.should == 0
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|