Convert to a tag to URL string in Rails

In rails mail processing, I sometimes wanted to convert the URL included in the character string to the a tag.

After a little research, you can easily get the URL of the string by using ʻURI.extract`. .. .. I thought it would be relatively easy to write, but in fact there were quite a few traps and I got stuck, so I decided to write it after reviewing it.

TL;DR The final code was solved by doing the following. How did you get to this? I will explain why this is good later.

def convert_url_to_a_element(text)
  uri_reg = URI.regexp(%w[http https])
  text.gsub(uri_reg) { %{<a href='#{$&}' target='_blank'>#{$&}</a>} }
end

text = 'url1: http://hogehoge.com/hoge url2: http://hogehoge.com/fuga'
convert_url_to_a_element(text)
=> "url1: <a href='http://hogehoge.com/hoge' target='_blank'>http://hogehoge.com/hoge</a> url2: <a href='http://hogehoge.com/fuga' target='_blank'>http://hogehoge.com/fuga</a>"

Anti-pattern

First of all, how to write the wrong process. However, even with this, if the text is as follows, it can be processed without any problem. That's why I didn't immediately notice this writing trap this time. .. ..

def convert_url_to_a_element(text)
  URI.extract(text, %w[http https]).uniq.each do |url|
    sub_text = "<a href='#{url}' target='_blank'>#{url}</a>"
    text.gsub(url, sub_text)
  end
  text
end

text = 'url1: http://hogehoge.com url2: http://fugafuga.com'
convert_url_to_a_element(text)
=> 'url1: http://hogehoge.com url2: http://fugafuga.com'

By using ʻURI.extract`, you can get all the character strings in URL format as shown below.

text = 'url1: http://hogehoge.com url2: http://fugafuga.com'
URI.extract(text, %w[http https])
=> ["http://hogehoge.com", "http://fugafuga.com"]

This is replaced by turning each. However, if you implement it with two types of URLs with the same domain name as shown below. .. ..

text = 'url1: http://hogehoge.com/hoge url2: http://hogehoge.com'
convert_url_to_a_element(text)
=> "url1: <a href='<a href='http://hogehoge.com' target='_blank'>http://hogehoge.com</a>/hoge' target='_blank'><a href='http://hogehoge.com' target='_blank'>http://hogehoge.com</a>/hoge</a> url2: <a href='http://hogehoge.com' target='_blank'>http://hogehoge.com</a>"

Somehow it's really crumbled. .. ..

Cause

The cause is that the text after a tag conversion was also replaced in the second replacement. As you can see, there is a pitfall that ** does not work well if there are two or more URLs with the same host name ** in the above writing method.

counter-measure

You can prevent double substitution by getting a regular expression and replacing it with the gsub pattern using the regular expression instead of turning the string obtained by ʻURI.extract` with each.

def convert_url_to_a_element(text)
  uri_reg = URI.regexp(%w[http https])
  text.gsub(uri_reg) { %{<a href='#{$&}' target='_blank'>#{$&}</a>} }
end

Supplementary memo

About URI.regexp

ʻURI.regexp` is a method that returns the pattern of the URL string of the specified schema as a regular expression. Regular expressions are strings, so you can write them yourself, but this method creates them quickly.

As you can see from the return value, I didn't feel like writing this from scratch. .. ..

