pg_party 1.1.0 → 1.2.0

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
  SHA256:
3
- metadata.gz: 11489588c1a9cccdbb8f39a73ebd3b91367c45640677211df0ff73afcb267598
4
- data.tar.gz: 0e2e6184b88e80afa186173c291ae3044976e1bd909f8d3501d35f780c024c09
3
+ metadata.gz: 5996516e7f3b76373b6fc4f9a29c9e31f48d9edb086b7e0f217031f0ed3707ea
4
+ data.tar.gz: d4e5d12b2bdb80ab928153efe9ec49e11b7e6e413f0a0edbeddfe2f24a7d4276
5
5
  SHA512:
6
- metadata.gz: 1dddde1408aeeee28b7bd0474c39483b3df2ccba68de294eeacc77421e8f8aae0508685f2c5926c695ab3c129d9ae0ffec45fca8d99be7ec8f15e4a7d7e5ead5
7
- data.tar.gz: dc26fa61e8c0d026e7c102c0100d275c9b1d1f5baf2880b6bca82fde7f3fa700d48ed30394e15d3d85fdf786d008148e8b90c23662c43914d4e852a80e976657
6
+ metadata.gz: fac73650e2065c300fabb4ad826e59b8877310033490778b81835d28f297135796befec74a3ca5f6f6d5b3204f65a0f6686d58875df53e1332d96b6ef09edbac
7
+ data.tar.gz: 0fec1b06196dc2fecd95464694ae1d9742fb72687ac676b5a6def977b1a965a2f137b70edb85310f273237d8a7f6370aaa6a193d5c926487e30710d53de720f0
data/README.md CHANGED
@@ -48,6 +48,32 @@ $ gem install pg_party
48
48
  Note that the gemspec does not require `pg`, as some model methods _may_ work for other databases.
49
49
  Migration methods will be unavailable unless `pg` is installed.
50
50
 
51
+ ## Configuration
52
+
53
+ These values can be accessed and set via `PgParty.config` and `PgParty.configure`.
54
+
55
+ - `caching`
56
+ - Whether to cache currently attached partitions and anonymous model classes
57
+ - Default: `true`
58
+ - `caching_ttl`
59
+ - Length of time (in seconds) that cache entries are considered valid
60
+ - Default: `-1` (never expire cache entries)
61
+ - `schema_exclude_partitions`
62
+ - Whether to exclude child partitions in `rake db:structure:dump` (supported in ActiveRecord 5.2+)
63
+ - Default: `true`
64
+
65
+ Note that caching is done in-memory for each process of an application. Attaching / detaching partitions _will_ clear the cache, but only for the process that initiated the request. For multi-process web servers, it is recommended to use a TTL or disable caching entirely.
66
+
67
+ ### Example
68
+
69
+ ```ruby
70
+ # in a Rails initializer
71
+ PgParty.configure do |c|
72
+ c.caching_ttl = 60
73
+ c.schema_exclude_partitions = false
74
+ end
75
+ ```
76
+
51
77
  ## Usage
52
78
 
53
79
  ### Migrations
data/lib/pg_party.rb CHANGED
@@ -1,8 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "pg_party/version"
4
+ require "pg_party/config"
5
+ require "pg_party/cache"
4
6
  require "active_support"
5
7
 
8
+ module PgParty
9
+ @config = Config.new
10
+ @cache = Cache.new
11
+
12
+ class << self
13
+ attr_reader :config, :cache
14
+
15
+ def configure(&blk)
16
+ blk.call(config)
17
+ end
18
+
19
+ def reset
20
+ @config = Config.new
21
+ @cache = Cache.new
22
+ end
23
+ end
24
+ end
25
+
6
26
  ActiveSupport.on_load(:active_record) do
7
27
  require "pg_party/model/methods"
8
28
 
@@ -20,6 +40,12 @@ ActiveSupport.on_load(:active_record) do
20
40
  PgParty::Hacks::SchemaCache
21
41
  )
22
42
 
