Ruby Rails: Upload File - ruby-on-rails

I am trying to follow this tutorial. It has written in previous version of Rails and I am using Rails 4. I am trying to upload file but I am getting following error:
NoMethodError in UploadController#uploadfile
undefined method `[]' for nil:NilClass
Extracted source (around line #3):
class DataFile < ActiveRecord::Base
def self.save(upload)
name = upload['datafile'].original_filename
directory = "public/data"
# create the file path
path = File.join(directory, name)
Rails.root: C:/Ruby193/mylibrary
Application Trace | Framework Trace | Full Trace
app/models/data_file.rb:3:in `save'
app/controllers/upload_controller.rb:6:in `uploadfile'
Here is data_file.rb
class DataFile < ActiveRecord::Base
def self.save(upload)
name = upload['datafile'].original_filename
directory = "public/data"
# create the file path
path = File.join(directory, name)
# write the file
File.open(path, "wb") { |f| f.write(upload['datafile'].read) }
end
end
Here is controller upload_controller.rb
class UploadController < ApplicationController
def index
render :file => 'app\views\upload\uploadfile.html'
end
def uploadfile
post = DataFile.save(params[:upload])
render :text => "File has been uploaded successfully"
end
end
Here is uploadfile.html
<h1>File Upload</h1>
<%= form_tag({:action => 'uploadfile'}, :multipart => true) do %>
<p><label for="upload_file">Select File</label>
<%= file_field 'upload', 'datafile' %></p>
<%= submit_tag "Upload" %>
<% end %>
What should I do? Thanks in advance

It looks like params[:upload] isn't what you think it is. You forgot to set the form to be multipart. If fixing that doesn't make it work, start inspecting params to see what you're actually getting.
def uploadfile
puts params.inspect # Add this line to see what's going on
post = DataFile.save(params[:upload])
render :text => "File has been uploaded successfully"
end
Also, it's not a great "answer," but I've had good success using paperclip to handle file uploads. If you just want something that works (rather than learning how to do it yourself), check into that.

Related

Rials Routing Error:Because of Controller name different from model name?

everyone! I am new to rails and working on Codecademy tutorials. But I wanted to see if I can run the same app on my mac using VS Code and got into some roadblocks. The application is basically to create a form that takes in messages and displays it (in the index view). I wanted to explore changing the names of controller and model to what I want and guess I messed up the internal routing. Following is the controller (messagec)
class MessagecController < ApplicationController
def index
#messages1 = MessagesMo1.all
end
def new
#messages2 = MessagesMo1.new
end
def create
#messages2 = MessagesMo1.new(message_params)
if #messages2.save #tells if the object is saved successfully in db or not
flash[:success] = "Great! Your post has been created!"
redirect_to '/messages'
else
flash.now[:error] = "Fix your mistakes, please."
render 'new'
end
end
private
def message_params
params.require(:message).permit(:content)
end
end
THe following is the model (messagesmo1)
class CreateMessagesMo1s < ActiveRecord::Migration[6.0]
def change
create_table :messages_mo1s do |t|
t.text :content
t.timestamps
end
end
end
The following is the routes.rb file
get '/messages' => 'messagec#index'
get '/messages/new' => 'messagec#new'
post 'messages' => 'messagec#create'
post 'messages_mo1s' => 'message_mo1s#create'
The following is the code in create.html.erb file
<%= form_for(#messages2) do |f| %>
<div class = "field">
<%= f.label :message %><br>
<%= f.text_area :content %>
</div>
<div class = "actions">
<%= f.submit "Create" %>
</div>
<% end %>
I am able to see the message list and able to go to create new message page. But when I submit the form, I am getting the following Routing error:
uninitialized constant MessageMo1sController Did you mean? MessagecController MessagesMController
My first questions is:
1) What am I missing in the routes.rb file?
2) Is there any rule between naming the model similar to that of the controller?
I just replicated all of the above, I think there are many things to keep in mind.
Your model file must be of name messagesmo1.rb and in this model:
class MessagesMo1 < ApplicationRecord
end
Your controller file should be of name messagec_controller.rb and in it:
def index
#messages1 = MessagesMo1.all
end
def new
#messages2 = MessagesMo1.new
end
def create
#messages2 = MessagesMo1.new(message_params)
if #messages2.save #tells if the object is saved successfully in db or not
flash[:success] = "Great! Your post has been created!"
redirect_to '/messages'
else
flash.now[:error] = "Fix your mistakes, please."
redirect_to '/messages/new'
end
end
private
def message_params
params.require(:messages_mo1).permit(:content)
end
In the above point, look at the message_params part, it must be :messages_mo1 and not :message
No changes required in _form.html.erb file
Your migration file must be of name timestamp__create_messages_mo1s.rb and it must have:
class CreateMessagesMo1s < ActiveRecord::Migration[6.0]
def change
create_table :messages_mo1s do |t|
t.text :content
t.timestamps
end
end
end
In your routes.rb file, change the last route:
get '/messages' => 'messagec#index'
get '/messages/new' => 'messagec#new'
post 'messages' => 'messagec#create'
post 'messages_mo1s' => 'messagec#create'
Make sure all your links are updated in index.html.erb, in show.html.erb and in new.html.erb -> Like links to show, delete, edit etc. Or if your just testing remove these links.
After making above changes, run rails db:drop db:create db:migrate as it will clean your DB of old migration.
That's it, now everything should work. The main problem is naming convention should be standard across all files. So it's better to use standard convention.
It finally worked. Following are the 2 changes:
1) Instead of <%= form_for(#messages2) do |f| %>, I used a URL parameter
<%= form_for(#messages2, url:'/messages/') do |f| %>
2)As #cdadityang mentioned, I updated the params to params.require(:messages_mo1).permit(:content)
without the URL being given explicitly, I think the rails is assuming '/message_mo1' are the path. So the URL is basically taking it to 'messagec#create'

Routing Error with Ruby on Rails

I'm fairly new to Ruby on Rails and I'm having what seems like an easy problem but I can't seem to figure out what I have done wrong. My homepage has a button and when you click the button, its supposed to create an xml file with information from the database.
Button code:
<%= button_to "Create Google File", :action => :create_google_file %>
Controller code:
class ProductsController < ApplicationController
def new
end
def create_google_file
#products = Product.find(:all)
file = File.new('dir.xml','w')
doc = #products.to_xml
file.puts doc
file.close
end
end
The error I'm getting is
No route matches {:action=>"create_google_file", :controller=>"products"}
Add this to your config/routes.rb file
match "/create_google_file" => "products#create_google_file", :as => :create_google_file
And change the button to this
<%= button_to "Create Google File", create_google_file_path %>

Uploading File into Server and store the path in database in Ruby on Rails

I am very new to ROR. I have a task to finish:
Here's the Model:
class File::DataImport < ActiveRecord::Base
attr_accessible :created_by, :file_name, :file_source, :updated_at, :updated_by
end
Here's the Controller:
class Files::DataImportsController < ApplicationController
def index
end
def new
end
end
And the views I have are index and new.
I want a field to upload data. The data should be stored in the server and save the filepath into the database in a specified column file_name. The path should be default to all uploading files.
I am stuck with how to start. Please help me to find the solution and I will learn from this.
Thanks in advance.
db/migrate/20110711000004_create_files.rb
class CreateFiles < ActiveRecord::Migration
def change
create_table :files do |t|
t.string :name
# If using MySQL, blobs default to 64k, so we have to give
# an explicit size to extend them
t.binary :data, :limit => 1.megabyte
end
end
end
app/controllers/upload_controller.rb
class UploadController < ApplicationController
def get
#file = File.new
end
end
app/views/upload/get.html.erb
<% form_for(:file,
url: {action: 'save'},
html: {multipart: true}) do |form| %>
Upload your file: <%= form.file_field("uploaded_file") %><br/>
<%= submit_tag("Upload file") %>
<% end %>
app/models/file.rb
class File < ActiveRecord::Base
def uploaded_file=(file_field)
self.name = base_part_of(file_field.original_filename)
self.data = file_field.read
end
def base_part_of(file_name)
File.basename(file_name).gsub(/[^\w._-]/, '')
end
end
app/controllers/upload_controller.rb
def save
#file = File.new(params[:file])
if #file.save
redirect_to(action: 'show', id: #file.id)
else
render(action: :get)
end
end
app/controllers/upload_controller.rb
def file
#file = File.find(params[:id])
send_data(#File.data,
filename: #File.name,
disposition: "inline")
end
app/controllers/upload_controller.rb
def show
#file = File.find(params[:id])
end
app/views/upload/show.html.erb
<h3><%= #file.name %></h3>
<img src="<%= url_for(:action => 'file', :id => #file.id) %>"/>
you should consider using one of the already available solutions like paperclip: https://github.com/thoughtbot/paperclip or carrierwave: https://github.com/jnicklas/carrierwave
Besides the Readmes there are also good tutorials out there:
http://railscasts.com/episodes/134-paperclip
http://railscasts.com/episodes/253-carrierwave-file-uploads
edit: As you want to implement it yourself I recommend examining the sources of the above on Github and try to understand what their code is doing. Also I would not bother implementing it myself, but if you have your reasons this might get you going..
You might want to look into a solution such as carrierwave.
The Github page provides a good explanation on how to use it, but this is also a nice guide.

Unable to pass uploaded file to model

I am relatively new to rails cannot get the following code to work. I am trying to upload a data file (Excel or csv), copy it to a temp folder and create a record in a Datafiles model which holds basic file information, such as filename, type, and date. Then I want to read the file and use the data to create or update records in several other models. If all goes well, move the file to a permanent location and write the new path in the Datafiles record.
Controller:
def new
#datafile = Datafile.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #datafile }
end
end
def create
#datafile = Datafile.new(params[:upload])
#datafile.save!
redirect_to datafile_path(#datafile), :notice => "Successfully imported datafile"
rescue => e
logger.error( 'Upload failed. ' + e.to_s )
flash[:error] = 'Upload failed. Please try again.'
render :action => 'new'
end
View:
<%= form_for #datafile, :html => {:multipart => true} do |f| %>
<p>
<%= f.label(:upload, "Select File:") %>
<%= f.file_field :upload %>
</p>
<p> <%= f.submit "Import" %> </p>
<% end %>
Model:
require 'spreadsheet'
class Datafile < ActiveRecord::Base
attr_accessor :upload
attr_accessible :upload
before_create :upload_file
def upload_file
begin
File.open(Rails.root.join('uploads/temp', upload.original_filename), 'wb') do |file|
file.write(upload.read)
self.filename = upload.original_filename
Spreadsheet.client_encoding = 'UTF-8'
#book = Spreadsheet.open(file.path)
self.import
end
rescue => e
#upload_path = Rails.root.join('uploads/temp', upload.original_filename)
File.delete(#upload_path) if File::exists?(#upload_path)
raise e
end
end
def import
case #book.worksheet(0).row(0)[0]
when "WIP Report - Inception to Date"
self.report_type = 'WIP'
puts 'report_type assigned'
self.import_wip
else
self.report_type = 'Unknown'
end
end
def import_wip
self.end_date = #book.worksheet(0).row(0)[3]
puts 'end_date assigned'
end
def formatted_end_date
end_date.strftime("%d %b, %Y")
end
end
However, it fails and the rails server window says
Started POST "/datafiles" for 127.0.0.1 at 2011-05-24 16:05:25 +0200
Processing by DatafilesController#create as HTML
Parameters: {"utf8"=>"✓", "datafile"=>{"upload"=>#<ActionDispatch::Http::UploadedFile:0xa0282d0 #original_filename="wip.xls", #content_type="application/vnd.ms-excel", #headers="Content-Disposition: form-data; name=\"datafile[upload]\"; filename=\"wip.xls\"\r\nContent-Type: application/vnd.ms-excel\r\n", #tempfile=#<File:/tmp/RackMultipart20110524-14236-1kcu3hm>>}, "commit"=>"Import"}
Upload failed. undefined method `original_filename' for nil:NilClass
Rendered datafiles/new.html.erb within layouts/application (54.5ms)
Completed 200 OK in 131ms (Views: 56.3ms | ActiveRecord: 0.0ms)
I have rspec model tests that pass and controller tests that fail to redirect after saving. I can post them if it would be useful.
I inserted the raise #datafile.to_yaml and got the following in the terminal.
ERROR RuntimeError: --- !ruby/object:Datafile
attributes:
filename:
report_type:
import_successful:
project:
begin_date:
end_date:
created_at:
updated_at:
attributes_cache: {}
changed_attributes: {}
destroyed: false
marked_for_destruction: false
persisted: false
previously_changed: {}
readonly: false
I notice that :upload is not listed. Can I set model instance variables from the form? :upload is an instance variable, not an attribute, because I do not want to keep the uploaded file in the database (just its path to the local directory). If I cannot set instance variables in the view's form, any suggestions? Does it make sense (in terms of MVC) to upload the file to a temp folder in the controller, then create a model record by passing it the temp file's path?
Hello I am pretty new to Rails and was strugling with this as well I found a solution though it probably isn't the best. It does work though.
in you model make a public function called import_upload
def import_upload( upload )
#uploaded_file = upload
end
now in your controller you can explicitly pass it. I don't know why this doesn't happen automatically if you make an attr_accsessor with the same name as the file_field but this was the solution that worked for me.
def new
foo = Foo.new( params[:foo] )
foo.import_upload( params[:foo][:uploaded_file] ) #This is were the majic happens
#Do your saving stuff and call it a day
end

