Detect links to site’s videos and show title - ruby-on-rails

I have a problem.
I have a Video and Comment model.
If the user inserted the link to video into the comment, it is link to replace with the title from video.
How should I write a method?
def to_show_title_instead_of_the_link
body.gsub!(%r{(videos)\/([0-9])}) {|link| link_to link)}
end
We input:
http://localhost:3000/videos/1
We receive:
test video

It sounds like you want to take a given URL on your site, and find the associated parameters for that route. This will let you get the id for the video in a clean, DRY way (without using a regex that might break later if your route changes). This will then let you look up the model instance and fetch its title field. The Rails method for this task is Rails.application.routes.recognize_path, which returns a hash with the action, controller, and path parameters.
In your view:
# app\views\comments\show.html.erb
# ...
<div class='comment-text'>
replace_video_url_with_anchor_tag_and_title(comment.text)
</div>
# ...
And here is the helper method:
# app\helpers\comments_helper.rb
def replace_video_url_with_anchor_tag_and_title(comment_text)
# assuming links will end with a period, comma, exclamation point, or white space
# this will match all links on your site
# the part in parentheses are relative paths on your site
# \w matches alphanumeric characters and underscores. we also need forward slashes
regex = %r{http://your-cool-site.com(/[\w/]+)[\.,!\s]?}
comment_text.gsub(regex) do |matched|
# $1 gives us the portion of the regex captured by the parentheses
params = Rails.application.routes.recognize_path $1
# if the link we found was a video link, replaced matched string with
# an anchor tag to the video, with the video title as the link text
if params[:controller] == 'video' && params[:action] == 'show'
video = Video.find params[:id]
link_to video.title, video_path(video)
# otherwise just return the string without any modifications
else
matched
end
end
end
I didn't know how to do this off the top of my head, but this is how I figured it out:
1) Google rails reverse route, and the first result was this stackoverflow question: Reverse rails routing: find the the action name from the URL. The answer mentions ActionController::Routing::Routes.recognize_path. I fired up rails console and tried this out, however it is deprecated, and didn't actually work.
2) I then google rails recognize_path. The first search result was the docs, which were not very helpful. The third search result was How do you use ActionDispatch::Routing::RouteSet recognize_path?, whose second solution actually worked.
3) Of course, I then had to go refresh my understanding of Ruby regex syntax and gsub!, and test out the regex I wrote above =)