43
+ require "pg_party/hacks/database_tasks"
44
+
45
+ ActiveRecord::Tasks::DatabaseTasks.extend(
46
+ PgParty::Hacks::DatabaseTasks
47
+ )
48
+
23
49
  begin
24
50
  require "active_record/connection_adapters/postgresql_adapter"
25
51
  require "pg_party/adapter/postgresql_methods"
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest"
4
- require "pg_party/cache"
5
4
 
6
5
  module PgParty
7
6
  class AdapterDecorator < SimpleDelegator
@@ -61,7 +60,7 @@ module PgParty
61
60
  DETACH PARTITION #{quote_table_name(child_table_name)}
62
61
  SQL
63
62
 
64
- PgParty::Cache.clear!
63
+ PgParty.cache.clear!
65
64
  end
66
65
 
67
66
  private
@@ -118,7 +117,7 @@ module PgParty
118
117
  FOR VALUES #{constraint_clause}
119
118
  SQL
120
119
 
121
- PgParty::Cache.clear!
120
+ PgParty.cache.clear!
122
121
  end
123
122
 
124
123
  # Rails 5.2 now returns boolean literals
@@ -6,32 +6,66 @@ module PgParty
6
6
  class Cache
7
7
  LOCK = Mutex.new
8
8
 
9
- class << self
10
- def clear!
11
- LOCK.synchronize { store.clear }
9
+ def initialize
10
+ # automatically initialize a new hash when
11
+ # accessing an object id that doesn't exist
12
+ @store = Hash.new { |h, k| h[k] = { models: {}, partitions: nil } }
13
+ end
14
+
15
+ def clear!
16
+ LOCK.synchronize { @store.clear }
17
+
18
+ nil
19
+ end
20
+
21
+ def fetch_model(key, child_table, &block)
22
+ return block.call unless caching_enabled?
23
+
24
+ LOCK.synchronize { fetch_value(@store[key][:models], child_table.to_sym, block) }
25
+ end
26
+
27
+ def fetch_partitions(key, &block)
28
+ return block.call unless caching_enabled?
29
+
30
+ LOCK.synchronize { fetch_value(@store[key], :partitions, block) }
31
+ end
32
+
33
+ private
34
+
35
+ def caching_enabled?
36
+ PgParty.config.caching
37
+ end
38
+
39
+ def fetch_value(subhash, key, block)
40
+ entry = subhash[key]
12
41
 
13
- nil
42
+ if entry.nil? || entry.expired?
43
+ entry = Entry.new(block.call)
44
+ subhash[key] = entry
14
45
  end
15
46
 
16
- def fetch_model(key, child_table, &block)
17
- LOCK.synchronize do
18
- store[key][:models][child_table.to_sym] ||= block.call
19
- end
47
+ entry.value
48
+ end
49
+
50
+ class Entry
51
+ attr_reader :value
52
+
53
+ def initialize(value)
54
+ @value = value
55
+ @timestamp = Time.now
20
56
  end
21
57
 
22
- def fetch_partitions(key, &block)
23
- LOCK.synchronize do
24
- store[key][:partitions] ||= block.call
25
- end
58
+ def expired?
59
+ ttl.positive? && Time.now - @timestamp > ttl
26
60
  end
27
61
 
28
62
  private
29
63
 
30
- def store
31
- # automatically initialize a new hash when
32
- # accessing an object id that doesn't exist
33
- @store ||= Hash.new { |h, k| h[k] = { models: {}, partitions: nil } }
64
+ def ttl
65
+ PgParty.config.caching_ttl
34
66
  end
35
67
  end
68
+
69
+ private_constant :Entry
36
70
  end
