pg_morph 0.3.0 → 1.0.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.
@@ -23,7 +23,9 @@ Gem::Specification.new do |spec|
23
23
  spec.add_development_dependency "rails", "~> 3"
24
24
  spec.add_development_dependency "pg", "~> 0.17"
25
25
 
26
- spec.add_development_dependency 'rspec-rails'
26
+ spec.add_development_dependency "codeclimate-test-reporter", "~> 0.4"
27
+ spec.add_development_dependency "rspec-rails", "~> 2.14"
28
+ spec.add_development_dependency "guard-rspec", "~> 4.2"
27
29
 
28
30
  spec.add_development_dependency "rake", "~> 10.3"
29
31
  spec.add_development_dependency "pry", "~> 0.10"
@@ -9,30 +9,27 @@
9
9
  # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
10
  # you'll amass, the slower it'll run and the greater likelihood for issues).
11
11
  #
12
- # It's strongly recommended that you check this file into your version control system.
12
+ # It's strongly recommended to check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(version: 20140401124727) do
14
+ ActiveRecord::Schema.define(:version => 20140401124727) do
15
15
 
16
- # These are extensions that must be enabled in order to support this database
17
- enable_extension "plpgsql"
18
-
19
- create_table "comments", force: true do |t|
16
+ create_table "comments", :force => true do |t|
20
17
  t.string "content"
21
- t.datetime "created_at"
22
- t.datetime "updated_at"
18
+ t.datetime "created_at", :null => false
19
+ t.datetime "updated_at", :null => false
23
20
  end
24
21
 
25
- create_table "likes", force: true do |t|
22
+ create_table "likes", :force => true do |t|
26
23
  t.string "likeable_type"
27
24
  t.integer "likeable_id"
28
- t.datetime "created_at"
29
- t.datetime "updated_at"
25
+ t.datetime "created_at", :null => false
26
+ t.datetime "updated_at", :null => false
30
27
  end
31
28
 
32
- create_table "posts", force: true do |t|
29
+ create_table "posts", :force => true do |t|
33
30
  t.string "content"
34
- t.datetime "created_at"
35
- t.datetime "updated_at"
31
+ t.datetime "created_at", :null => false
32
+ t.datetime "updated_at", :null => false
36
33
  end
37
34
 
38
35
  end
