associate_jsonb 0.0.1 → 0.0.7

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +81 -1
  3. data/lib/associate_jsonb.rb +111 -1
  4. data/lib/associate_jsonb/arel_extensions/nodes/binary.rb +14 -0
  5. data/lib/associate_jsonb/arel_extensions/nodes/table_alias.rb +38 -0
  6. data/lib/associate_jsonb/arel_extensions/table.rb +40 -0
  7. data/lib/associate_jsonb/arel_extensions/visitors/postgresql.rb +113 -0
  8. data/lib/associate_jsonb/arel_extensions/visitors/visitor.rb +19 -0
  9. data/lib/associate_jsonb/arel_nodes/jsonb/attribute.rb +38 -0
  10. data/lib/associate_jsonb/arel_nodes/sql_casted_binary.rb +20 -0
  11. data/lib/associate_jsonb/arel_nodes/sql_casted_equality.rb +26 -12
  12. data/lib/associate_jsonb/associations/alias_tracker.rb +13 -0
  13. data/lib/associate_jsonb/associations/association_scope.rb +18 -45
  14. data/lib/associate_jsonb/associations/belongs_to_association.rb +8 -8
  15. data/lib/associate_jsonb/associations/builder/belongs_to.rb +5 -3
  16. data/lib/associate_jsonb/associations/join_dependency.rb +21 -0
  17. data/lib/associate_jsonb/attribute_methods.rb +19 -0
  18. data/lib/associate_jsonb/attribute_methods/read.rb +15 -0
  19. data/lib/associate_jsonb/connection_adapters/schema_creation.rb +162 -0
  20. data/lib/associate_jsonb/connection_adapters/schema_definitions/add_jsonb_foreign_key_function.rb +9 -0
  21. data/lib/associate_jsonb/connection_adapters/schema_definitions/add_jsonb_nested_set_function.rb +9 -0
  22. data/lib/associate_jsonb/connection_adapters/schema_definitions/alter_table.rb +40 -0
  23. data/lib/associate_jsonb/connection_adapters/schema_definitions/constraint_definition.rb +60 -0
  24. data/lib/associate_jsonb/connection_adapters/schema_definitions/reference_definition.rb +88 -0
  25. data/lib/associate_jsonb/connection_adapters/schema_definitions/table.rb +12 -0
  26. data/lib/associate_jsonb/connection_adapters/schema_definitions/table_definition.rb +25 -0
  27. data/lib/associate_jsonb/connection_adapters/schema_statements.rb +116 -0
  28. data/lib/associate_jsonb/persistence.rb +14 -0
  29. data/lib/associate_jsonb/predicate_builder.rb +15 -0
  30. data/lib/associate_jsonb/reflection.rb +2 -2
  31. data/lib/associate_jsonb/relation/where_clause.rb +19 -0
  32. data/lib/associate_jsonb/supported_rails_version.rb +6 -0
  33. data/lib/associate_jsonb/version.rb +1 -1
  34. data/lib/associate_jsonb/with_store_attribute.rb +59 -23
  35. metadata +39 -14
  36. data/lib/associate_jsonb/arel_node_extensions/binary.rb +0 -12
  37. data/lib/associate_jsonb/connection_adapters/reference_definition.rb +0 -64
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module AssociateJsonb
5
+ module ConnectionAdapters
6
+ class AddJsonbForeignKeyFunction
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module AssociateJsonb
5
+ module ConnectionAdapters
6
+ class AddJsonbNestedSetFunction
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module AssociateJsonb
5
+ module ConnectionAdapters
6
+ module AlterTable # :nodoc:
7
+ attr_reader :constraint_adds, :constraint_drops
8
+ def initialize(td)
9
+ super
10
+ @constraint_adds = []
11
+ @constraint_drops = []
12
+ end
13
+
14
+ def add_constraint(name = nil, **opts)
15
+ unless opts[:value].present?
16
+ raise ArgumentError.new("Invalid Add Constraint Options")
17
+ end
18
+
19
+ @constraint_adds << ConstraintDefinition.new(
20
+ **opts.reverse_merge(name: name)
21
+ )
22
+ end
23
+
24
+ def alter_constraint(name = nil, **opts)
25
+ opts[:force] = true
26
+ add_constraint(name, **opts)
27
+ end
28
+
29
+ def drop_constraint(name = nil, **opts)
30
+ opts = opts.reverse_merge(force: true, name: name, value: nil)
31
+
32
+ unless opts[:name].present? || opts[:value].present?
33
+ raise ArgumentError.new("Invalid Drop Constraint Options")
34
+ end
35
+
36
+ @constraint_drops << ConstraintDefinition.new(**opts)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module AssociateJsonb
5
+ module ConnectionAdapters
6
+ class ConstraintDefinition
7
+ # rubocop:disable Metrics/ParameterLists
8
+ attr_reader :name, :value, :not_valid, :deferrable, :force
9
+ def initialize(value:, name: nil, not_valid: false, force: false, deferrable: true, **)
10
+ @name = name.presence
11
+ @value = value
12
+ @not_valid = not_valid
13
+ @deferrable = deferrable
14
+ @force = force
15
+
16
+ @name ||=
17
+ "rails_constraint_" \
18
+ "#{@value.hash}" \
19
+ "_#{not_valid ? "nv" : "v"}" \
20
+ "_#{deferrable ? "d" : "nd"}"
21
+ end
22
+
23
+ def deferrable_default?
24
+ deferrable.nil?
25
+ end
26
+
27
+
28
+ def name?
29
+ !!name
30
+ end
31
+
32
+ def value?
33
+ !!value
34
+ end
35
+
36
+ def not_valid?
37
+ !!not_valid
38
+ end
39
+
40
+ def deferrable?
41
+ !!deferrable
42
+ end
43
+
44
+ def force?
45
+ !!force
46
+ end
47
+
48
+ def to_h
49
+ {
50
+ name: name,
51
+ value: value,
52
+ not_valid: not_valid,
53
+ deferrable: deferrable,
54
+ force: force
55
+ }
56
+ end
57
+ alias :to_hash :to_h
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,88 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module AssociateJsonb
5
+ module ConnectionAdapters
6
+ module ReferenceDefinition
7
+ ForeignKeyDefinition = ActiveRecord::ConnectionAdapters::ForeignKeyDefinition
8
+ # rubocop:disable Metrics/ParameterLists
9
+ def initialize(
10
+ name,
11
+ store: false,
12
+ store_key: false,
13
+ **options
14
+ )
15
+ @store = store && store.to_sym
16
+ @store_key = store_key && store_key.to_s unless options[:polymorphic]
17
+ @nullable = options[:null] != false
18
+
19
+ super(name, **options)
20
+ end
21
+ # rubocop:enable Metrics/ParameterLists
22
+
23
+ def column_name
24
+ store_key || super
25
+ end
26
+
27
+ def add_to(table)
28
+ return super unless store
29
+
30
+ should_add_col = false
31
+ if table.respond_to? :column_exists?
32
+ should_add_col = !table.column_exists?(store)
33
+ elsif table.respond_to? :columns
34
+ should_add_col = table.columns.none? {|col| col.name.to_sym == store}
35
+ end
36
+
37
+ if should_add_col
38
+ opts = { null: false, default: {} }
39
+ table.column(store, :jsonb, **opts)
40
+ end
41
+
42
+ if foreign_key && column_names.length == 1
43
+ fk = ForeignKeyDefinition.new(table.name, foreign_table_name, foreign_key_options)
44
+ columns.each do |col_name, type, options|
45
+ options ||= {}
46
+ value = <<-SQL.squish
47
+ jsonb_foreign_key(
48
+ '#{fk.to_table}'::text,
49
+ '#{fk.primary_key}'::text,
50
+ #{store}::jsonb,
51
+ '#{col_name}'::text,
52
+ '#{type}'::text,
53
+ #{nullable}
54
+ )
55
+ SQL
56
+ table.constraint(
57
+ name: "#{table.name}_#{col_name}_foreign_key",
58
+ value: value,
59
+ not_valid: true,
60
+ deferrable: true
61
+ )
62
+ end
63
+ end
64
+
65
+ return unless index
66
+
67
+ columns.each do |col_name, type, opts|
68
+ type = :text if type == :string
69
+ table.index(
70
+ "CAST (\"#{store}\"->>'#{col_name}' AS #{type || :bigint})",
71
+ using: :btree,
72
+ name: "index_#{table.name}_on_#{store}_#{col_name}"
73
+ )
74
+
75
+ table.index(
76
+ "(\"#{store}\"->>'#{col_name}')",
77
+ using: :btree,
78
+ name: "index_#{table.name}_on_#{store}_#{col_name}_text"
79
+ )
80
+ end
81
+ end
82
+
83
+ protected
84
+
85
+ attr_reader :store, :store_key, :nullable
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module AssociateJsonb
5
+ module ConnectionAdapters
6
+ module Table
7
+ def constraint(**opts)
8
+ @base.add_constraint(name, **opts)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -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::DashArrow.
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::DashArrow.
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