37
71
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgParty
4
+ class Config
5
+ attr_accessor \
6
+ :caching,
7
+ :caching_ttl,
8
+ :schema_exclude_partitions
9
+
10
+ def initialize
11
+ @caching = true
12
+ @caching_ttl = -1
13
+ @schema_exclude_partitions = true
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgParty
4
+ module Hacks
5
+ module DatabaseTasks
6
+ def structure_dump(*)
7
+ old_ignore_list = ActiveRecord::SchemaDumper.ignore_tables
8
+
9
+ if PgParty.config.schema_exclude_partitions
10
+ new_ignore_list = partitions.map { |table| "*.#{table}" }
11
+ else
12
+ new_ignore_list = []
13
+ end
14
+
15
+ ActiveRecord::SchemaDumper.ignore_tables = old_ignore_list + new_ignore_list
16
+
17
+ super
18
+ ensure
19
+ ActiveRecord::SchemaDumper.ignore_tables = old_ignore_list
20
+ end
21
+
22
+ def partitions
23
+ ActiveRecord::Base.connection.select_values(
24
+ "SELECT DISTINCT inhrelid::regclass::text FROM pg_inherits"
25
+ )
26
+ rescue
27
+ []
28
+ end
29
+ end
30
+ end
31
+ end
@@ -6,11 +6,19 @@ module PgParty
6
6
  module Model
7
7
  module SharedMethods
8
8
  def reset_primary_key
9
- PgParty::ModelDecorator.new(self).partition_primary_key
9
+ if self != base_class
10
+ base_class.primary_key
11
+ elsif partition_name = partitions.first
12
+ in_partition(partition_name).get_primary_key(base_class.name)
13
+ else
14
+ get_primary_key(base_class.name)
15
+ end
10
16
  end
11
17
 
12
18
  def table_exists?
13
- PgParty::ModelDecorator.new(self).partition_table_exists?
19
+ target_table = partitions.first || table_name
20
+
21
+ connection.schema_cache.data_source_exists?(target_table)
14
22
  end
15
23
 
16
24
  def partitions
@@ -1,27 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "pg_party/cache"
4
-
5
3
  module PgParty
6
4
  class ModelDecorator < SimpleDelegator
7
- def partition_primary_key
8
- if self != base_class
9
- base_class.primary_key
10
- elsif partition_name = partitions.first
11
- in_partition(partition_name).get_primary_key(base_class.name)
12
- else
13
- get_primary_key(base_class.name)
14
- end
15
- end
16
-
17
- def partition_table_exists?
18
- target_table = partitions.first || table_name
19
-
20
- connection.schema_cache.data_source_exists?(target_table)
21
- end
22
-
23
5
  def in_partition(child_table_name)
24
- PgParty::Cache.fetch_model(cache_key, child_table_name) do
6
+ PgParty.cache.fetch_model(cache_key, child_table_name) do
25
7
  Class.new(__getobj__) do
26
8
  self.table_name = child_table_name
27
9
 
@@ -41,6 +23,11 @@ module PgParty
41
23
  def self.new(*args, &blk)
42
24
  superclass.new(*args, &blk)
43
25
  end
26
+
27
+ # to avoid unnecessary db lookups
28
+ def self.partitions
29
+ []
30
+ end
44
31
  end
45
32
  end
46
33
  end
@@ -76,7 +63,7 @@ module PgParty
76
63
  end
77
64
 
78
65
  def partitions
79
- PgParty::Cache.fetch_partitions(cache_key) do
66
+ PgParty.cache.fetch_partitions(cache_key) do
80
67
  connection.select_values(<<-SQL)
81
68
  SELECT pg_inherits.inhrelid::regclass::text
82
69
  FROM pg_tables
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgParty
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_party
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Krage
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-07 00:00:00.000000000 Z
11
+ date: 2019-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -219,6 +219,8 @@ files:
219
219
  - lib/pg_party/adapter/postgresql_methods.rb
220
220
  - lib/pg_party/adapter_decorator.rb
221
221
  - lib/pg_party/cache.rb
222
+ - lib/pg_party/config.rb
223
+ - lib/pg_party/hacks/database_tasks.rb
222
224
  - lib/pg_party/hacks/schema_cache.rb
223
225
  - lib/pg_party/model/list_methods.rb
224
226
  - lib/pg_party/model/methods.rb