simple-sql 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e55274f31115f6ee480e24a6d539b0709dc9328
4
- data.tar.gz: 8756486ff1f0d4f6c71e65d8f0cc945a45cfc73b
3
+ metadata.gz: 8f45fc27ee92d2023cdcd3eb2b5a0cfbd1a05672
4
+ data.tar.gz: 8036ee0f56cd039cf1555f8f3fbe323dbaf76a09
5
5
  SHA512:
6
- metadata.gz: cefcba8027cc317ae3fbcced4ccbf30a6ebc24b29525b0601231d6db6eb8f32df6d7283b54824c55d235816d9861ca055ca2088d946a209d1c40c27e5d73d34e
7
- data.tar.gz: 4d5b0b7303076914e8cfe1a154442ffdbb4e2eed2e77e1776a714604a76f0d30308320ae133483a7e276c60e263dc72abe419eccf33e534e8396266a855eaafb
6
+ metadata.gz: fcef39ebaa97f2807ea6812257b22775d70b3ac21620bdfcf508e4a06340c4142791d7bb6a93d7a17a1c40950236aa3431691afe7c3fcd4072cd1b78d199e1fe
7
+ data.tar.gz: af8c3af9c817824a76e89c2ed5e7cb9856ad87a696da7e87c26c5cad8a0b9d48d5df92841484e8c27a33a48152069be9a22a0e4269ae7bfb8043a00938cac8d5
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- simple-sql (0.3.1)
4
+ simple-sql (0.3.2)
5
5
  pg (~> 0.20)
6
6
  pg_array_parser (~> 0)
7
7
 
@@ -1,41 +1,89 @@
1
- # rubocop:disable Metrics/MethodLength
2
- # rubocop:disable Metrics/AbcSize
1
+ # rubocop:disable Style/StructInheritance
3
2
 
4
3
  module Simple
5
4
  module SQL
6
- def duplicate(table, ids, except: [])
5
+ class Fragment < Struct.new(:to_sql)
6
+ end
7
+
8
+ def fragment(str)
9
+ Fragment.new(str)
10
+ end
11
+
12
+ #
13
+ # Creates duplicates of record in a table.
14
+ #
15
+ # This method handles timestamp columns (these will be set to the current
16
+ # time) and primary keys (will be set to NULL.) You can pass in overrides
17
+ # as a third argument for specific columns.
18
+ #
19
+ # Parameters:
20
+ #
21
+ # - ids: (Integer, Array<Integer>) primary key ids
22
+ # - overrides: Hash[column_names => SQL::Fragment]
23
+ #
24
+ def duplicate(table, ids, overrides = {})
7
25
  ids = Array(ids)
8
26
  return [] if ids.empty?
9
27
 
10
- timestamp_columns = Reflection.timestamp_columns(table)
11
- primary_key_columns = Reflection.primary_key_columns(table)
28
+ Duplicator.new(table, overrides).call(ids)
29
+ end
30
+
31
+ class Duplicator
32
+ attr_reader :table_name, :custom_overrides
33
+
34
+ def initialize(table_name, overrides)
35
+ @table_name = table_name
36
+ @custom_overrides = validated_overrides(overrides)
37
+ end
38
+
39
+ def call(ids)
40
+ Simple::SQL.all query, ids
41
+ rescue PG::UndefinedColumn => e
42
+ raise ArgumentError, e.message
43
+ end
44
+
45
+ private
46
+
47
+ # This stringify all keys of the overrides hash, and verifies that
48
+ # all values in there are SQL::Fragments
49
+ def validated_overrides(overrides)
50
+ overrides.inject({}) do |hsh, (key, value)|
51
+ raise ArgumentError, "Unknown value #{value.inspect}" unless value.is_a?(Fragment)
52
+ hsh.update key.to_s => value.to_sql
53
+ end
54
+ end
12
55
 
