Can you stream HTML with Slim in a Sinatra app? - ruby

I have a Sinatra app that wraps a command line application. It doesn't have may users, so performance is not a problem.
I am using Sinatra's streaming api to allow me to stream out the HTML as the command is run. This means the user gets to see progress as the command runs.
post "/reorder" do
#project = params["project"]
#id_or_range = params["id_or_range"]
#output_log = "[OUTPUT]"
before, after = slim(:index).split(#output_log)
stream do |out|
out << before
run(#project, #id_or_range, StreamOutput.new(out))
out << after
end
end
https://gist.github.com/NigelThorne/04775270abd46b78e262
Currently I am doing a hack where I render the template (as if I had all the data), then split the template text where the data should be inserted. I then render out the beginning of the template, then render the data as I receive it (on a stream), then the end of the template.
Slim is supposed to support streaming...
I'd like to write.
post "/reorder" do
...
stream do |out|
out << slim(:index)
end
end
or better
post "/reorder" do
...
slim(:index, stream: true)
end
How do I get slim to yield to the stream of data when rendering, so I stream out the template in one go?

Yes, you can if you overwrite the slim helper in Sinatra. See:
https://github.com/slim-template/slim/issues/540
https://github.com/slim-template/sinatra-stream

Related

Upload multiple files in form with ruby mechanize

I can successfully upload a single file using a Mechanize form like this:
def add_attachment(form, attachments)
attachments.each_with_index do |attachment, i|
form.file_uploads.first.file_name = attachment[:path]
end
end
where form is a mechanize form. But if attachments has more than one element, the last one overwrites the previous ones. This is obviously because I'm using the first accessor which always returns the same element of the file_uploads array.
To fix this, I tried this, which results an error, because there is only one element in this array.
def add_attachment(form, attachments)
attachments.each_with_index do |attachment, i|
form.file_uploads[i].file_name = attachment[:path]
end
end
If I try to create a new file_upload object, it also doesn't work:
def add_attachment(form, attachments)
attachments.each_with_index do |attachment, i|
form.file_uploads[i] ||= Mechanize::Form::FileUpload.new(form, attachment[:path])
form.file_uploads[i].file_name = attachment[:path]
end
end
Any idea how I can upload multiple files using Mechanize?
So, I solved this issue, but not exactly how I imagined it would work out.
The site I was trying to upload files to was a Redmine project. Redmine is using JQueryUI for the file uploader, which confused me, since Mechanize doesn't use Javascipt. But, it turns out that Redmine degrades nicely if Javascript is disabled and I could take advantage of this.
When Javascript is disabled, only one file at time can be uploaded in the edit form, but going to the 'edit' url for the issue that was just created gives the chance to upload a second file. My solution was to simply attach a file, upload the form and then click the 'Update' link on the resulting page, which presented a page with a new form and another upload field, which I could then use to attach the next file to. I did this for all attachments but the last, so that the form processing could be completed and then uploaded for a final time. Here is the relavant bit of code:
def add_attachment(agent,form, attachments)
attachments.each_with_index do |attachment, i|
form.file_uploads.first.file_name = attachment[:path]
if i < attachments.length - 1
submit_form(agent, form)
agent.page.links_with(text: 'Update').first.click
form = get_form(agent)
end
end
form
end
I used the following
form.file_uploads[0].file_name = "path to the first file that to be uploaded" form.file_uploads[1].file_name = "path to the second file that to be uploaded" form.file_uploads[2].file_name = "path to the third file that to be uploaded".
and worked fine. Hope this helps.

How can you access page properties (YAML front matter) within a converter plugin

I'm writing a converter plugin for Jekyll and need access to some of the page header (YAML front matter) properties. Only the content is passed to the main converter method and does not seem possible to access the context.
Example:
module Jekyll
class UpcaseConverter < Converter
safe true
priority :low
def matches(ext)
ext =~ /^\.upcase$/i
end
def output_ext(ext)
".html"
end
def convert(content)
###########
#
# Its here that I need access to the content page header data
#
#
###########
content.upcase
end
end
end
Any ideas how I can access the page header data within a converter plugin?
Based on the Jekyll source code, it is not possible to retrieve the YAML front matter in a converter.
I see two solutions that could work depending on your situation.
Your file extension could be descriptive enough to provide the information you would have included in the front matter. It looks like the Converter plugin was designed to be this basic.
If modifying Jekyll is an option, you could change the Convertible.transform method to send the front matter to Converter.convert. The Converters that come with Jekyll would have to be modified as well. Fork it on GitHub and see if others like the idea. Here's where to start: https://github.com/mojombo/jekyll/blob/cb1a2d1818770ca5088818a73860198b8ccca27a/lib/jekyll/convertible.rb#L49
Good luck.
devnull, I ran into a similar situation and I figured a way of doing it.
In the converter, I registered a pre-render hook to pull YAML into a variable, so that in the actual convert method, I have access to the info I just pulled. Also, another post_render hook is needed to remove that piece of info since this should be a per-post data.
A side note. I found that the convert will be called twice, once for use in the html <meta> tag, once for the actual content. The hook will be only invoked for the second case, not the first. You may need to guard you convert function.
Another side note. I think having YAML in the converter is not unreasonable. Just like in pandoc where you can specify bibliography file in the YAML section and do other fine tuning, people should be given freedom to customize a single post using YAML, too.
def initialize(config)
super(config)
Jekyll::Hooks.register :posts, :pre_render do |post|
if matches(post.data["ext"])
# extract per post metadata, including those in YAML
#myconfig["meta"] = post.data
# you may need the path to the post: post.path
end
end
Jekyll::Hooks.register :posts, :post_render do |post|
if matches(post.data["ext"])
# remove per post metadata
#myconfig.delete("meta")
end
end
end
def convert(content)
return content unless #myconfig["meta"]
# actual conversion goes here
end

Process image request programmatically and return stream in Sinatra

This idea is currently just on the drawing board, and I was first wondering whether it is possible, then how it could be done.
Say that an app in Sinatra has the following app file:
#!/usr/bin/env ruby
# encoding: UTF-8
require 'sinatra'
get '/hi' do
"Hello World"
end
get '/' do
erb :index
end
get '/url_to_img.jpg'
#parse url
#process an image
#stream the image back to the client as nothing have happened
end
Could the image request be intercepted, and how could an image file be returned wrapped in a HTTP respond.
Sorry, for the very crude question.
What you are describing is possible. All you need to do is to return the binary data in your Sinatra route, ensuring that you have the right MIME type for the file.
Here's an example that detects the image MIME, creates a thumbnail and returns the thumbnail to the browser:
get '/:filename' do |filename|
redirect 404 unless File.readable?(filename)
content_type detect_mime_type(filename)
create_thumbnail filename
end
I'm using the following helpers:
require 'filemagic'
require 'rmagick'
def detect_mime_type(path)
FileMagic.new(FileMagic::MAGIC_MIME)
.file(path).gsub(/\n/,"").split(";").first
end
def create_thumbnail(path)
Magick::Image.read(filename)
.first.resize_to_fill(680, 500)
end
Of course, you should not serve files from your main website directory; this is for illustrative purposes only.

Ruby get RSS feed won't get the latest feed

I have a problem parsing an RSS feed.
When I do this:
feed = getFeed("http://example.com/rss)
If the feed content changes it don't update.
If I do it like this:
feed = getFeed("http://example.com/rss?" + Random.rand(20).to_s)
It works most of the time but not always.
getFeed() is implemented like this:
def getFeed(url)
rss_content = ""
open(url) do |f|
rss_content = f.read
end
return rss_content
end
I used this in Sinatra with Ruby 1.9.3, if this make a difference.
On my opinion somewhere it gets cached but I have no idea where.
Edit:
Okey after 1/2 day running on the server it works with out a problem.
This:
feed = getFeed("http://example.com/rss?" + Random.rand(20).to_s)
implies the problem is with caching, but Ruby, OpenURI and Sinatra shouldn't be caching anything. Perhaps your code is running behind a caching device or app that is handling outgoing requests as well as incoming?
This isn't the fix, but your code can be streamlined greatly:
def getFeed(url)
open(url).read
end

How do I use CGI in a Heroku application written with Ruby and Sinatra?

I am trying to move information from a text form to a new web page using CGI. To do this, I set action to action="new.html" in the form. Then, in the relevant part of my .rb file, I have:
get "/new.html" do
#graph = Koala::Facebook::API.new(session[:access_token])
#app = #graph.get_object(ENV["FACEBOOK_APP_ID"])
if session[:access_token]
#query=CGI.new() # Line of interest
#input=#query["tool_1"] # Line of interest
end
erb :my_tools_F
end
post "/new.html" do
redirect "/new.html"
end
The new web page loads, but #input is blank when I call it in the .erb file. Prior to this part of the script, I did require CGI. My web host is Heroku, and both of the .erb files are in a directory called views. The application is built to be launched on Facebook.
The example code is here.
It seems like you're trying to get the parameters for the form. I had another answer here but that wasn't working for you. You can easily do this without cgi and you should consider using the built in methods to do so. However, before you can do that I noticed some errors in your github post.
Your folder Views should read views. Small but it matters. I couldn't get the pages rendering correctly.
On your new.erb and index.erb on line 33 it reads:
<input type="submit" value="Add"">
There is an extra " at the end. Just remove it to look like:
<input type="submit" value="Add">
Lastly, to do what you need to do:
get "/new.html" do
erb :new
end
post "/new.html" do
#input = params[:tool_1]
erb :new
end
instead of what you did. Do a find on http://www.sinatrarb.com/intro for params.

Resources