I'm building a ruby on rails app that lets the user pick a file from their hard drive, and then writes specific lines from the text file into a database. I've built the form, but now I don't know how to get the content of the file into a variable, format it and then write it into the database.
I have a database table named drafts with colums set_1, set_2, set_3. I want to write line 13(from the text file) into set_1, line 165 into set_2 and line 317 into set_3. I also want to format the lines before writing them. In the file they look like this ------ FRF ------ and I only want FRF.
I've spent a lot of time searching stackoverflow and rubyonrails guides but have a hard time figuring this out. I'm very new to ruby and rails in general, so any help is appreciated.
Here's my controller (drafts_controller.rb):
class DraftsController < ApplicationController
def new
#page_title = "Upload MTGO Draft"
#draft = Draft.new
end
def create
#draft = Draft.new(draft_params)
if #draft.save
flash[:notice] = "Draft Saved!"
redirect_to drafts_show_path
else
render "new"
end
end
def index
#page_title = "MTGO Draft"
end
def show
#page_title = "MTGO Draft Replayer"
end
def search
#page_title = "Search MTGO Draft"
end
def destroy
end
def draft_params
params.require(:draft).permit(:name, :set_1, :set_2, :set3, :file_setter)
end
end
and my view: (new.html.erb):
<%= form_for #draft, :multipart => true do |f| %>
<div class="form-group">
Draft Log: <%= f.file_field :file_setter %>
<%= f.submit 'Save' %>
</div>
<% end %>
here's the model (draft.rb)
class Draft < ActiveRecord::Base
def file_setter=(file)
path = file.tempfile.to_path.to_s
lines = File.read(path).split("\r\n")
self.set_1 = lines[12]
self.set_2 = lines[164]
self.set_3 = lines[316]
end
attr_accessor :file_setter
end
If you need more info or want to get into the whole app more he's a gitHub repository.
Thanks for the help.
Here's the log excerp:
Started POST "/drafts" for 127.0.0.1 at 2015-04-10 10:52:57 +0200
Processing by DraftsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"8IjtOXuXm3fswWTslDTq5ijjo0dpCWHlu9X7UFb5prvawrZfkLC46v7PbqTgrSrvukLxAAoBIBftsGqiXv79JA==", "draft"=>{"file_setter"=>#<ActionDispatch::Http::UploadedFile:0x00000005ee4a08 #tempfile=#<Tempfile:/tmp/RackMultipart20150410-3067-1fssm13>, #original_filename="1", #content_type="application/octet-stream", #headers="Content-Disposition: form-data; name=\"draft[file_setter]\"; filename=\"1\"\r\nContent-Type: application/octet-stream\r\n">}, "commit"=>"Save"}
[1m[36m (0.2ms)[0m [1mBEGIN[0m
[1m[35mSQL (1.8ms)[0m INSERT INTO `drafts` (`created_at`, `updated_at`) VALUES ('2015-04-10 08:52:57', '2015-04-10 08:52:57')
[1m[36m (41.6ms)[0m [1mCOMMIT[0m
Redirected to http://localhost:3000/drafts/show
Completed 302 Found in 57ms (ActiveRecord: 43.6ms)
Add this method to your model:
def file_setter=(file)
path = file.tempfile.to_path.to_s
lines = File.read(path).split("\r\n")
self.set_1 = lines[12]
self.set_2 = lines[164]
self.set_3 = lines[316]
end
In your form change the file_field value from :file to :file_setter like this:
<%= form_for #draft, :multipart => true do |f| %>
<div class="form-group">
Draft Log: <%= f.file_field :file_setter %>
</div>
<% end %>
// Its very important that you add :multipart => true to the form_for method. Otherwise file uploads wont work.
// Also add this to your model:
attr_accessor :file_setter
// In your controller you have to change this:
def draft_params
params.require(:draft).permit(:name, :set_1, :set_2, :set3)
end
To this:
def draft_params
params.require(:draft).permit(:name, :set_1, :set_2, :set3, :file_setter)
end
Related
I am stuck in that although my array parameter is being captured, it fails to insert it into the database. I do not get an unpermitted parameters error or anything. It just fails to recognize the array when inserting to the DB.
What I would like to do: Capture any box that is checked off, and insert the data as separate rows into the database.
Here is what I have:
/subscribe/categories/2
<div>
<%= simple_form_for #subscription do |f| %>
<div class="form-inputs">
<%= f.hidden_field :dashboard_id, value: 1 %>
<%= f.hidden_field :category_id, value: #category.id %>
<%= f.collection_check_boxes :feed_id, Feed.where("category_id = ?", #category), :id, :name %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
</div>
CategoriesController
def show
#subscription = Subscription.new
end
SubscriptionsController
def subscription_params
params.require(:subscription).permit(:dashboard_id, :category_id, :feed_id => [])
end
When submitted, here is the console output:
Processing by SubscriptionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Zw2VkwujDLQjV4krjPF8N1EiYo5L/XOrUwedlHCvwB0=", "subscription"=>{"dashboard_id"=>"1", "category_id"=>"2", "feed_id"=>["3", "4", ""]}, "commit"=>"Create Subscription"}
(0.2ms) BEGIN
SQL (1.6ms) INSERT INTO `subscriptions` (`category_id`, `created_at`, `dashboard_id`, `updated_at`) VALUES (2, '2014-01-06 02:17:41', 1, '2014-01-06 02:17:41')
(116.6ms) COMMIT
Redirected to http://localhost:3000/subscriptions/3
Completed 302 Found in 173ms (ActiveRecord: 119.3ms)
Two questions:
Why is there an extra "" for my feed_id array? (Only 2 possible checkboxes)
Why am I not capturing the array to insert it into the database?
Thanks!
The reason your array is not being inserted into the database is that Active Record currently does not support the Postgresql array type. In order to insert these as separate rows the check-boxes need to be represented as individual instances of a model.
Possibly something like...
Category < ActiveRecord::Base
has_many: feeds
...
end
Feed < ActiveRecord::Base
belongs_to: category
...
end
Now this would also mean that you would need to use the form_tag helper instead of the form_for. This would allow you to create a composite form consisting of multiple individual objects. Inserting this would just mean iterating and inserting over each object; giving you separate rows. Hope this helps.
For anyone that wants to know how to do this, here is one solution I've come up with. Everything in my first post remains the same. In my SubscriptionsController (from which the form is created), here is my create action:
def create
dashboard = params[:subscription][:dashboard_id]
category = params[:subscription][:category_id]
feed = params[:subscription][:feed_id]
#subscription = feed.map { |subscribe| Subscription.create(dashboard_id: dashboard, category_id: category, feed_id: subscribe) }
end
Works as advertised. If anyone thinks for some reason that I am overlooking this is a terrible idea, please comment.
I'm following along with this tutorial (http://www.yoniweisbrod.com/autocomplete-magic-with-rails/) using jQuery-ui's autocomplete, but when I attempt to search using the text field, it routes to the controller's show method instead of the autocomplete_ingredient_name method.
Here's the code for my form:
<%= form_tag(cocktail_path(1), :method => 'get', :class => "search_form", :remote => true) do %>
<%= label_tag(:query, "Choose ingredients:") %>
<%= autocomplete_field_tag(:query, params[:query], autocomplete_ingredient_name_cocktails_path, {class: "search-query", placeholder: "", type: "search"}) %>
<% #ingredients.each do |ingredient| %>
<%= hidden_field_tag "ingredients[]", ingredient.name %>
<% end %>
<%= submit_tag("Search") %>
<% end %>
And my controller.
class CocktailsController < ApplicationController
autocomplete :ingredient, :name
def index
#cocktails = []
#ingredients = []
end
def autocomplete_ingredient_name
#ingredients = Ingredient.order(:name).where("name LIKE ?", "'%#{params[:query]}%'")
respond_to do |format|
format.html
format.json {
render json: #ingredients.map(&:name)
}
end
end
def show
hash = {}
#cocktails = []
#ingredients = Ingredient.all.map {|ingredient| ingredient}
#ingredients.select! {|ingredient| ingredient.name.downcase.include?(params[:query])}
if params[:ingredients]
old_ingredients = []
params[:ingredients].each do |ing|
old_ingredients << Ingredient.find_by(name: ing)
end
cocktails = #ingredients.map {|ingredient| ingredient.cocktails }.flatten
old_cocktails = old_ingredients.map {|ingredient| #cocktails << ingredient.cocktails }.flatten!
old_cocktails.each do |cocktail|
hash[cocktail] = 1
end
cocktails.each do |cocktail|
if hash.has_key?(cocktail)
#cocktails << cocktail
end
end
#cocktails = #cocktails.uniq.flatten
else
#cocktails = #ingredients.map {|ingredient| ingredient.cocktails }.flatten
end
end
end
And here is the message from my server, going to the CocktailsController#show method, instead of the autocomplete method.
Started GET "/cocktails/autocomplete_ingredient_name?term=mi" for ::1 at 2015-10-12 15:32:21 -0500
Started GET "/cocktails/autocomplete_ingredient_name?term=mi" for ::1 at 2015-10-12 15:32:21 -0500
Processing by CocktailsController#show as JSON
Processing by CocktailsController#show as JSON
Parameters: {"term"=>"mi", "id"=>"autocomplete_ingredient_name"}
Parameters: {"term"=>"mi", "id"=>"autocomplete_ingredient_name"}
Ingredient Load (8.6ms) SELECT "ingredients".* FROM "ingredients"
Ingredient Load (8.6ms) SELECT "ingredients".* FROM "ingredients"
Completed 500 Internal Server Error in 38ms (ActiveRecord: 8.6ms)
Completed 500 Internal Server Error in 38ms (ActiveRecord: 8.6ms)
TypeError (no implicit conversion of nil into String):
app/controllers/cocktails_controller.rb:25:in `include?'
app/controllers/cocktails_controller.rb:25:in `block in show'
app/controllers/cocktails_controller.rb:25:in `select!'
app/controllers/cocktails_controller.rb:25:in `show'
TypeError (no implicit conversion of nil into String):
app/controllers/cocktails_controller.rb:25:in `include?'
app/controllers/cocktails_controller.rb:25:in `block in show'
app/controllers/cocktails_controller.rb:25:in `select!'
app/controllers/cocktails_controller.rb:25:in `show'
The code is supposed to create a jQuery-ui dropdown that predicts what you're searching, but the dropdown never shows up and it immediately returns a 500 error.
Any thoughts on why this isn't routing to the right method would be extremely appreciated!
This is likely because of a routing error, i.e. your GET "/cocktails/autocomplete_ingredient_name?term=mi" directive is handled by the wrong entry in your /config/routes.rb file.
Make sure the route that handles your autocomplete process is defined prior to the route that handles the show action of your cocktails controller.
Since the latter usually takes the form get 'cocktails/:id', the 'autocomplete_ingredient_name' part of the URI is affected to the :id component and the processing is delegated to the show action of your controller with said id.
The autocomplete route is defined, since the autocomplete_ingredient_name_cocktails_path directive in your form generates a properly formatted URI ; so I believe this is merely an issue of precedence.
You have another potential issue, however: your autocomplete query parameter is 'term' in your request, but it is 'query' in your controller action. They should have the one and same name.
I've searched and searched and nothing came up, so I need to ask you guys for help.
I have a simple new form that should take two fields from form_for and then post it into database. The problem is it doesn't.
My sample db has only two fields: name and site_id
Here's my new.html.erb:
<%= form_for #kejsu do |f| %>
<%= f.text_field :name %>
<%= f.text_field :site_id %>
<%= f.submit "Create" %>
<% end %>
Here's the controller:
def new
#kejsu = Kejs.new
end
def create
#kejsu = Kejs.new(params[:kejsu])
if #kejsu.save
redirect_to kejs_index_path
else
render "new"
end
end
After hitting submit button only timestamps are inserted. Here's the snippet from rails server:
Started POST "/cases" for 192.168.56.1 at 2013-12-10 23:11:03 +0000
Processing by KejsyController#create as HTML
Parameters: {"utf8"=>"â", "authenticity_token"=>"Sr2ssiwtRtk9pRT5VfuDFglsEmGnjzwVkRGGBSb2zhA=", "kejs"=> {"name"=>"aa", "site_id"=>"3"}, "commit"=>"Create"}
(0.1ms) begin transaction
SQL (5.1ms) INSERT INTO "kejs" ("created_at", "updated_at") VALUES (?, ?) [["created_at", Tue, 10 Dec 2013 23:11:03 UTC +00:00], ["updated_at", Tue, 10 Dec 2013 23:11:03 UTC +00:00]]
(4.0ms) commit transaction
Redirected to http://192.168.56.101:3000/cases
Completed 302 Found in 14ms (ActiveRecord: 9.2ms)
As you can see those fields are passed as parameters, but INSERT doesn't insert them at all.
I've tried it with default restful routing, writing my own routes and it doesn't work either way.
As a bonus my routes:
get 'cases' => 'kejsy#index', as: :kejs_index
get 'cases/new' => 'kejsy#new', as: :new_kejs
post 'cases' => 'kejsy#create'
Your params[:kejsu] reference uses :kejsu, but your parameters exist under params[:kejs], so you're picking up nil for the value.
The key in params is determined by the class name of the object passed to form_for (i.e. Kejs in this case), not by the variable name (i.e. #kejsu). If you think about it, this makes sense because the variable name is not even available to form_for, since the value of the #kejsu is what is passed.
to debug you can use render :text => params.inspect inside your controller so I will change my code like this
def create
render :text => params.inspect
end
Now go and sumbit your form. What is name of key inside your params? Is it kejsu or kejs?
If its kejs than you can change your code to
def create
#kejsu = Kejs.new(params[:kejs])
if #kejsu.save
redirect_to kejs_index_path
else
render "new"
end
end
As promised, the answer with strong parameters:
def create
#kejsu = Kejs.new(kejs_params)
if #kejsu.save
redirect_to kejs_index_path
else
render "new"
end
end
def kejs_params
params.require(:kejs).permit(:name, :site_id)
end
I needed to change the way I'm dealing with parameters passed via form by creating new method that permits given fields. Now it works like it supposed to.
Thanks for all the hints.
I have a form with multiple file uploads, The issue is when i am submitting the form and an validation error occurs, the file input field gets reset.
I basically wanted to persist those files inside the file input field for the complete process.
I have also gone through few links
How can I "keep" the uploaded image on a form validation error?
Please let me know what are the various options in such cases that one can follow.
Carrierwave is a great tool for handling file uploads and can handle this for you
https://github.com/jnicklas/carrierwave#making-uploads-work-across-form-redisplays
I took a completely different approach to the other solutions on offer here, as I didn't fancy switching to CarrierWave or using yet another gem to implement a hack to get around this.
Basically, I define placeholders for validation error messages and then make an AJAX call to the relevant controller. should it fail validation I simply populate the error message placeholders - this leaves everything in place client side including the file input ready for resubmission.
Example follows, demonstrating an organisation with nested address model and a nested logo model (that has a file attachment) - this has been cut for brevity :
organisations/_form.html.erb
<%= form_for #organisation, html: {class: 'form-horizontal', role: 'form', multipart: true}, remote: true do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<p class='name error_explanation'></p>
<%= f.fields_for :operational_address do |fa| %>
<%= fa.label :postcode %>
<%= fa.text_field :postcode %>
<p class='operational_address postcode error_explanation'></p>
<% end %>
<%= f.fields_for :logo do |fl| %>
<%= fl.file_field :image %>
<p class='logo image error_explanation'></p>
<% end %>
<% end %>
organisations_controller.rb
def create
if #organisation.save
render :js => "window.location = '#{organisations_path}'"
else
render :validation_errors
end
end
organisations/validation_errors.js.erb
$('.error_explanation').html('');
<% #organisation.errors.messages.each do |attribute, messages| %>
$('.<%= attribute %>.error_explanation').html("<%= messages.map{|message| "'#{message}'"}.join(', ') %>");
<% end %>
Created a repo with a example of using Paperclip on rails and mainting your files when validation error occurs
https://github.com/mariohmol/paperclip-keeponvalidation
I had to fix this on a recent project using the Paperclip Gem. It's a bit hacky but it works. I've tried calling cache_images() using after_validation and before_save in the model but it fails on create for some reason that I can't determine so I just call it from the controller instead. Hopefully this saves someone else some time!
model:
class Shop < ActiveRecord::Base
attr_accessor :logo_cache
has_attached_file :logo
def cache_images
if logo.staged?
if invalid?
FileUtils.cp(logo.queued_for_write[:original].path, logo.path(:original))
#logo_cache = encrypt(logo.path(:original))
end
else
if #logo_cache.present?
File.open(decrypt(#logo_cache)) {|f| assign_attributes(logo: f)}
end
end
end
private
def decrypt(data)
return '' unless data.present?
cipher = build_cipher(:decrypt, 'mypassword')
cipher.update(Base64.urlsafe_decode64(data).unpack('m')[0]) + cipher.final
end
def encrypt(data)
return '' unless data.present?
cipher = build_cipher(:encrypt, 'mypassword')
Base64.urlsafe_encode64([cipher.update(data) + cipher.final].pack('m'))
end
def build_cipher(type, password)
cipher = OpenSSL::Cipher::Cipher.new('DES-EDE3-CBC').send(type)
cipher.pkcs5_keyivgen(password)
cipher
end
end
controller:
def create
#shop = Shop.new(shop_params)
#shop.user = current_user
#shop.cache_images
if #shop.save
redirect_to account_path, notice: 'Shop created!'
else
render :new
end
end
def update
#shop = current_user.shop
#shop.assign_attributes(shop_params)
#shop.cache_images
if #shop.save
redirect_to account_path, notice: 'Shop updated.'
else
render :edit
end
end
view:
= f.file_field :logo
= f.hidden_field :logo_cache
- if #shop.logo.file?
%img{src: #shop.logo.url, alt: ''}
Well - I thought of taking a different approach to this; Instead of temporarily storing the file on the server, why not serve it back to the client to be resubmitted when the user fixes the validation issues.
This might still need a bit of refinement but it's the general concept:
# in the controller - save the file and its attributes to params
def create
# ...
if params[:doc] # a regular file uploaded through the file form element
# when the form re-renders, it will have those additional params available to it
params[:uploaded_file] = params[:doc].read # File contents
params[:uploaded_file_original_filename] = params[:doc].original_filename
params[:uploaded_file_headers] = params[:doc].headers
params[:uploaded_file_content_type] = params[:doc].content_type
elsif params[:uploaded_file] # a file coming through the form-resubmit
# generate an ActionDispatch::Http::UploadedFile
tempfile = Tempfile.new("#{params[:uploaded_file_original_filename]}-#{Time.now}")
tempfile.binmode
tempfile.write CGI.unescape(params[:uploaded_file]) #content of the file / unescaped
tempfile.close
# merge into the params
params.merge!(doc:
ActionDispatch::Http::UploadedFile.new(
:tempfile => tempfile,
:filename => params[:uploaded_file_original_filename],
:head => params[:uploaded_file_headers],
:type => params[:uploaded_file_content_type]
)
)
end
#...
# params (including the UploadedFile) can be used to generate and save the model object
end
# in the form (haml)
- if !params[:uploaded_file].blank?
# file contents in hidden textarea element
= text_area_tag(:uploaded_file, CGI.escape(params[:uploaded_file]), style: 'display: none;') #escape the file content
= hidden_field_tag :uploaded_file_headers, params[:uploaded_file_headers]
= hidden_field_tag :uploaded_file_content_type, params[:uploaded_file_content_type]
= hidden_field_tag :uploaded_file_original_filename, params[:uploaded_file_original_filename]
A workaround for this rather than an outright solution is to use client side validation so that the file isn't lost because the whole form persists.
The few users that don't have JavaScript enabled will lose the files between requests, but perhaps this % is so low for you as to make it an acceptable compromise. If this is the route you decide to go down I'd recommend this gem
https://github.com/bcardarella/client_side_validations
Which makes the whole process really simple and means you don't have to rewrite your validation in JavaScript
Browsers block against setting the value attribute on input of file
type for security reasons so that you can't upload a file without the
user's selected any file itself.
Pre-Populate HTML form file input
You can use carrierwave: https://github.com/carrierwaveuploader/carrierwave
Or validate the model via js request.
I found a way to keep files without using gems, it can probably be improved but I am still a young dev :)
I draw the whole thing from solution 2 contained in this article: https://medium.com/earthvectors/validations-and-file-caching-using-activestorage-e16418060f8f
First of all, you need to add an hidden_field within your form that contains the signed_id of the attachment:
<%= f.hidden_field :file_signed_id, value: #model.file.signed_id if #model.file.attached? %>
<%= f.input :file %>
The problem when validations fail (in my case), it keeps the file in memory but do not send anymore the ActionDispatch object as parameters. To override it, I did the following in my controller:
if file = params.dig(:model, :file)
blob = ActiveStorage::Blob.create_and_upload!(
io: File.open(file.tempfile),
filename: file.original_filename
)
#model.file.attach(blob)
elsif file_signed_id = params.dig(:model, file_signed_id)
blob = ActiveStorage::Blob.find_signed(file_signed_id)
#model.file.attach(blob)
end
You then can display your file when rendering your view again:
<%= link_to #model.file.filename, url_for(#model.file) if #model.file.attached? %>
The only problem I see with this workaround it is that it will create a blob object even if validations fail.
I hope it will help someone!
I am new to ROR. I want to capture the filename of the browse button after selected the file with the extension. And i want to store the filename into the File_name column in the database. Please help me to do that. Is it possible to get the full path of the file stored in the File_name column instead of just a filename? please suggest me to do that. I dont want to upload the file. Just i want a filename to store.
View(New)
--------------
<div class="pro-data-imports">
<%= form_for #pro_data_import do %>
<div class="field">
Browse the file to upload:<br />
<%= file_field_tag :File_name %>
</div>
<div class="actions">
<%= submit_tag 'Import File' %>
</div>
<% end %>
</div>
Controller
----------
class Pro::DataImportsController < ApplicationController
before_filter :authenticate_user!
layout "layouts/enr/energy_master"
def index
#pro_data_imports = Pro::DataImport.all
end
def new
#pro_data_import = Pro::DataImport.new
#pro_data_import.updated_by = current_user
end
def create
#pro_data_import = Pro::DataImport.new(params[:pro_data_import])
#pro_data_import.updated_by = current_user
end
end
Model
-------
I have File_name, File_validate, :updated_by Columns
I don't know what you meant by that. In your comments you mentioned to want to know, how to call the store procedure with two parameters. If its your database is SQL try the following and change your field name as per your spec.
In your model, create a method
def update
parameters = [self.File_Name, File_Location]
parameters.map!{|p| ActiveRecord::Base.connection.quote p}
sql = "your store procedure name"(#{parameters.join(',')})"
ActiveRecord::Base.connection.execute(sql)
end
In your controller,
Inside your create method, call the update method.
def create
#update = Pro::DataImport.new(params[:pro_data_import])
#pro_data_import.updated_by = current_user
#update.update
.........
...
end
I hope it will help. This is just an example to follow. Do not copy and paste.. :)