13
- # duplicate all columns in the table that need to be SELECTed.
14
- #
15
- columns_to_dupe = Reflection.columns(table)
56
+ def timestamp_overrides
57
+ Reflection.timestamp_columns(table_name).inject({}) do |hsh, column|
58
+ hsh.update column => "now() AS #{column}"
59
+ end
60
+ end
16
61
 
17
- # Primary keys will not be selected from the table, they should be set
18
- # automatically by the database, via a DEFAULT role on the column.
19
- columns_to_dupe -= primary_key_columns
62
+ def copy_columns
63
+ (Reflection.columns(table_name) - Reflection.primary_key_columns(table_name)).inject({}) do |hsh, column|
64
+ hsh.update column => column
65
+ end
66
+ end
20
67
 
21
- # timestamp_columns will not be selected from the table, they will be
22
- # set to now() explicitely.
23
- columns_to_dupe -= timestamp_columns
68
+ # build SQL query
69
+ def query
70
+ sources = {}
24
71
 
25
- # If some other columns must be excluded they have to be added in the
26
- # except: keyword argument. This is helpful for UNIQUE columns, but
27
- # a column which is supposed to be UNIQUE and NOT NULL can not be dealt
28
- # with.
29
- columns_to_dupe -= except.map(&:to_s)
72
+ sources.update copy_columns
73
+ sources.update timestamp_overrides
74
+ sources.update custom_overrides
30
75
 
31
- # build query
32
- select_columns = columns_to_dupe + timestamp_columns
33
- select_values = columns_to_dupe + timestamp_columns.map { |col| "now() AS #{col}" }
76
+ # convert into an Array, to make sure that keys and values aka firsts
77
+ # and lasts are always in the correct order.
78
+ sources = sources.to_a
34
79
 
35
- Simple::SQL.all <<~SQL, ids
36
- INSERT INTO #{table}(#{select_columns.join(', ')})
37
- SELECT #{select_values.join(', ')} FROM #{table} WHERE id = ANY($1) RETURNING id
38
- SQL
80
+ <<~SQL
81
+ INSERT INTO #{table_name}(#{sources.map(&:first).join(', ')})
82
+ SELECT #{sources.map(&:last).join(', ')}
83
+ FROM #{table_name}
84
+ WHERE id = ANY($1) RETURNING id
85
+ SQL
86
+ end
39
87
  end
40
88
  end
41
89
  end
@@ -1,5 +1,5 @@
1
1
  module Simple
2
2
  module SQL
3
- VERSION = "0.3.1"
3
+ VERSION = "0.3.2"
4
4
  end
5
5
  end
@@ -10,24 +10,24 @@ describe "Simple::SQL.duplicate" do
10
10
  end
11
11
 
12
12
  it "does not fail on a non-existing user" do
13
- dupe_ids = SQL.duplicate "users", source_ids.first
13
+ dupe_ids = SQL.duplicate "users", -1
14
14
 
15
- expect(dupe_ids.length).to eq(1)
16
- expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(3)
15
+ expect(dupe_ids.length).to eq(0)
16
+ expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(USER_COUNT)
17
17
  end
18
18
 
19
19
  it "duplicates a single user" do
20
20
  dupe_ids = SQL.duplicate "users", source_ids.first
21
21
 
22
22
  expect(dupe_ids.length).to eq(1)
23
- expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(3)
23
+ expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(1 + USER_COUNT)
24
24
  end
25
25
 
26
26
  it "duplicates many users" do
27
27
  dupe_ids = SQL.duplicate "users", (source_ids + [ -10 ])
28
28
 
29
29
  expect(dupe_ids.length).to eq(2)
30
- expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(4)
30
+ expect(SQL.ask("SELECT COUNT(*) FROM users")).to eq(2 + USER_COUNT)
31
31
  end
32
32
 
