How can I convert this hash into workable JSON? - ruby-on-rails

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

Related

How to DRY a list of functions in ruby that are differ only by a single line of code?

I have a User model in a ROR application that has multiple methods like this
#getClient() returns an object that knows how to find certain info for a date
#processHeaders() is a function that processes output and updates some values in the database
#refreshToken() is function that is called when an error occurs when requesting data from the object returned by getClient()
def transactions_on_date(date)
if blocked?
# do something
else
begin
output = getClient().transactions(date)
processHeaders(output)
return output
rescue UnauthorizedError => ex
refresh_token()
output = getClient().transactions(date)
process_fitbit_rate_headers(output)
return output
end
end
end
def events_on_date(date)
if blocked?
# do something
else
begin
output = getClient().events(date)
processHeaders(output)
return output
rescue UnauthorizedError => ex
refresh_token()
output = getClient().events(date)
processHeaders(output)
return output
end
end
end
I have several functions in my User class that look exactly the same. The only difference among these functions is the line output = getClient().something(date). Is there a way that I can make this code look cleaner so that I do not have a repetitive list of functions.
The answer is usually passing in a block and doing it functional style:
def handle_blocking(date)
if blocked?
# do something
else
begin
output = yield(date)
processHeaders(output)
output
rescue UnauthorizedError => ex
refresh_token
output = yield(date)
process_fitbit_rate_headers(output)
output
end
end
end
Then you call it this way:
handle_blocking(date) do |date|
getClient.something(date)
end
That allows a lot of customization. The yield call executes the block of code you've supplied and passes in the date argument to it.
The process of DRYing up your code often involves looking for patterns and boiling them down to useful methods like this. Using a functional approach can keep things clean.
Yes, you can use Object#send: getClient().send(:method_name, date).
BTW, getClient is not a proper Ruby method name. It should be get_client.
How about a combination of both answers:
class User
def method_missing sym, *args
m_name = sym.to_s
if m_name.end_with? '_on_date'
prop = m_name.split('_').first.to_sym
handle_blocking(args.first) { getClient().send(prop, args.first) }
else
super(sym, *args)
end
end
def respond_to? sym, private=false
m_name.end_with?('_on_date') || super(sym, private)
end
def handle_blocking date
# see other answer
end
end
Then you can call "transaction_on_date", "events_on_date", "foo_on_date" and it would work.

Ruby: strip iframe and convert its src to a var

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

Spree error when using decorator with the original code

