associate_jsonb 0.0.1 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
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