niceql 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f9f1dc89c0c7353d4b8c63056d43968b0ad2f71a602d4e50ba5a48bfa568ed0
4
- data.tar.gz: 9fc1ea32fc0ed3053702b3014dbb270457af028e61b6b1bb63ff6c28005d8ce4
3
+ metadata.gz: cfbeddac683e41bcbbc3d8fc2c42fcbb505a6d129c58dc9653a93650317c8125
4
+ data.tar.gz: e4de50a94af55592a2f31ced37b6a1986abdea37ca462ce529bd7bdfabde08be
5
5
  SHA512:
6
- metadata.gz: e0b76948b56fe949aac0b5582081d42a60e35dede01332772c4fb233bd8c14bd2cf1d029187d25e0bb85c20d660f29763d401f2283695f28da36c0053293411c
7
- data.tar.gz: 2be8cfca05e187114624a2c70b9318c2dd161c42aca5c3ad90456e2ea2a46cda59de1aadd4d22e8e1111155078fa03b986ce4e7624e809776934eb0c0c94dcb5
6
+ metadata.gz: 64dc4d5869ee5e79b408b99fe963bcafd595d44a8c8495e72429d104478ee6811b8eba211ef13527e060746a52e4c4a6c192e17aef95237c6d79248b0a421a12
7
+ data.tar.gz: 865161e1af50386a06a98ba9e1af708f388f44f96f2566fb3f0695ac4756a66030731c8f8e603d5f7c88a009d4a6f4d840c0c2d9f140e81500411cccba17ee41
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # 0.3.0
2
+ * ruby forced to >= 2.4
3
+ * String match extension no longer needed
4
+ * fixed issue with missing HINT and DETAIL string ( https://github.com/alekseyl/niceql/issues/18 )
5
+ * both new and old activerecord StatementInvalid formats supported
6
+ * major prettify_pg_err refactoring ( much cleaner code now )
7
+
1
8
  # 0.2.0
2
9
  * Fix to issue https://github.com/alekseyl/niceql/pull/17#issuecomment-924278172. ActiveRecord base config is no longer a hash,
3
10
  so it does not have dig method, hence it's breaking the ar_using_pg_adapter? method.
@@ -1,3 +1,3 @@
1
1
  module Niceql
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.1"
3
3
  end
data/lib/niceql.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require "niceql/version"
2
- require 'niceql/string'
3
2
 
4
3
  module Niceql
5
4
 
@@ -45,167 +44,181 @@ module Niceql
45
44
  SQL_COMMENTS_CLEARED = /(\s*?--.+\s{1})|(\s*$\s*\/\*[^\/\*]*\*\/\s{1})/
46
45
  COMMENT_CONTENT = /[\S]+[\s\S]*[\S]+/
47
46
 
48
- def self.config
49
- Niceql.config
50
- end
47
+ class << self
48
+ def config
49
+ Niceql.config
50
+ end
51
51
 
52
- def self.prettify_err(err)
53
- prettify_pg_err( err.to_s )
54
- end
52
+ def prettify_err(err, original_sql_query = nil)
53
+ prettify_pg_err( err.to_s, original_sql_query )
54
+ end
55
55
 
56
56
 
57
- # Postgres error output:
58
- # ERROR: VALUES in FROM must have an alias
59
- # LINE 2: FROM ( VALUES(1), (2) );
60
- # ^
61
- # HINT: For example, FROM (VALUES ...) [AS] foo.
62
-
63
- # May go without HINT or DETAIL:
64
- # ERROR: column "usr" does not exist
65
- # LINE 1: SELECT usr FROM users ORDER BY 1
66
- # ^
67
-
68
- # ActiveRecord::StatementInvalid will add original SQL query to the bottom like this:
69
- # ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "usr" does not exist
70
- # LINE 1: SELECT usr FROM users ORDER BY 1
71
- # ^
72
- #: SELECT usr FROM users ORDER BY 1
73
-
74
- # prettify_pg_err parses ActiveRecord::StatementInvalid string,
75
- # but you may use it without ActiveRecord either way:
76
- # prettify_pg_err( err + "\n" + sql ) OR prettify_pg_err( err, sql )
77
- # don't mess with original sql query, or prettify_pg_err will deliver incorrect results
78
- def self.prettify_pg_err(err, original_sql_query = nil)
79
- return err if err[/LINE \d+/].nil?
80
- err_line_num = err[/LINE \d+/][5..-1].to_i
81
-
82
- #
83
- start_sql_line = err.lines[3][/(HINT|DETAIL)/] ? 4 : 3
84
- err_body = start_sql_line < err.lines.length ? err.lines[start_sql_line..-1] : original_sql_query&.lines
85
-
86
-
87
- # this means original query is missing so it's nothing to prettify
88
- return err unless err_body
89
-
90
- err_quote = ( err.lines[1][/\.\.\..+\.\.\./] && err.lines[1][/\.\.\..+\.\.\./][3..-4] ) ||
91
- ( err.lines[1][/\.\.\..+/] && err.lines[1][/\.\.\..+/][3..-1] )
92
-
93
- # line[2] is err carret line i.e.: ' ^'
94
- # err.lines[1][/LINE \d+:/].length+1..-1 - is a position from error quote begin
95
- err_carret_line = err.lines[2][err.lines[1][/LINE \d+:/].length+1..-1]
96
- # err line will be painted in red completely, so we just remembering it and use
97
- # to replace after paiting the verbs
98
- err_line = err_body[err_line_num-1]
99
-
100
- # when err line is too long postgres quotes it part in double '...'
101
- if err_quote
102
- err_quote_carret_offset = err_carret_line.length - err.lines[1].index( '...' ) + 3
103
- err_carret_line = ' ' * ( err_line.index( err_quote ) + err_quote_carret_offset ) + "^\n"
104
- end
57
+ # Postgres error output:
58
+ # ERROR: VALUES in FROM must have an alias
59
+ # LINE 2: FROM ( VALUES(1), (2) );
60
+ # ^
61
+ # HINT: For example, FROM (VALUES ...) [AS] foo.
105
62
 
106
- err_carret_line = " " + err_carret_line if err_body[0].start_with?(': ')
107
- # if mistake is on last string than err_line.last != \n so we need to prepend \n to carret line
108
- err_carret_line = "\n" + err_carret_line unless err_line[-1] == "\n"
63
+ # May go without HINT or DETAIL:
64
+ # ERROR: column "usr" does not exist
65
+ # LINE 1: SELECT usr FROM users ORDER BY 1
66
+ # ^
109
67
 
110
- #colorizing verbs and strings
111
- err_body = err_body.join.gsub(/#{VERBS}/ ) { |verb| StringColorize.colorize_verb(verb) }
112
- .gsub(STRINGS){ |str| StringColorize.colorize_str(str) }
68
+ # ActiveRecord::StatementInvalid will add original SQL query to the bottom like this:
69
+ # ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "usr" does not exist
70
+ # LINE 1: SELECT usr FROM users ORDER BY 1
71
+ # ^
72
+ #: SELECT usr FROM users ORDER BY 1
113
73
 
114
- #reassemling error message
115
- err_body = err_body.lines
116
- err_body[err_line_num-1]= StringColorize.colorize_err( err_line )
117
- err_body.insert( err_line_num, StringColorize.colorize_err( err_carret_line ) )
74
+ # prettify_pg_err parses ActiveRecord::StatementInvalid string,
75
+ # but you may use it without ActiveRecord either way:
76
+ # prettify_pg_err( err + "\n" + sql ) OR prettify_pg_err( err, sql )
77
+ # don't mess with original sql query, or prettify_pg_err will deliver incorrect results
78
+ def prettify_pg_err(err, original_sql_query = nil)
79
+ return err if err[/LINE \d+/].nil?
80
+ err_line_num = err[/LINE \d+/][5..-1].to_i
81
+ # LINE 1: SELECT usr FROM users ORDER BY 1
82
+ err_address_line = err.lines[1]
118
83
 
119
- err.lines[0..start_sql_line-1].join + err_body.join
120
- end
84
+ start_sql_line = 3 if err.lines.length <= 3
85
+ # error not always contains HINT
86
+ start_sql_line ||= err.lines[3][/(HINT|DETAIL)/] ? 4 : 3
87
+ sql_body = start_sql_line < err.lines.length ? err.lines[start_sql_line..-1] : original_sql_query&.lines
121
88
 
122
- def self.prettify_sql( sql, colorize = true )
123
- indent = 0
124
- parentness = []
125
-
126
- sql = sql.split( SQL_COMMENTS ).each_slice(2).map{ | sql_part, comment |
127
- # remove additional formatting for sql_parts but leave comment intact
128
- [sql_part.gsub(/[\s]+/, ' '),
129
- # comment.match?(/\A\s*$/) - SQL_COMMENTS gets all comment content + all whitespaced chars around
130
- # so this sql_part.length == 0 || comment.match?(/\A\s*$/) checks does the comment starts from new line
131
- comment && ( sql_part.length == 0 || comment.match?(/\A\s*$/) ? "\n#{comment[COMMENT_CONTENT]}\n" : comment[COMMENT_CONTENT] ) ]
132
- }.flatten.join(' ')
133
-
134
- sql.gsub!(/ \n/, "\n")
135
-
136
- sql.gsub!(STRINGS){ |str| StringColorize.colorize_str(str) } if colorize
137
-
138
- first_verb = true
139
- prev_was_comment = false
140
-
141
- sql.gsub!( /(#{VERBS}|#{BRACKETS}|#{SQL_COMMENTS_CLEARED})/) do |verb|
142
- if 'SELECT' == verb
143
- indent += config.indentation_base if !config.open_bracket_is_newliner || parentness.last.nil? || parentness.last[:nested]
144
- parentness.last[:nested] = true if parentness.last
145
- add_new_line = !first_verb
146
- elsif verb == '('
147
- next_closing_bracket = Regexp.last_match.post_match.index(')')
148
- # check if brackets contains SELECT statement
149
- add_new_line = !!Regexp.last_match.post_match[0..next_closing_bracket][/SELECT/] && config.open_bracket_is_newliner
150
- parentness << { nested: add_new_line }
151
- elsif verb == ')'
152
- # this also covers case when right bracket is used without corresponding left one
153
- add_new_line = parentness.last.nil? || parentness.last[:nested]
154
- indent -= ( parentness.last.nil? ? 2 * config.indentation_base : (parentness.last[:nested] ? config.indentation_base : 0) )
155
- indent = 0 if indent < 0
156
- parentness.pop
157
- elsif verb[POSSIBLE_INLINER]
158
- # in postgres ORDER BY can be used in aggregation function this will keep it
159
- # inline with its agg function
160
- add_new_line = parentness.last.nil? || parentness.last[:nested]
161
- else
162
- add_new_line = verb[/(#{INLINE_VERBS})/].nil?
163
- end
89
+ # this means original query is missing so it's nothing to prettify
90
+ return err unless sql_body
164
91
 
165
- # !add_new_line && previous_was_comment means we had newlined comment, and now even
166
- # if verb is inline verb we will need to add new line with indentation BUT all
167
- # inliners match with a space before so we need to strip it
168
- verb.lstrip! if !add_new_line && prev_was_comment
92
+ # err line will be painted in red completely, so we just remembering it and use
93
+ # to replace after painting the verbs
94
+ err_line = sql_body[err_line_num - 1]
169
95
 
170
- add_new_line = prev_was_comment unless add_new_line
171
- add_indent = !first_verb && add_new_line
172
96
 
173
- if verb[SQL_COMMENTS_CLEARED]
174
- verb = verb[COMMENT_CONTENT]
175
- prev_was_comment = true
176
- else
177
- first_verb = false
178
- prev_was_comment = false
97
+ #colorizing verbs and strings
98
+ colorized_sql_body = sql_body.join.gsub(/#{VERBS}/ ) { |verb| StringColorize.colorize_verb(verb) }
99
+ .gsub(STRINGS){ |str| StringColorize.colorize_str(str) }
100
+
101
+ #reassemling error message
102
+ err_body = colorized_sql_body.lines
103
+ # replacing colorized line contained error and adding caret line
104
+ err_body[err_line_num - 1]= StringColorize.colorize_err( err_line )
105
+
106
+ err_caret_line = extract_err_caret_line( err_address_line, err_line, sql_body, err )
107
+ err_body.insert( err_line_num, StringColorize.colorize_err( err_caret_line ) )
108
+
109
+ err.lines[0..start_sql_line-1].join + err_body.join
110
+ end
111
+
112
+ def prettify_sql( sql, colorize = true )
113
+ indent = 0
114
+ parentness = []
115
+
116
+ sql = sql.split( SQL_COMMENTS ).each_slice(2).map{ | sql_part, comment |
117
+ # remove additional formatting for sql_parts but leave comment intact
118
+ [sql_part.gsub(/[\s]+/, ' '),
119
+ # comment.match?(/\A\s*$/) - SQL_COMMENTS gets all comment content + all whitespaced chars around
120
+ # so this sql_part.length == 0 || comment.match?(/\A\s*$/) checks does the comment starts from new line
121
+ comment && ( sql_part.length == 0 || comment.match?(/\A\s*$/) ? "\n#{comment[COMMENT_CONTENT]}\n" : comment[COMMENT_CONTENT] ) ]
122
+ }.flatten.join(' ')
123
+
124
+ sql.gsub!(/ \n/, "\n")
125
+
126
+ sql.gsub!(STRINGS){ |str| StringColorize.colorize_str(str) } if colorize
127
+
128
+ first_verb = true
129
+ prev_was_comment = false
130
+
131
+ sql.gsub!( /(#{VERBS}|#{BRACKETS}|#{SQL_COMMENTS_CLEARED})/) do |verb|
132
+ if 'SELECT' == verb
133
+ indent += config.indentation_base if !config.open_bracket_is_newliner || parentness.last.nil? || parentness.last[:nested]
134
+ parentness.last[:nested] = true if parentness.last
135
+ add_new_line = !first_verb
136
+ elsif verb == '('
137
+ next_closing_bracket = Regexp.last_match.post_match.index(')')
138
+ # check if brackets contains SELECT statement
139
+ add_new_line = !!Regexp.last_match.post_match[0..next_closing_bracket][/SELECT/] && config.open_bracket_is_newliner
140
+ parentness << { nested: add_new_line }
141
+ elsif verb == ')'
142
+ # this also covers case when right bracket is used without corresponding left one
143
+ add_new_line = parentness.last.nil? || parentness.last[:nested]
144
+ indent -= ( parentness.last.nil? ? 2 * config.indentation_base : (parentness.last[:nested] ? config.indentation_base : 0) )
145
+ indent = 0 if indent < 0
146
+ parentness.pop
147
+ elsif verb[POSSIBLE_INLINER]
148
+ # in postgres ORDER BY can be used in aggregation function this will keep it
149
+ # inline with its agg function
150
+ add_new_line = parentness.last.nil? || parentness.last[:nested]
151
+ else
152
+ add_new_line = verb[/(#{INLINE_VERBS})/].nil?
153
+ end
154
+
155
+ # !add_new_line && previous_was_comment means we had newlined comment, and now even
156
+ # if verb is inline verb we will need to add new line with indentation BUT all
157
+ # inliners match with a space before so we need to strip it
158
+ verb.lstrip! if !add_new_line && prev_was_comment
159
+
160
+ add_new_line = prev_was_comment unless add_new_line
161
+ add_indent = !first_verb && add_new_line
162
+
163
+ if verb[SQL_COMMENTS_CLEARED]
164
+ verb = verb[COMMENT_CONTENT]
165
+ prev_was_comment = true
166
+ else
167
+ first_verb = false
168
+ prev_was_comment = false
169
+ end
170
+
171
+ verb = StringColorize.colorize_verb(verb) if !%w[( )].include?(verb) && colorize
172
+
173
+ subs = ( add_indent ? indent_multiline(verb, indent) : verb)
174
+ !first_verb && add_new_line ? "\n" + subs : subs
179
175
  end
180
176
 
181
- verb = StringColorize.colorize_verb(verb) if !['(', ')'].include?(verb) && colorize
177
+ # clear all spaces before newlines, and all whitespaces before strings endings
178
+ sql.tap{ |slf| slf.gsub!( /\s+\n/, "\n" ) }.tap{ |slf| slf.gsub!(/\s+\z/, '') }
179
+ end
182
180
 
183
- subs = ( add_indent ? indent_multiline(verb, indent) : verb)
184
- !first_verb && add_new_line ? "\n" + subs : subs
181
+ def prettify_multiple( sql_multi, colorize = true )
182
+ sql_multi.split( /(?>#{SQL_COMMENTS})|(\;)/ ).inject(['']) { |queries, pattern|
183
+ queries.last << pattern
184
+ queries << '' if pattern == ';'
185
+ queries
186
+ }.map!{ |sql|
187
+ # we were splitting by comments and ;, so if next sql start with comment we've got a misplaced \n\n
188
+ sql.match?(/\A\s+\z/) ? nil : prettify_sql( sql, colorize )
189
+ }.compact.join("\n\n")
185
190
  end
186
191
 
187
- # clear all spaces before newlines, and all whitespaces before string end
188
- sql.tap{ |slf| slf.gsub!( /\s+\n/, "\n" ) }.tap{ |slf| slf.gsub!(/\s+\z/, '') }
189
- end
192
+ private_class_method
193
+ def indent_multiline( verb, indent )
194
+ if verb.match?(/.\s*\n\s*./)
195
+ verb.lines.map!{|ln| ln.prepend(' ' * indent)}.join("\n")
196
+ else
197
+ verb.prepend(' ' * indent)
198
+ end
199
+ end
190
200
 
191
- def self.prettify_multiple( sql_multi, colorize = true )
192
- sql_multi.split( /(?>#{SQL_COMMENTS})|(\;)/ ).inject(['']) { |queries, pattern|
193
- queries.last << pattern
194
- queries << '' if pattern == ';'
195
- queries
196
- }.map!{ |sql|
197
- # we were splitting by comments and ;, so if next sql start with comment we've got a misplaced \n\n
198
- sql.match?(/\A\s+\z/) ? nil : prettify_sql( sql, colorize )
199
- }.compact.join("\n\n")
200
- end
201
+ def extract_err_caret_line( err_address_line, err_line, sql_body, err )
202
+ # LINE could be quoted ( both sides and sometimes only from one ):
203
+ # "LINE 1: ...t_id\" = $13 AND \"products\".\"carrier_id\" = $14 AND \"product_t...\n",
204
+ err_quote = (err_address_line.match(/\.\.\.(.+)\.\.\./) || err_address_line.match(/\.\.\.(.+)/) ).try(:[], 1)
205
+
206
+ # line[2] is original err caret line i.e.: ' ^'
207
+ # err_address_line[/LINE \d+:/].length+1..-1 - is a position from error quote begin
208
+ err_caret_line = err.lines[2][err_address_line[/LINE \d+:/].length+1..-1]
209
+
210
+ # when err line is too long postgres quotes it in double '...'
211
+ # so we need to reposition caret against original line
212
+ if err_quote
213
+ err_quote_caret_offset = err_caret_line.length - err_address_line.index( '...' ).to_i + 3
214
+ err_caret_line = ' ' * ( err_line.index( err_quote ) + err_quote_caret_offset ) + "^\n"
215
+ end
201
216
 
202
- private
203
- def self.indent_multiline( verb, indent )
204
- #
205
- if verb.match?(/.\s*\n\s*./)
206
- verb.lines.map!{|ln| "#{' ' * indent}" + ln}.join("\n")
207
- else
208
- "#{' ' * indent}" + verb.to_s
217
+ # older versions of ActiveRecord were adding ': ' before an original query :(
218
+ err_caret_line.prepend(' ') if sql_body[0].start_with?(': ')
219
+ # if mistake is on last string than err_line.last != \n then we need to prepend \n to caret line
220
+ err_caret_line.prepend("\n") unless err_line[-1] == "\n"
221
+ err_caret_line
209
222
  end
210
223
  end
211
224
  end
@@ -228,7 +241,9 @@ module Niceql
228
241
 
229
242
  module ErrorExt
230
243
  def to_s
231
- Niceql.config.prettify_pg_errors ? Prettifier.prettify_err(super) : super
244
+ # older rails version do not provide sql as a standalone query, instead they
245
+ # deliver joined message
246
+ Niceql.config.prettify_pg_errors ? Prettifier.prettify_err(super, try(:sql) ) : super
232
247
  end
233
248
  end
234
249
 
@@ -280,5 +295,3 @@ module Niceql
280
295
  end
281
296
 
282
297
  end
283
-
284
-
data/niceql.gemspec CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["alekseyl"]
10
10
  spec.email = ["leshchuk@gmail.com"]
11
11
 
12
- spec.summary = %q{This is simple and nice sql prettifier, it splits, indent and colorize SQL query and PG errors if any }
13
- spec.description = %q{This is simple and nice sql prettifier, it splits, indent and colorize SQL query and PG error if any }
12
+ spec.summary = %q{This is simple and nice gem for sql prettifying and formatting. Niceql splits, indent and colorize SQL query and PG errors if any }
13
+ spec.description = %q{This is simple and nice gem for sql prettifying and formatting. Niceql splits, indent and colorize SQL query and PG errors if any. Seemless activerecord integration }
14
14
  spec.homepage = "https://github.com/alekseyl/niceql"
15
15
  spec.license = "MIT"
16
16
 
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- spec.required_ruby_version = '>= 2.3'
33
+ spec.required_ruby_version = '>= 2.4'
34
34
  spec.add_development_dependency "activerecord", ">= 6.1"
35
35
 
36
36
  spec.add_development_dependency "bundler", ">= 1"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: niceql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - alekseyl
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-09-22 00:00:00.000000000 Z
11
+ date: 2021-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -122,8 +122,9 @@ dependencies:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
124
  version: '0'
125
- description: 'This is simple and nice sql prettifier, it splits, indent and colorize
126
- SQL query and PG error if any '
125
+ description: 'This is simple and nice gem for sql prettifying and formatting. Niceql
126
+ splits, indent and colorize SQL query and PG errors if any. Seemless activerecord
127
+ integration '
127
128
  email:
128
129
  - leshchuk@gmail.com
129
130
  executables: []
@@ -147,7 +148,6 @@ files:
147
148
  - lib/generators/niceql/install_generator.rb
148
149
  - lib/generators/templates/niceql_initializer.rb
149
150
  - lib/niceql.rb
150
- - lib/niceql/string.rb
151
151
  - lib/niceql/version.rb
152
152
  - niceql.gemspec
153
153
  - to_niceql.png
@@ -164,7 +164,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
164
164
  requirements:
165
165
  - - ">="
166
166
  - !ruby/object:Gem::Version
167
- version: '2.3'
167
+ version: '2.4'
168
168
  required_rubygems_version: !ruby/object:Gem::Requirement
169
169
  requirements:
170
170
  - - ">="
@@ -174,6 +174,6 @@ requirements: []
174
174
  rubygems_version: 3.1.4
175
175
  signing_key:
176
176
  specification_version: 4
177
- summary: This is simple and nice sql prettifier, it splits, indent and colorize SQL
178
- query and PG errors if any
177
+ summary: This is simple and nice gem for sql prettifying and formatting. Niceql splits,
178
+ indent and colorize SQL query and PG errors if any
179
179
  test_files: []
data/lib/niceql/string.rb DELETED
@@ -1,5 +0,0 @@
1
- class String
2
- def match?(pattern)
3
- self =~ pattern
4
- end
5
- end unless String.method_defined?(:match?)