associate_jsonb 0.0.4 → 0.0.10
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 +4 -4
- data/README.md +145 -31
- data/Rakefile +1 -3
- data/lib/associate_jsonb.rb +133 -5
- data/lib/associate_jsonb/arel_extensions/table.rb +1 -1
- data/lib/associate_jsonb/arel_extensions/visitors/postgresql.rb +113 -0
- data/lib/associate_jsonb/associations/association_scope.rb +2 -4
- data/lib/associate_jsonb/associations/belongs_to_association.rb +8 -8
- data/lib/associate_jsonb/associations/builder/belongs_to.rb +2 -2
- data/lib/associate_jsonb/attribute_methods.rb +19 -0
- data/lib/associate_jsonb/attribute_methods/read.rb +15 -0
- data/lib/associate_jsonb/connection_adapters/schema_creation.rb +168 -0
- data/lib/associate_jsonb/connection_adapters/schema_definitions/add_jsonb_foreign_key_function.rb +9 -0
- data/lib/associate_jsonb/connection_adapters/schema_definitions/add_jsonb_nested_set_function.rb +9 -0
- data/lib/associate_jsonb/connection_adapters/schema_definitions/alter_table.rb +40 -0
- data/lib/associate_jsonb/connection_adapters/schema_definitions/constraint_definition.rb +60 -0
- data/lib/associate_jsonb/connection_adapters/schema_definitions/reference_definition.rb +102 -0
- data/lib/associate_jsonb/connection_adapters/schema_definitions/table.rb +12 -0
- data/lib/associate_jsonb/connection_adapters/schema_definitions/table_definition.rb +25 -0
- data/lib/associate_jsonb/connection_adapters/schema_statements.rb +116 -0
- data/lib/associate_jsonb/persistence.rb +14 -0
- data/lib/associate_jsonb/predicate_builder.rb +15 -0
- data/lib/associate_jsonb/reflection.rb +2 -2
- data/lib/associate_jsonb/relation/where_clause.rb +3 -0
- data/lib/associate_jsonb/version.rb +1 -1
- data/lib/associate_jsonb/with_store_attribute.rb +31 -23
- metadata +29 -12
- data/lib/associate_jsonb/connection_adapters/reference_definition.rb +0 -64
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module AssociateJsonb
|
5
|
+
module ConnectionAdapters
|
6
|
+
module TableDefinition
|
7
|
+
attr_reader :constraints
|
8
|
+
|
9
|
+
def initialize(*,**)
|
10
|
+
super
|
11
|
+
@constraints = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def constraint(name = nil, **opts)
|
15
|
+
unless opts[:value].present?
|
16
|
+
raise ArgumentError.new("Invalid Drop Constraint Options")
|
17
|
+
end
|
18
|
+
|
19
|
+
@constraints << ConstraintDefinition.new(
|
20
|
+
**opts.reverse_merge(name: name)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module AssociateJsonb
|
5
|
+
module ConnectionAdapters
|
6
|
+
module SchemaStatements
|
7
|
+
def add_jsonb_nested_set_function
|
8
|
+
execute schema_creation.accept(AddJsonbNestedSetFunction.new)
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_jsonb_foreign_key_function
|
12
|
+
execute schema_creation.accept(AddJsonbForeignKeyFunction.new)
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_table(table_name, **options)
|
16
|
+
td = create_table_definition(table_name, **options)
|
17
|
+
|
18
|
+
if options[:id] != false && !options[:as]
|
19
|
+
pk = options.fetch(:primary_key) do
|
20
|
+
ActiveRecord::Base.get_primary_key table_name.to_s.singularize
|
21
|
+
end
|
22
|
+
|
23
|
+
if pk.is_a?(Array)
|
24
|
+
td.primary_keys pk
|
25
|
+
else
|
26
|
+
td.primary_key pk, options.fetch(:id, :primary_key), **options.except(:comment)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
yield td if block_given?
|
31
|
+
|
32
|
+
if options[:force]
|
33
|
+
drop_table(table_name, **options, if_exists: true)
|
34
|
+
end
|
35
|
+
|
36
|
+
result = execute schema_creation.accept td
|
37
|
+
|
38
|
+
td.indexes.each do |column_name, index_options|
|
39
|
+
add_index(table_name, column_name, index_options)
|
40
|
+
end
|
41
|
+
|
42
|
+
td.constraints.each do |ct|
|
43
|
+
add_constraint(table_name, **ct)
|
44
|
+
end
|
45
|
+
|
46
|
+
if table_comment = options[:comment].presence
|
47
|
+
change_table_comment(table_name, table_comment)
|
48
|
+
end
|
49
|
+
|
50
|
+
td.columns.each do |column|
|
51
|
+
change_column_comment(table_name, column.name, column.comment) if column.comment.present?
|
52
|
+
end
|
53
|
+
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_constraint(table_name, **options)
|
58
|
+
at = create_alter_table table_name
|
59
|
+
at.add_constraint(**options)
|
60
|
+
execute schema_creation.accept at
|
61
|
+
end
|
62
|
+
|
63
|
+
def constraints(table_name) # :nodoc:
|
64
|
+
scope = quoted_scope(table_name)
|
65
|
+
|
66
|
+
result = query(<<~SQL, "SCHEMA")
|
67
|
+
SELECT
|
68
|
+
con.oid,
|
69
|
+
con.conname,
|
70
|
+
con.connamespace,
|
71
|
+
con.contype,
|
72
|
+
con.condeferrable,
|
73
|
+
con.condeferred,
|
74
|
+
con.convalidated,
|
75
|
+
pg_get_constraintdef(con.oid) as consrc
|
76
|
+
FROM pg_catalog.pg_constraint con
|
77
|
+
INNER JOIN pg_catalog.pg_class rel
|
78
|
+
ON rel.oid = con.conrelid
|
79
|
+
INNER JOIN pg_catalog.pg_namespace nsp
|
80
|
+
ON nsp.oid = connamespace
|
81
|
+
WHERE nsp.nspname = #{scope[:schema]}
|
82
|
+
AND rel.relname = #{scope[:name]}
|
83
|
+
ORDER BY rel.relname
|
84
|
+
SQL
|
85
|
+
|
86
|
+
result.map do |row|
|
87
|
+
{
|
88
|
+
oid: row[0],
|
89
|
+
name: row[1],
|
90
|
+
deferrable: row[4],
|
91
|
+
deferred: row[5],
|
92
|
+
validated: row[6],
|
93
|
+
definition: row[7],
|
94
|
+
type:
|
95
|
+
case row[3].to_s.downcase
|
96
|
+
when "c"
|
97
|
+
"CHECK"
|
98
|
+
when "f"
|
99
|
+
"FOREIGN KEY"
|
100
|
+
when "p"
|
101
|
+
"PRIMARY KEY"
|
102
|
+
when "u"
|
103
|
+
"UNIQUE"
|
104
|
+
when "t"
|
105
|
+
"TRIGGER"
|
106
|
+
when "x"
|
107
|
+
"EXCLUDE"
|
108
|
+
else
|
109
|
+
"UNKNOWN"
|
110
|
+
end
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module AssociateJsonb
|
5
|
+
module Persistence
|
6
|
+
private
|
7
|
+
def _update_row(attribute_names, attempted_action = "update")
|
8
|
+
self.class._update_record(
|
9
|
+
attributes_with_info(attribute_names),
|
10
|
+
@primary_key => id_in_database
|
11
|
+
)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module AssociateJsonb
|
5
|
+
module PredicateBuilder # :nodoc:
|
6
|
+
def build_bind_attribute(column_name, value)
|
7
|
+
if value.respond_to?(:value_before_type_cast)
|
8
|
+
attr = ActiveRecord::Relation::QueryAttribute.new(column_name.to_s, value.value_before_type_cast, table.type(column_name), value)
|
9
|
+
else
|
10
|
+
attr = ActiveRecord::Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
|
11
|
+
end
|
12
|
+
Arel::Nodes::BindParam.new(attr)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -60,7 +60,7 @@ module AssociateJsonb
|
|
60
60
|
Arel::Nodes::NamedFunction.new(
|
61
61
|
"CAST",
|
62
62
|
[
|
63
|
-
Arel::Nodes::Jsonb::
|
63
|
+
Arel::Nodes::Jsonb::DashDoubleArrow.
|
64
64
|
new(table, table[foreign_store_attr], foreign_store_key || key).
|
65
65
|
as(foreign_klass.columns_hash[foreign_key.to_s].sql_type)
|
66
66
|
]
|
@@ -83,7 +83,7 @@ module AssociateJsonb
|
|
83
83
|
Arel::Nodes::NamedFunction.new(
|
84
84
|
"CAST",
|
85
85
|
[
|
86
|
-
Arel::Nodes::Jsonb::
|
86
|
+
Arel::Nodes::Jsonb::DashDoubleArrow.
|
87
87
|
new(foreign_table, foreign_table[jsonb_store_attr], jsonb_store_key || foreign_key).
|
88
88
|
as(klass.columns_hash[key.to_s].sql_type)
|
89
89
|
]
|
@@ -34,7 +34,7 @@ module AssociateJsonb
|
|
34
34
|
end
|
35
35
|
|
36
36
|
included do
|
37
|
-
instance_eval
|
37
|
+
instance_eval <<~CODE, __FILE__, __LINE__ + 1
|
38
38
|
initialize_store_column_attribute_tracker
|
39
39
|
|
40
40
|
after_initialize &set_store_column_attribute_values_on_init
|
@@ -106,7 +106,7 @@ module AssociateJsonb
|
|
106
106
|
array = attribute_opts[:array]
|
107
107
|
attribute attr, cast_type, **attribute_opts
|
108
108
|
|
109
|
-
instance_eval
|
109
|
+
instance_eval <<~CODE, __FILE__, __LINE__ + 1
|
110
110
|
add_store_column_attribute_name("#{attr}", :#{store}, "#{key}", { sql_type: sql_type, type: cast_type, opts: attribute_opts })
|
111
111
|
CODE
|
112
112
|
|
@@ -117,44 +117,52 @@ module AssociateJsonb
|
|
117
117
|
class InstanceMethodsOnActivation < Module
|
118
118
|
def initialize(mixin, store, attribute, key, is_array)
|
119
119
|
is_array = !!(is_array && attribute.to_s =~ /_ids$/)
|
120
|
-
on_attr_change =
|
121
|
-
is_array \
|
122
|
-
? "write_attribute(:#{attribute}, Array(given))" \
|
123
|
-
: "super(given)"
|
124
|
-
on_store_change = ->(var) {
|
125
|
-
"write_attribute(:#{attribute}, #{
|
126
|
-
is_array \
|
127
|
-
? "Array(#{var})" \
|
128
|
-
: var
|
129
|
-
})"
|
130
|
-
}
|
131
120
|
|
121
|
+
array_or_attr = ->(value) {
|
122
|
+
is_array \
|
123
|
+
? %Q(Array(#{value})) \
|
124
|
+
: %Q(#{value})
|
125
|
+
}
|
126
|
+
|
127
|
+
on_store_change = "_write_attribute(:#{attribute}, #{array_or_attr.call %Q(#{store}["#{key}"])})"
|
128
|
+
on_attr_change = "super(#{array_or_attr.call %Q(given)})"
|
132
129
|
|
133
130
|
if is_array
|
134
|
-
mixin.class_eval
|
131
|
+
mixin.class_eval <<~CODE, __FILE__, __LINE__ + 1
|
135
132
|
def #{attribute}
|
136
133
|
_read_attribute(:#{attribute}) || []
|
137
134
|
end
|
138
135
|
CODE
|
139
136
|
end
|
140
137
|
|
141
|
-
mixin.class_eval
|
138
|
+
mixin.class_eval <<~CODE, __FILE__, __LINE__ + 1
|
142
139
|
def #{store}=(given)
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
140
|
+
if given.is_a?(::String)
|
141
|
+
given = ActiveSupport::JSON.decode(given) rescue nil
|
142
|
+
end
|
143
|
+
|
144
|
+
if AssociateJsonb.merge_hash?(self.class.attribute_types["#{store}"])
|
145
|
+
if !given
|
146
|
+
given = {}
|
147
|
+
#{store}.keys.each do |k|
|
148
|
+
given[k] = nil
|
149
|
+
end
|
150
|
+
end
|
151
|
+
super(#{store}.deep_merge(given.deep_stringify_keys))
|
152
|
+
|
153
|
+
self.#{attribute}= #{store}["#{key}"] if #{store}.key?("#{key}")
|
147
154
|
else
|
148
|
-
|
155
|
+
super given || {}
|
156
|
+
self.#{attribute}= #{store}["#{key}"]
|
149
157
|
end
|
158
|
+
|
150
159
|
#{store}
|
151
160
|
end
|
152
161
|
|
153
162
|
def #{attribute}=(given)
|
154
163
|
#{on_attr_change}
|
155
|
-
value = #{store}["#{key}"] = #{attribute}
|
156
|
-
#{store}.delete("#{key}")
|
157
|
-
_write_attribute(:#{store}, #{store})
|
164
|
+
value = #{store}["#{key}"] = #{attribute}.presence
|
165
|
+
#{store}.delete("#{key}") unless !value.nil? || AssociateJsonb.merge_hash?(self.class.attribute_types["#{store}"])
|
158
166
|
value
|
159
167
|
end
|
160
168
|
CODE
|
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.
|
4
|
+
version: 0.0.10
|
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-
|
11
|
+
date: 2020-09-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -96,22 +96,26 @@ dependencies:
|
|
96
96
|
requirements:
|
97
97
|
- - "~>"
|
98
98
|
- !ruby/object:Gem::Version
|
99
|
-
version: 3.
|
99
|
+
version: '3.9'
|
100
100
|
type: :development
|
101
101
|
prerelease: false
|
102
102
|
version_requirements: !ruby/object:Gem::Requirement
|
103
103
|
requirements:
|
104
104
|
- - "~>"
|
105
105
|
- !ruby/object:Gem::Version
|
106
|
-
version: 3.
|
107
|
-
description: |
|
108
|
-
|
106
|
+
version: '3.9'
|
107
|
+
description: |
|
108
|
+
This gem extends ActiveRecord to add additional functionality to JSONB
|
109
109
|
|
110
|
-
|
111
|
-
|
110
|
+
- use PostgreSQL JSONB data for associations
|
111
|
+
- thread-safe single-key updates to JSONB columns using `jsonb_set`
|
112
|
+
- extended `table#references` for easy migrations and indexes
|
113
|
+
- virtual JSONB foreign keys using check constraints
|
114
|
+
(NOTE: real foreign key constraints are not possible with PostgreSQL JSONB)
|
112
115
|
|
113
|
-
|
114
|
-
|
116
|
+
Inspired by activerecord-jsonb-associations, but for use in Rails 6+ and
|
117
|
+
ruby 2.7+ and with some unnecessary options and features (HABTM) removed
|
118
|
+
and some additional features added
|
115
119
|
email:
|
116
120
|
- sampsonsprojects@gmail.com
|
117
121
|
executables: []
|
@@ -125,6 +129,7 @@ files:
|
|
125
129
|
- lib/associate_jsonb/arel_extensions/nodes/binary.rb
|
126
130
|
- lib/associate_jsonb/arel_extensions/nodes/table_alias.rb
|
127
131
|
- lib/associate_jsonb/arel_extensions/table.rb
|
132
|
+
- lib/associate_jsonb/arel_extensions/visitors/postgresql.rb
|
128
133
|
- lib/associate_jsonb/arel_extensions/visitors/visitor.rb
|
129
134
|
- lib/associate_jsonb/arel_nodes/jsonb/at_arrow.rb
|
130
135
|
- lib/associate_jsonb/arel_nodes/jsonb/attribute.rb
|
@@ -147,8 +152,20 @@ files:
|
|
147
152
|
- lib/associate_jsonb/associations/has_many_association.rb
|
148
153
|
- lib/associate_jsonb/associations/join_dependency.rb
|
149
154
|
- lib/associate_jsonb/associations/preloader/association.rb
|
155
|
+
- lib/associate_jsonb/attribute_methods.rb
|
156
|
+
- lib/associate_jsonb/attribute_methods/read.rb
|
150
157
|
- lib/associate_jsonb/connection_adapters.rb
|
151
|
-
- lib/associate_jsonb/connection_adapters/
|
158
|
+
- lib/associate_jsonb/connection_adapters/schema_creation.rb
|
159
|
+
- lib/associate_jsonb/connection_adapters/schema_definitions/add_jsonb_foreign_key_function.rb
|
160
|
+
- lib/associate_jsonb/connection_adapters/schema_definitions/add_jsonb_nested_set_function.rb
|
161
|
+
- lib/associate_jsonb/connection_adapters/schema_definitions/alter_table.rb
|
162
|
+
- lib/associate_jsonb/connection_adapters/schema_definitions/constraint_definition.rb
|
163
|
+
- lib/associate_jsonb/connection_adapters/schema_definitions/reference_definition.rb
|
164
|
+
- lib/associate_jsonb/connection_adapters/schema_definitions/table.rb
|
165
|
+
- lib/associate_jsonb/connection_adapters/schema_definitions/table_definition.rb
|
166
|
+
- lib/associate_jsonb/connection_adapters/schema_statements.rb
|
167
|
+
- lib/associate_jsonb/persistence.rb
|
168
|
+
- lib/associate_jsonb/predicate_builder.rb
|
152
169
|
- lib/associate_jsonb/reflection.rb
|
153
170
|
- lib/associate_jsonb/relation/where_clause.rb
|
154
171
|
- lib/associate_jsonb/supported_rails_version.rb
|
@@ -173,7 +190,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
190
|
- !ruby/object:Gem::Version
|
174
191
|
version: '0'
|
175
192
|
requirements: []
|
176
|
-
rubygems_version: 3.1.
|
193
|
+
rubygems_version: 3.1.4
|
177
194
|
signing_key:
|
178
195
|
specification_version: 4
|
179
196
|
summary: Store database references in PostgreSQL Jsonb columns
|
@@ -1,64 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module AssociateJsonb
|
4
|
-
module ConnectionAdapters
|
5
|
-
module ReferenceDefinition #:nodoc:
|
6
|
-
# rubocop:disable Metrics/ParameterLists
|
7
|
-
def initialize(
|
8
|
-
name,
|
9
|
-
store: false,
|
10
|
-
**options
|
11
|
-
)
|
12
|
-
@store = store && store.to_sym
|
13
|
-
|
14
|
-
super(name, **options)
|
15
|
-
end
|
16
|
-
# rubocop:enable Metrics/ParameterLists
|
17
|
-
|
18
|
-
def add_to(table)
|
19
|
-
return super unless store
|
20
|
-
|
21
|
-
should_add_col = false
|
22
|
-
if table.respond_to? :column_exists?
|
23
|
-
should_add_col = !table.column_exists?(store)
|
24
|
-
elsif table.respond_to? :columns
|
25
|
-
should_add_col = table.columns.none? {|col| col.name.to_sym == store}
|
26
|
-
end
|
27
|
-
|
28
|
-
table.column(store, :jsonb, null: false, default: {}) if should_add_col
|
29
|
-
|
30
|
-
return unless index
|
31
|
-
|
32
|
-
# should_add_idx = false
|
33
|
-
# if table.respond_to? :index_exists?
|
34
|
-
# should_add_idx = !table.index_exists?([ store ], using: :gin)
|
35
|
-
# elsif table.respond_to? :indexes
|
36
|
-
# should_add_idx = table.indexes.none? do |idx, opts|
|
37
|
-
# (idx == [ store ]) \
|
38
|
-
# && (opts == { using: :gin })
|
39
|
-
# end
|
40
|
-
# end
|
41
|
-
#
|
42
|
-
# table.index([ store ], using: :gin) if should_add_idx
|
43
|
-
|
44
|
-
column_names.each do |column_name|
|
45
|
-
table.index(
|
46
|
-
"CAST (\"#{store}\"->'#{column_name}' AS #{@type || :bigint})",
|
47
|
-
using: :btree,
|
48
|
-
name: "index_#{table.name}_on_#{store}_#{column_name}"
|
49
|
-
)
|
50
|
-
|
51
|
-
table.index(
|
52
|
-
"(#{store}->>'#{column_name}')",
|
53
|
-
using: :btree,
|
54
|
-
name: "index_#{table.name}_on_#{store}_#{column_name}_text"
|
55
|
-
)
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
protected
|
60
|
-
|
61
|
-
attr_reader :store
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|