I did refactoring code and used a gem rinku
def links_in_body(text)
auto_link(text) do |url|
video_id = url.match(/\Ahttps?:\/\/#{request.host_with_port}\/videos\/(\d+)\z/)
if video_id.present?
Video.find(video_id[1]).title
else
truncate(url, length: AppConfig.truncate.length)
end
end
end

Related

Creating a custom URL in Rails 5 Resource's show action

Given the resource Events, I want /events/1 to navigate to /events/1/column_name in the URL bar when entered. Column name is a t.string :name in the Events DB migration. This column_name will need parameterize to be called on it before redirecting. Any ideas on how to get this done?
Example:
If you navigate to https://stackoverflow.com/users/4180797 the URL will automatically become https://stackoverflow.com/users/4180797/james-lowrey after loading. Holds true for 4180797/any-other-txt-here. So "James Lowrey" would be the name column, and it would become james-lowrey after calling parameterize on it.
Another option will be to use pushState with javascript/jquery, for example:
First, add the following script in show.html.erb
<script>
$(document).ready(function() {
history.pushState(null, null, "/events/<%= #event.id %>/<%= #event.name.parameterize %>");
});
</script>
This will change the url on load every time, no matter what comes after /:id.
Second, modify routes to accept anything after /:id (and ignore it):
Rails.application.routes.draw do
#...
get "/events/:id/*other" => "events#show"
end
This option has the added benefit of avoiding redirects if any text after /:id do not match, it will just get the id and replace any other text after that with #event.name.parameterize in the url (without any reload).
Ok this was really tricky to figure out, didn't even know I had access to a lot of these variables before.
First edit your config/routes.rb to accept id/other_stuff
Rails.application.routes.draw do
resources :events
get "/events/:id/*other" => "events#show" #if any txt is trailing id, also send this route to events#show
end
Next modify event_controller.show to redirect if the URL is incorrect.
def show
#redirect if :name is not seen in the URL
if request.format.html?
name_param = #event.name.parameterize
url = request.original_url
id_end_indx = url.index(#event.id.to_s) + (#event.id.to_s).length + 1 #+1 for '/' character
##all URL txt after id does not match name.parameterize
if url[id_end_indx..-1] != #event.name.parameterize
redirect_to "/events/#{#event.id}/#{name_param}"
end
end
end
This will result in the exact same behavior as the Stack Overflow examples gave in the question.

Find all instances of a certain character and replace character & following word with hyperlink

I'm working on a Twitter replica project for a course I'm taking and am attempting to hyperlink any instance of #username in a tweet with a hyperlink to the appropriate username's profile page. Using gsub I can get the replacement to work, but if the tweet has multiple instances of different #usernames, it replaces them all with just the first username. Here's what I have so far:
def twet_link_replacer(twet)
if twet.content.include? "#"
username = twet.content.match(/#(\w+)/)
content_tag :p, twet.content.gsub!(/#(\w+)/, link_to(username, '/twets/'+username.to_s.gsub(/#/,""))).html_safe
else
content_tag :p, twet.content
end
end
Thanks!
You're doing way too much work here. There's no reason to call twet.content.include? "#" when you're using gsub, because gsub will just do nothing if # isn't found. You don't need the if...else, either, for the same reason. Something like this will suffice:
def twet_link_replacer(twet)
new_content = twet.content.gsub(/#(\w+)/) do |username|
link_to(username, "/twets/#{$1}")
end
content_tag :p, new_content
end
This uses a block argument to gsub, letting us replace matches with the result of link_to. Inside the block, username is the entire matched text (e.g. "#Jordan") and $1 is the first (and only) capture group (e.g. "Jordan").
There are a couple other issues with your code. First of all, do not use html_safe on user input. I'm assuming that twet.content comes from user input, and so is inherently unsafe. By trusting it (which is what html_safe implies—it tells Rails, "do not escape this string because I believe it is safe") you're making your app wide open to XSS attacks.
Second, when you're using string concatenation or interpolation (e.g. "/twets/" + username or "/twets/#{username}") to create a URL or path to give to link_to, you're probably making a mistake. It depends on what your routes look like, but if you're using resourceful routes, which you should, then e.g.
# instead of this...
link_to(username, "/users/" + username)
# you can just do this...
link_to(username, user_path(username))
...which will automatically generate a URL for you, and if you change your routes later on you won't have to change your views or helpers because user_path will change automatically along with the routes.
Again, this depends on how you've defined your routes, but it's the direction you should try to go.
The problem is you are using two different regex to match the username. Combine them to get what you want.
def twet_link_replacer(twet)
if twet.content.include? "#"
content_tag :p, twet.content.gsub!(/#(\w+)/, link_to('\1', '/twets/\1')).html_safe
else
content_tag :p, twet.content
end
end

Filter by checkbox with seo friendly url?

I want to add filtering (checkboxes) to our houses listing page. I was thinking to do it by ajax in case of good usabilty, but what is also important that the URL structure is SEO friendly for seo purpose.
An example:
theme checkbox is "nearthesea". The url is domain.com/houses/nearthesea
theme checkbox is "idealfortwo". The url is domain.com/houses/idealfortwo
amenity checkbox is "wifi" The url must be domain.com/houses/idealfortwo/wifi
Has someone ideas, links, posts ect to set this up?
Unfortunately there is no real way to do that, i.e. define a route with an undefined number of path components. You could choose a maximum number, and define a series of routes, like
get '/houses/:f1' => 'houses#index'
get '/houses/:f1/:f2' =>'houses#index'
...
get '/houses/:f1/:f2/:f3/:f4/:f5/:f6' =>'houses#index'
But that would get ugly quick, I'd recommend to go with a URL scheme like this:
get '/houses' => 'houses#index'
Then your URL's could look like:
/houses?filter=goodschool,bbq,northeast
Then in your index action:
def index
if params[:filter]
filters = params[:filter].split(",")
filters.each do |f|
# do something with each term in the filter, I have no idea
end
#houses = House.where(.....???)
else
#houses = House.all
end
end
It won't yield the SEO friendly URLs you're after, but I'm not sure just how this would affect SEO in your situation.

How to identify and make a link out of '#' in user comments like they do on Youtube

I want to look into making functionality like Youtube has on their website when a user types in a comment and if they have something like '#username' the system recognizes the '#' sign and makes a link to the username's comments. I'm developing using Rails 3 but any information regarding about the logic required for this feature would be appreciated, thanks.
EDIT: Additonal question: Where's the best place to put the logic for this? I would feel like the controller would be so if a client doesn't have javascript it'll still work.
I am sure there are multiple (and even better) ways of doing this. How I did it was -
A function to parse the input string: I wrote a helper function -
It traversed every word in the post and for all words starting with an '#'
For every such word it checked if the user existed in the application.
If yes then replace the word with a link to the user profile.
Write the new post (one with the links) to the database.
def mention_users_and_tags(post)
post_with_user_links = ""
words = post.message.split
words.each do |word|
if word[0] =~ /^#.*/ and word.length > 1
if extract_user_if_exists(word[1,word.length-1])
post_with_user_links << "#{link_to "#{word}", :controller => "users", :action => "show", :id => #mentioned_user.id} "
else
post_with_user_links << word << " "
end
end
end
end
EDIT - I think this method should be written in the model and called in the controller. This was the first time I was coding in rails, so I wasn't very sure where everything goes and I wrote this as a helper function. But now I know all business logic goes in the Model. Since mentioning users in posts can be considered part of the business logic, I'd write a function for it in the model.

In a Rails 3 view, how do I detect an URL in a display string and format it as a link to that URL?

I have user generated comments on my site. If a user adds an URL in their comment, I'd like it to be formatted as a link and actually link to that URL. How do I do that?
Rails has an auto_link text helper.
auto_link("Go to http://www.rubyonrails.org and say hello to david#loudthinking.com")
# => "Go to http://www.rubyonrails.org and
# say hello to david#loudthinking.com"
auto_link("Visit http://www.loudthinking.com/ or e-mail david#loudthinking.com", :link => :urls)
# => "Visit http://www.loudthinking.com/
# or e-mail david#loudthinking.com"
In rails 3.1 auto_link has ben removed, its now a standalone gem: https://github.com/tenderlove/rails_autolink
You could also use the "auto_html" gem, see https://github.com/dejan/auto_html.
Disclaimer: Haven't used it myself yet, but it looks like it could do what you want.
I'd also recommend thinking about something like Markdown for your comments. Then you can let the Markdown engine worry about stuff like this for you.
First, you should define a regexp matching http strings, for example
IPv4_PART = /\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]/ # 0-255
REGEXP = %r{
https?:// # http:// or https://
([^\s:#]+:[^\s:#]*#)? # optional username:pw#
( (([^\W_]+\.)*xn--)?[^\W_]+([-.][^\W_]+)*\.[a-z]{2,6}\.? | # domain (including Punycode/IDN)...
#{IPv4_PART}(\.#{IPv4_PART}){3} ) # or IPv4
(:\d{1,5})? # optional port
([/?]\S*)?
}iux
then, suppose the comment body is str, you do:
str.gsub(REGEXP) do |m|
link_to m, m
end

Resources