associate_jsonb 0.0.2 → 0.0.8
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 +111 -2
- data/lib/associate_jsonb/arel_extensions/nodes/binary.rb +14 -0
- data/lib/associate_jsonb/arel_extensions/nodes/table_alias.rb +38 -0
- data/lib/associate_jsonb/arel_extensions/table.rb +40 -0
- data/lib/associate_jsonb/arel_extensions/visitors/postgresql.rb +113 -0
- data/lib/associate_jsonb/arel_extensions/visitors/visitor.rb +19 -0
- data/lib/associate_jsonb/arel_nodes/jsonb/attribute.rb +38 -0
- data/lib/associate_jsonb/arel_nodes/sql_casted_binary.rb +20 -0
- data/lib/associate_jsonb/arel_nodes/sql_casted_equality.rb +26 -12
- data/lib/associate_jsonb/associations/alias_tracker.rb +13 -0
- data/lib/associate_jsonb/associations/association_scope.rb +18 -45
- data/lib/associate_jsonb/associations/belongs_to_association.rb +8 -8
- data/lib/associate_jsonb/associations/builder/belongs_to.rb +5 -3
- data/lib/associate_jsonb/associations/join_dependency.rb +21 -0
- 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 +167 -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 +19 -0
- data/lib/associate_jsonb/version.rb +1 -1
- data/lib/associate_jsonb/with_store_attribute.rb +59 -23
- metadata +34 -10
- data/lib/associate_jsonb/arel_node_extensions/binary.rb +0 -12
- data/lib/associate_jsonb/connection_adapters/reference_definition.rb +0 -64
@@ -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
|
]
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
1
4
|
module AssociateJsonb
|
2
5
|
module Relation
|
3
6
|
module WhereClause
|
@@ -15,6 +18,22 @@ module AssociateJsonb
|
|
15
18
|
[name, value]
|
16
19
|
}.to_h
|
17
20
|
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def equalities(predicates)
|
24
|
+
equalities = []
|
25
|
+
|
26
|
+
predicates.each do |node|
|
27
|
+
case node
|
28
|
+
when Arel::Nodes::Equality, Arel::Nodes::SqlCastedEquality
|
29
|
+
equalities << node
|
30
|
+
when Arel::Nodes::And
|
31
|
+
equalities.concat equalities(node.children)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
equalities
|
36
|
+
end
|
18
37
|
end
|
19
38
|
end
|
20
39
|
end
|
@@ -12,18 +12,29 @@ module AssociateJsonb
|
|
12
12
|
@names_list ||= {}
|
13
13
|
end
|
14
14
|
|
15
|
-
def add_name(name, store, key)
|
16
|
-
|
17
|
-
@names_list[name.to_s.freeze] = { store: store, key: key }
|
15
|
+
def add_name(name, store, key, cast)
|
16
|
+
names_list[name.to_s.freeze] = { store: store, key: key, cast: cast }
|
18
17
|
end
|
19
18
|
|
20
19
|
def has_name?(name)
|
21
20
|
names_list.key? name.to_s
|
22
21
|
end
|
22
|
+
|
23
|
+
def get(name)
|
24
|
+
names_list[name.to_s]
|
25
|
+
end
|
26
|
+
|
27
|
+
def store_for(name)
|
28
|
+
(get(name) || {})[:store]
|
29
|
+
end
|
30
|
+
|
31
|
+
def key_for(name)
|
32
|
+
(get(name) || {})[:key]
|
33
|
+
end
|
23
34
|
end
|
24
35
|
|
25
36
|
included do
|
26
|
-
instance_eval
|
37
|
+
instance_eval <<~CODE, __FILE__, __LINE__ + 1
|
27
38
|
initialize_store_column_attribute_tracker
|
28
39
|
|
29
40
|
after_initialize &set_store_column_attribute_values_on_init
|
@@ -37,6 +48,10 @@ module AssociateJsonb
|
|
37
48
|
super
|
38
49
|
end
|
39
50
|
|
51
|
+
def arel_table
|
52
|
+
super.with_store_tracker(store_column_attribute_tracker)
|
53
|
+
end
|
54
|
+
|
40
55
|
def initialize_store_column_attribute_tracker
|
41
56
|
@store_column_attribute_tracker = const_set(:StoreColumnAttributeTracker, StoreColumnAttributeTracker.new)
|
42
57
|
private_constant :StoreColumnAttributeTracker
|
@@ -56,9 +71,9 @@ module AssociateJsonb
|
|
56
71
|
end
|
57
72
|
end
|
58
73
|
|
59
|
-
def add_store_column_attribute_name(name, store, key)
|
74
|
+
def add_store_column_attribute_name(name, store, key, cast_opts)
|
60
75
|
store_column_attribute_tracker.synchronize do
|
61
|
-
store_column_attribute_tracker.add_name(name, store, key)
|
76
|
+
store_column_attribute_tracker.add_name(name, store, key, cast_opts)
|
62
77
|
end
|
63
78
|
end
|
64
79
|
|
@@ -83,16 +98,16 @@ module AssociateJsonb
|
|
83
98
|
store_column_attribute :data, *args, **opts
|
84
99
|
end
|
85
100
|
|
86
|
-
def store_column_attribute(store, attr,
|
101
|
+
def store_column_attribute(store, attr, cast_type = ActiveRecord::Type::Value.new, sql_type: nil, key: nil, **attribute_opts)
|
87
102
|
store = store.to_sym
|
88
103
|
attr = attr.to_sym
|
89
104
|
key ||= attr
|
90
105
|
key = key.to_s
|
91
106
|
array = attribute_opts[:array]
|
92
|
-
attribute attr,
|
107
|
+
attribute attr, cast_type, **attribute_opts
|
93
108
|
|
94
|
-
instance_eval
|
95
|
-
add_store_column_attribute_name("#{attr}", :#{store}, "#{key}")
|
109
|
+
instance_eval <<~CODE, __FILE__, __LINE__ + 1
|
110
|
+
add_store_column_attribute_name("#{attr}", :#{store}, "#{key}", { sql_type: sql_type, type: cast_type, opts: attribute_opts })
|
96
111
|
CODE
|
97
112
|
|
98
113
|
include WithStoreAttribute::InstanceMethodsOnActivation.new(self, store, attr, key, array)
|
@@ -102,31 +117,52 @@ module AssociateJsonb
|
|
102
117
|
class InstanceMethodsOnActivation < Module
|
103
118
|
def initialize(mixin, store, attribute, key, is_array)
|
104
119
|
is_array = !!(is_array && attribute.to_s =~ /_ids$/)
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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)})"
|
129
|
+
|
109
130
|
if is_array
|
110
|
-
mixin.class_eval
|
131
|
+
mixin.class_eval <<~CODE, __FILE__, __LINE__ + 1
|
111
132
|
def #{attribute}
|
112
133
|
_read_attribute(:#{attribute}) || []
|
113
134
|
end
|
114
135
|
CODE
|
115
136
|
end
|
116
137
|
|
117
|
-
mixin.class_eval
|
138
|
+
mixin.class_eval <<~CODE, __FILE__, __LINE__ + 1
|
118
139
|
def #{store}=(given)
|
119
|
-
given
|
120
|
-
|
121
|
-
|
122
|
-
|
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}")
|
154
|
+
else
|
155
|
+
super given || {}
|
156
|
+
self.#{attribute}= #{store}["#{key}"]
|
157
|
+
end
|
158
|
+
|
159
|
+
#{store}
|
123
160
|
end
|
124
161
|
|
125
162
|
def #{attribute}=(given)
|
126
163
|
#{on_attr_change}
|
127
|
-
value = #{store}["#{key}"] = #{attribute}
|
128
|
-
#{store}.delete("#{key}")
|
129
|
-
_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}"])
|
130
166
|
value
|
131
167
|
end
|
132
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.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-
|
11
|
+
date: 2020-08-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -104,14 +104,18 @@ dependencies:
|
|
104
104
|
- - "~>"
|
105
105
|
- !ruby/object:Gem::Version
|
106
106
|
version: 3.7.0
|
107
|
-
description: |
|
108
|
-
|
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: []
|
@@ -122,15 +126,22 @@ files:
|
|
122
126
|
- README.md
|
123
127
|
- Rakefile
|
124
128
|
- lib/associate_jsonb.rb
|
125
|
-
- lib/associate_jsonb/
|
129
|
+
- lib/associate_jsonb/arel_extensions/nodes/binary.rb
|
130
|
+
- lib/associate_jsonb/arel_extensions/nodes/table_alias.rb
|
131
|
+
- lib/associate_jsonb/arel_extensions/table.rb
|
132
|
+
- lib/associate_jsonb/arel_extensions/visitors/postgresql.rb
|
133
|
+
- lib/associate_jsonb/arel_extensions/visitors/visitor.rb
|
126
134
|
- lib/associate_jsonb/arel_nodes/jsonb/at_arrow.rb
|
135
|
+
- lib/associate_jsonb/arel_nodes/jsonb/attribute.rb
|
127
136
|
- lib/associate_jsonb/arel_nodes/jsonb/bindable_operator.rb
|
128
137
|
- lib/associate_jsonb/arel_nodes/jsonb/dash_arrow.rb
|
129
138
|
- lib/associate_jsonb/arel_nodes/jsonb/dash_double_arrow.rb
|
130
139
|
- lib/associate_jsonb/arel_nodes/jsonb/double_pipe.rb
|
131
140
|
- lib/associate_jsonb/arel_nodes/jsonb/hash_arrow.rb
|
132
141
|
- lib/associate_jsonb/arel_nodes/jsonb/operator.rb
|
142
|
+
- lib/associate_jsonb/arel_nodes/sql_casted_binary.rb
|
133
143
|
- lib/associate_jsonb/arel_nodes/sql_casted_equality.rb
|
144
|
+
- lib/associate_jsonb/associations/alias_tracker.rb
|
134
145
|
- lib/associate_jsonb/associations/association.rb
|
135
146
|
- lib/associate_jsonb/associations/association_scope.rb
|
136
147
|
- lib/associate_jsonb/associations/belongs_to_association.rb
|
@@ -139,9 +150,22 @@ files:
|
|
139
150
|
- lib/associate_jsonb/associations/builder/has_one.rb
|
140
151
|
- lib/associate_jsonb/associations/conflicting_association.rb
|
141
152
|
- lib/associate_jsonb/associations/has_many_association.rb
|
153
|
+
- lib/associate_jsonb/associations/join_dependency.rb
|
142
154
|
- lib/associate_jsonb/associations/preloader/association.rb
|
155
|
+
- lib/associate_jsonb/attribute_methods.rb
|
156
|
+
- lib/associate_jsonb/attribute_methods/read.rb
|
143
157
|
- lib/associate_jsonb/connection_adapters.rb
|
144
|
-
- 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
|
145
169
|
- lib/associate_jsonb/reflection.rb
|
146
170
|
- lib/associate_jsonb/relation/where_clause.rb
|
147
171
|
- lib/associate_jsonb/supported_rails_version.rb
|