associate_jsonb 0.0.7 → 0.0.8

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: f0832e64c43e22ba206a98d0603c0e231c1606b17315f715a4050e19a1fdb21b
4
- data.tar.gz: ff37b2777adf131f20ad9a95076d8797ecf30dc2ddeaa86913de5a457cf9fed4
3
+ metadata.gz: 9d9a2ca078138c49415e16c98a42191a1545502f41033ab7ec73494352fda01c
4
+ data.tar.gz: 312f1fa2f579903c190ec72a875a000f44f32cdb9df0e218e5b276a902bb4a92
5
5
  SHA512:
6
- metadata.gz: 00fbd32bc9e66fa8bd85fcf9cab51850bc65443a99f66dbc3bcff5ca5d05e855624c62ac9a0aa7bcbed330a2650dbe149806c49bc3bb40ecd3aa6cdf002fde19
7
- data.tar.gz: b134266f93576482f3558be28840081b803982ce369bd75497af2d9d2d2db30e3c27364247485ef2a4712705fb01183d80b812f6fa28f18d8f7db37db555bb88
6
+ metadata.gz: 524b2455ff9c493b76605d232af61e4dcb576b1262814d13c200db7a686026230be11f837230eb6116be1c0c8d6043360cf2f22c14209ce323d8fc7875760316
7
+ data.tar.gz: a76a0db4c95e3be203c397290f95110a5b8dce02a65c0fe6097df293be149e47e0d3252bf07482f679b562755f271e462f314635f3ce69ac5178a096de64965e
data/README.md CHANGED
@@ -2,97 +2,98 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/associate_jsonb.svg)](https://badge.fury.io/rb/associate_jsonb)
4
4
 
5
- #### PostgreSQL JSONB extensions including:
6
- - Basic ActiveRecord Associations using PostgreSQL JSONB columns, with built-in accessors and column indexes
7
- - Thread-Safe JSONB updates (well, as safe as they can be) using a custom nested version of `jsonb_set` (`jsonb_nested_set`)
5
+ #### Easy PostgreSQL JSONB extensions
6
+ **including:**
7
+
8
+ - Basic ActiveRecord Associations using PostgreSQL JSONB columns, with built-in accessors and column indexes
9
+ - Thread-Safe JSONB updates (well, as safe as they can be) using a custom nested version of `jsonb_set` (`jsonb_nested_set`)
8
10
 
9
11
  **Requirements:**
10
12
 
11
13
  - PostgreSQL (>= 12)
12
14
  - Rails 6.0.3.2
13
15
 
14
- ## Usage
16
+ ## Installation
15
17
 
16
- ### Jsonb Associations
18
+ Add this line to your application's Gemfile:
17
19
 
20
+ ```ruby
21
+ gem 'associate_jsonb'
22
+ ```
18
23
 
19
- ### jsonb_set based hash updates
24
+ And then execute:
20
25
 
21
- When enabled, *only* keys present in the updated hash and with values changed in memory will be updated.
22
- To completely delete a key, value pair from an enabled attribute, set the key's value to `nil`.
26
+ ```bash
27
+ $ bundle install
28
+ ```
23
29
 
24
- e.g.
25
- ```ruby
26
- # given: instance#data == { "key_1"=>1,
27
- # "key_2"=>2,
28
- # "key_3"=> { "key_4"=>7,
29
- # "key_5"=>8,
30
- # "key_6"=>9 } }
30
+ ## Usage
31
31
 
32
- instance.update({ key_1: "asdf", a: 1, key_2: nil, key_3: { key_5: nil }})
33
- # instance#data => { "key_1"=>"asdf",
34
- # "a"=>"asdf",
35
- # "key_3"=> { "key_4"=>7,
36
- # "key_6"=>9 } }
32
+ ### Jsonb Associations
37
33
 
38
- ```
34
+ #### One-to-One and One-to-Many associations
35
+
36
+ To set up your jsonb column, you can use the built in `add_reference`/`table.references` function. This will only add a new store column if it doesn't already exist
39
37
 
40
- #### enabling/adding attribute types
41
- first, create the sql function
42
38
  ```bash
43
- rails g migration add_jsonb_nested_set_function
39
+ rails g migration add_foreign_key_store_to_my_table
44
40
  ```
45
41
  ```ruby
46
- class CreateState < ActiveRecord::Migration[6.0]
47
- def up
48
- add_jsonb_nested_set_function
42
+ class AddForeignKeyStoreToMyTable < ActiveRecord::Migration[6.0]
43
+ def change
44
+ add_reference :my_table, :user, store: :extra # => store created
45
+ add_reference :my_table, :label, store: :extra, null: false # => store already exists, NOT NULL check constraint added to `store->'label_id'`
46
+ # NOTE: you can also use a `change_table(:my_table) block`
49
47
  end
50
48
  end
51
49
  ```
52
50
 
53
- then in an initializer, enable key based updates:
51
+ and
52
+
54
53
  ```ruby
55
- # config/initializers/associate_jsonb.rb
56
- AssociateJsonb.enable_jsonb_set
54
+ class CreateMyTable < ActiveRecord::Migration[6.0]
55
+ def change
56
+ create_table(:my_table) do |t|
57
+ t.references :user, store: :extra
58
+ t.references :label, store: :extra, null: false
59
+ end
60
+ end
61
+ end
57
62
  ```
58
63
 
59
- Key based updates rely on inheritance for allowed attribute types. Any attributes that respond true to `attr_type.is_a?(GivenClass)` for any enabled type classes will use `jsonb_nested_set`
60
-
61
- To add classes to the enabled list, pass them as arguments to `AssociateJsonb.add_hash_type(*klasses)`. Any arguments passed to `AssociateJsonb.enable_jsonb_set` are forwarded to `AssociateJsonb.add_hash_type`
62
-
63
- By default, calling `AssociateJsonb.enable_jsonb_set(*klasses)` without arguments, and no classes previously added, adds `ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb` to the allowed classes list
64
+ If you add the `jsonb_foreign_key` function to your database, you can also create a foreign_key **check** constraint by using the same built-in `:foreign_key` option used in normal reference definitions.
64
65
 
65
- #### disabling/removing attribute types
66
- by default `jsonb_nested_set` updates are disabled.
67
-
68
- if you've enabled them and need to disable, use: `AssociateJsonb.disable_jsonb_set`
69
-
70
- To remove a class from the allowed list while leaving nested set updates enabled, use `AssociateJsonb.remove_hash_type(*klasses)`.
71
- Any arguments passed to `AssociateJsonb.disable_jsonb_set` are forwarded to `AssociateJsonb.remove_hash_type`
72
-
73
- ### Automatically delete nil value hash keys
74
-
75
- When jsonb_set updates are disabled, jsonb columns are replaced with the current document (i.e. default rails behavior)
66
+ **NOTE**: true foreign key references are not possible with jsonb attributes. This will instead create a CHECK constraint that looks for the referenced column using an `EXISTS` statement
76
67
 
77
- You are also given the option to automatically clear nil/null values from the hash automatically
78
-
79
- in an initializer, enable stripping nil values:
68
+ ```bash
69
+ rails g migration add_jsonb_foreign_key_function
70
+ ```
80
71
  ```ruby
81
- # config/initializers/associate_jsonb.rb
82
- AssociateJsonb.jsonb_delete_nil = true
72
+ class AddJsonbForeignKeyFunction < ActiveRecord::Migration[6.0]
73
+ def up
74
+ add_jsonb_foreign_key_function
75
+ end
76
+ end
77
+ ```
78
+ ```ruby
79
+ class CreateMyTable < ActiveRecord::Migration[6.0]
80
+ def change
81
+ create_table(:my_table) do |t|
82
+ t.references :user, store: :extra, foreign_key: true, null: false
83
+ end
84
+ end
85
+ end
86
+ ```
87
+ ```ruby
88
+ class CreateMyTable < ActiveRecord::Migration[6.0]
89
+ def change
90
+ create_table(:my_table) do |t|
91
+ t.references :person, store: :extra, foreign_key: { to_table: :users }, null: false
92
+ end
93
+ end
94
+ end
83
95
  ```
84
96
 
85
- Rules for classes to with this applies are the same as for `jsonb_nested_set`; add and remove classes through `AssociateJsonb.(add|remove)_hash_type(*klasses)`
86
-
87
- <!-- This gem was created as a solution to this [task](http://cultofmartians.com/tasks/active-record-jsonb-associations.html) from [EvilMartians](http://evilmartians.com).
88
-
89
- **Requirements:**
90
-
91
- - PostgreSQL (>= 9.6)
92
-
93
- ## Usage
94
-
95
- ### One-to-one and One-to-many associations
96
97
 
97
98
  You can store all foreign keys of your model in one JSONB column, without having to create multiple columns:
98
99
 
@@ -115,49 +116,87 @@ class User < ActiveRecord::Base
115
116
  end
116
117
  ```
117
118
 
118
- Foreign keys for association on one model have to be unique, even if they use different store column.
119
+ ### Many-to-Many associations
119
120
 
120
- You can also use `add_references` in your migration to add JSONB column and index for it (if `index: true` option is set):
121
+ Due to the ease of getting out-of-sync, and the complexity needed to build it, HABTM relation functionality has not been implemented through JSONB
122
+
123
+ ### jsonb_set based hash updates
124
+
125
+ When enabled, *only* keys present in the updated hash and with values changed in memory will be updated.
126
+ To completely delete a `key/value` pair from an enabled attribute, set the key's value to `nil`.
127
+
128
+ e.g.
121
129
 
122
130
  ```ruby
123
- add_reference :profiles, :users, store: :extra, index: true
131
+ # given: instance#data == { "key_1"=>1,
132
+ # "key_2"=>2,
133
+ # "key_3"=> { "key_4"=>7,
134
+ # "key_5"=>8,
135
+ # "key_6"=>9 } }
136
+
137
+ instance.update({ key_1: "asdf", a: 1, key_2: nil, key_3: { key_5: nil }})
138
+
139
+ # instance#data => { "key_1"=>"asdf",
140
+ # "a"=>"asdf",
141
+ # "key_3"=> { "key_4"=>7,
142
+ # "key_6"=>9 } }
124
143
  ```
125
144
 
126
- ### Many-to-many associations
145
+ #### enabling/adding attribute types
127
146
 
128
- Due to the ease of getting out-of-sync, and the complexity needed to build it, HABTM relation functionality has not been implemented through JSONB
147
+ first, create the sql function
148
+
149
+ ```bash
150
+ rails g migration add_jsonb_nested_set_function
151
+ ```
152
+ ```ruby
153
+ class AddJsonbNestedSetFunction < ActiveRecord::Migration[6.0]
154
+ def up
155
+ add_jsonb_nested_set_function
156
+ end
157
+ end
158
+ ```
129
159
 
130
- #### Performance
160
+ then in an initializer, enable key based updates:
131
161
 
132
- Compared to regular associations, fetching models associated via JSONB column has no drops in performance.
162
+ ```ruby
163
+ # config/initializers/associate_jsonb.rb
164
+ AssociateJsonb.enable_jsonb_set
165
+ ```
133
166
 
134
- Getting the count of connected records is ~35% faster with associations via JSONB (tested on associations with up to 10 000 connections).
167
+ - Key based updates rely on inheritance for allowed attribute types. Any attributes that respond true to `attr_type.is_a?(GivenClass)` for any enabled type classes will use `jsonb_nested_set`
135
168
 
136
- Adding new connections is slightly faster with JSONB, for scopes up to 500 records connected to another record (total count of records in the table does not matter that much. If you have more then ~500 records connected to one record on average, and you want to add new records to the scope, JSONB associations will be slower then traditional:
169
+ - To add classes to the enabled list, pass them as arguments to `AssociateJsonb.add_hash_type(*klasses)`. Any arguments passed to `AssociateJsonb.enable_jsonb_set` are forwarded to `AssociateJsonb.add_hash_type`
137
170
 
138
- <img src="https://github.com/lebedev-yury/associate_jsonb/blob/master/doc/images/adding-associations.png?raw=true | width=500" alt="JSONB HAMTB is slower on adding associations" width="600">
171
+ - By default, calling `AssociateJsonb.enable_jsonb_set(*klasses)` without arguments, and no classes previously added, adds `ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb` to the allowed classes list
139
172
 
140
- On the other hand, unassociating models from a big amount of associated models if faster with JSONB HABTM as the associations count grows:
173
+ #### disabling/removing attribute types
141
174
 
142
- <img src="https://github.com/lebedev-yury/associate_jsonb/blob/master/doc/images/deleting-associations.png?raw=true | width=500" alt="JSONB HAMTB is faster on removing associations" width="600">
175
+ - by default `jsonb_nested_set` updates are disabled.
143
176
 
144
- ## Installation
177
+ - if you've enabled them and need to disable, use: `AssociateJsonb.disable_jsonb_set`
145
178
 
146
- Add this line to your application's Gemfile:
179
+ - To remove a class from the allowed list while leaving nested set updates enabled, use `AssociateJsonb.remove_hash_type(*klasses)`.
180
+ Any arguments passed to `AssociateJsonb.disable_jsonb_set` are forwarded to `AssociateJsonb.remove_hash_type`
147
181
 
148
- ```ruby
149
- gem 'associate_jsonb'
150
- ```
182
+ ### Automatically delete nil value hash keys
151
183
 
152
- And then execute:
184
+ When jsonb_set updates are disabled, jsonb columns are replaced with the current document (i.e. default rails behavior)
153
185
 
154
- ```bash
155
- $ bundle install
186
+ You are also given the option to automatically clear nil/null values from the hash automatically when jsonb_set is disabled
187
+
188
+ in an initializer:
189
+
190
+ ```ruby
191
+ # config/initializers/associate_jsonb.rb
192
+ AssociateJsonb.jsonb_delete_nil = true
156
193
  ```
157
194
 
195
+ Rules for classes to which this applies are the same as for `jsonb_nested_set`; add and remove classes through `AssociateJsonb.(add|remove)_hash_type(*klasses)`
196
+
158
197
  ## Developing
159
198
 
160
- To setup development environment, just run:
199
+ To setup development environment, run:
161
200
 
162
201
  ```bash
163
202
  $ bin/setup
@@ -169,11 +208,6 @@ To run specs:
169
208
  $ bundle exec rspec
170
209
  ```
171
210
 
172
- To run benchmarks (that will take a while):
173
-
174
- ```bash
175
- $ bundle exec rake benchmarks:habtm
176
- ``` -->
177
-
178
211
  ## License
212
+
179
213
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -4,13 +4,11 @@ rescue LoadError
4
4
  puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
5
  end
6
6
 
7
- Rake.add_rakelib 'benchmarks'
8
-
9
7
  require 'rdoc/task'
10
8
 
11
9
  RDoc::Task.new(:rdoc) do |rdoc|
12
10
  rdoc.rdoc_dir = 'rdoc'
13
- rdoc.title = 'AssociateJsonb::Associations'
11
+ rdoc.title = 'AssociateJsonb'
14
12
  rdoc.options << '--line-numbers'
15
13
  rdoc.rdoc_files.include('README.md')
16
14
  rdoc.rdoc_files.include('lib/**/*.rb')
@@ -89,11 +89,16 @@ module AssociateJsonb
89
89
  DECLARE
90
90
  does_exist BOOLEAN;
91
91
  BEGIN
92
- IF store->key IS NULL
92
+ IF store->>key IS NULL
93
93
  THEN
94
94
  return nullable;
95
95
  END IF;
96
96
 
97
+ IF store->>key = ''
98
+ THEN
99
+ return FALSE;
100
+ END IF;
101
+
97
102
  EXECUTE FORMAT('SELECT EXISTS (SELECT 1 FROM %1$I WHERE %1$I.%2$I = CAST($1 AS ' || type || '))', table_name, foreign_key)
98
103
  INTO does_exist
99
104
  USING store->>key;
@@ -60,11 +60,25 @@ module AssociateJsonb
60
60
  deferrable: true
61
61
  )
62
62
  end
63
+ elsif !nullable
64
+ columns.each do |col_name, *|
65
+ value = <<-SQL.squish
66
+ #{store}->>'#{col_name}' IS NOT NULL
67
+ AND
68
+ #{store}->>'#{col_name}' <> ''
69
+ SQL
70
+ table.constraint(
71
+ name: "#{table.name}_#{col_name}_not_null",
72
+ value: value,
73
+ not_valid: false,
74
+ deferrable: true
75
+ )
76
+ end
63
77
  end
64
78
 
65
79
  return unless index
66
80
 
67
- columns.each do |col_name, type, opts|
81
+ columns.each do |col_name, type, *|
68
82
  type = :text if type == :string
69
83
  table.index(
70
84
  "CAST (\"#{store}\"->>'#{col_name}' AS #{type || :bigint})",
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module AssociateJsonb
5
- VERSION = "0.0.7"
5
+ VERSION = "0.0.8"
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: associate_jsonb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sampson Crowley
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-04 00:00:00.000000000 Z
11
+ date: 2020-08-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails