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,6 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ module AssociateJsonb
5
+ SUPPORTED_RAILS_VERSION = "6.0.3.2"
6
+ end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module AssociateJsonb
5
- VERSION = "0.0.1"
5
+ VERSION = "0.0.7"
6
6
  end
@@ -12,18 +12,29 @@ module AssociateJsonb
12
12
  @names_list ||= {}
13
13
  end
14
14
 
15
- def add_name(name, store, key)
16
- @names_list ||= {}
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 <<-CODE, __FILE__, __LINE__ + 1
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, *opts, key: nil, **attribute_opts)
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, *opts, **attribute_opts
107
+ attribute attr, cast_type, **attribute_opts
93
108
 
94
- instance_eval <<-CODE, __FILE__, __LINE__ + 1
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
- on_attr_change =
106
- is_array \
107
- ? "write_attribute(:#{attribute}, Array(given))" \
108
- : "super(given)"
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 <<-CODE, __FILE__, __LINE__ + 1
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 <<-CODE, __FILE__, __LINE__ + 1
138
+ mixin.class_eval <<~CODE, __FILE__, __LINE__ + 1
118
139
  def #{store}=(given)
119
- given = super((given || {}).with_indifferent_access)
120
- write_attribute(:#{attribute}, given["#{key}"])
121
- given["#{key}"] = #{attribute} unless #{attribute}.nil?
122
- super(given)
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}") if value.nil?
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.1
4
+ version: 0.0.7
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-07-14 00:00:00.000000000 Z
11
+ date: 2020-08-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '6'
19
+ version: 6.0.3
20
20
  - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 6.0.3.1
22
+ version: 6.0.3.2
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - "~>"
28
28
  - !ruby/object:Gem::Version
29
- version: '6'
29
+ version: 6.0.3
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 6.0.3.1
32
+ version: 6.0.3.2
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: pg
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -104,14 +104,18 @@ dependencies:
104
104
  - - "~>"
105
105
  - !ruby/object:Gem::Version
106
106
  version: 3.7.0
107
- description: |2
108
- This gem extends ActiveRecord to let you use PostgreSQL JSONB data for associations
107
+ description: |
108
+ This gem extends ActiveRecord to add additional functionality to JSONB
109
109
 
110
- Inspired by activerecord-jsonb-associations, but for use in Rails 6+ and
111
- ruby 2.7+ and with some unnecessary options and features (HABTM) removed
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
- BONUS: extended `table#references` for easy migrations and indexes
114
- (NOTE: real foreign key constraints are not possible with PostgreSQL JSONB)
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/arel_node_extensions/binary.rb
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,11 +150,25 @@ 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/reference_definition.rb
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
171
+ - lib/associate_jsonb/supported_rails_version.rb
147
172
  - lib/associate_jsonb/version.rb
148
173
  - lib/associate_jsonb/with_store_attribute.rb
149
174
  homepage: https://github.com/SampsonCrowley/associate_jsonb
@@ -1,12 +0,0 @@
1
- # encoding: utf-8
2
- # frozen_string_literal: true
3
-
4
- module AssociateJsonb
5
- module ArelNodeExtensions
6
- module Binary
7
- def original_left
8
- left
9
- end
10
- end
11
- end
12
- end
@@ -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