arel_toolkit 0.3.0 → 0.4.0

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