@@ -0,0 +1,263 @@
1
+ require 'spec_helper'
2
+
3
+ describe PgMorph::Adapter do
4
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
5
+ include PgMorph::Adapter
6
+
7
+ def pg_views(name = nil)
8
+ query(%Q{
9
+ SELECT viewname
10
+ FROM pg_views
11
+ WHERE schemaname = ANY (current_schemas(false))
12
+ }, 'SCHEMA').map { |row| row[0] }
13
+ end
14
+
15
+ def run(query)
16
+ ActiveRecord::Base.connection.select_value(query)
17
+ end
18
+ end
19
+
20
+ let(:comment) { Comment.create(content: 'comment') }
21
+
22
+ before do
23
+ @adapter = ActiveRecord::Base.connection
24
+ @comments_polymorphic = PgMorph::Polymorphic.new(:likes, :comments, column: :likeable)
25
+ @posts_polymorphic = PgMorph::Polymorphic.new(:likes, :posts, column: :likeable)
26
+ begin
27
+ Like.destroy_all
28
+ Comment.destroy_all
29
+ @adapter.remove_polymorphic_foreign_key(:likes, :comments, column: :likeable)
30
+ @adapter.remove_polymorphic_foreign_key(:likes, :posts, column: :likeable)
31
+ rescue
32
+ end
33
+ end
34
+
35
+ after do
36
+ begin
37
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
38
+ rescue
39
+ end
40
+ end
41
+
42
+ describe '#add_polymorphic_foreign_key' do
43
+ it 'renames base table' do
44
+ expect(@adapter.tables).to include "likes"
45
+ expect(@adapter.tables).not_to include "likes_base"
46
+
47
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
48
+
49
+ expect(@adapter.tables).not_to include "likes"
50
+ expect(@adapter.tables).to include "likes_base"
51
+ end
52
+
53
+ it 'creates base table view' do
54
+ expect(@adapter.pg_views).to be_empty
55
+ expect(@adapter.tables).to include "likes"
56
+
57
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
58
+
59
+ expect(@adapter.pg_views).to include "likes"
60
+ expect(@adapter.tables).not_to include "likes"
61
+ end
62
+
63
+ it 'creates proxy table' do
64
+ expect(@adapter.tables).not_to include "likes_comments"
65
+
66
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
67
+
68
+ expect(@adapter.tables).to include "likes_comments"
69
+ end
70
+
71
+ it 'creates before insert trigger fun' do
72
+ fun_name = @comments_polymorphic.before_insert_fun_name
73
+ expect(@adapter.run("select proname from pg_proc where proname = '#{fun_name}'")).
74
+ to be nil
75
+
76
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
77
+
78
+ expect(@adapter.run("select proname from pg_proc where proname = '#{fun_name}'")).
79
+ to eq fun_name
80
+ end
81
+
82
+ it 'creates before insert trigger' do
83
+ trigger_name = @comments_polymorphic.before_insert_trigger_name
84
+ expect(@adapter.query("SELECT * FROM pg_trigger WHERE tgname = '#{trigger_name}'")).
85
+ to be_empty
86
+
87
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
88
+
89
+ expect(@adapter.query("SELECT count(*) FROM pg_trigger WHERE tgname = '#{trigger_name}'")).
90
+ to eq [["1"]]
91
+ end
92
+ end
93
+
94
+ describe '#remove_polymorphic_foreign_key' do
95
+ before do
96
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
97
+ end
98
+
99
+ it 'removes before insert trigger' do
100
+ fun_name = @comments_polymorphic.before_insert_fun_name
101
+ expect(@adapter.run("select proname from pg_proc where proname = '#{fun_name}'")).
102
+ to eq fun_name
103
+
104
+ @adapter.remove_polymorphic_foreign_key(:likes, :comments, column: :likeable)
105
+
106
+ expect(@adapter.run("select proname from pg_proc where proname = '#{fun_name}'")).
107
+ to be nil
108
+ end
109
+
110
+ it 'removes before insert trigger fun' do
111
+ trigger_name = @comments_polymorphic.before_insert_trigger_name
112
+ expect(@adapter.query("SELECT count(*) FROM pg_trigger WHERE tgname = '#{trigger_name}'")).
113
+ to eq [["1"]]
114
+
115
+ @adapter.remove_polymorphic_foreign_key(:likes, :comments, column: :likeable)
116
+
117
+ expect(@adapter.query("SELECT * FROM pg_trigger WHERE tgname = '#{trigger_name}'")).
118
+ to be_empty
119
+ end
120
+
121
+ it 'removes proxy table' do
122
+ expect(@adapter.tables).to include "likes_comments"
123
+
124
+ @adapter.remove_polymorphic_foreign_key(:likes, :comments, column: :likeable)
125
+
126
+ expect(@adapter.tables).not_to include "likes_comments"
127
+ end
128
+
129
+ it 'prevents from removing proxy with data' do
130
+ Like.create(likeable: comment)
131
+
132
+ -> { @adapter.remove_polymorphic_foreign_key(:likes, :comments, column: :likeable) }
133
+ .should raise_error PG::Error
134
+ end
135
+
136
+ it 'removes table view if empty' do
137
+ expect(@adapter.pg_views).to include "likes"
138
+ expect(@adapter.tables).not_to include "likes"
139
+
140
+ @adapter.remove_polymorphic_foreign_key(:likes, :comments, column: :likeable)
141
+
142
+ expect(@adapter.pg_views).to be_empty
143
+ end
144
+
145
+ it 'renames base table to original name' do
146
+ expect(@adapter.pg_views).to include "likes"
147
+ expect(@adapter.tables).not_to include "likes"
148
+
149
+ @adapter.remove_polymorphic_foreign_key(:likes, :comments, column: :likeable)
150
+
151
+ expect(@adapter.tables).to include "likes"
152
+ end
153
+
154
+ context 'with more than one partitions' do
155
+ before do
156
+ @adapter.add_polymorphic_foreign_key(:likes, :posts, column: :likeable)
157
+ end
158
+
159
+ it 'does not rename base table to original name' do
160
+ expect(@adapter.pg_views).to include "likes"
161
+ expect(@adapter.tables).not_to include "likes"
162
+
163
+ @adapter.remove_polymorphic_foreign_key(:likes, :comments, column: :likeable)
164
+
165
+ expect(@adapter.tables).not_to include "likes"
166
+ expect(@adapter.pg_views).to include "likes"
167
+ end
168
+ end
169
+ end
170
+
171
+ describe 'operations on a partition' do
172
+ let(:comment) { Comment.create(content: 'comment') }
173
+ let(:post) { Post.create(content: 'content') }
174
+
175
+ before do
176
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
177
+ @comment_like = Like.create(likeable: comment)
178
+ end
179
+
180
+ context "creating records" do
181
+ it "works with single partition" do
182
+ expect(Like.count).to eq(1)
183
+ expect(@comment_like.id).to eq(Like.last.id)
184
+ end
185
+
186
+ it "works with multiple partitions" do
187
+ @adapter.add_polymorphic_foreign_key(:likes, :posts, column: :likeable)
188
+ post_like = Like.create(likeable: post)
189
+
190
+ expect(Like.count).to eq(2)
191
+ expect(post_like.id).to eq(Like.last.id)
192
+ end
193
+
194
+ it "raises error for a missing partition" do
195
+ -> { Like.create(likeable: post) }
196
+ .should raise_error ActiveRecord::StatementInvalid
197
+ end
198
+
199
+ it "works if no partitions" do
200
+ @comment_like.destroy
201
+ expect(Like.count).to eq(0)
202
+ @adapter.remove_polymorphic_foreign_key(:likes, :comments, column: :likeable)
203
+ like = Like.create(likeable: post)
204
+
205
+ expect(Like.count).to eq(1)
206
+ expect(like.id).to eq(Like.last.id)
207
+ end
208
+ end
209
+
210
+ context "updating records" do
211
+ let(:another_comment) { Comment.create(content: 'comment') }
212
+
213
+ before do
214
+ @adapter.add_polymorphic_foreign_key(:likes, :posts, column: :likeable)
215
+ end
216
+
217
+ it 'works within one partition' do
218
+ expect(@comment_like.likeable).to eq(comment)
219
+
220
+ @comment_like.likeable = another_comment
221
+ @comment_like.save
222
+
223
+ @comment_like.reload
224
+ expect(@comment_like.likeable).to eq(another_comment)
225
+ end
226
+
227
+ it 'does not allow to change associated type' do
228
+ expect(@comment_like.likeable).to eq(comment)
229
+
230
+ @comment_like.likeable = post
231
+ expect { @comment_like.save }.to raise_error ActiveRecord::StatementInvalid
232
+ end
233
+ end
234
+
235
+ context "deleting records" do
236
+ before do
237
+ expect(@adapter.run("SELECT id from likes where id = #{@comment_like.id}"))
238
+ .to eq @comment_like.id.to_s
239
+ expect(@adapter.run("SELECT id from likes_comments where id = #{@comment_like.id}"))
240
+ .to eq @comment_like.id.to_s
241
+ @comment_like.destroy
242
+ end
243
+
244
+ it "works on a partition" do
245
+ expect(@adapter.run("SELECT id from likes where id = #{@comment_like.id}")).to eq nil
246
+ expect(@adapter.run("SELECT id from likes_comments where id = #{@comment_like.id}")).to eq nil
247
+ end
248
+
249
+ context "after removing paritions" do
250
+ before do
251
+ @adapter.remove_polymorphic_foreign_key(:likes, :comments, column: :likeable)
252
+ @like = Like.create(likeable: comment)
253
+ end
254
+
255
+ it "works on a master table" do
256
+ @like.destroy
257
+ expect(@adapter.run("SELECT id from likes where id = #{@comment_like.id}")).to eq nil
258
+ end
259
+ end
260
+ end
261
+ end
262
+
263
+ end
@@ -3,15 +3,6 @@ require 'spec_helper'
3
3
  describe PgMorph::Adapter do
