updateable_views_inheritance 1.4.4 → 1.4.6

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
  SHA256:
3
- metadata.gz: 5a592cb214e5825bb4af87c5412ae3f91730d0e3882931f71bd16378ace7640a
4
- data.tar.gz: 2fbf14052dde92e69724d574c0b5fb7b4d79297928220bc8bee5851cc8ee60ad
3
+ metadata.gz: 1c36199deb17d6a32a617a2193c52ef0cb5e6b955464fb31b8509eb4aef0212d
4
+ data.tar.gz: 62286805f012a6db4bbc44de4a3ac9848f735d7d0e90d674f7605cbd48c590da
5
5
  SHA512:
6
- metadata.gz: b4da55b7632575a0fba84248130308890f56dd726e6aaec835d3423b874a959d88ecf51dbf482795ba97869e5255411b9599b0117df61c6e70b3f095322c23fe
7
- data.tar.gz: edd29da257c91e7d8a53df032bc70dd2317c78433637649e23285118b058109116259ed22efc86b8495f44301c5c6831c125e26565c1aa203ad00663205c8f6c
6
+ metadata.gz: a533be494b122a7b0e9bf2fcebd0083b314892cfe6da865faed721bd7cd5859820bc1eb969f098cfbb43ef4ec6d762521dbd9b911e57444fd286bdfa85540911
7
+ data.tar.gz: 45cd523f6e1430c3638ce7df73aabc87e09246a51d6b22591eea762c4d191fc7114c3f9a3f7d358dadf808c09844b8d5fa6a5f7480b45e61158f91796d1b8bf0
data/CHANGELOG.md CHANGED
@@ -1,4 +1,10 @@
1
- ## 1.4.4 (09 October 2024)
1
+ ## 1.4.6 (22 October 2024)
2
+
3
+ Features:
4
+
5
+ - Add option for child primary key.
6
+
7
+ ## 1.4.5 (09 October 2024)
2
8
 
3
9
  Bugfixes:
4
10
 
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Class Table Inheritance
2
2
 
