How to write a channel.html file in Rails (for Facebook) - ruby-on-rails

According to the FB SDK I must include a channel file with the appropriate headers.
Being a major NOOB and a Rails not PHP developer I have no idea how to do this.
Here is the example they provide for php:
<?php
$cache_expire = 60*60*24*365;
header("Pragma: public");
header("Cache-Control: max-age=".$cache_expire);
header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$cache_expire) . ' GMT');
?>
<script src="//connect.facebook.net/en_US/all.js"></script>
I want to know how do I do the same thing in Rails 3

I got tired of polluting my routes.rb file in every facebook connected app so I wrapped a rack handler that gives the correct channel.html response in a Rails Engine and published it as a gem. You can simply include the 'fb-channel-file' gem in your Gemfile and it will be automatically mounted at /channel.html
https://github.com/peterlind/fb-channel-file

Inside your controller:
cache_expire = 1.year
response.headers["Pragma"] = "public"
response.headers["Cache-Control"] = "max-age=#{cache_expire.to_i}"
response.headers["Expires"] = (Time.now + cache_expire).strftime("%d %m %Y %H:%I:%S %Z")
render :layout => false, :inline => "<script src='//connect.facebook.net/en_US/all.js'></script>"

Use the response.headers hash in your controller. Docs
Example from your example
cache_expire = 60*60*24*365
response.headers["Pragma"] = "public"
response.headers["Cache-Control"] = "max-age=#{cache_expire}"
response.headers["Expires"] = ... # I'll leave this one to you.
# (Or ask another Q.)
# gmdate('D, d M Y H:i:s', time()+$cache_expire) . ' GMT');

I simply added 'channel.html' to my public directory and inserted this one line in it:
<script src="//connect.facebook.net/en_US/all.js"></script>

Related

How to download each zip file from a url and unpack using rails

Right now I have a URL which is populated with a list of .zip files in the browser. I am trying to use rails to download the files and then open them using Zip::File from the rubyzip gem. Currently I am doing this using the typhoeus gem:
response = Typhoeus.get("http://url_with_zip_files.com")
But the response.response_body is an HTML doc inside a string. I am new to programming so a hint in the right direction using best practices would help a lot.
response.response_body => "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n<html>\n <head>\n <title>Index of /mainstream/posts</title>\n </head>\n <body>\n<h1>Index of /mainstream/posts</h1>\n<table><tr><th>Name</th><th>Last modified</th><th>Size</th><th>Description</th></tr><tr><th colspan=\"4\"><hr></th></tr>\n<tr><td>Parent Directory</td><td> </td><td align=\"right\"> - </td><td> </td></tr>\n<tr><td>1476536091739.zip</td><td align=\"right\">15-Oct-2016 16:01 </td><td align=\"right\"> 10M</td><td> </td></tr>\n<tr><td>1476536487496.zip</td><td align=\"right\">15-Oct-2016 16:04 </td><td align=\"right\"> 10M</td><td> </td></tr>"
To break this down you need to:
Get the initial HTML index page with Typhoeus
base_url = "http://url_with_zip_files.com/"
response = Typhoeus.get(base_url)
Then Use Nokogiri to parse that HTML to extract all the links to the zip files (see: extract links (URLs), with nokogiri in ruby, from a href html tags?)
doc = Nokogiri::HTML(response)
links = doc.css('a').map { |link| link['href'] }
links.map { |link| base_url + '/' + link}
# Should look like:
# links = ["http://url_with_zip_files.com/1476536091739.zip", "http://url_with_zip_files.com/1476536487496.zip" ...]
# The first link is a link to Parent Directory which you should probably drop
# looks like: "/5Rh5AMTrc4Pv/mainstream/"
links.pop
Once you have all the links: you then visit all the extracted links to download the zip files with ruby and unzip them (see: Ruby: Download zip file and extract)
links.each do |link|
download_and_parse(link)
end
def download_and_parse(zip_file_link)
input = Typhoeus.get(zip_file_link).body
Zip::InputStream.open(StringIO.new(input)) do |io|
while entry = io.get_next_entry
puts entry.name
parse_zip_content io.read
end
end
end
If you want to use Typhoeus to stream the file contents from the url to memory see the Typhoeus documentation section titled: "Streaming the response body". You can also use Typhoeus to download all of the files in paralell which would increase your performance.
I believe Nokogiri will be your best bet.
base_url = "http://url_with_zip_files.com/"
doc = Nokogiri::HTML(Typhoeus.get(base_url))
zip_array = []
doc.search('a').each do |link|
if link.attr("href").match /.+\.zip/i
zip_array << Typhoeus.get(base_url + link.attr("href"))
end
end