33
33
  it "updates the timestamp columns" do
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+
3
+ describe "Simple::SQL.duplicate/unique indices" do
4
+ let!(:unique_users) { 1.upto(USER_COUNT).map { create(:unique_user) } }
5
+
6
+ let!(:source_ids) { SQL.all("SELECT id FROM unique_users") }
7
+
8
+ it "cannot duplicate unique_user" do
9
+ expect {
10
+ SQL.duplicate "unique_users", source_ids
11
+ }.to raise_error(PG::UniqueViolation)
12
+ end
13
+
14
+ it "raises an ArgumentError when called with unknown columns" do
15
+ expect {
16
+ SQL.duplicate "unique_users", source_ids, foo: SQL.fragment("baz")
17
+ }.to raise_error(ArgumentError)
18
+ end
19
+
20
+ it "raises an ArgumentError when called with invalid overrides" do
21
+ expect {
22
+ SQL.duplicate "unique_users", source_ids, first_name: "I am invalid"
23
+ }.to raise_error(ArgumentError)
24
+ end
25
+
26
+ it "duplicates unique_users" do
27
+ overrides = {
28
+ first_name: SQL.fragment("first_name || '.' || id"),
29
+ last_name: SQL.fragment("last_name || '.' || id")
30
+ }
31
+
32
+ dupe_ids = SQL.duplicate "unique_users", source_ids, overrides
33
+
34
+ expect(dupe_ids.length).to eq(2)
35
+ expect(SQL.ask("SELECT COUNT(*) FROM unique_users")).to eq(4)
36
+ end
37
+ end
@@ -15,11 +15,6 @@ describe "Simple::SQL.insert" do
15
15
  expect(user.first_name).to eq("foo")
16
16
  expect(user.last_name).to eq("bar")
17
17
  expect(user.created_at).to be_a(Time)
18
-
19
- #
20
- # r = SQL.record("SELECT COUNT(*) AS count FROM users")
21
- # r = SQL.record("SELECT COUNT(*) AS count FROM users")
22
- # expect(r).to eq({count: 2})
23
18
  end
24
19
  end
25
20
 
@@ -35,4 +35,13 @@ ActiveRecord::Schema.define do
35
35
 
36
36
  t.timestamps null: true
37
37
  end
38
+
39
+ create_table :unique_users, force: true do |t|
40
+ t.string :first_name
41
+ t.string :last_name
42
+ end
43
+
44
+ execute <<-SQL
45
+ CREATE UNIQUE INDEX unique_users_ix1 ON unique_users(first_name, last_name)
46
+ SQL
38
47
  end
@@ -1,8 +1,14 @@
1
1
  FactoryGirl.define do
2
2
  factory :user do
3
3
  role_id 123
4
- first_name "Uni"
5
- last_name "Corn"
4
+
5
+ sequence :first_name do |n| "First #{n}" end
6
+ sequence :last_name do |n| "Last #{n}" end
6
7
  access_level "viewable"
7
8
  end
9
+
10
+ factory :unique_user do
11
+ sequence :first_name do |n| "First #{n}" end
12
+ sequence :last_name do |n| "Last #{n}" end
13
+ end
8
14
  end
@@ -1,2 +1,5 @@
1
1
  class User < ActiveRecord::Base
2
2
  end
3
+
4
+ class UniqueUser < ActiveRecord::Base
5
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple-sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - radiospiel
@@ -198,6 +198,7 @@ files:
198
198
  - spec/simple/sql_config_spec.rb
199
199
  - spec/simple/sql_conversion_spec.rb
200
200
  - spec/simple/sql_duplicate_spec.rb
201
+ - spec/simple/sql_duplicate_unique_spec.rb
201
202
  - spec/simple/sql_insert_spec.rb
202
203
  - spec/simple/sql_record_spec.rb
203
204
  - spec/simple/sql_reflection_spec.rb
@@ -238,6 +239,7 @@ test_files:
238
239
  - spec/simple/sql_config_spec.rb
239
240
  - spec/simple/sql_conversion_spec.rb
240
241
  - spec/simple/sql_duplicate_spec.rb
242
+ - spec/simple/sql_duplicate_unique_spec.rb
241
243
  - spec/simple/sql_insert_spec.rb
242
244
  - spec/simple/sql_record_spec.rb
243
245
  - spec/simple/sql_reflection_spec.rb