Ruby: strip iframe and convert its src to a var - ruby-on-rails

I'm trying to parse a string that has an iframe in it, convert its src attribute to a specially-formatted Ruby variable, then replace the iframe in the string with the Ruby variable formatted in a particular way. So far I've written this:
def video_parse(string)
if string.include?('youtube.com/?v=')
url = 'youtube.com/?v='
string.gsub!('<iframe>.*</iframe>', video_service('YOUTUBE', vid(string, url)))
end
if string.include?('player.vimeo.com/video/')
url = 'player.vimeo.com/video/'
string.gsub!('<iframe>.*</iframe>', video_service('VIMEO', vid(string, url)))
end
string
end
def vid(string, url)
string.split(url).last.split(/['"]/).first
end
def video_service(service, vid)
"*|#{service}:[$vid=#{vid}]|*"
end
But it doesn't replace anything. I suspect my wildcard iframe tag selection is wrong, plus my vid method is a little clunky. How can I get my wildcard in gsub to work correctly? And for bonus points, can I write it a little more efficiently so I'm not parsing string to reformat the src in iframe?
Update
String looks something like this:
string = 'resources rather than creating our luck through innovation.\n<br>\n<br> \n<iframe allowfullscreen=\"\" frameborder=\"0\" height=\"311\" mozallowfullscreen=\"\" name=\"vimeo\" src=\"http://player.vimeo.com/video/222234444\" webkitallowfullscreen=\"\" width=\"550\"></iframe>\n<br>\n<br>\nThat hasn’t stoppe'
Second attempt looks like this, still doesn't replace anything:
def mailchimp_video_parse(string)
if string.include?('youtube.com/?v=')
string.gsub!(iframe) { video_service('YOUTUBE', vid(Regexp.last_match[1])) }
end
if string.include?('player.vimeo.com/video/')
string.gsub!(iframe) { video_service('VIMEO', vid(Regexp.last_match[1])) }
end
string
end
def vid(iframe)
iframe.split!('src').last.split!(/"/).first
end
def iframe
'<iframe.*<\/iframe>'
end
def video_service(service, vid)
"*|#{service}:[$vid=#{vid}]|*"
end
Still nothing.

A bit safer with Nokogiri:
d = Nokogiri::HTML(string)
d.css('iframe').each do |i|
if i['src'] =~ %r{(youtube|vimeo).*?([^/]+)$}i
i.replace(video_service($1.upcase, $2)
end
end
puts d.to_html
(But note that it is less efficient than the pure regexp solution, as Nokogiri will parse the whole HTML.)

The iframe method should be /<iframe.*<\/iframe>/ for it to be properly be recognized as a regex
The Regexp.last_match[1] should be Regexp.last_match[0] in the mailchimp_video_parse method
split! needs to be just split in the vid method (there is no split! method in Ruby)
Edited methods:
def mailchimp_video_parse(string)
if string.include?('youtube.com/?v=')
string.gsub!(iframe) { video_service('YOUTUBE', vid(Regexp.last_match[0])) }
end
if string.include?('player.vimeo.com/video/')
string.gsub!(iframe) { video_service('VIMEO', vid(Regexp.last_match[0])) }
end
string
end
def vid(iframe)
iframe.split('src').last.split(/"/).first
end
def iframe
/<iframe.*<\/iframe>/
end

Related

How to create case(switch) statement with a hash of constants using ruby on rails?

I want to create the equivalent of this code, except using a hash and some meta programming.
def current_verb
case params[:controller]
when "apps"
#current_verb = "MADE "
when "articles"
#current_verb = "LEARNED "
when "articles"
#current_verb = "WONDERED " # Change later to specify articles with a certain tag.
else
#current_verb = "IS "
end
end
Intuitively, I tried something like this, but it seems to be invalid.
So far I have this in the application_controller.rb
class ApplicationController < ActionController::Base
before_action :current_verb
private
verbs = { pages: "IS",
apps: "MADE",
articles: "TAUGHT",
articles: "WONDERED"
}
def current_verb
case params[:controller]
verbs.each |key, value| do
# need this to spit out literal code, not evaluate code
when key
#current_verb = value
end
else
#current_verb = verbs[:pages]
end
end
The tricky part about this is that I don't think I can use define_method or send because the looped portion isn't the entirety of the method. Thanks for the help.
It looks like you're just looking up a key in a hash and returning its value. The only caveat appears to be that if the ke isn't found then you want to return a default value of "IS". I would suggest using Hash#fetch for this.
class ApplicationController < ActionController::Base
VERBS = { pages: "IS",
apps: "MADE",
articles: "TAUGHT",
articles: "WONDERED"
}.stringify_keys.freeze
before_action :set_current_verb
private
def set_current_verb
#current_verb = VERBS.fetch(controller_name) { "IS" }
end
end
Note that I made VERBS a constant so that it would be visible to the set_current_verb method. And I included it above the private designation because constants can't be private anyway. Using the VERBS hash inside of the set_current_verb method would cause it to be evaluated every single time, so the constant is a better solution still. Also, controller_name is preferred over params[:controller] for its expressiveness and because it will avoid returning namespaces should any exist in the future.
Also, the VERBS hash seems to have two identical keys. I assume that's just a typo and can be corrected by you.

How can I convert this hash into workable JSON?

IE10 is returning parameters in what looks like a double conversion to JSON :
=> {"{\"statementId\":"=>
{"\"b3dsecret9-bsecret741-23secreta806c\""=>
{",\"Content-Type\":"=>
{"\"application/json\""=>
{",\"content\":"=>
{"\"{\\\"context\\\":{\\\"registration\\\":\\\"27\\\",\\\"contextActivities\\\":{\\\"parent\\\":{\\\"id\\\":\\\"6LHIJumrnmV_course_id\\\"},\\\"grouping\\\":{\\\"id\\\":\\\"6LHIJumrnmV_course_id\\\"}}},\\\"actor\\\":213,\\\"verb\\\":\\\"attempted\\\",\\\"object\\\":{\\\"id\\\":\\\"6LHIJumrnmV_course_id\\\",\\\"definition\\\":{\\\"name\\\":{\\\"und\\\":\\\"\\\"},\\\"type\\\":\\\"Course\\\",\\\"description\\\":{\\\"und\\\":\\\"\\\"}}}}\""=>
{",\"registration\":"=>
{"\"27\""=>
{",\"AWSAccessKeyId\":"=>
{"\"secretIAIVsecretPHsecretQ\""=>
{",\"Signature\":"=>
{"\"PJ /OW K5secretasyXsecret5A"=>
"\"],\"Expires\":[\"1396873090\"],\"Authorization\":[\"\"]}"}}}}}}}}}}},
"method"=>"PUT",
"controller"=>"quizzes",
"action"=>"statements"}
IE Edge, Safari, Chrome, and Firefox return my params like this :
=> {"registration"=>["27"],
"Content-Type"=>["application/json"],
"Signature"=>["secretkqPJGPEsecret01ksecret"],
"AWSAccessKeyId"=>["Asecret6secretPHsecretQ"],
"statementId"=>["5919c4f4-b71c-40dd-81dc-ab63cfc824bd"],
"Expires"=>["1396873699"],
"Authorization"=>[""],
"content"=>
["{\"object\":{\"definition\":{\"type\":\"Course\",\"name\":{\"und\":\"\"},\"description\":{\"und\":\"\"}},\"id\":\"6LHIJumrnmV_course_id\"},\"verb\":\"attempted\",\"context\":{\"registration\":\"27\",\"contextActivities\":{\"parent\":{\"id\":\"6LHIJumrnmV_course_id\"},\"grouping\":{\"id\":\"6LHIJumrnmV_course_id\"}}},\"actor\":213}"],
"method"=>"PUT",
"controller"=>"quizzes",
"action"=>"statements",
"quiz"=>{}
So my code parses this conveniently doing this :
content = params[:content] || params['content']
response = JSON.parse(content.first)
And presto! I have a workable piece of content. But with that first aforementioned Hash, I'm not sure how to convert that. Should I just be thinking of using a match/gsub technique to remove all those evil forward slashes? Is there a way to decipher that into something that looks like my latter hash?
Starting from your answer, I would parse key using the escape_utils gem:
require 'escape_utils'
def nested_hash_value(obj,key)
# nested_hash_value(params, ",\"content\":")
if obj.respond_to?(:key?) && obj.key?(key)
obj[key]
elsif obj.respond_to?(:each)
r = nil
obj.find{ |*a| r=nested_hash_value(a.last,key) }
r
end
end
extract = nested_hash_value(params, ",\"content\":")
key = extract.keys.first
response = JSON.parse EscapeUtils.unescape_javascript(key).gsub(/^"|"$/,'')
This avoids using the evil eval thing.
More generally, I think you should build your processing in this way:
def smell_of_ie_weirdness?
# Detects whether the request seems like the one sent by IE 10,
# something like params keys formatting checking etc.
end
def extracted_response
if smell_of_ie_weirdness?
# Do weird stuff
extract_response_for_weird_ie
else
# Be clean and polite
extract_response
end
end
Well this gives me an answer.. it's not the best. It's definitely a hack. But I feel like it's the only thing I got.
def nested_hash_value(obj,key)
# nested_hash_value(params, ",\"content\":")
if obj.respond_to?(:key?) && obj.key?(key)
obj[key]
elsif obj.respond_to?(:each)
r = nil
obj.find{ |*a| r=nested_hash_value(a.last,key) }
r
end
end
extract = nested_hash_value(params, ",\"content\":")
key = extract.keys.first
decoded_hash = key.to_s.gsub(/\\/,'').gsub(/\"/,"'").gsub(/'$|^'/,'').gsub(':','=>')
response = eval decoded_hash
If I do a comparison == between the two outputs they return true .
Then I just throw it all in a rescue block..
begin
content = params[:content] || params['content']
response = JSON.parse(content.first)
rescue
perform_fd_up_IE_fixer # :)
end

Rails: using gsub to find and make links

I've got a text area called body in a model. I'd like to change every image to a link to that image. I've got this method which is supposed to do that but it doesn't seem to work. It breaks at each image.
def get_images_in(body)
body_with_links = body.gsub( %r{http://[^\s<]+} ) do |url|
if url[/(?:png|jpe?g|gif|svg)$/]
"<a href='#{url}' target='_blank'><img src='#{url}' /></a>"
end
end
return body_with_links
end
Any ideas? Thank-you!
UPDATE
Here's a link to a gist with sample body text.
First things first, you don't need to use a return statement in ruby. Ruby will return the last thing by default. In your case, this is the string returned from the gsub:
def wrap_image_with_link(body)
body.gsub( %r{http://[^\s<]+} ) do |url|
if url[/(?:png|jpe?g|gif|svg)$/]
"<a href='#{url}' target='_blank'><img src='#{url}' /></a>"
end
end
end
This still isn't quite right. I would then focus my initial regular expression on the img tag:
def wrap_image_with_link(body)
body.gsub(/\<img.*src\=['"](.*)['"].*\/\>/) do |url|
"<a href='#{url}' target='_blank'><img src='#{url}' /></a>"
end
end
Rails has a couple helpers to clean this up.
def wrap_images_with_links(body)
body.gsub(/\<img.*src\=['"](.*)['"].*\/\>/) do
link_to(image_tag($1), $1, :target => '_blank')
end
end
You probably want to make the reg ex case insensitive:
def wrap_images_with_links(body)
body.gsub(/.*\<img.*src\=['"](.*)['"].*\/\>/i) do
link_to(image_tag($1), $1, :target => '_blank')
end
end
So gsub, as opposed to sub, changes every instance of matched reg-ex, so need to tweak the reg ex slightly to accommodate more explicit matches:
def wrap_images_with_links(body)
body.gsub(/\<img[\s\w"=]+src\=['"](http[s]?:\/\/[\w.\/]+)['"]\s?[\/]?\>/i) do
link_to(image_tag($1), $1, :target => '_blank')
end
end
Depending on the complexities of your urls, you might need to tweak the reg-ex to support ? or characters other than white space, but this work pretty well.

How to simplify this Rails code -- gets first line from file, parses it and downcases each element

fields = CSV.parse(File.open(filename).first)[0]
fields.each_with_index do |field, i|
fields[i] = field.downcase
end
I want to get the first line from the line, parse it as CSV and make each element lowercase.
This code seems too redundant to me. Any suggestions?
You can make the looping stuff a bit more concise if you wish:
fields.map!(&:downcase)
or even:
fields = CSV.parse(File.open(filename).first)[0].map(&:downcase)
I think you're leaving a file handle hanging there too so you might want to try something like:
fields = []
File.open(filename) do |f|
fields = CSV.parse(f.readline)[0].map(&:downcase)
end
I don't think there's anything wrong with what you have but you could say this:
fields = CSV.parse(File.open(filename, 'r').first).first.map(&:downcase)
Or you could make it easier to read with some methods:
def first_line_of(filename)
File.open(filename, 'r').first
end
def csv_to_array(string)
CSV.parse(string).first
end
def downcase(a)
a.map(&:downcase)
end
fields = downcase csv_to_array first_line_of filename

Ruby shorthand for "use this if it isn't blank, otherwise use that"

I have the following code:
url = file.s3_url.blank? ? file.url : file.s3_url
Is there a shorter way to write this?
Thanks!
There is an abstraction for that in ActiveSupport, Object#presence:
url = file.s3_url.presence || file.url
Well, you could write a method on whatever file is an instance of (say S3File):
class S3File
def real_url
self.s3_url.blank? ? self.url : self.s3_url
end
#...
end
Then it gets real simple:
url = file.real_url
As #tokland said, you could monkey patch Object to use an or_if method, which would be implemented like this:
class Object
def or_if(method, val = nil)
self.send(method) ? (block_given? ? yield : val) : self
end
end
This way, you'd be able to do this:
url = file.s3_url.or_if(:blank?) { file.url }
Or this:
url = file.s3_url.or_if(:blank?, file.url)
Maybe, you can do the following:
url = file.s3_url || file.url
This code will only use file.url if file.s3_url is nil. That means that an empty string won't work though. If you want to ensure that an empty string is not used, like you do in your example, then there isn't a shorter way to do this.
I use a utility method.
def until_not_blank(*args)
args.find {|a| !a.blank? }
end
url = until_not_blank(file.s3_url, file.url)
I typically just put that in my ApplicationController and make it a helper. If you wanted it to be available globally, you could put it in Kernel, or you could monkey-patch Array
class Array
def first_not_blank
find {|a| !a.blank?}
end
end
url = [file.s3_url, file.url].first_not_blank
url = (file.s3_url.blank? && file.s3_url) || file.url
Looks cool but I actually find it confusing and prefer good old ternary if.

Resources