How to parse the contents of a uploaded file in RoR

I am new to Rails.
In my project where users have to upload a file, I store it
then I have to parse the file contents and show it in new form.
I have successfully done the file uploading portion,
now how should I read the contents of it?
Try something like this:
upload = params[:your_upload_form_element]
content = upload.is_a?(StringIO) ? upload.read : File.read(upload.local_path)
Very small files can be passed as strings instead of uploaded files, therefore you should check for that and handle it accordingly.
You can open files and read their contents in Ruby using the File class, as this simple example demonstrates:
# Open a file in read-only mode and print each line to the console
file = File.open('afile.txt', 'r') do |f|
f.each do |line|
puts line
end
end
Complete Example
Take, for example, uploading an import file containing contacts. You don't need to store this import file, just process it and discard it.
Routes
routes.rb
resources :contacts do
collection do
get 'import/new', to: :new_import # import_new_contacts_path
post :import, on: :collection # import_contacts_path
end
end
Form
views/contacts/new_import.html.erb
<%= form_for #contacts, url: import_contacts_path, html: { multipart: true } do |f| %>
<%= f.file_field :import_file %>
<% end %>
Controller
controllers/contacts_controller.rb
def new_import
end
def import
begin
Contact.import( params[:contacts][:import_file] )
flash[:success] = "<strong>Contacts Imported!</strong>"
redirect_to contacts_path
rescue => exception
flash[:error] = "There was a problem importing that contacts file.<br>
<strong>#{exception.message}</strong><br>"
redirect_to import_new_contacts_path
end
end
Contact Model
models/contact.rb
def import import_file
File.foreach( import_file.path ).with_index do |line, index|
# Process each line.
# For any errors just raise an error with a message like this:
# raise "There is a duplicate in row #{index + 1}."
# And your controller will redirect the user and show a flash message.
end
end
Hope that helps others!
JP

Resources