URI.regexp(%w[http https])
=> /(?=(?-mix:http|https):)
        ([a-zA-Z][\-+.a-zA-Z\d]*):                           (?# 1: scheme)
        (?:
           ((?:[\-_.!~*'()a-zA-Z\d;?:@&=+$,]|%[a-fA-F\d]{2})(?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*)                    (?# 2: opaque)
        |
           (?:(?:
             \/\/(?:
                 (?:(?:((?:[\-_.!~*'()a-zA-Z\d;:&=+$,]|%[a-fA-F\d]{2})*)@)?        (?# 3: userinfo)
                   (?:((?:(?:[a-zA-Z0-9\-.]|%\h\h)+|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|(?:(?:[a-fA-F\d]{1,4}:)*[a-fA-F\d]{1,4})?::(?:(?:[a-fA-F\d]{1,4}:)*(?:[a-fA-F\d]{1,4}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))?)\]))(?::(\d*))?))? (?# 4: host, 5: port)
               |
                 ((?:[\-_.!~*'()a-zA-Z\d$,;:@&=+]|%[a-fA-F\d]{2})+)                 (?# 6: registry)
               )
             |
             (?!\/\/))                           (?# XXX: '\/\/' is the mark for hostport)
             (\/(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*(?:\/(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*(?:;(?:[\-_.!~*'()a-zA-Z\d:@&=+$,]|%[a-fA-F\d]{2})*)*)*)?                    (?# 7: path)
           )(?:\?((?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))?                 (?# 8: query)
        )
        (?:\#((?:[\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]]|%[a-fA-F\d]{2})*))?                  (?# 9: fragment)
      /x

About gsub

The gsub method itself can be replaced by passing a string instead of a regular expression. In the former case, the acquired URL character string is simply passed by each and replaced, but as a result, if the URL contains the same domain, the character string after a tag conversion is also replaced. Seems to be executed and the string becomes strange.

If you think about it, that's right. .. .. I was worried because I couldn't think of this measure. First of all, gsub

text.gsub!(uri_reg) { %{<a href="#{$&}">#{$&}</a>} }

About URI.extract

First of all, ʻURI.extract` was used first, but you can get only the URL string from the text by specifying the schema. I didn't use it this time, but it seemed to be useful if I wanted to get only the URL string simply.

text = 'aaaaa http://xxx.com/hoge bbbbb http://xxx.com'
URI.extract(text, %w[http https])
=> ["http://xxx.com/hoge" "http://xxx.com"]

Summary

There were twists and turns, but I think it was a good code. If you have any other good writing style, please let me know.

Reference URL

Recommended Posts

Convert to a tag to URL string in Rails
Convert a Java byte array to a string in hexadecimal notation
I tried to convert a string to a LocalDate type in Java
How to insert a video in Rails
Notation to put a variable in a string
[Android] How to convert a character string to resourceId
URL to String conversion
Code to escape a JSON string in Java
Add a tag function to Rails. Use acts-as-taggable-on
Convert a string to a character-by-character array with swift
How to implement a like feature in Rails
How to easily create a pull-down in Rails
[Spring Boot] How to get properties dynamically from a string contained in a URL
Sample code to convert List to List <String> in Java Stream
How to implement a like feature in Ajax in Rails
Throw raw SQL to a read replica in Rails
[How to insert a video in haml with Rails]
How to write a date comparison search in Rails
I want to define a function in Rails Console
How to convert A to a and a to A using AND and OR in Java
How to convert a file to a byte array in Java
[Rails 6] How to set a background image in Rails [CSS]
[Rails] How to load JavaScript in a specific view
Convert String type to Timestamp type
Convert to Ruby Leet string
How to change a string in an array to a number in Ruby
How to store a string from ArrayList to String in Java (Personal)
How to display a graph in Ruby on Rails (LazyHighChart)
Apply CSS to a specific View in Ruby on Rails
Preparing to create a Rails application
Create a new app in Rails
Implement a contact form in Rails
How to install Swiper in Rails
How to rename a model with foreign key constraints in Rails
About the method to convert a character string to an integer / decimal number (cast data) in Java
How to implement search functionality in Rails
[Rails] I want to send data of different models in a form
How to change app name in rails
How to implement a slideshow using slick in Rails (one by one & multiple by one)
[Rails] How to convert from erb to haml
How to create a query using variables in GraphQL [Using Ruby on Rails]
Convert request parameter to Enum in Spring
[Java] Convert Object type null to String type
How to use MySQL in Rails tutorial
How to update user edits in Rails Devise without entering a password
Create your own dialect to convert line feed code to line feed tag in Thymeleaf3
How to get the current date as a string in yyyyMMdd format
Convert SVG files to PNG files in Java
[rails] How to configure routing in resources
[rails] How to create a partial template
How to implement ranking functionality in Rails
Split a string with ". (Dot)" in Java
How to publish a library in jCenter
How to use credentials.yml.enc introduced in Rails 5.2
StackOverflowError to String causes a circular reference
Introduced # 10 devise_token_auth to build a bulletin board API with authentication authorization in Rails 6
Introducing # 15 pundit to build a bulletin board API with authentication authorization in Rails 6
A memo to simply create a form using only HTML and CSS in Rails 6
How to store data simultaneously in a model associated with a nested form (Rails 6.0.0)
How to make a unique combination of data in the rails intermediate table
Convert from C String pointer to Swift String type