Uploading multiple images to S3 in nested Rails form - ruby-on-rails

I'm trying to get file uploading to work with a nested fields_for tag in a Rails 4 app. I've followed several Railscasts, namely: 253, 381, 383, but still can't quite get it fully functioning. Also using Carrierwave & jquery file upload.
Basic app structure is as follows:
blogpost.rb
class Blogpost < ActiveRecord::Base
has_many :blogpics
end
blogpic.rb
class Blogpic < ActiveRecord::Base
belongs_to :blogpost
end
blogposts_controller.rb
def new
#blogpost = Blogpost.new
blogpic = #blogpost.blogpics.build
end
blogpost_form.html.erb
<div>
<%= form_for #blogpost do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.hidden_field :post_id %>
<%= f.text_field :title %>
<%= f.text_field :location %>
<%= f.text_area :content %>
<%= f.fields_for :blogpics do |builder| %>
<%= builder.file_field :image %>
<%= builder.hidden_field :blogpost_id %>
<% end %>
<p><%= f.submit %></p>
<% end %>
Uploading a single file works. But, adding ":multiple => true, :name => 'blogpic[image]'" to the file field breaks functionality and no files upload.
When I edit blogposts_controller.rb as such:
def new
#blogpost = Blogpost.new
3.times do
blogpic = #blogpost.blogpics.build
end
end
I am able to input three files individually, then upload successfully. Is there any way I can achieve this functionality while being able to drag & drop multiple files into one input?
I really appreciate any help and direction, thanks.

Your blogpost model is missing an accepts_nested_attributes association.
class Blogpost < ActiveRecord::Base
has_many :blogpics
accepts_nested_attributes_for :blogpics
end
I'm not quite sure how handling multiple files in one dialog box. I'd imagine you'd be using some javascript to detect that multiple files were selected, and creating field forms for each of them.

You can pass :multiple => true as a param on builder.file_field :image. See http://apidock.com/rails/ActionView/Helpers/FormTagHelper/file_field_tag for details
With the multiple attribute on the file input, you can drag and drop ONTO the input element
See for details http://www.html5rocks.com/en/tutorials/file/dndfiles/#toc-selecting-files
I've run into the same issue (where multiple: true breaks the nested form) and my understanding is that you have to manually transform the params before the controller receives it. If you inspect (using pry or debugger) the params hash, you need to compare between submitting Parent Model with several input files (on individual inputs) VERSUS Parent Model with multiple input files (in one input). The former creates an array of child objects (each with their own file), while the latter creates only one child object that has all the images in one array.

Related

Uploading 2 images in rails using two field