Need a little help over here :-)
I'm trying to extend the Order class using a decorator, but I get an error back, even when I use the exactly same code from source. For example:
order_decorator.rb (the method is exactly like the source, I'm just using a decorator)
Spree::Order.class_eval do
def update_from_params(params, permitted_params, request_env = {})
success = false
#updating_params = params
run_callbacks :updating_from_params do
attributes = #updating_params[:order] ? #updating_params[:order].permit(permitted_params).delete_if { |k,v| v.nil? } : {}
# Set existing card after setting permitted parameters because
# rails would slice parameters containg ruby objects, apparently
existing_card_id = #updating_params[:order] ? #updating_params[:order][:existing_card] : nil
if existing_card_id.present?
credit_card = CreditCard.find existing_card_id
if credit_card.user_id != self.user_id || credit_card.user_id.blank?
raise Core::GatewayError.new Spree.t(:invalid_credit_card)
end
credit_card.verification_value = params[:cvc_confirm] if params[:cvc_confirm].present?
attributes[:payments_attributes].first[:source] = credit_card
attributes[:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
attributes[:payments_attributes].first.delete :source_attributes
end
if attributes[:payments_attributes]
attributes[:payments_attributes].first[:request_env] = request_env
end
success = self.update_attributes(attributes)
set_shipments_cost if self.shipments.any?
end
#updating_params = nil
success
end
end
When I run this code, spree never finds #updating_params[:order][:existing_card], even when I select an existing card. Because of that, I can never complete the transaction using a pre-existent card and bogus gateway(gives me empty blanks errors instead).
I tried to bind the method in order_decorator.rb using pry and noticed that the [:existing_card] is actuality at #updating_params' level and not at #updating_params[:order]'s level.
When I delete the decorator, the original code just works fine.
Could somebody explain to me what is wrong with my code?
Thanks,
The method you want to redefine is not really the method of the Order class. It is the method that are mixed by Checkout module within the Order class.
You can see it here: https://github.com/spree/spree/blob/master/core/app/models/spree/order/checkout.rb
Try to do what you want this way:
Create file app/models/spree/order/checkout.rb with code
Spree::Order::Checkout.class_eval do
def self.included(klass)
super
klass.class_eval do
def update_from_params(params, permitted_params, request_env = {})
...
...
...
end
end
end
end

Bot spamming with invalid utf-8 characters

Our rails app is being slammed by a random spamming bot and they are trying to post some weird stuff.
I already tried different solutions:
The gem invalid_utf8_rejector
The gem rack-uri_sanitizer
Some custom code in our application_controller.rb file in a
before_filter method. before_filter :force_utf8_params
def force_utf8_params
traverse = lambda do |object, block|
if object.kind_of?(Hash)
object.each_value { |o| traverse.call(o, block) }
elsif object.kind_of?(Array)
object.each { |o| traverse.call(o, block) }
else
block.call(object)
end
object
end
force_encoding = lambda do |o|
return redirect_to #MYAPP_URL+"/not-found" if o.is_a?(String) && !o.valid_encoding?
end
traverse.call(params, force_encoding)
end
Here is the error we are getting: invalid byte sequence in UTF-8
I don't know what else to do.
you need to change your code a bit, the original solution for this problem uses lamda, which is appropriate, since it tries to force encode or strip some characters. But you need to redirect and proceed with execution, so you need to use proc.
force_encoding = proc do |o|
redirect_to #MYAPP_URL+"/not-found" and return if o.is_a?(String) && !o.valid_encoding?
end

How to remove special characters from params hash?

I have one application with the following code:
quantity = 3
unit_types = ['MarineTrac','MotoTrac','MarineTrac']
airtime_plan = 'Monthly Airtime Plan'
url = "http://localhost:3000/home/create_units_from_paypal?quantity=#{quantity}&unit_types=#{unit_types}&airtime_plan=#{airtime_plan}"
begin
resp = Net::HTTP.get(URI.parse(URI.encode(url.strip)))
resp = JSON.parse(resp)
puts "resp is: #{resp}"
true
rescue => error
puts "Error: #{error}"
return nil
end
It sends data to my other application via the URL params query string. This is what the controller method of that other application looks like:
def create_units_from_paypal
quantity = params[:quantity]
unit_types = params[:unit_types]
airtime_plan = params[:airtime_plan]
quantity.times do |index|
Unit.create! unit_type_id: UnitType.find_by_name(unit_types[index]),
airtime_plan_id: AirtimePlan.find_by_name(airtime_plan),
activation_state: ACTIVATION_STATES[:activated]
end
respond_to do |format|
format.json { render :json => {:status => "success"}}
end
end
I get this error:
<h1>
NoMethodError
in HomeController#create_units_from_paypal
</h1>
<pre>undefined method `times' for "3":String</pre>
<p><code>Rails.root: /Users/johnmerlino/Documents/github/my_app</code></p>
I tried using both raw and html_safe on the params[:quantity] and other params, but still I get the error. Note I had to use URI.encode(url) because URI.parse(url) returned bad uri probably because of the array of unit_types.
Change:
quantity.times do |index|
To:
quantity.to_i.times do |index|
The reason you are having this problem is because you are treating the params values as the types that you originally tried to send, but they are actually always going to be strings. Converting back to the expected 'type' solves your problem.
However, you have some more fundamental problems. Firstly, you are trying to send an array by simply formatting it to a string. However, this is not the format that the receiving application expects to translate back to an array. Secondly, there is duplication in your request - you don't need to specify a quantity. The length of the array itself is the quantity. A better method would be to build your url like this:
url = 'http://localhost:3000/home/create_units_from_paypal?'
url << URI.escape("airtime_plan=#{airtime_plan}") << "&"
url << unit_types.map{|ut| URI.escape "unit_types[]=#{ut}" }.join('&')
On the receiving side, you can do this:
def create_units_from_paypal
unit_types = params[:unit_types]
airtime_plan = params[:airtime_plan]
quantity = unit_types.try(:length) || 0
#...

Resources