arel_toolkit 0.2.0 → 0.4.3

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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +3 -0
  3. data/.github/workflows/develop.yml +90 -0
  4. data/.github/workflows/master.yml +67 -0
  5. data/.gitignore +8 -0
  6. data/.rubocop.yml +13 -2
  7. data/Appraisals +13 -0
  8. data/CHANGELOG.md +132 -6
  9. data/Gemfile +5 -0
  10. data/Gemfile.lock +92 -12
  11. data/Guardfile +23 -12
  12. data/README.md +104 -6
  13. data/Rakefile +18 -0
  14. data/arel_toolkit.gemspec +19 -4
  15. data/benchmark.rb +54 -0
  16. data/bin/console +1 -0
  17. data/ext/pg_result_init/extconf.rb +52 -0
  18. data/ext/pg_result_init/pg_result_init.c +138 -0
  19. data/ext/pg_result_init/pg_result_init.h +6 -0
  20. data/gemfiles/active_record_6.gemfile +7 -0
  21. data/gemfiles/active_record_6.gemfile.lock +210 -0
  22. data/gemfiles/arel_gems.gemfile +10 -0
  23. data/gemfiles/arel_gems.gemfile.lock +284 -0
  24. data/gemfiles/default.gemfile +5 -0
  25. data/gemfiles/default.gemfile.lock +208 -0
  26. data/lib/arel/enhance.rb +17 -0
  27. data/lib/arel/enhance/context_enhancer/arel_table.rb +92 -0
  28. data/lib/arel/enhance/node.rb +232 -0
  29. data/lib/arel/enhance/path.rb +38 -0
  30. data/lib/arel/enhance/path_node.rb +26 -0
  31. data/lib/arel/enhance/query.rb +38 -0
  32. data/lib/arel/enhance/query_methods.rb +23 -0
  33. data/lib/arel/enhance/visitor.rb +97 -0
  34. data/lib/arel/extensions.rb +55 -3
  35. data/lib/arel/extensions/active_model_attribute_with_cast_value.rb +22 -0
  36. data/lib/arel/extensions/active_record_relation_query_attribute.rb +22 -0
  37. data/lib/arel/extensions/active_record_type_caster_connection.rb +7 -0
  38. data/lib/arel/extensions/active_record_type_caster_map.rb +7 -0
  39. data/lib/arel/extensions/array.rb +2 -9
  40. data/lib/arel/extensions/assignment.rb +22 -0
  41. data/lib/arel/extensions/at_time_zone.rb +37 -0
  42. data/lib/arel/extensions/attributes_attribute.rb +47 -0
  43. data/lib/arel/extensions/binary.rb +7 -0
  44. data/lib/arel/extensions/bind_param.rb +15 -0
  45. data/lib/arel/extensions/bit_string.rb +2 -9
  46. data/lib/arel/extensions/case.rb +17 -0
  47. data/lib/arel/extensions/coalesce.rb +17 -3
  48. data/lib/arel/extensions/conflict.rb +9 -0
  49. data/lib/arel/extensions/contained_within_equals.rb +10 -0
  50. data/lib/arel/extensions/contains.rb +27 -5
  51. data/lib/arel/extensions/contains_equals.rb +10 -0
  52. data/lib/arel/extensions/current_catalog.rb +4 -0
  53. data/lib/arel/extensions/current_date.rb +4 -0
  54. data/lib/arel/extensions/current_of_expression.rb +2 -9
  55. data/lib/arel/extensions/current_role.rb +4 -0
  56. data/lib/arel/extensions/current_row.rb +7 -0
  57. data/lib/arel/extensions/current_schema.rb +4 -0
  58. data/lib/arel/extensions/current_user.rb +4 -0
  59. data/lib/arel/extensions/dealocate.rb +31 -0
  60. data/lib/arel/extensions/default_values.rb +4 -0
  61. data/lib/arel/extensions/delete_manager.rb +25 -0
  62. data/lib/arel/extensions/delete_statement.rb +32 -8
  63. data/lib/arel/extensions/distinct_from.rb +3 -16
  64. data/lib/arel/extensions/dot.rb +11 -0
  65. data/lib/arel/extensions/equality.rb +2 -4
  66. data/lib/arel/extensions/exists.rb +59 -0
  67. data/lib/arel/extensions/extract_from.rb +25 -0
  68. data/lib/arel/extensions/factorial.rb +10 -2
  69. data/lib/arel/extensions/false.rb +7 -0
  70. data/lib/arel/extensions/function.rb +44 -14
  71. data/lib/arel/extensions/greatest.rb +17 -3
  72. data/lib/arel/extensions/indirection.rb +3 -12
  73. data/lib/arel/extensions/infer.rb +7 -7
  74. data/lib/arel/extensions/infix_operation.rb +17 -0
  75. data/lib/arel/extensions/insert_manager.rb +21 -0
  76. data/lib/arel/extensions/insert_statement.rb +35 -9
  77. data/lib/arel/extensions/into.rb +21 -0
  78. data/lib/arel/extensions/json_get_field.rb +10 -0
  79. data/lib/arel/extensions/json_get_object.rb +10 -0
  80. data/lib/arel/extensions/json_path_get_field.rb +10 -0
  81. data/lib/arel/extensions/json_path_get_object.rb +10 -0
  82. data/lib/arel/extensions/jsonb_all_key_exists.rb +10 -0
  83. data/lib/arel/extensions/jsonb_any_key_exists.rb +10 -0
  84. data/lib/arel/extensions/jsonb_key_exists.rb +10 -0
  85. data/lib/arel/extensions/least.rb +17 -3
  86. data/lib/arel/extensions/named_argument.rb +24 -0
  87. data/lib/arel/extensions/named_function.rb +7 -0
  88. data/lib/arel/extensions/node.rb +10 -0
  89. data/lib/arel/extensions/not_distinct_from.rb +3 -16
  90. data/lib/arel/extensions/not_equal.rb +2 -4
  91. data/lib/arel/extensions/ordering.rb +21 -6
  92. data/lib/arel/extensions/overlap.rb +1 -1
  93. data/lib/arel/extensions/overlaps.rb +49 -0
  94. data/lib/arel/extensions/overlay.rb +53 -0
  95. data/lib/arel/extensions/position.rb +27 -0
  96. data/lib/arel/extensions/prepare.rb +39 -0
  97. data/lib/arel/extensions/range_function.rb +10 -2
  98. data/lib/arel/extensions/row.rb +3 -8
  99. data/lib/arel/extensions/select_core.rb +73 -0
  100. data/lib/arel/extensions/select_manager.rb +25 -0
  101. data/lib/arel/extensions/select_statement.rb +31 -9
  102. data/lib/arel/extensions/session_user.rb +4 -0
  103. data/lib/arel/extensions/set_to_default.rb +4 -0
  104. data/lib/arel/extensions/substring.rb +46 -0
  105. data/lib/arel/extensions/table.rb +43 -10
  106. data/lib/arel/extensions/time_with_precision.rb +6 -0
  107. data/lib/arel/extensions/to_sql.rb +27 -0
  108. data/lib/arel/extensions/top.rb +8 -0
  109. data/lib/arel/extensions/transaction.rb +45 -0
  110. data/lib/arel/extensions/tree_manager.rb +15 -0
  111. data/lib/arel/extensions/trim.rb +44 -0
  112. data/lib/arel/extensions/true.rb +7 -0
  113. data/lib/arel/extensions/type_cast.rb +11 -0
  114. data/lib/arel/extensions/unary.rb +7 -0
  115. data/lib/arel/extensions/unary_operation.rb +16 -0
  116. data/lib/arel/extensions/unknown.rb +4 -0
  117. data/lib/arel/extensions/update_manager.rb +25 -0
  118. data/lib/arel/extensions/update_statement.rb +31 -6
  119. data/lib/arel/extensions/user.rb +4 -0
  120. data/lib/arel/extensions/values_list.rb +15 -0
  121. data/lib/arel/extensions/variable_set.rb +55 -0
  122. data/lib/arel/extensions/variable_show.rb +26 -0
  123. data/lib/arel/middleware.rb +27 -0
  124. data/lib/arel/middleware/active_record_extension.rb +13 -0
  125. data/lib/arel/middleware/cache_accessor.rb +35 -0
  126. data/lib/arel/middleware/chain.rb +172 -0
  127. data/lib/arel/middleware/database_executor.rb +77 -0
  128. data/lib/arel/middleware/no_op_cache.rb +9 -0
  129. data/lib/arel/middleware/postgresql_adapter.rb +62 -0
  130. data/lib/arel/middleware/railtie.rb +25 -0
  131. data/lib/arel/middleware/result.rb +170 -0
  132. data/lib/arel/middleware/to_sql_executor.rb +15 -0
  133. data/lib/arel/middleware/to_sql_middleware.rb +33 -0
  134. data/lib/arel/sql_to_arel.rb +8 -4
  135. data/lib/arel/sql_to_arel/error.rb +6 -0
  136. data/lib/arel/sql_to_arel/pg_query_visitor.rb +324 -76
  137. data/lib/arel/sql_to_arel/pg_query_visitor/frame_options.rb +112 -0
  138. data/lib/arel/sql_to_arel/result.rb +30 -0
  139. data/lib/arel/transformer.rb +8 -0
  140. data/lib/arel/transformer/prefix_schema_name.rb +183 -0
  141. data/lib/arel/transformer/remove_active_record_info.rb +40 -0
  142. data/lib/arel/transformer/replace_table_with_subquery.rb +31 -0
  143. data/lib/arel_toolkit.rb +16 -1
  144. data/lib/arel_toolkit/version.rb +1 -1
  145. metadata +278 -25
  146. data/.travis.yml +0 -21
  147. data/lib/arel/extensions/generate_series.rb +0 -9
  148. data/lib/arel/extensions/rank.rb +0 -9
  149. data/lib/arel/sql_to_arel/frame_options.rb +0 -110
  150. data/lib/arel/sql_to_arel/unbound_column_reference.rb +0 -5
