I have been banging my head against the wall trying to get Carrierwave, Rails 4, and Multiple Uploads all working together. I can get a single file upload working just fine as in this and many other projects.
This is not a nested situation - just simply uploading to a single model called Transcription and wanting to create a record for each document uploaded.
I cannot seem to find the correct way to declare the "document" field used for the carrierwave mount
mount_uploader :document, DocumentUploader
as an array for the strong parameters to recognize.
I have tried whitelisting: whitelisted[:document] = params[:transcription]['document'],
declaring the "document" as an array:
params.require(:transcription).permit(..... ,:document => [])
params.require(:transcription).permit(..... , { document: [] })
This all seems more like I am declaring the array for a nested model, but I really want Rails 4's strong parameters to simply see the "document" array created by the file_field, :multiple => true
ie. from the log: form-data; name=\"transcription[document][]
Has anybody successfully accomplished multiple uploads in Rails 4 with strong parameters? If so would you please share?
Thanks...
Cheers,
Bill
This is solution to upload multiple images using carrierwave in rails 4 from scratch
To do just follow these steps.
rails new multiple_image_upload_carrierwave
In gem file
gem 'carrierwave'
bundle install
rails generate uploader Avatar
Create post scaffold
rails g scaffold post title:string
Create post_attachment scaffold
rails g scaffold post_attachment post_id:integer avatar:string
rake db:migrate
In post.rb
class Post < ActiveRecord::Base
has_many :post_attachments
accepts_nested_attributes_for :post_attachments
end
In post_attachment.rb
class PostAttachment < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
belongs_to :post
end
In post_controller.rb
def show
#post_attachments = #post.post_attachments.all
end
def new
#post = Post.new
#post_attachment = #post.post_attachments.build
end
def create
#post = Post.new(post_params)
respond_to do |format|
if #post.save
params[:post_attachments]['avatar'].each do |a|
#post_attachment = #post.post_attachments.create!(:avatar => a, :post_id => #post.id)
end
format.html { redirect_to #post, notice: 'Post was successfully created.' }
else
format.html { render action: 'new' }
end
end
end
def update
respond_to do |format|
if #post.update(post_params)
params[:post_attachments]['avatar'].each do |a|
#post_attachment = #post.post_attachments.create!(:avatar => a, :post_id => #post.id)
end
end
end
def destroy
#post.destroy
respond_to do |format|
format.html { redirect_to #post }
format.json { head :no_content }
end
end
private
def post_params
params.require(:post).permit(:title, post_attachments_attributes: [:id, :post_id, :avatar])
end
In views/posts/_form.html.erb
<%= form_for(#post, :html => { :multipart => true }) do |f| %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<%= f.fields_for :post_attachments do |p| %>
<div class="field">
<%= p.label :avatar %><br>
<%= p.file_field :avatar, :multiple => true, name: "post_attachments[avatar][]" %>
</div>
<% end %>
<% if params[:controller] == "post" && params[:action] == "edit" %>
<% #post.post_attachments.each do |p| %>
<%= image_tag p.avatar, :size => "150x150" %>
<% end %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
In views/posts/show.html.erb
<p id="notice"><%= notice %></p>
<p>
<strong>Title:</strong>
<%= #post.title %>
</p>
<% #post_attachments.each do |p| %>
<%= image_tag p.avatar_url, :size => "150x150" %>
<%= link_to "Destroy", p, method: :delete %>
<% end %>
<%= link_to 'Edit', edit_post_path(#post) %> |
<%= link_to 'Back', posts_path %>
In rails 3 no need to define strong parameters and as you can define attribute_accessible in both the model and accept_nested_attribute to post model because attribute accessible is deprecated in rails 4.
CarrierWave doesn't support multiple uploads. It's designed to associate a single file with a single field.
If you want multiple uploads, you need either multiple fields (each with a CarrierWave uploader), or multiple objects each with a single CarrierWave uploader field.
The multiple attribute is also unsupported, so if you use it, it's entirely up to you to get the parameters assigned properly.
I would create a model called Documents with a field that's mounted
class Documents < ActiveRecord::Base
belongs_to :transcription
mount_uploader :doc, DocumentUploader
end
class Transcriptions < ActiveRecord::Base
has_many :documents
end
And I would still have user the below line in my controller:
params.require(:transcription).permit(..... , { document: [] })
The best method for this that I have come across is using the native approach of CarrierWave. If you already have single file upload done, with the native approach it takes less than 5 minutes to get multiple file upload. https://github.com/carrierwaveuploader/carrierwave#multiple-file-uploads
Related
In a rails 5.2.3 app, I have a model Post, which uses active_storage to attach a file and has fields for duration and place. The duration must be present.
class Post
has_one_attached :video
validates :duration, presence: true
end
Using simple_form
the fields in the form are declared as
<%= f.input :duration %>
<%= f.input :place %>
<%= f.input :video %>
The controller has the following logic for the create
def create
#post = current_user.posts.build(post_params)
if #post.save
flash[:success] = 'Post has been saved'
redirect_to root_path
else
#title = 'New Post'
#user = current_user
render :new
end
end
private
def post_params
params.require(:post).permit(:duration, :video)
end
If the validation fails, the form shows value of place, but I lose the file name for the video. This means the user has to choose the file again. How do I fix this?
Following Thanh's suggestion, I did check this SO question, and tried changing the simple_form field to
<%= f.hidden_field :video, value: f.object.image.signed_id if f.object.video.attached? %>
<%= f.file_field :video %>
This remembered the file name, but did not display it. So I did the following work around:
<% if f.object.video.attached? %>
<span><strong>Video File Name:</strong> <%= f.object.video.blob.filename.to_s %>. To change, choose different file below:</span>
<% end %>
<%= f.hidden_field :video, value: f.object.image.signed_id if f.object.video.attached? %>
<%= f.file_field :video %>
I have problems with adding pictures for comments. Comments have a link has_many to the pictures as: :imageable. How I can add a pictures upload to a comment form?
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :article
belongs_to :user
has_many :images, as: :imageable
accepts_nested_attributes_for :images, allow_destroy: true
end
# app/models/Image.rb
class Image < ApplicationRecord
has_attached_file :img
validates_attachment :img, content_type: { content_type:
["image/jpg",
"image/jpeg", "image/png", "image/gif"] }
belongs_to :imageable, polymorphic: true, required: false
end
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.create(comment_params)
#comment.user_id = current_user.id
#comment.save
redirect_to article_path(#article)
end
def destroy
#article = Article.find(params[:article_id])
#comment = #article.comments.find(params[:id])
#comment.destroy
redirect_to article_path(#article)
end
private
def comment_params
params.require(:comment).permit(:commenter, :body)
end
end
# app/views/comments/_form.html.erb
<%= form_with(model: [ #article, #article.comments.build ],
local:true) do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
# app/views/articles/show.html.erb
<%= render 'comments/form'%>
I managed to add images in comments through ActiveAdmin, but I dont know how to do it with form.
So, what should I add to the form for images? Should I write a method create in images_controller?
User PaperClip Gem.
in your view add :
<%= file_field_tag "imageables[]", type: :file, multiple: true %>
in your create method inside controller:
if params[:imageables]
params[:imageables].each { |image|
#comment.imagables.create(image: image)
}
end
Another way which is better:
Use PaperClip & Cocoon gem for nested forms.
If I understand your question correctly, youre asking how to nest a child form inside your Comment form. This is typically achieved with the #fields_for helper.
For a one-to-many relationship, you can simply do something like the following:
<%= form_for(...) ... do |comment_form| %>
# beginning of form for Comment ...
<p class="imageables">
<ul>
<%= form.fields_for :imageables do |comment_image_form| %>
<li>
# Add Image fields here
</li>
<% end %>
</ul>
</p>
# rest of form / submit button
<% end %>
Adapt the above to your application's current form. This is only achievable through the accepts_nested_attributes_for call you've made in Comment.
You will also need to look into whitelisting the parameters for the Image associations via the strong parameters pattern. That is, the #comment_params method of your CommentsController will need the Image attributes added, or they will be disregarded with a warning.
I also recommend you load the fields_for fields from a partial view, like this:
<%= render 'comment_image_fields', parent_form: comment_form %>
Check out the guides for nested forms for more detailed info.
Ok So I have a simple rails application called ticket. here I create ticket and store into database. I generated it using scaffold.
It has 5 columns. ( I generated it using scaffold)
Name
Seat id seq
Address
Price paid
Email Address
And the application is working fine. I can create , edit, and update the ticket. I am much interested in creating the ticket rather than editing and deleting.
Now I want to add a new column in the database named attachment where a person can upload word, pdf files. I have seen many tutorials but none is explaining how I can incorporate into an existing table which already have some fields.
tickets_controller.rb
class TicketsController < ApplicationController
before_action :set_ticket, only: [:show, :edit, :update, :destroy]
def index
#tickets = Ticket.all
end
def show
end
def new
#ticket = Ticket.new
end
def edit
end
def create
#ticket = Ticket.new(ticket_params)
respond_to do |format|
if #ticket.save
format.html { redirect_to #ticket, notice: 'Ticket was successfully created.' }
format.json { render :show, status: :created, location: #ticket }
else
format.html { render :new }
format.json { render json: #ticket.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #ticket.update(ticket_params)
format.html { redirect_to #ticket, notice: 'Ticket was successfully updated.' }
format.json { render :show, status: :ok, location: #ticket }
else
format.html { render :edit }
format.json { render json: #ticket.errors, status: :unprocessable_entity }
end
end
end
def destroy
#ticket.destroy
respond_to do |format|
format.html { redirect_to tickets_url, notice: 'Ticket was successfully destroyed.' }
format.json { head :no_content }
end
end
private
def set_ticket
#ticket = Ticket.find(params[:id])
end
def ticket_params
params.require(:ticket).permit(:name, :seat_id_seq, :address, :price_paid, :email_address)
end
end
tickets Model
class Ticket < ApplicationRecord
end
_form.html.erb
<%= form_for(ticket) do |f| %>
<% if ticket.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(ticket.errors.count, "error") %> prohibited this ticket from being saved:</h2>
<ul>
<% ticket.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :seat_id_seq %>
<%= f.text_field :seat_id_seq %>
</div>
<div class="field">
<%= f.label :address %>
<%= f.text_area :address %>
</div>
<div class="field">
<%= f.label :price_paid %>
<%= f.text_field :price_paid %>
</div>
<div class="field">
<%= f.label :email_address %>
<%= f.text_field :email_address %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
new.html.erb
<h1>New Ticket</h1>
<%= render 'form', ticket: #ticket %>
<%= link_to 'Back', tickets_path %>
I found some on some of the sources that to upload a document we need to create a table with three columns ( filename, content_type, data) , and I thought to add these column into the existing tickets table, but then I don't know what chnages i need to make in the new.html.erb file. On this file i am loading the form where you can enter the information, and I want to show a field to upload a file on this page.
ruby -v : 2.2.6p396
rails -v : 5.0.0.1 ( Please reply in way that I can use the solution on the rails 4.0 too)
As #Brad mentioned you can use paperclip or carrierwave to add attachment. I will use paperclip in this example. ( Note: I'm using rails 5.0.0.1 in this example, and comand rails db:migrate. in Rails 4.x it will be rake db:migrate)
Let's do some scaffolding first and then you can alter your code to suits your needs.
rails g scaffold User name:string
rails g scaffold Post title:string body:text user:references
Add has_many :posts to User model, and run rails db:migrate
Add gem file: gem "paperclip", "~> 5.0.0" and run: bundle install
Let's generate paperclip attachment: rails generate paperclip user docs and run rails db:migrate
Add attachment, validation and content type to our User model:
has_attached_file :docs
validates_attachment :docs, :content_type => {:content_type => %w(application/pdf application/msword application/vnd.openxmlformats-officedocument.wordprocessingml.document)}
Add to users_controller.rb strong params for attribute whitelisting :
def user_params
params.require(:user).permit(:name, :docs)
end
The form’s encoding must be set to multipart/form-data for uploading files html: { multipart: true } so for our attachment we need to alter our form partial app/views/users/_form.html.erb:
<%= form_for #user, html: { multipart: true } do |f| %>
And add required fields to our form:
<div class="field">
<%= f.label :docs %>
<%= f.file_field :docs %>
</div>
After restarting our server rails s, and going to url users/new we can see new field attachment. After uploading a document we can check in our console if it's uploaded: rails c and run for example User.all, which shows that our fields in DB are populated:
And the last step would be to show the document in our views app/views/users/show.html.erb so that users can download or open it in the browser:
<%= link_to "My document", #user.docs.url, target: "_blank" %>
EDIT
Let's get back to your project and do it:
First you should add Paperclip gem to your Gemfile gem "paperclip", "~> 5.0.0" and to run bundle install in command line.
Generate attachment: rails generate paperclip ticket attachment ( This will generate the migration file and add attachment field to it )
run rails db:migrate ( rake db:migrate in Rails 4 )
go to Ticket model and add:
has_attached_file :attachment
validates_attachment :attachment, :content_type => {:content_type => %w(application/pdf application/msword application/vnd.openxmlformats-officedocument.wordprocessingml.document)}
Then to tickets_controller.rb and edit strong params:
def ticket_params
params.require(:ticket).permit(:name, :seat_id_seq, :address, :price_paid, :email_address, :attachment)
end
Go to your partial _form.html.erb:
Edit this code below in your form:
<%= form_for #ticket, html: { multipart: true } do |f| %>
Add this code below to your form: (That would be new field with attachment )
<div class="field">
<%= f.label :attachment %>
<%= f.file_field :attachment %>
</div>
and the last step to your show page add:
<%= link_to "My document", #ticket.attachment.url, target: "_blank" %>
restart your server, upload the document and try if it works.
For adding attachments you can use a gem like carrierwave or paperclip. I personally use paperclip but both are good.
To do it manually:
In your terminal cd to app folder and run:
rails g migration AddAttachmentToTickets attachment:blob
run rails 4 rake db:migrate or in rails 5 rails db:migrate
in your ticket form you can now add:
<div class="field">
<%= f.label :attachment %>
<%= f.file_field :attachment %>
</div>
and make sure you permit this in your ticketsController params. It should look something like this:
def ticket_params
params.require(:ticket).permit(:ticket_number,:description, :attachement)
end
Hope this helps.
My nested form is not working properly no matter what I try and I searched all the StackExchange's for a solution to this seemingly easy problem. This is where I am right now to get it to work at show up in the view at all.
The form is using the Event controller create action from a non-restful location, hence the global variable (a pages controller, with a specific page, where the form is generated). My ticket model gets generated when the nested form is submitted, and the Event ID gets passed, but it doesn't fill in the "Name" field for the ticket model because it says "Unpermitted Parameters: Ticket." But they're defined as whitelisted in the Events controller! Argh! I'm thinking something is wrong with the form, but nothing I try seems to work.
Any help would be appreciated.
* UPDATED CODE THAT IS NOW WORKING *
Form.html.erb:
<div class="form-inputs">
<%= simple_form_for #event, :html => { :class => 'form-horizontal' } do |f| %>
<div class="row">
<div class="col-xs-6">
<%= f.input :name, class: "control-label" %>
</div>
</div>
<div class="row">
<div class="col-xs-6">
<%= f.simple_fields_for :tickets do |ticket| %>
<%= ticket.input :name %>
<% end %>
</div>
</div>
<div class="form-actions">
<%= f.button :submit, :class => 'btn-primary' %>
<%= link_to t('.cancel', :default => t("helpers.links.cancel")),
launchpad_path, :class => 'btn btn-default' %>
<% end %>
</div>
</div>
Event_Controller.rb
def new (this is totally skipped and unnecessary)
#event = Event.new
#ticket = #event.tickets.build
end
def create
#event = current_user.events.build(event_params)
respond_to do |format|
if #event.save
format.html { redirect_to #event, notice: 'Your event was created.' }
else
format.html { render :new }
end
end
end
def event_params
params.require(:event).permit(:name, tickets_attributes: [ :name, :id, :event_id, :_destroy ])
end
Pages_Controller.rb (where the form originate
def new
#event = Event.new
#ticket = #event.tickets.build
end
Event.rb
class Event < ActiveRecord::Base
# Database Relationships
has_many :tickets, dependent: :destroy
accepts_nested_attributes_for :tickets, :allow_destroy => true
end
Ticket.rb
class Ticket < ActiveRecord::Base
belongs_to :event
end
Routes.rb
resources :events do
resources :tickets
end
As well as the information from Alejandro (which is correct), you also have f.simple_fields_for #ticket, ... whereas you should have f.simple_fields_for :tickets, ...
If you check your log/development.log for the Processing by EventsController#create the line after will be a Parameters: line, you'll see that the parameters that have been sent through are under a :ticket key instead of a :tickets_attributes key because of the fields_for error.
Fix that, and the permit line and you should be fine.
Update
Hopefully you realized that you also don't need the #ticket = #event.tickets.build(event_params[:ticket_attributes]) line at all once that fields_for is fixed too. The setting of all the associated tickets is all done via the Event object thanks to the accepts_nested_attributes_for helper.
Just, remove from create action this line:
#ticket = #event.tickets.build(event_params[:ticket_attributes])
And, change your event_params:
def event_params
params.require(:event).permit(:name, :main_event_image, tickets_attributes: [:id, :name, :cost, :event_id, :registration_id, :created_at])
end
Te field name must be: tickets_attributes: [ ... (tickets in plural). I think this do the trick.
Edit: I'm agree with #smathy, if no fix to f.simple_fields_for :tickets ... it can't work.
Your new method must look like this:
def new
#new_event = Event.new
#new_event.tickets.build
end
I'm a fan of standards, and I prefer use #event instead of #new_event as in your form (it's part of convention over configuration on rails)
I was stuck at the same problem like crazy and at the end I was able to fix it... Try placing the binding.pry in the first line of create method and print the event_params hash and check if you see ticket_attributes hash inside of it ... That's when it ll throw unpermitted parameter ... And I see event has_many tickets , so I am guessing ticket_attributes needs to be pluralized to be tickets_attributes
I have an app where users can create courses, and each course has_one syllabus. How could I go about configuring my courses and syllabuses (I know it's Syllabi but apparently Rails doesn't) controller, and my routes, so on a course's page there is a link to create or show the course's syllabus, and a link back to the course from the show syllabus page?
In my routes I have:
resources :courses do
resources :syllabuses
member do
put :enroll #this is so users can enroll in the course
end
end
Currently , so the course_id will be saved in the syllabus table in my courses_controller, I have:
def create_syllabus
#course = Course.find(params[:id])
#syllabus = #course.build_syllabus(params[:syllabus])
if #syllabus.save
redirect_to #syllabus, notice: "Successfully created syllabus."
else
render :new
end
end
then in my courses show page I have:
<section>
<% if (current_user.courses.includes(#course) ||
current_user.coursegroups.find_by_course_id_and_role(#course.id, "admin")) %>
<%= render 'create_syllabus' %>
<% end %>
</section>
then in my create_syllabus form (in my courses views folder) I have tried starting it off with:
# I have #course = Course.find(params[:id]) defined in show in the
#courses_controller
<%= form_for #course.create_syllabus do |f| %>
<%= form_for #course.syllabus.create_syllabus do |f| %>
<%= form_for #course.syllabus.create do |f| %>
and I get an undefined method error for each of those.
If you want to create a new syllabus in your show action of a specific course, you can add this to your controllers and views:
courses_controller.rb
#course = Course.find(params[:id])
# Build a new #syllabus object, only if there is none for the current course
unless #course.syllabus
#syllabus = #course.build_syllabus
end
views/courses/show.html.erb
# Show the syllabus name if there is one, or show the form to create a new one
<% if #course.syllabus.name %>
<p>Syllabus: <%= #course.syllabus.name %></p>
<% else %>
<p>Create Syllabus:</p>
<%= form_for([#course, #syllabus]) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<% end %>
syllabuses_controller.rb
def create
#course = Course.find(params[:course_id])
# Build new syllabus object based on form input
#syllabus = #course.build_syllabus(params[:syllabus])
if #syllabus.save
# redirect to /course/:id
redirect_to #course, notice: 'Syllabus was successfully created.' }
end
end
course.rb
class Course < ActiveRecord::Base
attr_accessible :name
has_one :syllabus
end
syllabus.rb
class Syllabus < ActiveRecord::Base
belongs_to :course
attr_accessible :name, :course_id
end
Some things that I left out but you should still include:
validations
rerendering form if something goes wrong
pulling things out into partials
fixing bad code like if #course.syllabus.name
pull out if/else logic into a helper
…