I have a form for a blog, and I would like to have two field for images. One image being the cover (in Show) and another image will serve as a preview (in index).
My form looks as follow:
<%= semantic_form_for #blog, :html => { :multipart => true } do |f| %>
<%= t :Choose_File_for_cover %> <%= f.file_field :image_path, id: "avatar-upload2", required: true %>
<img id="img_prev3" width="100%" height=200 src="#" alt="your image" class="img-thumbnail hidden"/>
<%= t :Choose_File_for_homepage %> <%= f.file_field :homepagepic, id: "avatar-upload3", required: true %>
<%= f.hidden_field :image_path_cache %>
<%= f.hidden_field :homepagepic_cache %>
<%= f.actions do %>
<%= f.action :submit, :as => :input %>
<% end %>
<% end %>
My model looks like:
class Blog < ApplicationRecord
belongs_to :user
acts_as_taggable
mount_uploader :image_path, BlogUploader
mount_uploader :homepagepic, BlogcoverUploader
end
It works well when I only have the image_path (the cover), but when I add a new field for homepagepic, i get a ROLLBACK at validation.
Can someone help me on how to select files through two separate fields on the same form please.
Thank you
The code you've provided is very sparse and it would be helpful to see a little bit more (e.g. the controller and the uploader).
I can, however, hazard a guess: image_path is an existing helper method provided by Rails (see https://api.rubyonrails.org/classes/ActionView/Helpers/AssetUrlHelper.html#method-i-image_path). I have absolutely no idea what happens when you use this as a name for a form field. It could also be because you declare your submit button to be an input (I've only ever seen and used as: :button for f.action :submit).
So overall, I would pick the following approach:
rename your upload fields to cover_image and the other one to preview_image (that's what you've described in your posts as their respective purpose, so you should name them accordingly)
change the submit to a button and remove all the noise from your template and start with the bare minimum: the two upload fields and nothing else (see sample code below – note that I haven't tested it but it should work or be very close to working)
after that works, start adding back the noise (i.e. the translations, the cache fields etc.)
Test that it still works after every step. If you can write a Capybara test, do that – otherwise test it manually.
If you have questions, feel free to ask.
<%= semantic_form_for #blog, html: { multipart: true } do |f| %>
<%= f.file_field :cover_image %>
<%= f.file_field :preview_image %>
<%= f.actions do %>
<%= f.action :submit, as: :button %>
<% end %>
<% end %>
class Blog < ApplicationRecord
belongs_to :user
acts_as_taggable
mount_uploader :preview_image, BlogUploader
mount_uploader :cover_image, BlogcoverUploader
end
As the previous poster said it's hard to debug your code without all the pieces to the puzzle. A ROLLBACK is happening because one or more validations failed.
Any time you have a ROLLBACK you can add a ! to the create or update method being called on the object being rolled back and ActiveRecord will throw an error telling you why the ROLLBACK happened instead of failing gracefully.
Once you know why your object isn't persisting you can check the params of the controller action that form is submitting to. Perhaps you forgot to whitelist a param via strong params?

Ruby on Rails multiple images connected to one object

I've been trying to create a form that would get parameters for multiple models. I have a photo model that belongs to a product model and I want to make it so that when you create a new product you can also upload images that are linked to that product by id.
<%= form_for #product, html:{multipart:true} do |f| %>
<div class="field">
<%= f.label :price %>
<%= f.text_field :price %>
</div>
<%=form_for #photo do |t| %>
<%t.productID = f.id%>
<div class="field">
<%= t.label (:image) %>
<%= t.file_field (:image) %>
</div>
<%end%>
<div class="actions">
<%= f.submit %>
</div>
<%end%>
right now I'm using paperclip for image attachments and the photo model accepts the images as parameters. I've used paperclip before but the product could only have one image connected to it. If I use the form above I get "First argument in form cannot contain nil or be empty" error and it points to where the form_for #photo starts.I have controllers for both with the usual methods of new, create, update, etc. I've routed resources to both product and photos but I'm still pretty new to rails and don't fully understand how this stuff works.
I think what you're trying to do is a good application for nested forms using the fields_for helper.
First, you'll need to ensure that your product model and photo model have the right associations (A product probably has_many photos, and a photo belongs to a product, right?). Then you'll make sure the product class 'accepts nested attributes for photo's which allows you to add attributes to the photos model from a products form.
in products.rb
class Product
has_many :photos
accepts_nested_attributes_for :photos
end
and in photo.rb
class Photo
belongs_to :product
end
Then you'll want to make sure any attributes you need for the photo are white-listed in your product params.
in products_controller.rb
private
def product_params
params.require(product).permit(:first_product_attribute, :second_produtc_attribute, photo_attributes: [:image])
end
Last, you'll create the form using the special helper fields_for
in your view
<%= form_for #product, html:{multipart:true} do |f| %>
<div class="field">
<%= f.label :price %>
<%= f.text_field :price %>
</div>
<%= f.fields_for :photo do |t| %>
<div>
<%= t.label :image %>
<%= t.file_field :image, :multiple => true %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<%end%>
You'll also need to make sure you're actually creating new photo objects in your product's create action:
in products_controller.rb
def create
#product = Product.new(product_params)
if #product.save!
params[:photo]['image'].each do |img|
#photo = #product.photos.create!(:image => img)
end
flash[:success] = 'product saved!'
redirect_to #product
end
end
Some of this is based on my experience doing the same thing but with Carrierwave instead of Paperclip so your specific implementation might be a little different.
I dont think this is a proper method <%t.productID = f.id%>. Maybe try <% t.text_field :productID, value = f.id, type = hidden %> or something along those lines?
heres some docs for the form helper so you know what to put after t.abcd
http://apidock.com/rails/v3.2.3/ActionView/Helpers/FormHelper/form_for
You're getting the
"First argument in form cannot contain nil or be empty"
..error because #photo is nil, you need to set it in your controller #photo = Photo.new.
Also, form tags inside form tags are invalid HTML.
https://www.w3.org/TR/html5/forms.html#the-form-element
Forms
Content model: Flow content, but with no form element
descendants.
You want to use f.fields_for instead. Learn how to use it here here
I have controllers for both with the usual methods of new, create,
update, etc.
You only ever hit one controller and action when you go to a path, say /photos will only hit the photos controller (as configured in your routes.rb). This I think is where you're messing up the #photo variable. Set both in the same controller in order for the view to be able to see both variables.

Rails: Create Model and join table at the same time, has_many through

I have three Models:
class Question < ActiveRecord::Base
has_many :factor_questions
has_many :bigfivefactors, through: :factor_questions
accepts_nested_attributes_for :factor_questions
accepts_nested_attributes_for :bigfivefactors
end
class Bigfivefactor < ActiveRecord::Base
has_many :factor_questions
has_many :questions, through: :factor_questions
end
and my join-table, which holds not only the bigfivefactor_id and question_id but another integer-colum value.
class FactorQuestion < ActiveRecord::Base
belongs_to :bigfivefactor
belongs_to :question
end
Creating an new Question works fine, using in my _form.html.erb
<%= form_for(#question) do |f| %>
<div class="field">
<%= f.label :questiontext %><br>
<%= f.text_field :questiontext %>
</div>
<%= f.collection_check_boxes :bigfivefactor_ids, Bigfivefactor.all, :id, :name do |cb| %>
<p><%= cb.check_box + cb.text %></p>
<% end %>
This let's me check or uncheck as many bigfivefactors as i want.
But, as i mentioned before, the join model also holds a value.
Question:
How can I add a text-field next to each check-box to add/edit the 'value' on the fly?
For better understanding, i added an image
In the console, i was able to basically do this:
q= Question.create(questiontext: "A new Question")
b5 = Bigfivefactor.create(name: "Neuroticism")
q.bigfivefactors << FactorQuestion.create(question: q, bigfivefactor: b5, value: 10)
I also found out to edit my questions_controller:
def new
#question = Question.new
#question.factor_questions.build
end
But i have no idea how to put that into my view.
Thank you so much for your help!
Big Five Factors model considerations
It looks like your Bigfivefactors are not supposed to be modified with each update to question. I'm actually assuming these will be CMS controlled fields (such that an admin defines them). If that is the case, remove the accepts_nested_attributes for the bigfivefactors in the questions model. This is going to allow param injection that will change the behavior sitewide. You want to be able to link to the existing bigfivefactors, so #question.factor_questions.first.bigfivefactor.name is the label and #question.factor_questions.first.value is the value. Notice, these exist on different 'planes' of the object model, so there wont be much magic we can do here.
Parameters
In order to pass the nested attributes that you are looking for the paramater needs to look like this:
params = {
question: {
questiontext: "What is the average air speed velocity of a sparrow?",
factor_questions_attributes: [
{ bigfivefactor_id: 1, value: 10 },
{ bigfivefactor_id: 2, value: 5 } ]
}
}
Once we have paramaters that look like that, running Question.create(params[:question]) will create the Question and the associated #question.factor_questions. In order to create paramaters like that, we need html form checkbox element with a name "question[factor_questions_attributes][0][bigfivefactor_id]" and a value of "1", then a text box with a name of "question[factor_question_attributes][0][value]"
Api: nested_attributes_for has_many
View
Here's a stab at the view you need using fields_for to build the nested attributes through the fields for helper.
<%= f.fields_for :factor_questions do |factors| %>
<%= factors.collection_check_boxes( :bigfivefactor_id, Bigfivefactor.all, :id, :name) do |cb| %>
<p><%= cb.check_box + cb.text %><%= factors.text_field :value %></p>
<% end %>
<% end %>
API: fields_for
I'm not sure exactly how it all comes together in the view. You may not be able to use the built in helpers. You may need to create your own collection helper. #question.factor_questions. Like:
<%= f.fields_for :factor_questions do |factors| %>
<%= factors.check_box :_destroy, {checked => factors.object.persisted?}, '0','1' %> # display all existing checked boxes in form
<%= factors.label :_destroy, factors.object.bigfivefactor.name %>
<%= factors.text_box :value %>
<%= (Bigfivefactor.all - #question.bigfivefactors).each do |bff| %>
<%= factors.check_box bff.id + bff.name %><%= factors.text_field :value %></p> # add check boxes that aren't currently checked
<% end %>
<% end %>
I honestly know that this isn't functional as is. I hope the insight about the paramters help, but without access to an actual rails console, I doubt I can create code that accomplishes what you are looking for. Here's a helpful link: Site point does Complex nested queries

Form has_many in text_area

I have the following situation:
A Order has many Pages. I want to let the User to paste a bunch (20+) URLs (it's a Page attribute) that they might have in a doc file into a text area.
Right now I am not using a Form associated with an Order object, because I fail to see how I can do a nested form of the URLs if those are inside a text area.
I have seen a similar question has been asked before here: Rails: Using a Textarea for :has_many relationship , but I fail to see how would I code the view and model in order to do so.
So, if I have this:
Order has_many Pages
And a form like this:
<%= form_for #order do |f| %>
<%= f.text_area :page_urls?? %> # This would let the user paste X URLs, which would be
# used to create X Pages associated with the Order.
<% end %>
You could retain the view code that you have:
<%= form_for #order do |f| %>
<%= f.text_area :page_urls %>
#other field and submit button
<% end %>
In your model, you'll need to do the following:
attr_accessor :page_urls
after_validation do
if page_urls
parse_page_urls.each do |url|
pages.create(url: url)
end
end
end
def parse_page_urls
#use regexp to extract urls from page_urls string and return an array of url strings
end
The accessor is defined so that you can use :page_urls in your form_builder. You could set easily validations in your model for :page_urls that way too.
Once order has been validated, it will create page objects according to the number of urls extracted from the page_urls attribute.
You could refer to this for some help with using regexp to extract the urls from the string.
Hope that helps!
This is a job best handled with nested form. It will let you submit attributes of a has_many relationship model from the parent model, like you wish to do. For example, from its docs:
Imagine you have a Project model that has_many :tasks. To be able to use this gem, you'll need to add accepts_nested_attributes_for :tasks to your Project model. If you wish to allow the nested objects to be destroyed, then add the :allow_destroy => true option to that declaration. See the accepts_nested_attributes_for documentation for details on all available options.
This will create a tasks_attributes= method, so you may need to add it to the attr_accessible array (attr_accessible :tasks_attributes).
Then use the nested_form_for helper method to enable the nesting.
<%= nested_form_for #project do |f| %>
You will then be able to use link_to_add and link_to_remove helper methods on the form builder in combination with fields_for to dynamically add/remove nested records.
<%= f.fields_for :tasks do |task_form| %>
<%= task_form.text_field :name %>
<%= task_form.link_to_remove "Remove this task" %>
<% end %>
<%= f.link_to_add "Add a task", :tasks %>
In response to your comment:
In order to do something like that, you would need to do processing in the controller to separate the URL's, then make a new Page object associated with #order object. Unfortunately, there isn't a way to do this without post-processing, unless you do it with JS on the client side with hidden inputs.

nested_form in rails - creating a belongs_to or use existing object

I have a nested form using 2 objects, ImageComponent (a kind of wrapper with extra attributes around the asset itself) and ImageAsset (simply the asset) and the relationships are as follows
ImageAsset has_many, :image_components
ImageComponent belongs_to :image_asset
I am using Ryan Bates nested_form gem to on a form to create an ImageAsset.
The problem arises when i would like to create an ImageAsset or associate an existing ImageAsset using nested attributes from my ImageComponent form.
I currently have this set up as follows in my form
<legend>Image Component</legend>
<%= f.fields_for :component do |component_fields| %>
<p>Select an existing image ...</p>
<%= component_fields.label 'image_asset_id', 'Image' %>
<%= component_fields.select "image_asset_id", options_for_image_select(component_fields.object.image_asset_id) %>
<p>... or upload a new one!</p>
<%= component_fields.fields_for :new_image_asset, ImageAsset.new do |asset_fields| %>
<%= asset_fields.label :title %>
<%= asset_fields.text_field :title %>
<%= asset_fields.label :image %>
<%= asset_fields.file_field :image %>
<% end %>
<%= component_fields.label :caption %>
<%= component_fields.text_field :caption %>
<% end %>
<%= f.hidden_field :component_type %>
<%= f.link_to_remove 'Remove Component' %>
The problem here is that I am posting 2 images. One being an existing image (depending on if it is a new or create action this could be nil) and the second being a newly uploaded images attributes.
My initial thought was to create a before_validation callback on the model like so (new_image_asset being an attr_accessor on the model to allow the attributes to be passed from the form)
if new_image_asset[:title].present? || new_image_asset[:image].present?
image_asset = ImageAsset.new(new_image_asset)
end
This however still gives me an issue in that image is supposedly blank (i guess because the asset has not yet been saved)
Is there another way to do this whilst keeping validation errors on the form for the image?
The hope here was that if the user includes anything for a new image asset then it will try to create on and validate it. If there is nothing in the new_image attributes it uses whatever is in the collection select for existing ImageAsset
Any help would be greatly appreciated.
Thanks in advance

Resources