I have implemented AJAX based upload in my rails project.
This is my example code. (I followed the tutorial from here.. https://github.com/valums/file-uploader)
********** Javascript Code **********
function createUploader(){
var uploader = new qq.FileUploader({
element: document.getElementById('file-uploader'),
action: '/product/upload',
});
}
window.onload = createUploader;
********** Server side **********
def upload
#filename = params['qqfile']
f = File.open('home/files/' + #filename, "wb")
str = request.body.read
f.write(str)
f.close
render :text => '{success:true}'
end
When i run my code i got the following error message in the line "str = request.body.read"
NoMethodError (undefined method `body' for #<ActionController::CgiRequest:0xb6cd71a0>):
error details More: http://www.heypasteit.com/clip/01NE
My configuration:
[mrblack# home]# ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-linux]
[mrblack# home]# rails -v
Rails 1.2.1
[mrblack# home]# gem -v
1.8.6
Can you please clear me, what did i wrong?
Becasue in Rails 1.2.1 neither ActionController::CgiRequest nor its parent ActionController::AbstractRequest contains 'body' method. You can see it if you look at these pages: cgi_process.rb and request.rb. Why do you use such old rails version??
UPDATE
#Mr. Black. You wrote "Can you please tell me any alternate way to read the incoming request similar to this request object".
You're using 'file-uploader' library from github. As I can see, that library can upload files to server side by two ways. If user has old FF, Chrome browsers or any IE version that library make a post request to iframe. If user has modern FF or Chrome version library prepares async post xmlHttpRequest. Therefore, in our rails controller we have to handle both situation. For example, for simplicity we want to write sended file data to some hard-coded place on disk.
******** app/controllers/greetings_controller.rb ********
class GreetingsController < ApplicationController
def upload
begin
is_xhr = params[:qqfile].is_a? String
data = is_xhr ? request.body : params[:qqfile]
File.open("/some_place_on_disk/bar.txt", "w") { |f| f.write(data.read) }
#result = {success: true}
if is_xhr
render :json => #result
end
# else render view
rescue Exception => e
logger.info e
#result = {error: "Error has been encountered during the processing"}
end
end
end
******** app/views/upload.html.erb ********
<%= #result.to_json %>
******** config/routes.rb ********
.....
match '/upload' => 'greetings#upload', :via => :post
.....
You can use demo.htm from github project, but you have to place it into public folder. Don't forget about fileuploader.css and fileuploader.js! :) You must change 'action' in createUploader function to action path, in my case that would be '/upload'
Related
This one is driving me nuts.
I have two websites who have to talk to each other trough JSONP.
Site A checks in its database if some value is present and returns json data {"result":"Found in library"} if found.
When doing a cURL from the command line, I get my data as expected.
Doing a Ajax call from site B, I can see in the logging of site A that the request is handled correct, but site B complains about "Unexpected end of data at line 1" The inspector shows me no data at all, so I assume that is the problem. Where did the data go?
As I don't want the inner workings of the Ajax call to be reveiled, I want to do it in the Ruby controller with a Net::HTTP call.
Again, I can see in the logging of site A that the call is handled correct and returns the result.
This time, Ruby logging shows:
Errno::EPROTONOSUPPORT (Failed to open TCP connection to xx.domain.com:443 (Protocol not supported - socket(2) for "xx.domain.com" port 443)):
My controller in site B:
def query
respond_to do |format|
format.js {
# Submit Ajax call
uri = URI(base_url + "/data_subjects/query.json")
data = {
key: api_key,
parmA: params[:parmA],
parmB: params[:parmB],
}
uri.query = URI.encode_www_form(data)
logger.info("QUERY: #{uri.inspect}")
res = Net::HTTP.get_response(uri)
render :json, JSON.parse(res)
}
end
end
My controller in site A:
def query
respond_to do |format|
format.json {
.... Do some stuff
if $rc == 1
render json: {:result => I18n.t("result_found")}, status: :ok , :layout => false
else
render json: {result: I18n.t("result_not_found")}, status: 404, :layout => false
end
}
end
As I said, using cURL everything looks okay, but with either jQuery or Ruby it fails.
UPDATE
Okay, found it!
As it turns out, you need to specify ":callback => params[:callback]" to the json response:
render json: {:result => I18n.t("result_found")}, :callback => params[:callback], status: :ok , :layout => false
Problem was triggered by not having IPv6 support in the FreeBSD jail in which the calling process runs. And the server which was called only has IPv6 connectivity.
I posted the same in github of Koala but noone answered to me so I put here.
So when I try to login with Twitter with Omniauth:
I, [2013-11-15T18:57:12.371006 #28412] INFO -- omniauth: (twitter) Request phase initiated.
127.0.0.1 - - [15/Nov/2013 18:57:13] "GET /auth/twitter HTTP/1.1" 500 144366 0.9355
I have also a Koala login to facebook I don't use Omniauth for Facebook I just use Omniauth for twitter, If I don't require Koala is ok, but if I have both it generates:
undefined method `[]=' for #<Koala::Facebook::OAuth:0x00000001b03348>
~>In oauth.rb line 31
I'm using 1.6.0 version of Koala and Sinatra.
My code is:
#Facebook
get '/loginfb' do
session['oauth'] = Koala::Facebook::OAuth.new($APP_ID, $APP_SECRET, "#{request.base_url}/callbackfb")
redirect session['oauth'].url_for_oauth_code(:permissions => ["publish_stream"])
end
get '/callbackfb' do
session['access_token'] = session['oauth'].get_access_token(params[:code])
registerUserFB() #Just register the user function
redirect '/accounts'
end
#Twitter
#By defualt logs in with /auth/twitter
get '/auth/twitter/callback' do
erb "<h1>#{params[:provider]}</h1><pre>#{JSON.pretty_generate(request.env['omniauth.auth'])}</pre>"
p auth['credentials']['token']
end
get '/auth/failure' do
erb "<h1>Authentication Failed:</h1><h3>message:<h3> <pre>#{params}</pre>"
end
Thanks guys in advance.
I used another gem for logging with twitter called twitter_oauth yo can find here
For use with sinatra is quite simple:
#Sinatra stuff
require 'twitter_oauth'
#more sinatra stuff
$CONSUMER_KEY = '32423...'
$CONSUMER_SECRET = '...adads...'
$CALLBACK_URL = 'http://....'
tw_client = TwitterOAuth::Client.new(
:consumer_key => $CONSUMER_KEY,
:consumer_secret => $CONSUMER_SECRET
)
$request_token = tw_client.request_token(:oauth_callback => $CALLBACK_URL)
#sinatra routes
get '/logintw' do
redirect $request_token.authorize_url
end
get '/callbacktw' do
#access_token = $request_token.get_access_token :oauth_verifier => params[:oauth_verifier]
p #access_token.params[:oauth_token]
p #access_token.params[:oauth_token_secret]
p #access_token.params[:screen_name]
p #access_token.params[:user_id]
redirect '/accounts'
end
#more sinatra routes
Is not the best solution but is one and for me works!
Thanks anyway.
I'm using the wicked gem
After I hit RequestStepsController#update, I'm being redirected to /request_steps/wicked_finish. I have no idea why. Any suggestions?
If it worked as I expected it to, then the next step after updating the object would be :the_frame, as described in steps.
From the log:
Started PUT "/request_steps/77" for 127.0.0.1
Processing by RequestStepsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"XXX", "request"=>{"background_information"=>"prefilled"}, "commit"=>"C
7"}
Redirected to http://localhost:3000/request_steps/wicked_finish
Started GET "/request_steps/wicked_finish" for 127.0.0.1
Processing by RequestStepsController#show as HTML
Parameters: {"id"=>"wicked_finish"}
Completed 404 Not Found in 180ms
ActiveRecord::RecordNotFound - Couldn't find Request without an ID:
This is my RequestStepsController
class RequestStepsController < ApplicationController
include Wicked::Wizard
steps :background_information,
:no_list_what_can_go_wrong,
:the_frame,
:select_group
def show
#request = Request.find(params[:request])
render_wizard
end
def update
#request = Request.find(params[:id])
#request.update_attributes(request_params)
render_wizard #request
end
def request_params
params.require(:request).permit(:title, :description, :goal,
:request_group_id,
:repository_url,
:background_information
)
end
end
This is my form:
= simple_form_for(#request, url: wizard_path(#request), method: :put, :html => { :class => 'form-inline span8 help_text' }) do |f|
(disclaimer: I did not read your complete question :))
The render_wizard method checks if it can save your #request object. If it can it will go to the next step and try to save it there.. and so on.. until the last step.
See the source code here: https://github.com/schneems/wicked/blob/master/lib/wicked/controller/concerns/render_redirect.rb#L17
To stop it from doing so you need to make sure your object can't be saved in the particular step. Something like described here: https://github.com/schneems/wicked/wiki/Building-Partial-Objects-Step-by-Step
You could also use render_step(params[:id]) instead of render_wizard.
I've written a simple Jekyll plugin to pull in my tweets using the twitter gem (see below). I'd like to keep the ruby script for the plugin on my open Github site, but following recent changes to the twitter API, the gem now requires authentication credentials.
require 'twitter' # Twitter API
require 'redcarpet' # Formatting links
module Jekyll
class TwitterFeed < Liquid::Tag
def initialize(tag_name, text, tokens)
super
input = text.split(/, */ )
#user = input[0]
#count = input[1]
if input[1] == nil
#count = 3
end
end
def render(context)
# Initialize a redcarpet markdown renderer to autolink urls
# Could use octokit instead to get GFM
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML,
:autolink => true,
:space_after_headers => true)
## Attempt to load credentials externally here:
require '~/.twitter_auth.rb'
out = "<ul>"
tweets = #client.user_timeline(#user)
for i in 0 ... #count.to_i
out = out + "<li>" + markdown.render(tweets[i].text) +
" <a href=\"http://twitter.com/" + #user + "/statuses/" +
tweets[i].id.to_s + "\">" + tweets[i].created_at.strftime("%I:%M %Y/%m/%d") +
"</a> " + "</li>"
end
out + "</ul>"
end
end
end
Liquid::Template.register_tag('twitter_feed', Jekyll::TwitterFeed)
If I replace the line
require '~/.twitter_auth.rb'
where twitter_auth.rb contains something like:
require 'twitter'
#client = Twitter::Client.new(
:consumer_key => "CEoYXXXXXXXXXXX",
:consumer_secret => "apnHXXXXXXXXXXXXXXXXXXXXXXXX",
:oauth_token => "105XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
:oauth_token_secret => "BJ7AlXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
)
If I place these contents directly into the script above, then my plugin script works just fine. But when I move them to an external file and try to read them in as shown, Jekyll fails to authenticate. The function seems to work just fine when I call it from irb, so I am not sure why it does not work during the Jekyll build.
I think that you may be confused about how require works. When you call require, first Ruby checks if the file has already been required, if so it just returns directly. If it hasn’t then the contents of the file are run, but not in the same scope as the require statement. In other words using require isn’t the same as replacing the require statement with the contents of the file (which is how, for example, C’s #include works).
In your case, when you require your ~/.twitter_auth.rb file, the #client instance variable is being created, but as an instance variable of the top level main object, not as an instance variable of the TwitterFeed instance where require is being called form.
You could do something like assign the Twitter::Client object to a constant that you could then reference from the render method:
MyClient = Twitter::Client.new{...
and then
require '~/twitter_auth.rb'
#client = MyClient
...
I only suggest this as an explanation of what’s happening with require, it’s not really a good technique.
A better option, I think, would be to keep your credentials in a simple data format in your home directory, then read them form your script and create the Twitter client with them. In this case Yaml would probably do the job.
First replace your ~/twitter_auth.rb with a ~/twitter_auth.yaml that looks soemthing like:
:consumer_key: "CEoYXXXXXXXXXXX"
:consumer_secret: "apnHXXXXXXXXXXXXXXXXXXXXXXXX"
:oauth_token: "105XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
:oauth_token_secret: "BJ7AlXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Then where you have requre "~/twitter_auth.rb" in your class, replace with this (you’ll also need require 'yaml' at the top of the file):
#client = Twitter::Client.new(YAML.load_file("~/twitter_auth.yaml"))
I am using Prawn to generate a PDF from my controller, and when accessed directly at the url, it works flawlessly, I.E. localhost:3000/responses/1.pdf
However, when I try to generate this file on the fly for inclusion in a Mailer, everything freezes up and it times out.
I have tried various methods for generating / attaching the file and none have changed the outcome.
I also tried modifying the timeout for Net::HTTP to no avail, it just takes LONGER to time out.
If I run this command on the Rails Console, I receive a PDF data stream.
Net::HTTP.get('127.0.0.1',"/responses/1.pdf", 3000)
But if I include this code in my controller, it times out.
I have tried two different methods, and both fail repeatedly.
Method 1
Controller:
http = Net::HTTP.new('localhost', 3000)
http.read_timeout = 6000
file = http.get(response_path(#response, :format => 'pdf')) #timeout here
ResponseMailer.confirmComplete(#response,file).deliver #deliver the mail!
Method 1 Mailer:
def confirmComplete(response,file)
email_address = response.supervisor_id
attachments["test.pdf"] = {:mime_type => "application/pdf", :content=> file}
mail to: email_address, subject: 'Thank you for your feedback!'
end
The above code times out.
Method 2 Controller:
ResponseMailer.confirmComplete(#response).deliver #deliver the mail!
Method 2 Mailer:
def confirmComplete(response)
email_address = response.supervisor_id
attachment "application/pdf" do |a|
a.body = Net::HTTP.get('127.0.0.1',"/responses/1.pdf", 3000) #timeout here
a.filename = "test.pdf"
end
mail to: email_address, subject: 'Thank you for your feedback!'
end
If I switch the a.body and a.filename, it errors out first with
undefined method `filename=' for #<Mail::Part:0x007ff620e05678>
Every example I find has a different syntax or suggestion but none fix the problem that Net::HTTP times out. Rails 3.1, Ruby 1.9.2
The problem is that, in development, you're only running one server process, which is busy generating the email. That process is sending another request (to itself) to generate a PDF and waiting for a response. The request for the PDF is basically standing in line at the server so that it can get it's PDF, but the server is busy generating the email and waiting to get the PDF before it can finish. And thus, you're waiting forever.
What you need to do is start up a second server process...
script/rails server -p 3001
and then get your PDF with something like ...
args = ['127.0.0.1','/responses/1.pdf']
args << 3001 unless Rails.env == 'production'
file = Net::HTTP.get(*args)
As an aside, depending on what server you're running on your production machine, you might run into issues with pointing at 127.0.0.1. You might need to make that dynamic and point to the full domain when in production, but that should be easy.
I agree with https://stackoverflow.com/users/811172/jon-garvin's analysis that you're only running one server process, but I would mention another solution. Refactor your PDF generation so you don't depend on your controller.
If you're using Prawnto, I'm guessing you have a view like
# app/views/response.pdf.prawn
pdf.text "Hello world"
Move this to your Response model: (or somewhere else more appropriate, like a presenter)
# app/models/response.rb
require 'tmpdir'
class Response < ActiveRecord::Base
def pdf_path
return #pdf_path if #pdf_generated == true
#pdf_path = File.join(Dir.tmpdir, rand(1e11).to_s)
Prawn::Document.generate(#pdf_path) do |pdf|
pdf.text "Hello world"
end
#pdf_generated = true
#pdf_path
end
def pdf_cleanup
if #pdf_generated and File.exist?(#pdf_path.to_s)
File.unlink #pdf_path
end
end
end
Then in your ResponsesController you can do:
# app/controllers/responses_controller.rb
def show
#response = Response.find params[:id]
respond_to do |format|
# this sends the PDF to the browser (doesn't email it)
format.pdf { send_file #response.pdf_path, :type => 'application/pdf', :disposition => 'attachment', :filename => 'test.pdf' }
end
end
And in your mailer you can do:
# this sends an email with the PDF attached
def confirm_complete(response)
email_address = response.supervisor_id
attachments['test.pdf'] = {:mime_type => "application/pdf", :content => File.read(response.pdf_path, :binmode => true) }
mail to: email_address, subject: 'Thank you for your feedback!'
end
Since you created it in the tmpdir, it will be automatically deleted when your server restarts. You can also call the cleanup function.
One final note: you might want to use a different model name like SupervisorReport or something - Response might get you in namespacing trouble later)