4
4
  class FakeAdapter
5
5
  include PgMorph::Adapter
6
-
7
- def execute(sql, name = nil)
8
- sql_statements << sql
9
- sql
10
- end
11
-
12
- def sql_statements
13
- @sql_statements || []
14
- end
15
6
  end
16
7
 
17
8
  before do
@@ -22,8 +22,4 @@ describe PgMorph::Naming do
22
22
 
23
23
  it { expect(@fake.before_insert_trigger_name).to eq('foos_baz_insert_trigger') }
24
24
 
25
- it { expect(@fake.after_insert_fun_name).to eq('delete_from_foos_master_fun') }
26
-
27
- it { expect(@fake.after_insert_trigger_name).to eq('foos_after_insert_trigger') }
28
-
29
25
  end
@@ -0,0 +1,125 @@
1
+ require 'spec_helper'
2
+
3
+ describe PgMorph::Polymorphic do
4
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
5
+ include PgMorph::Adapter
6
+
7
+ def run(query)
8
+ ActiveRecord::Base.connection.select_value(query)
9
+ end
10
+ end
11
+
12
+ before do
13
+ @adapter = ActiveRecord::Base.connection
14
+ @comments_polymorphic = PgMorph::Polymorphic.new(:likes, :comments, column: :likeable)
15
+ @posts_polymorphic = PgMorph::Polymorphic.new(:likes, :posts, column: :likeable)
16
+ begin
17
+ Like.destroy_all
18
+ Comment.destroy_all
19
+ @adapter.remove_polymorphic_foreign_key(:likes, :comments, column: :likeable)
20
+ rescue
21
+ end
22
+ end
23
+
24
+ describe '#can_rename_to_base_table?' do
25
+ it 'returns true if base table does not exist' do
26
+ @adapter.stub(:table_exists?).with(@comments_polymorphic.base_table)
27
+ .and_return(false)
28
+
29
+ expect(@comments_polymorphic.can_rename_to_base_table?)
30
+ .to be_true
31
+ end
32
+
33
+ it 'returns false if there is compatible base table' do
34
+ @adapter.add_polymorphic_foreign_key(:likes, :posts, column: :likeable)
35
+
36
+ expect(@comments_polymorphic.can_rename_to_base_table?)
37
+ .to be_false
38
+ end
39
+
40
+ it 'raises an exception if existing base table is not compatible table' do
41
+ @adapter.create_table(@comments_polymorphic.base_table)
42
+
43
+ expect { @comments_polymorphic.can_rename_to_base_table? }.
44
+ to raise_error PgMorph::Exception
45
+ end
46
+ end
47
+
48
+ describe '#create_trigger_body' do
49
+ before do
50
+ @adapter.stub(:raise_unless_postgres)
51
+ end
52
+
53
+ it 'raises error for updating trigger with duplicated partition' do
54
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
55
+
56
+ expect { @comments_polymorphic.send(:create_trigger_body) }
57
+ .to raise_error PG::Error
58
+ end
59
+
60
+ it 'updates trigger with new partition' do
61
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
62
+
63
+ expect(@posts_polymorphic.send(:create_trigger_body).squeeze(' ')).to eq %Q{
64
+ IF (NEW.likeable_type = 'Comment') THEN
65
+ INSERT INTO likes_comments VALUES (NEW.*);
66
+ ELSIF (NEW.likeable_type = 'Post') THEN
67
+ INSERT INTO likes_posts VALUES (NEW.*);
68
+ }.squeeze(' ')
69
+ end
70
+ end
71
+
72
+ describe '#create_before_insert_trigger_sql' do
73
+ it 'returns sql' do
74
+ expect(@comments_polymorphic.create_before_insert_trigger_sql.squeeze(' ')).to eq %Q{
75
+ DROP TRIGGER IF EXISTS likes_likeable_insert_trigger ON likes;
76
+ CREATE TRIGGER likes_likeable_insert_trigger
77
+ INSTEAD OF INSERT ON likes
78
+ FOR EACH ROW EXECUTE PROCEDURE likes_likeable_fun();
79
+ }.squeeze(' ')
80
+ end
81
+ end
82
+
83
+ describe '#remove_before_insert_trigger_sql' do
84
+ it 'returns proper sql if more partitions' do
85
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
86
+ @adapter.add_polymorphic_foreign_key(:likes, :posts, column: :likeable)
87
+
88
+ expect(@comments_polymorphic.remove_before_insert_trigger_sql.squeeze(' ')).to eq %Q{
89
+ CREATE OR REPLACE FUNCTION likes_likeable_fun() RETURNS TRIGGER AS $$
90
+ BEGIN
91
+ IF (NEW.#{@posts_polymorphic.column_name}_type = 'Post') THEN
92
+ INSERT INTO #{@posts_polymorphic.parent_table}_#{@posts_polymorphic.child_table} VALUES (NEW.*);
93
+ ELSE
94
+ RAISE EXCEPTION 'Wrong "#{@posts_polymorphic.column_name}_type"="%" used. Create proper partition table and update likes_likeable_fun function', NEW.likeable_type;
95
+ END IF;
96
+ RETURN NEW;
97
+ END; $$ LANGUAGE plpgsql;
98
+ }.squeeze(' ')
99
+ end
100
+ end
101
+
102
+ describe '#remove_proxy_table' do
103
+ it 'returns sql' do
104
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
105
+
106
+ expect(@comments_polymorphic.remove_proxy_table.squeeze(' ')).to eq %Q{ DROP TABLE IF EXISTS likes_comments; }
107
+ end
108
+ end
109
+
110
+ describe '#remove_base_table_view_sql' do
111
+ it 'returns proper sql if no more partitions' do
112
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
113
+
114
+ expect(@comments_polymorphic.remove_base_table_view_sql.squeeze(' ')).to eq %Q{ DROP VIEW likes; }
115
+ end
116
+
117
+ it 'returns empty string if there are more partitions' do
118
+ @adapter.add_polymorphic_foreign_key(:likes, :comments, column: :likeable)
119
+ @adapter.add_polymorphic_foreign_key(:likes, :posts, column: :likeable)
120
+
121
+ expect(@comments_polymorphic.remove_base_table_view_sql.squeeze(' ')).to eq ''
122
+ end
123
+ end
124
+
125
+ end