@@ -0,0 +1,5 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec path: "../"
@@ -0,0 +1,208 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ arel_toolkit (0.4.2)
5
+ activerecord
6
+ pg (~> 1.1.4)
7
+ pg_query (~> 1.2.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activemodel (5.2.3)
13
+ activesupport (= 5.2.3)
14
+ activerecord (5.2.3)
15
+ activemodel (= 5.2.3)
16
+ activesupport (= 5.2.3)
17
+ arel (>= 9.0)
18
+ activesupport (5.2.3)
19
+ concurrent-ruby (~> 1.0, >= 1.0.2)
20
+ i18n (>= 0.7, < 2)
21
+ minitest (~> 5.1)
22
+ tzinfo (~> 1.1)
23
+ addressable (2.7.0)
24
+ public_suffix (>= 2.0.2, < 5.0)
25
+ ansi (1.5.0)
26
+ appraisal (2.2.0)
27
+ bundler
28
+ rake
29
+ thor (>= 0.14.0)
30
+ approvals (0.0.24)
31
+ nokogiri (~> 1.6)
32
+ thor (~> 0.18)
33
+ arel (9.0.0)
34
+ ast (2.4.0)
35
+ binding_of_caller (0.8.0)
36
+ debug_inspector (>= 0.0.1)
37
+ coderay (1.1.2)
38
+ concurrent-ruby (1.1.5)
39
+ database_cleaner (1.7.0)
40
+ debug_inspector (0.0.3)
41
+ diff-lcs (1.3)
42
+ docile (1.3.2)
43
+ dpl (1.10.12)
44
+ faraday (0.17.0)
45
+ multipart-post (>= 1.2, < 3)
46
+ faraday-http-cache (2.0.0)
47
+ faraday (~> 0.8)
48
+ ffi (1.11.1)
49
+ formatador (0.2.5)
50
+ github_changelog_generator (1.15.0)
51
+ activesupport
52
+ faraday-http-cache
53
+ multi_json
54
+ octokit (~> 4.6)
55
+ rainbow (>= 2.2.1)
56
+ rake (>= 10.0)
57
+ retriable (~> 3.0)
58
+ guard (2.15.0)
59
+ formatador (>= 0.2.4)
60
+ listen (>= 2.7, < 4.0)
61
+ lumberjack (>= 1.0.12, < 2.0)
62
+ nenv (~> 0.1)
63
+ notiffany (~> 0.0)
64
+ pry (>= 0.9.12)
65
+ shellany (~> 0.0)
66
+ thor (>= 0.18.1)
67
+ guard-compat (1.2.1)
68
+ guard-rake (1.0.0)
69
+ guard
70
+ rake
71
+ guard-rspec (4.7.3)
72
+ guard (~> 2.1)
73
+ guard-compat (~> 1.1)
74
+ rspec (>= 2.99.0, < 4.0)
75
+ guard-rubocop (1.3.0)
76
+ guard (~> 2.0)
77
+ rubocop (~> 0.20)
78
+ hirb (0.7.3)
79
+ i18n (1.7.0)
80
+ concurrent-ruby (~> 1.0)
81
+ interception (0.5)
82
+ jaro_winkler (1.5.3)
83
+ json (2.2.0)
84
+ listen (3.1.5)
85
+ rb-fsevent (~> 0.9, >= 0.9.4)
86
+ rb-inotify (~> 0.9, >= 0.9.7)
87
+ ruby_dep (~> 1.2)
88
+ lumberjack (1.0.13)
89
+ memory_profiler (0.9.14)
90
+ method_source (0.9.2)
91
+ mini_portile2 (2.4.0)
92
+ minitest (5.13.0)
93
+ multi_json (1.14.1)
94
+ multipart-post (2.1.1)
95
+ nenv (0.3.0)
96
+ nokogiri (1.10.3)
97
+ mini_portile2 (~> 2.4.0)
98
+ notiffany (0.1.1)
99
+ nenv (~> 0.1)
100
+ shellany (~> 0.0)
101
+ octokit (4.14.0)
102
+ sawyer (~> 0.8.0, >= 0.5.3)
103
+ parallel (1.17.0)
104
+ parser (2.6.3.0)
105
+ ast (~> 2.4.0)
106
+ pg (1.1.4)
107
+ pg_query (1.2.0)
108
+ pry (0.12.2)
109
+ coderay (~> 1.1.0)
110
+ method_source (~> 0.9.0)
111
+ pry-alias (0.0.1)
112
+ binding_of_caller
113
+ pry
114
+ pry-doc (1.0.0)
115
+ pry (~> 0.11)
116
+ yard (~> 0.9.11)
117
+ pry-nav (0.3.0)
118
+ pry (>= 0.9.10, < 0.13.0)
119
+ pry-rescue (1.5.0)
120
+ interception (>= 0.5)
121
+ pry (>= 0.12.0)
122
+ pry-stack_explorer (0.4.9.3)
123
+ binding_of_caller (>= 0.7)
124
+ pry (>= 0.9.11)
125
+ public_suffix (4.0.1)
126
+ rainbow (3.0.0)
127
+ rake (13.0.1)
128
+ rake-compiler (1.0.7)
129
+ rake
130
+ rb-fsevent (0.10.3)
131
+ rb-inotify (0.10.0)
132
+ ffi (~> 1.0)
133
+ retriable (3.1.2)
134
+ rspec (3.8.0)
135
+ rspec-core (~> 3.8.0)
136
+ rspec-expectations (~> 3.8.0)
137
+ rspec-mocks (~> 3.8.0)
138
+ rspec-core (3.8.2)
139
+ rspec-support (~> 3.8.0)
140
+ rspec-expectations (3.8.4)
141
+ diff-lcs (>= 1.2.0, < 2.0)
142
+ rspec-support (~> 3.8.0)
143
+ rspec-mocks (3.8.1)
144
+ diff-lcs (>= 1.2.0, < 2.0)
145
+ rspec-support (~> 3.8.0)
146
+ rspec-support (3.8.2)
147
+ rubocop (0.71.0)
148
+ jaro_winkler (~> 1.5.1)
149
+ parallel (~> 1.10)
150
+ parser (>= 2.6)
151
+ rainbow (>= 2.2.2, < 4.0)
152
+ ruby-progressbar (~> 1.7)
153
+ unicode-display_width (>= 1.4.0, < 1.7)
154
+ ruby-progressbar (1.10.1)
155
+ ruby_dep (1.5.0)
156
+ sawyer (0.8.2)
157
+ addressable (>= 2.3.5)
158
+ faraday (> 0.8, < 2.0)
159
+ shellany (0.0.1)
160
+ simplecov (0.16.1)
161
+ docile (~> 1.1)
162
+ json (>= 1.8, < 3)
163
+ simplecov-html (~> 0.10.0)
164
+ simplecov-console (0.4.2)
165
+ ansi
166
+ hirb
167
+ simplecov
168
+ simplecov-html (0.10.2)
169
+ stackprof (0.2.13)
170
+ thor (0.20.3)
171
+ thread_safe (0.3.6)
172
+ tzinfo (1.2.5)
173
+ thread_safe (~> 0.1)
174
+ unicode-display_width (1.6.0)
175
+ yard (0.9.20)
176
+
177
+ PLATFORMS
178
+ ruby
179
+
180
+ DEPENDENCIES
181
+ appraisal (~> 2.2.0)
182
+ approvals (~> 0.0.24)
183
+ arel_toolkit!
184
+ bundler (~> 2.0)
185
+ database_cleaner (~> 1.7.0)
186
+ dpl (~> 1.10.11)
187
+ github_changelog_generator (~> 1.15)
188
+ guard (~> 2.15)
189
+ guard-rake (~> 1.0.0)
190
+ guard-rspec (~> 4.7)
191
+ guard-rubocop (~> 1.3.0)
192
+ memory_profiler (~> 0.9)
193
+ pry
194
+ pry-alias
195
+ pry-doc
196
+ pry-nav
197
+ pry-rescue
198
+ pry-stack_explorer
199
+ rake (~> 13.0)
200
+ rake-compiler (~> 1.0)
201
+ rspec (~> 3.8)
202
+ rubocop (= 0.71.0)
203
+ simplecov (~> 0.16.1)
204
+ simplecov-console (~> 0.4.2)
205
+ stackprof (~> 0.2)
206
+
207
+ BUNDLED WITH
208
+ 2.0.2
@@ -0,0 +1,17 @@
1
+ require_relative './enhance/node'
2
+ require_relative './enhance/path'
3
+ require_relative './enhance/path_node'
4
+ require_relative './enhance/query'
5
+ require_relative './enhance/query_methods'
6
+ require_relative './enhance/visitor'
7
+
8
+ module Arel
9
+ module Enhance
10
+ end
11
+
12
+ def self.enhance(object)
13
+ return object if object.is_a?(Arel::Enhance::Node)
14
+
15
+ Arel::Enhance::Visitor.new.accept(object)
16
+ end
17
+ end
@@ -0,0 +1,92 @@
1
+ module Arel
2
+ module Enhance
3
+ module ContextEnhancer
4
+ class ArelTable
5
+ # rubocop:disable Metrics/PerceivedComplexity
6
+ # rubocop:disable Metrics/CyclomaticComplexity
7
+ # rubocop:disable Metrics/AbcSize
8
+ def self.call(node)
9
+ context = node.context.merge!(
10
+ range_variable: false, column_reference: false, alias: false,
11
+ )
12
+ parent_object = node.parent.object
13
+
14
+ # Using Arel::Table as SELECT ... FROM <table>
15
+ if parent_object.is_a?(Arel::Nodes::JoinSource)
16
+ context[:range_variable] = true
17
+
18
+ # NOTE: only applies to ActiveRecord generated Arel
19
+ # which does not use Arel::Table#alias but Arel::TableAlias instead
20
+ # Using Arel::Table as SELECT ... FROM <table> AS alias
21
+ elsif parent_object.is_a?(Arel::Nodes::TableAlias) &&
22
+ node.parent.parent.object.is_a?(Arel::Nodes::JoinSource)
23
+ context[:range_variable] = true
24
+
25
+ # Using Arel::Table as SELECT ... FROM [<table>]
26
+ elsif parent_object.is_a?(Array) &&
27
+ node.parent.parent.object.is_a?(Arel::Nodes::JoinSource)
28
+ context[:range_variable] = true
29
+
30
+ # NOTE: only applies to ActiveRecord generated Arel
31
+ # which does not use Arel::Table#alias but Arel::TableAlias instead
32
+ # Using Arel::Table as SELECT ... FROM [<table> AS alias]
33
+ elsif parent_object.is_a?(Arel::Nodes::TableAlias) &&
34
+ node.parent.parent.object.is_a?(Array) &&
35
+ node.parent.parent.parent.object.is_a?(Arel::Nodes::JoinSource)
36
+ context[:range_variable] = true
37
+
38
+ # Using Arel::Table as SELECT ... INNER JOIN <table> ON TRUE
39
+ elsif parent_object.is_a?(Arel::Nodes::Join)
40
+ context[:range_variable] = true
41
+
42
+ # Using Arel::Table as an attribute SELECT <table>.id ...
43
+ elsif parent_object.is_a?(Arel::Attributes::Attribute)
44
+ context[:column_reference] = true
45
+
46
+ # Using Arel::Table in an INSERT INTO <table>
47
+ elsif parent_object.is_a?(Arel::Nodes::InsertStatement)
48
+ context[:range_variable] = true
49
+
50
+ # Using Arel::Table in an UPDATE <table> ...
51
+ elsif parent_object.is_a?(Arel::Nodes::UpdateStatement)
52
+ context[:range_variable] = true
53
+
54
+ # Arel::Table in UPDATE ... FROM [<table>]
55
+ elsif parent_object.is_a?(Array) &&
56
+ node.parent.parent.object.is_a?(Arel::Nodes::UpdateStatement)
57
+ context[:range_variable] = true
58
+
59
+ # Using Arel::Table in an DELETE FROM <table>
60
+ elsif parent_object.is_a?(Arel::Nodes::DeleteStatement)
61
+ context[:range_variable] = true
62
+
63
+ # Arel::Table in DELETE ... USING [<table>]
64
+ elsif parent_object.is_a?(Array) &&
65
+ node.parent.parent.object.is_a?(Arel::Nodes::DeleteStatement)
66
+ context[:range_variable] = true
67
+
68
+ # Using Arel::Table as an "alias" for WITH <table> AS (SELECT 1) SELECT 1
69
+ elsif parent_object.is_a?(Arel::Nodes::As) &&
70
+ node.parent.parent.parent.object.is_a?(Arel::Nodes::With)
71
+ context[:alias] = true
72
+
73
+ # Using Arel::Table as an "alias" for WITH RECURSIVE <table> AS (SELECT 1) SELECT 1
74
+ elsif parent_object.is_a?(Arel::Nodes::As) &&
75
+ node.parent.parent.parent.object.is_a?(Arel::Nodes::WithRecursive)
76
+ context[:alias] = true
77
+
78
+ # Using Arel::Table as an "alias" for SELECT INTO <table> ...
79
+ elsif parent_object.is_a?(Arel::Nodes::Into)
80
+ context[:alias] = true
81
+
82
+ else
83
+ raise "Unknown AST location for table #{node.inspect}, #{node.root_node.to_sql}"
84
+ end
85
+ end
86
+ # rubocop:enable Metrics/PerceivedComplexity
87
+ # rubocop:enable Metrics/CyclomaticComplexity
88
+ # rubocop:enable Metrics/AbcSize
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,232 @@
1
+ module Arel
2
+ module Enhance
3
+ class Node
4
+ attr_reader :object
5
+ attr_reader :parent
6
+ attr_reader :local_path
7
+ attr_reader :fields
8
+ attr_reader :children
9
+ attr_reader :root_node
10
+ attr_reader :context
11
+
12
+ def initialize(object)
13
+ @object = object
14
+ @root_node = self
15
+ @fields = []
16
+ @children = {}
17
+ @dirty = false
18
+ @context = {}
19
+ end
20
+
21
+ def inspect
22
+ recursive_inspect('')
23
+ end
24
+
25
+ def value
26
+ return unless value?
27
+
28
+ @fields.first
29
+ end
30
+
31
+ def each(&block)
32
+ return enum_for(:each) unless block_given?
33
+
34
+ yield self
35
+
36
+ children.each_value do |child|
37
+ child.each(&block)
38
+ end
39
+ end
40
+
41
+ def value?
42
+ children.empty?
43
+ end
44
+
45
+ def dirty?
46
+ root_node.instance_values.fetch('dirty')
47
+ end
48
+
49
+ def remove
50
+ mutate(nil, remove: true)
51
+ end
52
+
53
+ def replace(new_arel_node)
54
+ mutate(new_arel_node)
55
+ end
56
+
57
+ def add(path_node, node)
58
+ node.local_path = path_node
59
+ node.parent = self
60
+ node.root_node = root_node
61
+ @children[path_node.value.to_s] = node
62
+ end
63
+
64
+ def to_sql(engine = Table.engine)
65
+ return nil if children.empty?
66
+
67
+ if object.respond_to?(:to_sql)
68
+ object.to_sql(engine)
69
+ else
70
+ collector = Arel::Collectors::SQLString.new
71
+ collector = engine.connection.visitor.accept object, collector
72
+ collector.value
73
+ end
74
+ end
75
+
76
+ def to_sql_and_binds(engine = Table.engine)
77
+ object.to_sql_and_binds(engine)
78
+ end
79
+
80
+ def method_missing(name, *args, &block)
81
+ child = @children[name.to_s]
82
+ return super if child.nil?
83
+
84
+ child
85
+ end
86
+
87
+ def respond_to_missing?(method, include_private = false)
88
+ child = @children[method.to_s]
89
+ child.present? || super
90
+ end
91
+
92
+ def [](key)
93
+ @children.fetch(key.to_s)
94
+ end
95
+
96
+ def child_at_path(path_items)
97
+ selected_node = self
98
+ path_items.each do |path_item|
99
+ selected_node = selected_node[path_item]
100
+ return nil if selected_node.nil?
101
+ end
102
+ selected_node
103
+ end
104
+
105
+ def query(**kwargs)
106
+ Arel::Enhance::Query.call(self, kwargs)
107
+ end
108
+
109
+ def full_path
110
+ the_path = [local_path]
111
+ current_parent = parent
112
+
113
+ while current_parent
114
+ the_path.unshift current_parent.local_path
115
+ current_parent = current_parent.parent
116
+ end
117
+
118
+ the_path.compact
119
+ end
120
+
121
+ protected
122
+
123
+ attr_writer :local_path
124
+ attr_writer :parent
125
+ attr_writer :root_node
126
+
127
+ # rubocop:disable Metrics/AbcSize
128
+ # rubocop:disable Metrics/CyclomaticComplexity
129
+ # rubocop:disable Metrics/PerceivedComplexity
130
+ def recursive_inspect(string, indent = 1)
131
+ string << "<#{inspect_name} #{full_path.inspect}\n"
132
+ string << "#{spacing(indent)}sql = #{to_sql}\n" unless to_sql.nil?
133
+ string << "#{spacing(indent)}parent = #{parent.nil? ? nil.inspect : parent.inspect_name}"
134
+ string << "\n" unless children.length.zero?
135
+ children.each do |key, child|
136
+ string << "#{spacing(indent)}#{key} =\n"
137
+ string << spacing(indent + 1)
138
+ child.recursive_inspect(string, indent + 2)
139
+ end
140
+ string << "\n" if children.length.zero? && value?
141
+ string << "#{spacing(indent)}value = #{value.inspect}" if value?
142
+
143
+ string << if children.length.zero?
144
+ ">\n"
145
+ else
146
+ "#{spacing(indent - 1)}>\n"
147
+ end
148
+ end
149
+
150
+ # rubocop:enable Metrics/AbcSize
151
+ # rubocop:enable Metrics/CyclomaticComplexity
152
+ # rubocop:enable Metrics/PerceivedComplexity
153
+ attr_writer :object
154
+
155
+ def inspect_name
156
+ "Node(#{object.class.name})"
157
+ end
158
+
159
+ def spacing(indent)
160
+ indent.times.reduce('') do |memo|
161
+ memo << ' '
162
+ memo
163
+ end
164
+ end
165
+
166
+ def deep_copy_object
167
+ # https://github.com/mvgijssel/arel_toolkit/issues/97
168
+ new_object = Marshal.load(Marshal.dump(object))
169
+ self.object = new_object
170
+
171
+ recursive_update_object(new_object)
172
+ end
173
+
174
+ def recursive_update_object(arel_tree)
175
+ children.each_value do |child|
176
+ tree_child = arel_tree.send(*child.local_path.method)
177
+ child.object = tree_child
178
+ child.recursive_update_object(tree_child)
179
+ end
180
+ end
181
+
182
+ def mark_as_dirty
183
+ return if dirty?
184
+
185
+ @dirty = true
186
+ deep_copy_object
187
+ end
188
+
189
+ private
190
+
191
+ # rubocop:disable Metrics/PerceivedComplexity
192
+ # rubocop:disable Metrics/CyclomaticComplexity
193
+ # rubocop:disable Metrics/AbcSize
194
+ def mutate(new_node, remove: false)
195
+ root_node.mark_as_dirty
196
+
197
+ parent_object = parent.object
198
+ new_arel_node = new_node.is_a?(Arel::Enhance::Node) ? new_node.object : new_node
199
+ new_arel_node = [] if remove && object.is_a?(Array)
200
+
201
+ if parent_object.respond_to?("#{local_path.value}=")
202
+ parent_object.send("#{local_path.value}=", new_arel_node)
203
+
204
+ elsif parent_object.instance_values.key?(local_path.value)
205
+ parent_object.instance_variable_set("@#{local_path.value}", new_arel_node)
206
+
207
+ elsif local_path.arguments? && parent_object.respond_to?(local_path.method[0])
208
+ if remove
209
+ parent_object.delete_at(local_path.value)
210
+
211
+ else
212
+ parent_object[local_path.value] = new_arel_node
213
+ end
214
+ else
215
+ raise "Don't know how to replace `#{local_path.value}` in #{parent_object.inspect}"
216
+ end
217
+
218
+ if new_node.is_a?(Arel::Enhance::Node)
219
+ parent.add(local_path, new_node)
220
+ parent[local_path.value]
221
+ else
222
+ new_parent_tree = Visitor.new.accept_with_root(parent_object, parent)
223
+ parent.parent.add(parent.local_path, new_parent_tree)
224
+ new_parent_tree[local_path.value]
225
+ end
226
+ end
227
+ # rubocop:enable Metrics/PerceivedComplexity
228
+ # rubocop:enable Metrics/CyclomaticComplexity
229
+ # rubocop:enable Metrics/AbcSize
230
+ end
231
+ end
232
+ end