Rails - How to pass Sprockets::Context in manual sass compiling

I'm using the following code snippet to manually compile a sass manifest with some variable overrides appended.
template = File.read("#{Rails.root}/app/assets/schemes/#{scheme}/css/styles.css.scss")
scheme_variables.each do |key, value|
template << "$#{key}:#{value};\n"
end
engine = Sass::Engine.new(template, {
:syntax => :scss,
:cache => false,
:read_cache => false,
:style => :compressed,
:filesystem_importer => Sass::Rails::SassImporter,
:load_paths => MyApp::Application.assets.paths,
:sprockets => {
:context => ?,
:environment => MyApp::Application.assets
}
})
output = engine.render
The Sass::Engine constructor wants a sprockets context and environment in the options hash. What do I put in for the context? The first thing I tried was...
:context => MyApp::Application.assets.context_class,
...but that gives me the following error "undefined method `font_path' for #" when it hits one of my sass asset helpers.
The second thing I tried was...
:context => ActionController::Base.helpers,
...That fixed the asset helper issue, but throws the following error "undefined method `depend_on' for #" when it tries to work through my glob imports (e.g. #import "mixins/*").
I'm using Rails 4.2 and sass-rails 5.0.3.
Any advice on this would be much appreciated. Thanks!
With using Sass::Rails::ScssTemplate you can render your sass code with this snippet:
template = '...' # Your sass code
logical_path = pathname = ''
environment = Rails.application.assets
context = environment.context_class.new(environment, logical_path, pathname)
template = Sass::Rails::ScssTemplate.new(pathname) { template }
output = template.render(context, {})
If you want to render from a file then just add its path to pathname and its asset path to logical_path.
For me it works with Rails 4.2.5.1 and sass-rails 5.0.4.
I ended up solving this in a slightly different way - using Sass::Rails::ScssTemplate's render method. Basically, I write my altered css string out to a file and pass it into the Sass::Rails::ScssTemplate constructor. I then compile and remove the temp file when it's done. This doesn't feel great, but it's working well for me.
scheme_css_dir = "#{Rails.root}/app/assets/schemes/#{scheme}/css"
css = File.read("#{scheme_css_dir}/styles.css.scss")
variables_str = ''
scheme_variables.each do |key, value|
variables_str << "$#{key}:#{value};\n"
end
css.gsub!('#import "./variables";', variables_str)
file = Tempfile.new(['styles', '.css.scss'], scheme_css_dir)
file.write(css)
file.close
abs_path = file.path
relative_path = abs_path[Rails.root.to_s.size + 1..-1]
template = Sass::Rails::ScssTemplate.new(abs_path)
environment = Evrconnect::Application.assets
context = environment.context_class.new(
:environment => environment,
:name => relative_path,
:filename => abs_path,
:metadata => {}
)
output = template.render(context)
file.unlink
To answer your original question, you need to supply either the current view_context, or create one with ActionView::Base.new.
http://apidock.com/rails/AbstractController/Rendering/view_context
In Rails 5.x, Sprockets v3.7.x
NOTE: Following method requires: Rails.application.config.assets.compile == true, i.e., set config.assets.compile = true. Check Sprockets README
I manually returned compiled source for sass/js, using the following code.
# For Scss Assets
def pdf_stylesheet_link_tag(name)
if Rails.env.development?
asset = Rails.application.assets.find_asset(name + '.scss')
raw ('<style type="text/css">' + asset.source + '</style>')
else
raw ('<style type="text/css">' + pdf_asset_contents(name, '.css') + '</style>')
end
end
# For Javascript Assets
def pdf_javascript_include_tag(name, *type)
if Rails.env.development?
if debug? # Can be used to check `debug == true` in params for current route
javascript_include_tag(name)
else
asset = Rails.application.assets.find_asset(name + '.js')
raw ('<script>' + asset.source + '</script>')
end
else
javascript_tag pdf_asset_contents(name, '.js')
end
end
Above code actually helps to dynamically pass compiled version of sass/js
to Wicked_PDF, which actually helps to load styles and js on Generated PDFs, for Dev Environments.
pdf_stylesheet_link_tag can be used as a helper for templates, in development/stage (where, config.assets.precompile == false), instead of using wicked_pdf_stylesheet_link_tag(which actually requires a path to precompiled source-file).
After overnight trial and errors, I found and want to share todays's way of doing it ("today" meaning: rails 5.2.1 and sprockets 3.7.2).
Work as expected: no need of a temp file, accept #import, allow asset path helpers.
# Compile SASS and return the resulting string
# Pass the file path and name without 'sass' extension, relative to assets/stylesheets
def compile_sass(stylesheet)
# Load file content
path = Rails.root.join 'app', 'assets', 'stylesheets', "#{stylesheet}.sass"
sass = File.read path
# Configure engine
environment = Sprockets::Railtie.build_environment Rails.application
scope = environment.context_class.new environment: environment, \
filename: "/", metadata: {}
scope.sass_config.merge! cache: false, style: :compressed
# Compile
engine = Sass::Rails::SassTemplate.new {sass}
engine.render scope
end
Other sass_config options can be found here : https://github.com/sass/ruby-sass/blob/stable/doc-src/SASS_REFERENCE.md#options
The following works with rails 5.2.0, sprockets 3.7.2, sassc-rails 1.3.0 and sassc 1.12.1:
template = '...' # Your sass code
environment = Sprockets::Railtie.build_environment(Rails.application)
engine = SassC::Rails::SassTemplate.new
engine.call(environment: environment,
filename: '/',
data: template,
metadata: {})[:data]

Ruby hexdigest sha1 pack('H*') string encoding...

I meet an encoding problem... No errors in the console, but the output is not well encoded.
I must use Digest::SHA1.hexdigest on a string and then must pack the result.
The below example should outputs '{´p)ODýGΗ£Iô8ü:iÀ' but it outputs '{?p)OD?GΗ?I?8?:i?' in the console and '{�p)OD�G^BΗ�I�8^D�:i�' in the log file.
So, my variable called pack equals '{?p)OD?GΗ?I?8?:i?' and not '{´p)ODýGΗ£Iô8ü:iÀ'. That's a big problem... I'm doing it in a Rails task.
Any idea guys?
Thanks
# encoding: utf-8
require 'digest/sha1'
namespace :my_app do
namespace :check do
desc "Description"
task :weather => :environment do
hexdigest = Digest::SHA1.hexdigest('29d185d98c984a359e6e6f26a0474269partner=100043982026&code=34154&profile=large&filter=movie&striptags=synopsis%2Csynopsisshort&format=json&sed=20130527')
pack = [hexdigest].pack("H*")
puts pack # => {?p)OD?GΗ?I?8?:i?
puts '{´p)ODýGΗ£Iô8ü:iÀ' # => {´p)ODýGΗ£Iô8ü:iÀ
end
end
end
This is what I did (my conversion from PHP to Ruby)
# encoding: utf-8
require 'open-uri'
require 'base64'
require 'digest/sha1'
class Allocine
$_api_url = 'http://api.allocine.fr/rest/v3'
$_partner_key
$_secret_key
$_user_agent = 'Dalvik/1.6.0 (Linux; U; Android 4.2.2; Nexus 4 Build/JDQ39E)'
def initialize (partner_key, secret_key)
$_partner_key = partner_key
$_secret_key = secret_key
end
def get(id)
# build the params
params = { 'partner' => $_partner_key,
'code' => id,
'profile' => 'large',
'filter' => 'movie',
'striptags' => 'synopsis,synopsisshort',
'format' => 'json' }
# do the request
response = _do_request('movie', params)
return response
end
private
def _do_request(method, params)
# build the URL
query_url = $_api_url + '/' + method
# new algo to build the query
http_build_query = Rack::Utils.build_query(params)
sed = DateTime.now.strftime('%Y%m%d')
sig = URI::encode(Base64.encode64(Digest::SHA1.digest($_secret_key + http_build_query + '&sed=' + sed)))
return sig
end
end
Then call
allocine = Allocine.new(ALLOCINE_PARTNER_KEY, ALLOCINE_SECRET_KEY)
puts allocine.get('any ID')
get method return 'e7RwKU9E%2FUcCzpejSfQ4BPw6acA%3D' in PHP and 'cPf6I4ZP0qHQTSVgdKTbSspivzg=%0A' in Ruby...
thanks again
I think this "encoding" issue has turned up due to debugging other parts of a conversion from PHP to Ruby. The target API that will consume a digest of params looks like it will accept a signature variable constructed in Ruby as follows (edit: well this is guess, there may also be relevant differences between Ruby and PHP in URI encoding and base64 defaults):
require 'digest/sha1'
require 'base64'
require 'uri'
sig_data = 'edhefhekjfhejk8edfefefefwjw69partne...'
sig = URI.encode( Base64.encode64( Digest::SHA1.digest( sig_data ) ) )
=> "+ZabHg22Wyf7keVGNWTc4sK1ez4=%0A"
The exact construction of sig_data from the parameters that are being signed is also important. That is generated by the PHP method http_build_query, and I do not know what order or escaping that will apply to input params. If your Ruby version gets them in a different order, or escapes differently to PHP, the signature will be wrong (edit: Actually it is possible we are looking here for a signature on the exact query string sent the API - I don't know). It is possibly an issue of that sort that has led you down the rabbit hole of how the signature is constructed?
Thank you guys for your help.
Problem is solved. With the following code I obtain exactly the same string as with PHP:
http_build_query = Rack::Utils.build_query(params)
sed = DateTime.now.strftime('%Y%m%d')
sig = CGI::escape(Base64.strict_encode64(Digest::SHA1.digest($_secret_key + http_build_query + '&sed=' + sed)))
Now I've another problem for which I opened a new question here.
thanks you very much.

Full url for an image-path in Rails 3

I have an Image, which contains carrierwave uploads:
Image.find(:first).image.url #=> "/uploads/image/4d90/display_foo.jpg"
In my view, I want to find the absolute url for this. Appending the root_url results in a double /.
root_url + image.url #=> http://localhost:3000//uploads/image/4d90/display_foo.jpg
I cannot use url_for (that I know of), because that either allows passing a path, or a list of options to identify the resource and the :only_path option. Since I do't have a resource that can be identified trough "controller"+"action" I cannot use the :only_path option.
url_for(image.url, :only_path => true) #=> wrong amount of parameters, 2 for 1
What would be the cleanest and best way to create a path into a full url in Rails3?
You can also set CarrierWave's asset_host config setting like this:
# config/initializers/carrierwave.rb
CarrierWave.configure do |config|
config.storage = :file
config.asset_host = ActionController::Base.asset_host
end
This ^ tells CarrierWave to use your app's config.action_controller.asset_host setting, which can be defined in one of your config/envrionments/[environment].rb files. See here for more info.
Or set it explicitly:
config.asset_host = 'http://example.com'
Restart your app, and you're good to go - no helper methods required.
* I'm using Rails 3.2 and CarrierWave 0.7.1
try path method
Image.find(:first).image.path
UPD
request.host + Image.find(:first).image.url
and you can wrap it as a helper to DRY it forever
request.protocol + request.host_with_port + Image.find(:first).image.url
Another simple method to use is URI.parse, in your case would be
require 'uri'
(URI.parse(root_url) + image.url).to_s
and some examples:
1.9.2p320 :001 > require 'uri'
=> true
1.9.2p320 :002 > a = "http://asdf.com/hello"
=> "http://asdf.com/hello"
1.9.2p320 :003 > b = "/world/hello"
=> "/world/hello"
1.9.2p320 :004 > c = "world"
=> "world"
1.9.2p320 :005 > d = "http://asdf.com/ccc/bbb"
=> "http://asdf.com/ccc/bbb"
1.9.2p320 :006 > e = "http://newurl.com"
=> "http://newurl.com"
1.9.2p320 :007 > (URI.parse(a)+b).to_s
=> "http://asdf.com/world/hello"
1.9.2p320 :008 > (URI.parse(a)+c).to_s
=> "http://asdf.com/world"
1.9.2p320 :009 > (URI.parse(a)+d).to_s
=> "http://asdf.com/ccc/bbb"
1.9.2p320 :010 > (URI.parse(a)+e).to_s
=> "http://newurl.com"
Just taking floor's answer and providing the helper:
# Use with the same arguments as image_tag. Returns the same, except including
# a full path in the src URL. Useful for templates that will be rendered into
# emails etc.
def absolute_image_tag(*args)
raw(image_tag(*args).sub /src="(.*?)"/, "src=\"#{request.protocol}#{request.host_with_port}" + '\1"')
end
There's quite a bunch of answers here. However, I didn't like any of them since all of them rely on me to remember to explicitly add the port, protocol etc. I find this to be the most elegant way of doing this:
full_url = URI( root_url )
full_url.path = Image.first.image.url
# Or maybe you want a link to some asset, like I did:
# full_url.path = image_path("whatevar.jpg")
full_url.to_s
And what is the best thing about it is that we can easily change just one thing and no matter what thing that might be you always do it the same way. Say if you wanted to drop the protocol and and use the The Protocol-relative URL, do this before the final conversion to string.
full_url.scheme = nil
Yay, now I have a way of converting my asset image urls to protocol relative urls that I can use on a code snippet that others might want to add on their site and they'll work regardless of the protocol they use on their site (providing that your site supports either protocol).
I used default_url_options, because request is not available in mailer and avoided duplicating hostname in config.action_controller.asset_host if haven't specified it before.
config.asset_host = ActionDispatch::Http::URL.url_for(ActionMailer::Base.default_url_options)
You can't refer to request object in an email, so how about:
def image_url(*args)
raw(image_tag(*args).sub /src="(.*?)"/, "src=\"//#{ActionMailer::Base.default_url_options[:protocol]}#{ActionMailer::Base.default_url_options[:host]}" + '\1"')
end
You can actually easily get this done by
root_url[0..-2] + image.url
I agree it doesn't look too good, but gets the job done.. :)
I found this trick to avoid double slash:
URI.join(root_url, image.url)

Get URL headers without the HTML

A bit of a strange question. Is there a way to ask a webserver to return only the headers and not the HTML itself ?
I want to ask a server for a URL and see if its valid (not 404/500/etc) and follow the redirections (if present) but not get the actual HTML content.
Thanks
Preferably a way to do this in Ruby
use HEAD instead of GET or POST
http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html Section 9.4
As suggested, check the Net::HTTP library..
require 'net/http'
Net::HTTP.new('www.twitter.com').request_head('/').class
This is exactly what HEAD HTTP method does.
For Ruby, there is a beautiful gem, much simpler than the low-level net/http that allows you to perform HEAD requests.
gem install rest-open-uri
then
irb> require 'rubygems'
=> true
irb> require 'rest-open-uri'
=> true
irb> sio = open("http://stackoverflow.com", :method => :head)
=> #
irb> sio.meta
=> {"expires"=>"Tue, 30 Nov 2010 18:08:47 GMT", "last-modified"=>"Tue, 30 Nov 2010 18:07:47 GMT", "content-type"=>"text/html; charset=utf-8", "date"=>"Tue, 30 Nov 2010 18:08:27 GMT", "content-length"=>"193779", "cache-control"=>"public, max-age=18", "vary"=>"*"}
irb> sio.status
=> ["200", "OK"]
It follows redirections. You have to rescue for SocketError when host doesn't exists or OpenURI::HTTPError if file doesn't exists.
If you want something more powerfull have a look at Mechanize or HTTParty.
Use Ruby's net/http and the HEAD method that Mak mentioned. Check ri Net::HTTP#head from the command line for info.
actually i had to fold pantulis' answer into my own. it seems like there are two kinds of urls neither fns worked alone so i did
module URI
def self.online?(uri)
URI.exists?(uri)
end
def self.exists?(uri)
URI.exists_ver1?(uri)
end
def self.exists_ver1?(url)
#url = url
["http://", "https://"].each do |prefix|
url = url.gsub(prefix, "")
end
begin
code = Net::HTTP.new(url).request_head('/').code
[2,3].include?(code.to_i/100)
rescue
URI.exists_ver2?(#url)
end
end
def self.exists_ver2?(url)
url = "http://#{url}" if URI.parse(url).scheme.nil?
return false unless URI.is_a?(url)
uri = URI(url)
begin
request = Net::HTTP.new uri.host
response= request.request_head uri.path
#http status code 200s and 300s are ok, everything else is an error
[2,3].include? response.code.to_i/100
rescue
false
end
end
end

Resources