3

I'm looking for a single regex expression that will match something that is 1 or more characters in length that does not match 500. This is to be used in a Rails routes file, particularly to handle exceptions.

routes.rb

match '/500', to: 'errors#server_error'
match '/:anything', :to => "errors#not_found", :constraints => { :anything => /THE REGEX GOES HERE/ }

I'm a little lost on how to define regex that matches something and simultaneously does not match something else.

Michael Durrant
  • 93,410
  • 97
  • 333
  • 497
Brad Rice
  • 1,334
  • 2
  • 17
  • 36
  • Since anything will be 0 or more characters in length, why can't you simply match things that are not 500? – IanPudney Jun 13 '13 at 13:58
  • You could simply use `(?m)^(?!500$).*$` [demo](http://regex101.com/r/zT0sX5) – HamZa Jun 13 '13 at 14:05
  • Sorry, I mistyped the question. I meant to match things that are **one** ore more characters. Can you think of a way to do that without matching `500`? – Brad Rice Jun 13 '13 at 14:05
  • @BradRice replace `*` with `+`:p – HamZa Jun 13 '13 at 14:05
  • @BradRice Sorry, the `m` modifier in php is not the same as in ruby, try the following `\A(?!500\z).+\z` [demo1](http://rubular.com/r/v6eB29uX0J), [demo2](http://rubular.com/r/n5Pn887irl), [demo3](http://rubular.com/r/UkultjrXS5) – HamZa Jun 13 '13 at 14:10
  • @HamZa that looks perfect! Add an answer for that and I'll accept it. In the answer, could you explain what the `?!` do? Is that like `not` for regex? – Brad Rice Jun 13 '13 at 14:27
  • @BradRice I'll maybe do that later on, `a(?!b)` would match `a` not followed by `b`. So in your case `\A(?!500\z)` this means match begin of line not followed by `500\z`, `\z` means end of line. – HamZa Jun 13 '13 at 14:30
  • @Jerry I'm more concerned with valid Rails routes here, so that shouldn't be a problem. – Brad Rice Jun 13 '13 at 14:30

4 Answers4

2

You can use this regex to check if a string doesn't contain the substring 500:

\A(?>[^5]++|5++(?!00))+\z

if you want to allow 5000 or 5500..., you can do this:

\A(?>[^5]++|5{2,}+|5(?!00(?!0)))+\z

First string explanation:

\A           # begining of the string
(?>          # opening of an atomic group
   [^5]++    # all characters but 5 one or more times (possessive)
  |          # OR
   5++(?!00) # one or more 5 not followed by 00
)+           # closing of the atomic group, one or more times
\z           # end of the string

Possessive quantifiers and atomic groups are here to avoid regex engine backtracks for better performances (the regex fails quickly).

Casimir et Hippolyte
  • 88,009
  • 5
  • 94
  • 125
1

Rails routes are matched in the order that they appear in routes.rb. By putting /500 first (or higher up) in the list, it guarantees that the routes further down do not match /500. You shouldn't have to worry about this.

So, instead, split this into more routes.

match '/500', to: 'errors#server_error'
match '.*500.*', to: 'somewhere else'
match '/:anything', :to => "errors#not_found"

and don't worry about the constraint.

000
  • 26,951
  • 10
  • 71
  • 101
1

Do you really need that Regex? Your route definition

match '/500', to: 'errors#server_error'

will catch all those /500 requests and it means that your next route rule

match '/:anything', :to => "errors#not_found"

won't get them automatically.

trushkevich
  • 2,657
  • 1
  • 28
  • 37
0

As stated in the comments, this regex should do the job \A(?!500\z).+\z.

Explanation

  • \A : match begin of line
  • (?!500\z) : negative lookahead, which means check if there is no 500 + end of line \z
  • .+ : match any character one or more times
  • \z : match end of line
HamZa
  • 14,671
  • 11
  • 54
  • 75