3
3
  [![Build](https://github.com/tutuf/updateable_views_inheritance/actions/workflows/build.yml/badge.svg)](https://github.com/tutuf/updateable_views_inheritance/actions?query=workflow:build)
4
- [![Coverage](https://app.deepsource.com/gh/tutuf/updateable_views_inheritance.svg/?label=code+coverage&show_trend=true&token=AMfm8-_-qDZoknMh9-8IYp3R)](https://app.deepsource.com/gh/tutuf/updateable_views_inheritance/)
4
+ [![Codecov](https://codecov.io/gh/tutuf/updateable_views_inheritance/graph/badge.svg?token=L8P5LYNNU4)](https://codecov.io/gh/tutuf/updateable_views_inheritance)
5
+ [![DeepSource](https://app.deepsource.com/gh/tutuf/updateable_views_inheritance.svg/?label=active+issues&show_trend=true&token=AMfm8-_-qDZoknMh9-8IYp3R)](https://app.deepsource.com/gh/tutuf/updateable_views_inheritance/)
5
6
 
6
7
  Class Table Inheritance for ActiveRecord using updateable views
7
8
 
@@ -138,6 +139,31 @@ end
138
139
  ```
139
140
  Useful when converting legacy DB schema to use inheritance.
140
141
 
142
+ ### Using view as a child table
143
+
144
+ ```ruby
145
+ execute <<-SQL.squish
146
+ CREATE VIEW punk_locomotives_data AS (
147
+ SELECT steam_locomotives.id,
148
+ steam_locomotives.coal_consumption AS coal,
149
+ NULL AS electro
150
+ FROM steam_locomotives
151
+ UNION ALL
152
+ SELECT electric_locomotives.id,
153
+ NULL AS coal,
154
+ electric_locomotives.electricity_consumption AS electro
155
+ FROM electric_locomotives)
156
+ SQL
157
+ create_child(:punk_locomotives,
158
+ { parent: :locomotives,
159
+ child_table: :punk_locomotives_data,
160
+ child_table_pk: :id,
161
+ skip_creating_child_table: true })
162
+ ```
163
+ Views in PostgreSQL cannot have primary keys, so you have to manually specify it
164
+ when you use. Note that views also cannot have `NOT NULL` constraints, although
165
+ the `NOT NULL` constraint of the underlying table will still be enforced.
166
+
141
167
  ## Compatibility with Single Table Inheritance
142
168
 
143
169
  The approach of this gem is completely independent from Rails built-in Single
@@ -7,11 +7,16 @@ module ActiveRecord #:nodoc:
7
7
  # Use this in migration to create child table and view.
8
8
  # Options:
9
9
  # [:parent]
10
- # parent relation
10
+ # Parent relation
11
11
  # [:table]
12
- # default is <tt>"#{child_view}_data"</tt>
12
+ # Deprecated. Use :child_table instead
13
+ # [:child_table]
14
+ # Default is <tt>"#{child_view}_data"</tt>
15
+ # [:child_table_pk]
16
+ # Handy when :child_table is a view and PK cannot be inferred
17
+ # from the database.
13
18
  # [:skip_creating_child_table]
14
- # use together with :table option
19
+ # When given, :child_table option also must be specified
15
20
  def create_child(child_view, options)
16
21
  raise 'Please call me with a parent, for example: create_child(:steam_locomotives, :parent => :locomotives)' unless options[:parent]
17
22
 
@@ -26,7 +31,7 @@ module ActiveRecord #:nodoc:
26
31
  parent_relation
27
32
  end
28
33
 
29
- child_table = options[:table] || quote_table_name("#{child_view}_data")
34
+ child_table = options[:child_table] || options[:table] || quote_table_name("#{child_view}_data")
30
35
 
31
36
  unless options.key?(:skip_creating_child_table)
32
37
  unqualified_child_view_name = Utils.extract_schema_qualified_name(child_view).identifier
@@ -41,7 +46,9 @@ module ActiveRecord #:nodoc:
41
46
  REFERENCES #{parent_table} ON DELETE CASCADE ON UPDATE CASCADE"
42
47
  end
43
48
 
44
- create_child_view(parent_relation, child_view, child_table)
49
+ child_table_pk ||= options[:child_table_pk].to_s if options[:child_table_pk]
50
+
51
+ create_child_view(parent_relation, child_view, child_table, child_table_pk)
45
52
  end
46
53
 
47
54
  # Drop child view and table
@@ -54,7 +61,7 @@ module ActiveRecord #:nodoc:
54
61
 
55
62
  # Creates aggregate updateable view of parent and child relations. The convention for naming child tables is
56
63
  # <tt>"#{child_view}_data"</tt>. If you don't follow it, supply +child_table_name+ as third argument.
57
- def create_child_view(parent_table, child_view, child_table=nil)
64
+ def create_child_view(parent_table, child_view, child_table=nil, child_table_pk=nil)
58
65
  child_table ||= child_view.to_s + "_data"
59
66
 
60
67
  parent_columns = columns(parent_table)
@@ -63,7 +70,7 @@ module ActiveRecord #:nodoc:
63
70
  child_column_names = child_columns.collect{|c| c.name}
64
71
  parent_column_names = parent_columns.collect{|c| c.name}
65
72
 
66
- child_pk = pk_and_sequence_for(child_table)[0]
73
+ child_pk = child_table_pk || pk_and_sequence_for(child_table)[0]
67
74
  child_column_names.delete(child_pk)
68
75
 
69
76
  parent_pk, parent_pk_seq = pk_and_sequence_for(parent_table)
@@ -106,10 +113,12 @@ module ActiveRecord #:nodoc:
106
113
  res && res.first
107
114
  end
108
115
 
109
- # Returns a relation's primary key and belonging sequence. If +relation+ is a table the result is its PK and sequence.
110
- # When it is a view, PK and sequence of the table at the root of the inheritance chain are returned.
116
+ # Returns a relation's primary key and belonging sequence.
117
+ # If +relation+ is a table the result is its PK and sequence.
118
+ # When it is a view, PK and sequence of the table at the root
119
+ # of the inheritance chain are returned.
111
120
  def pk_and_sequence_for(relation)
112
- result = query(<<-SQL, 'PK')[0]
121
+ result = query(<<-SQL.squish, 'PK')[0]
113
122
  SELECT attr.attname
114
123
  FROM pg_attribute attr,
115
124
  pg_constraint cons
@@ -118,15 +127,13 @@ module ActiveRecord #:nodoc:
118
127
  AND cons.contype = 'p'
119
128
  AND attr.attnum = ANY(cons.conkey)
120
129
  SQL
121
- if result.nil? or result.empty?
130
+ if result.blank? #result.empty?
122
131
  parent = parent_table(relation)
123
132
  pk_and_sequence_for(parent) if parent
124
133
  else
125
134
  # log(result[0], "PK for #{relation}") {}
126
135
  [result[0], query("SELECT pg_get_serial_sequence('#{relation}', '#{result[0]}') ")[0][0]]
127
136
  end
128
- rescue
129
- nil
130
137
  end
131
138
 
132
139
  # Drops a view from the database.
@@ -305,7 +312,7 @@ module ActiveRecord #:nodoc:
305
312
  def do_create_child_view(parent_table, parent_columns, parent_pk, child_view, child_columns, child_pk, child_table)
306
313
  view_columns = parent_columns + child_columns
307
314
  execute(<<~SQL)
308
- CREATE OR REPLACE VIEW #{quote_column_name(child_view)} AS (
315
+ CREATE OR REPLACE VIEW #{quote_table_name(child_view)} AS (
309
316
  SELECT parent.#{parent_pk},
310
317
  #{ view_columns.map { |col| quote_column_name(col) }.join(",") }
311
318
  FROM #{parent_table} parent
@@ -322,7 +329,7 @@ module ActiveRecord #:nodoc:
322
329
  # Setting the sequence to its value (explicitly supplied or the default) covers both cases.
323
330
  execute(<<~SQL)
324
331
  CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_insert")} AS
325
- ON INSERT TO #{quote_column_name(child_view)} DO INSTEAD (
332
+ ON INSERT TO #{quote_table_name(child_view)} DO INSTEAD (
326
333
  INSERT INTO #{parent_table}
327
334
  ( #{ [parent_pk, parent_columns].flatten.map { |col| quote_column_name(col) }.join(", ") } )
328
335
  VALUES( DEFAULT #{ parent_columns.empty? ? '' : ' ,' + parent_columns.collect{ |col| "NEW.#{quote_column_name(col)}" }.join(", ") } ) ;
@@ -336,14 +343,14 @@ module ActiveRecord #:nodoc:
336
343
  # delete
337
344
  execute(<<~SQL)
338
345
  CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_delete")} AS
339
- ON DELETE TO #{quote_column_name(child_view)} DO INSTEAD
346
+ ON DELETE TO #{quote_table_name(child_view)} DO INSTEAD
340
347
  DELETE FROM #{parent_table} WHERE #{parent_pk} = OLD.#{parent_pk}
341
348
  SQL
342
349
 
343
350
  # update
344
351
  execute(<<~SQL)
345
352
  CREATE OR REPLACE RULE #{quote_column_name("#{child_view}_update")} AS
346
- ON UPDATE TO #{quote_column_name(child_view)} DO INSTEAD (
353
+ ON UPDATE TO #{quote_table_name(child_view)} DO INSTEAD (
347
354
  #{ if parent_columns.empty?
348
355
  ''
349
356
  else
@@ -364,9 +371,9 @@ module ActiveRecord #:nodoc:
364
371
 
365
372
  def insert_returning_clause(parent_pk, child_pk, child_view)
366
373
  columns_cast_to_null = columns(child_view)
367
- .reject { |c| c.name == parent_pk}
368
- .map { |c| "CAST (NULL AS #{c.sql_type})" }
369
- .join(", ")
374
+ .reject { |c| c.name == parent_pk }
375
+ .map { |c| "CAST (NULL AS #{c.sql_type})" }
376
+ .join(", ")
370
377
  "RETURNING #{child_pk}, #{columns_cast_to_null}"
371
378
  end
372
379
 
@@ -1,3 +1,3 @@
1
1
  module UpdateableViewsInheritance
2
- VERSION = "1.4.4"
2
+ VERSION = "1.4.6"
3
3
  end
@@ -5,15 +5,15 @@ class CreateWithDefaultTable < ActiveRecord::Migration
5
5
  t.column :max_speed, :integer
6
6
  t.column :type, :string
7
7
  end
8
-
9
- create_child(:steam_locomotives, :parent => :locomotives) do |t|
10
- t.decimal :water_consumption, :precision => 6, :scale => 2
11
- t.decimal :coal_consumption, :precision => 6, :scale => 2
8
+
9
+ create_child(:steam_locomotives, parent: :locomotives) do |t|
10
+ t.decimal :water_consumption, precision: 6, scale: 2, null: false
11
+ t.decimal :coal_consumption, precision: 6, scale: 2, null: false
12
12
  end
13
13
  end
14
-
14
+
15
15
  def self.down
16
16
  drop_child :steam_locomotives
17
17
  drop_table :locomotives
18
18
  end
19
- end
19
+ end
data/test/schema_test.rb CHANGED
@@ -80,6 +80,10 @@ class UpdateableViewsInheritanceSchemaTest < ActiveSupport::TestCase
80
80
  assert RackLocomotive.new.narrow_gauge
81
81
  end
82
82
 
83
+ def test_does_not_preserve_not_null_on_views
84
+ assert SteamLocomotive.columns.find { |c| c.name == 'water_consumption' }.null
85
+ end
86
+
83
87
  class ChangeDefaultValueOfColumn < ActiveRecord::Migration
84
88
  def self.up
85
89
  remove_parent_and_children_views(:rack_locomotives)
@@ -184,22 +188,22 @@ class UpdateableViewsInheritanceSchemaTest < ActiveSupport::TestCase
184
188
  t.column :interrail_max_speed, :integer
185
189
  t.column :type, :string
186
190
  end
187
- create_child('interrail.steam_locomotives', :parent => 'interrail.locomotives') do |t|
188
- t.decimal :interrail_water_consumption, :precision => 6, :scale => 2
189
- t.decimal :interrail_coal_consumption, :precision => 6, :scale => 2
191
+ create_child('interrail.steam_locomotives', parent: 'interrail.locomotives') do |t|
192
+ t.decimal :interrail_water_consumption, precision: 6, scale: 2
193
+ t.decimal :interrail_coal_consumption, precision: 6, scale: 2
190
194
  end
191
195
  end
192
196
  end
193
197
 
194
198
  def test_create_child_in_schema
195
199
  CreateChildInSchema.up
196
- assert_equal %w(id
200
+ assert_equal %w[id
197
201
  interrail_coal_consumption
198
202
  interrail_max_speed
199
203
  interrail_name
200
204
  interrail_water_consumption
201
- type),
202
- @connection.columns('interrail.steam_locomotives').map{ |c| c.name }.sort
205
+ type],
206
+ @connection.columns('interrail.steam_locomotives').map(&:name).sort
203
207
  end
204
208
 
205
209
  class ChangeTablesInTwoInheritanceChains < ActiveRecord::Migration
@@ -251,4 +255,31 @@ class UpdateableViewsInheritanceSchemaTest < ActiveSupport::TestCase
251
255
  ReservedSQLWords.up
252
256
  assert @connection.columns(:table).map(&:name).include?("column")
253
257
  end
258
+
259
+ class ChildTableIsActuallyView < ActiveRecord::Migration
260
+ def self.up
261
+ execute <<-SQL.squish
262
+ CREATE VIEW punk_locomotives_data AS (
263
+ SELECT steam_locomotives.id,
264
+ steam_locomotives.coal_consumption AS coal,
265
+ NULL AS electro
266
+ FROM steam_locomotives
267
+ UNION ALL
268
+ SELECT electric_locomotives.id,
269
+ NULL AS coal,
270
+ electric_locomotives.electricity_consumption AS electro
271
+ FROM electric_locomotives)
272
+ SQL
273
+ create_child(:punk_locomotives,
274
+ { parent: :locomotives,
275
+ child_table: :punk_locomotives_data,
276
+ child_table_pk: :id,
277
+ skip_creating_child_table: true })
278
+ end
279
+ end
280
+
281
+ def test_child_table_is_view
282
+ ChildTableIsActuallyView.up
283
+ assert @connection.columns(:punk_locomotives).map(&:name).sort == %w(coal electro id max_speed name type)
284
+ end
254
285
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: updateable_views_inheritance
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.4
4
+ version: 1.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sava Chankov
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-10-09 00:00:00.000000000 Z
12
+ date: 2024-10-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -271,7 +271,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
271
271
  - !ruby/object:Gem::Version
272
272
  version: '0'
273
273
  requirements: []
274
- rubygems_version: 3.4.12
274
+ rubygems_version: 3.1.6
275
275
  signing_key:
276
276
  specification_version: 4
277
277
  summary: Class table inheritance for ActiveRecord