simple-sql 0.3.1 → 0.3.2

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.
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