Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to automatically link to objects in text submission in Rails

So, you're in Github filing an issue and you refer to issue #31. Then, while writing this issue, you note that @johnmetta has suggested some possible solutions that he's working on. Then you hit "Submit New Issue" and when you do, "#31" and "@johnmetta" are links, and @johnmetta has been notified, and issue #31 has a notification that it has been referenced.

I realize that there are more than one technologies at work here (Javascript goodies, etc), but what I'm looking for are some examples of how to do this type of thing in the Rails world. It's an interestingly difficult subject to search for.

What I've come up with conceptually is:

  1. Have some identifier, such as # or @ that is reserved
  2. Upon submission, search for that identifier in the appropriate attribute
  3. Upon finding it, search for the appropriate model with a field matching what follows
  4. Once finding that, replace that text string with a link
  5. Optionally, do whatever necessary to notify the referenced object

That said, it seems like it's super simple (explicitly coded, assumes friendly_id).

def prettify_user_links(str, source):
  result = str
  str.scan(/(@\S+)+/).each do |mtch|
    # Strip off whatever identifier we're using
    search_string = mtch[0].gsub('@','')
    # Search for the matching model in the appropriate table
    user = User.find(search_string)
    if user
      # If we find a matching model, create some link text and link it
      link_txt = "<a href=>'#{user.url}'>#{mtch}</a>"
      result.gsub!(search_string, link_txt)
      # Notification. Not sure how/where, maybe with a message bus, or something more brute-force like
      Comment.create :user_id => user.id, :body => "You have been mentioned in #{link_to comment.excerpt, comment} by #{link_to comment.owner, owner}"
  return result

That would be my first cut, but I feel there have to be much more elegant solutions.

An additional aspect to this question: How would you grab a snippit of surrounding text. The brute force way would be to search n words before and m words after that string and grab all of that, then grab that sub-string from the results and do the search. Still, seems like there'd be a more elegant solution.

like image 277
JohnMetta Avatar asked Nov 04 '22 09:11

JohnMetta


1 Answers

What you've described is the basic way; anything else is not terribly more elegant. It's helpful to see it as two parts: one is on receipt of the comment (when you should do notifications) and the other is on display of the comment, when you should do linkification.

This allows you to keep the original comment in its original form, which is helpful.

Perhaps put an after_create (so notifications aren't sent on every edit) on the comment model (assuming a comment model that includes a 'body' field):

[edit: added contextual info]

after_create :notify_mentions

def notify_mentions
  body.scan %r{(.{0,40})@(\w+)(.{0,20})} do |match|
    username = match[1]
    context = [match.first, match.last]
    Notification.send(match, context, self) if User.exists?(:login => username)
  end
end

I use \w+ in place of \S+ because people often say things like:

Hey @JohnMetta, how are you doing?

and \S+ will capture the , which might be wrong. Pulling the @ out of the capture group lets me ignore it during notification.

The context in the above match groups consists of the 40 characters before and 20 characters after the matched username for your snippet. Adjust to taste.

Then when displaying the message, you essentially create a helper something like what you had:

def linkify(body)
  body.gsub %r{@\w+} do |match|
    link_to match, :controller => :users, :action => :show, :id => match
  end
end

#gsub is awesome like that, in that it takes a block and replaces with the contents.

It's not a lot more elegant than what you had, but it should give a pretty decent result.

like image 121
Cyberfox Avatar answered Nov 14 '22 23:11

Cyberfox