Grab file path from upload - ruby-on-rails-3

I have an RoR script that when given the file path for an XML file, will parse it. I want to be able to find the file with the GUI, and then allow for my script to run. Here's what I have in the view:
<%= form_for :setup, :html=>{:multipart=>true}, :url=>{action=>"create"} do |f| %>
<%= f.file_filed :my_file %>
<%= f.submit "Upload" %>
<% end %>
The controller for the create area is:
$XML_FILE = params[:my_file]
routers = Router.new
#title = routers.overall_setup
if #title == "Everything worked" then
redirect_to "/"
end
Basically from here, how do you snag the file path out? I know that once the correct file path can be placed to $XML_FILE that the rest of the code works

After you've submited a form you can find upploaded file this way:
path = params[:setup][:my_file][:tempfile].path
where params[:setup][:my_file] is your file_filed name in the form
Rails 3
path = params[:setup][:my_file].path

Related

Encoding::UndefinedConversionError when trying to upload an image

I am using the following code to upload the image in /public/uploads/ folder in my root rails directory.
uploaded_io = params[:product_image]
File.open(Rails.root.join('public','uploads', uploaded_io.original_filename), 'w') do |file|
file.write(uploaded_io.read)
end
My form looks like this
<%= form_tag({:action => :configure_product}, :multipart => true) do %>
<%= label_tag(:product_image, "Image:") %><br />
<%= file_field_tag 'product_image' %>
<%= submit_tag "Save and add another", :name => 'save and add another' %>
<%= submit_tag "Save", :name => 'save' %>
<% end %>
but when trying to submit the form I get the following error.
Encoding::UndefinedConversionError in ConfigureCategoryController#configure_product
"\xFF" from ASCII-8BIT to UTF-8
I replaced the writing mode from 'w' to 'wb' and now I am getting
NoMethodError in ConfigureCategoryController#configure_product
undefined method `name' for nil:NilClass
New at rails. Would surely appreciate the help.
You'll need to open the file as a binary file by appending b to the open type.
File.open("#{ Rails.root }/tmp/uploaded_image.gif", "wb") do |f|
The other issue you're having is specific to whatever it is your application does.

How to compute path to file in public folder?

I created a folder called Uploads in my public folder.
I'd like to have people be able to click a link and download a file in that Uploads folder.
Here's my code:
<p>
<b>Certifications</b>
<%= link_to #tutor.tutor_degrees.first.name, # => "AS Level English"
root_path + #tutor.tutor_degrees.first.certification_scan %>
</p>
http://localhost:3000/Screen%20shot%202013-02-04%20at%206.01.11%20PM.png
It's missing the /uploads/ bit in the URL path.
If I add that string manually, I get a borked URL:
<p>
<b>Certifications</b>
<%= link_to #tutor.tutor_degrees.first.name,
root_path + '/uploads/' + #tutor.tutor_degrees.first.certification_scan %>
</p>
http://uploads/Screen%20shot%202013-02-04%20at%206.01.11%20PM.png
Try this...
<%= link_to #tutor.tutor_degrees.first.name, "http://#{request.host}/uploads/#{#tutor.tutor_degrees.first.certification_scan}" %>
must work :)

In Rails how to check if javascript file exists before using javascript_include_tag

In a rails 3.2 app, I would like to check for the existence of a javascript file in the asset pipeline before including the javascript_include_tag in a file. Something like:
<% if javascript_file_exists? %>
<%= javascript_include_tag "#{controller_name}_controller" %>
<% end %>
I would like to do this so that the absence of a javascript file will not result in an error. Is there a way to do this?
A more Rails way is to use a helper. This allows you to check for a coffeescript or a javascript version of a file:
def javascript_exists?(script)
script = "#{Rails.root}/app/assets/javascripts/#{params[:controller]}.js"
File.exists?(script) || File.exists?("#{script}.coffee")
end
Then you can use it in your layout:
<%= javascript_include_tag params[:controller], :media => "all" if javascript_exists?(params[:controller]) %>
You can do the same with your CSS:
-- helper --
def stylesheet_exists?(stylesheet)
stylesheet = "#{Rails.root}/app/assets/stylesheets/#{params[:controller]}.css"
File.exists?(stylesheet) || File.exists?("#{stylesheet}.scss")
end
-- layout --
<%= stylesheet_link_tag params[:controller], :media => "all" if stylesheet_exists?(params[:controller]) %>
EDIT: updated #javascript_exists?
I have recently made some changes to my javascript_exists? helper:
def javascript_exists?(script)
script = "#{Rails.root}/app/assets/javascripts/#{script}.js"
extensions = %w(.coffee .erb .coffee.erb) + [""]
extensions.inject(false) do |truth, extension|
truth || File.exists?("#{script}#{extension}")
end
end
call it in the application layout:
<%= javascript_include_tag params[:controller] if javascript_exists?(params[:controller]) %>
This will now handle more extensions and use an inject to determine if the file exists. You can then add a bunch more extensions to the extensions array, as needed for your app.
EDIT DEUX: Updated #stylesheet_exists?
Same, but for stylesheets:
def stylesheet_exists?(stylesheet)
stylesheet = "#{Rails.root}/app/assets/stylesheets/#{stylesheet}.css"
extensions = %w(.scss .erb .scss.erb) + [""]
extensions.inject(false) do |truth, extension|
truth || File.exists?("#{stylesheet}#{extension}")
end
end
EDIT Last (probably): DRY it up
def asset_exists?(subdirectory, filename)
File.exists?(File.join(Rails.root, 'app', 'assets', subdirectory, filename))
end
def image_exists?(image)
asset_exists?('images', image)
end
def javascript_exists?(script)
extensions = %w(.coffee .erb .coffee.erb) + [""]
extensions.inject(false) do |truth, extension|
truth || asset_exists?('javascripts', "#{script}.js#{extension}")
end
end
def stylesheet_exists?(stylesheet)
extensions = %w(.scss .erb .scss.erb) + [""]
extensions.inject(false) do |truth, extension|
truth || asset_exists?('stylesheets', "#{stylesheet}.css#{extension}")
end
end
Although I know about the assets pipeline and the manifest in application.js, my approach is to keep app's "essential" javascript in application.js and load only the specific javascript for each controller using
<%= javascript_include_tag "application" %>
<%= javascript_include_tag controller_name if File.exists?("#{Rails.root}/app/assets/javascripts/#{controller_name}.js") %>
at the end of my application.html.erb before the </body>
I know that causes the browser to issue one more request to get the controller specific javascript but perhaps after the first request, that javascript will be cached by the browser.
I know this is a pretty old question, but I would suggest using the find_asset function. For Rails 4 you could do something like:
<%= javascript_include_tag params[:controller] if ::Rails.application.assets.find_asset("#{params[:controller]}.js") %>
Custom ViewHelpers to the Rescue!
Add this to your ApplicationHelper:
module ApplicationHelper
def controller_stylesheet(opts = { media: :all })
if Rails.application.assets.find_asset("#{controller_name}.css")
stylesheet_link_tag(controller_name, opts)
end
end
def controller_javascript(opts = {})
if Rails.application.assets.find_asset("#{controller_name}.js")
javascript_include_tag(controller_name, opts)
end
end
end
and you can use them like this in your application.html.erb:
<%= controller_stylesheet %>
<%= controller_javascript %>
Note: This works with all .js, .coffee, .css, .scss even though it just says .css and .js
In Rails3.2 all what you need is to write the list of your js files at the top of application.js file. For example,
//=require jquery.min
//=require jquery_ujs
//=require general
If one of these files don't really exists (btw put them at app/assets/javascripts) - not a problem, no errors will be shown.
Catch the error:
#/app/helpers/application_helpers.rb
def javascript_include_tag_if_assets(*args)
javascript_include_tag(*args)
rescue Sprockets::Rails::Helper::AssetNotFound
''
end
Alternatively, since this asks to check before using the method, javascript_include_tag uses javascript_path so you might as well use it to check and then catch that error. This works for all javascript-like assets. For css-like assets, use stylesheet_path.
#/app/helpers/application_helpers.rb
def javascript_include_tag_if_assets(*files, **opts)
files.each { |file| javascript_path(file) }
javascript_include_tag(*files, **opts)
rescue Sprockets::Rails::Helper::AssetNotFound
''
end

Rails file uploading and Heroku

I have a problem with file uploading on Heroku server.
(Also a hint about the right way of doing such type of things with rails would be greatly appreciated - I'm very new in RoR).
All this code is about uploading some CSV file, then allowing user to tweak couple of settings, and parse file after all. This usually work on localhost (several times I get troubles with value stored in session), but on Heroku it is always die on upload.
In one of the neighborhood questions was written that Heroku store file only during singe instance run, but I still couldn't find anything about this in Heroku's docs. Should I store file data in the db right after upload, so in such case it always be available? The downside - files could be pretty big, about 10-20Mb, it isn't looks nice.
Heroku logs say:
2012-05-21T19:27:20+00:00 app[web.1]: Started POST "/products/upload" for 46.119
.175.140 at 2012-05-21 19:27:20 +0000
2012-05-21T19:27:20+00:00 app[web.1]: Processing by ProductsController#upload
as HTML
2012-05-21T19:27:20+00:00 app[web.1]: Parameters: {"utf8"=>"тЬУ", "authenticit
y_token"=>"aqJFg3aqENfxS2lKCE4o4txxkZTJgPx36SZ7r3nyZBw=", "upload"=>{"my_file"=>
#<ActionDispatch::Http::UploadedFile:0x000000053af020 #original_filename="marina
-AutoPalmaPriceList_2011-07-30.txt", #content_type="text/plain", #headers="Conte
nt-Disposition: form-data; name=\"upload[my_file]\"; filename=\"marina-AutoPalma
PriceList_2011-07-30.txt\"\r\nContent-Type: text/plain\r\n", #tempfile=#<File:/t
mp/RackMultipart20120521-1-10g8xmx>>}, "commit"=>"Upload"}
2012-05-21T19:27:20+00:00 app[web.1]:
2012-05-21T19:27:20+00:00 app[web.1]: LoadError (no such file to load -- CSV):
2012-05-21T19:27:20+00:00 app[web.1]: app/controllers/products_controller.rb:8
2:in `upload'
2012-05-21T19:27:20+00:00 app[web.1]:
2012-05-21T19:27:20+00:00 app[web.1]:
2012-05-21T19:27:20+00:00 app[web.1]: cache: [POST /products/upload] invalidate,
pass
The code itself:
ProductsController:
def import
respond_to do |format|
format.html
end
end
def import_adjust
case params[:commit]
when "Adjust"
#col_default = params[:col_data]
#abort #col_default.to_yaml
#update csv reader with form data, restore filters from params
when "Complete"
#all ok, read the whole file
#abort params.to_yaml
redirect_to import_complete
else
#col_default = nil
end
#read first part of the file
#tmp = session[:import_file]
#csv = []
source = CSV.open #tmp, {col_sep: ";"}
5.times do
line = source.readline
if line.size>0
#line_size = line.size
#csv.push line
end
end
#generate a selection array
#selection = select_tag 'col_data[]', options_for_select([['name','name'], ['brand','brand'], ['delivery_time','delivery_time'], ['price','price']])
##csv = [selection * line_size] + #csv
end
def import_complete
#remove all items
#todo check products with line items will not be destroyed.
Product.destroy_all
#abort params.to_yaml
map = {}
cnt = 0
#todo check for params count.
params[:col_data].each do |val|
map[cnt] = val if val != 'ignore'
cnt += 1
end
source = CSV.open session[:import_file], {col_sep: ';'}
source.each do |row|
cnt += 1
if row.size > 0
item = Product.new
map.each do |col, attr|
item[attr] = row[col]
end
item[:provider_id] = params[:adjust][:provider]
item.save
#abort item.to_yaml
end
end
#abort map.to_yaml
#todo response needed.
end
def upload
require 'CSV' #looks like I dont need this in fact.
#tmp = params[:upload][:my_file].path #tempfile
#csv = []
#source = CSV.open #tmp, {col_sep: ";"}
session[:import_file] = params[:upload][:my_file].path
respond_to do |format|
format.html { redirect_to action: 'import_adjust' }
end
end
upload.html.erb:
<h1>Uploaded</h1>
<%= #tmp %>
<% #csv.each do |val| %>
<%= val %>
<% end %>
_form_import.html.erb:
<%= form_for :upload, :html => {:multipart => true}, :url => {action: "upload"} do |f| %>
<%= f.file_field :my_file %>
<%= f.submit "Upload" %>
<% end %>
import_adjust.html.erb:
<h1>New product</h1>
<%= form_for :adjust, :url => {action: "import_adjust"} do |f| %>
<% if #csv %>
<table>
<tr>
<% #line_size.times do |cnt| %>
<td>
<%= select_tag 'col_data[]',
options_for_select([
['--ignore--', 'ignore'],
['name','name'],
['brand','brand'],
['delivery_time','delivery_time'],
['price','price']
], #col_default!=nil ? #col_default[cnt] : nil) %>
</td>
<% end %>
</tr>
<% #csv.each do |val| %>
<tr>
<% val.each do |cell| %>
<td>
<%= cell %>
</td>
<% end %>
</tr>
<% end %>
</table>
<% end %>
<%= f.label :delimiter, 'Разделитель' %>
<%= f.text_field :delimiter %>
<br>
<%= f.label :provider, 'Поставщик' %>
<%#todo default empty option needed! Human mistakes warning! %>
<%= f.select :provider, Provider.all.collect { |item| [item.name, item.id] } %>
<br>
<%= f.label :delimiter, 'Разделитель' %>
<%= f.text_field :delimiter %>
<br>
<%# Adjust for proceed adjusting or Complete for parsing %>
<%= f.submit "Adjust" %>
<%= f.submit "Complete" %>
<% end %>
<%= link_to 'Back', products_path %>
Could you paste the entire controller code? The problem is on line #82, but I can't be 100% confident what line that is if you've stripped the class def and before_filters out.
That said, it looks like the problem is with one of the CSV.open lines. The way you're trying to set session[:import_file] is not guaranteed to work. If you ever run the app on more than one dyno you could have the first request served by your web.1 dyno and the second served by web.2, and they have different file systems and would not be able to see the same temp files.
I'd suggest one of the following:
Do all the processing immediately on the upload and avoid the re-direct.
An improvement on that would be to have the upload store the data somewhere shared and accessible (the database or S3) and have start a background job/process to do the processing.
Best of all would be to upload directly to S3 (I believe the S3 Uploader library can do this, there are probably others) and issue a callback to create a background job to process.
That last option means your web dynos are never tied up handling massive uploads and you don't burden the user with waiting for the latency involved in upload to server->store in S3->schedule background job, it is reduced simply to store in S3 from their perspective.
I have an identical scenario as Lifecoder where a user uploads a file, names the columns using a map_fields plugin (by Andrew Timberlake), and then the file is parsed and processed. Here's how I handle it:
file_field = params[options[:file_field]]
map_fields_file_name = "map_fields_#{Time.now.to_i}_#{$$}"
bucket = S3.buckets[CSV_COUPON_BUCKET_NAME] # gets an existing bucket
obj = bucket.objects[map_fields_file_name]
obj.write( file_field.read )
# Save the name and bucket to retrieve on second pass
session[:map_fields][:bucket_name] = map_fields_file_name
Then on the second pass to process the file, I open the file and read it back into temp for the dyno to process:
# Get CSV data out of bucket and stick it back into temp, so we pick up where
# we left off as far as map_fields is concerned.
bucket = S3.buckets[CSV_COUPON_BUCKET_NAME]
obj = bucket.objects[session[:map_fields][:bucket_name]]
temp_path = File.join(Dir::tmpdir, "map_fields_#{Time.now.to_i}_#{$$}")
File.open(temp_path, 'wb') do |f|
f.write obj.read
end
I had to use the plugin so I could modify the code, as obviously the gem is handled by Heroku and doesn't allow for modifications.

Redirecting outside a web app?

This is my first time developing a rails app from scratch. The goal of my code is to use a title and link (both stored in a database table) to redirect users to the link. (Problem) When I click the link title, I'm redirected to localhost:3000/google.com instead of google.com. (Assuming google.com was the value in link.link)
<h1>Links#index</h1>
<% #links.each do |link| %>
<p>
<%= link_to link.title, link.link %>
</p>
<% end %>
Notes:
(1) Using Rails 3.1
(2) The contents of my routes.rb file are below (Not sure if the use of resources :links has something to do with my problem)
CodeHed::Application.routes.draw do
resources :links
get "links/index"
root :to => "links#index"
end
Are your links prefixed with "http://"? If not, try adding that in programmatically with something like:
def add_http(link)
if (link =~ /http(?:s)?:\/\//)
link
else
"http://#{link}"
end
end
If that doesn't work then you could simply enter raw html:
<h1>Links#index</h1>
<% #links.each do |link| %>
<p>
<%= link_to title, add_http(link) %>
</p>
<% end %>
(I haven't checked this code)