I would like to make my app upload multiple files with Shrine, but one doc suggests two file_fields whereas the other suggests only one. After posting a question to their discourse forum, it was suggested that I hide the one named files[]. Whether I do this or not, the first file_field always fails to render. Why does this field not display?
<%= form_for #item, html: { enctype: "multipart/form-data" } do |f| %>
<%= f.fields_for :photos do |i| %>
<%= i.label :image %>
<%= i.hidden_field :image, value: i.object.cached_photos_data, class: "upload-data" %>
<%= i.file_field :image, class: "upload-file" %> /// why is this not rendering?😢
<% end %>
<%= file_field_tag "files[]", multiple: true %> // what purpose does this one serve?
<%= f.text_field :title %>
<%= f.submit "Submit" %>
<% end %>
Models:
class Item < ApplicationRecord
has_many :photos, as: :imageable, dependent: :destroy
end
class Photo < ApplicationRecord
include ImagesUploader::Attachment(:image)
belongs_to :imageable, polymorphic: true
validates_presence_of :image
end
Controller:
class ItemsController < ApplicationController
def new
#item = current_user.items.new
end
def create
#item = current_user.items.create(item_params)
#item.save
end
private
def item_params
params.require(:item).permit(:title, photos_attributes: { image: [] })
end
end
Read the first link carefully: It says that the single field (i.file_field :image) is used to display existing images (which is why it's wrapped in f.fields_for :photos in the example) and the multiple field (file_field_tag "files[]", multiple: true) is used to upload new files. So if your #item doesn't have an :image, then the field isn't shown.
Let me know if this needs any further clarification – happy to help!
Related
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.
This is quite a common error and I have went through many answers but nothing worked for me. Can you please help, where I am going wrong?
Scenario: I have an item and image table and when user adds an item, he/she must add at least one image. When I save the form after attaching an image it gives an error. The code snippets and error screenshot are attached below:
item.rb
class Item < ApplicationRecord
belongs_to :user, class_name: 'User', foreign_key: :user_id
has_many :images, class_name: 'Image', foreign_key: :item_id, dependent: :destroy
accepts_nested_attributes_for :images, allow_destroy: true
end
image.rb
class Image < ApplicationRecord
belongs_to :item, foreign_key: :item_id, optional: true
has_attached_file :image, styles: { small: '64x64', med: '100x100', large: '200x200' },
default_url: '/images/:id/:filename'
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
end
items_controller.rb
class ItemsController < ApplicationController
def new
#item = Item.new
#item.images.build
#new.html.erb
end
def create
#item = Item.new(item_params.merge(user_id: current_user.id))
if #item.save
redirect_to items_path
else
render :new
end
end
private
def item_params
params.require(:item).permit(:name, :category, :qty, :cost, :description,
:city, :postal_code, :country,
images_attributes: [:id, :item_id, :_destroy, image: []])
end
end
new.html.erb
<div class="container">
<h2> <% if current_user.items.blank? %> Become a seller! <% end %> Add a new item </h2> <br>
<%= form_for #item, url: {action: :create}, html: { multipart: true } do |f| %>
<%= item_error_messages! %>
<div class="form-group">
<%= f.label :name, class:'control-label col-sm-2' %>
<div class="col-sm-10">
<%= f.text_field :name, class: 'form-control', placeholder: 'Enter name' %>
</div>
</div>
<%= f.fields_for :images, #item.images.build do |img| %>
<%= img.file_field :image, multiple: true %>
<% end%>
<br><br>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10" style="margin-bottom: 40px;">
<%= f.submit 'Save', class:'btn btn-default' %>
</div>
</div>
<% end %>
</div>
Error:
What could be the possible issue? Most of the solutions I've read suggest adding multipart: true in form tag but it is already there.
Thanks
I think you problem is related with the fact that you are trying to save more than one attached file per model. Apparently, Paperclip don't treat this case very well, so you need to do it by your own hand.
If you want to save multiples files at the same time, you can follow the accept answer in the link below that example how to achieve that:
Just a resume from the link below:
1 - To save multiple files in the above example you, do not need add
multiple: true option, it will cause an error when saving.
2 - About params: This will cause an error because the model does not know what to do with an array of images
3 - To save more than one file at the same time, you need to use your own
handler
Rails 4 multiple file attachments with Paperclip
Good luck!
#UPDATE
Ideas about to how save multiples file at once:
1 - You will need to have a form with a dynamically input for files.
So each input can get the entry for each file. You can achieve that
writing your own .js lib or using a gem like "Cocoon". You will be
using nested attributes here.
2 - The strong parameter in your controller will be accepting you Item
model and an array of files.
3 - You will have to edit your method to save your data. E.g (in your
case #item.save won't work because paperclips it's not design to
accept an array of files). So, in your Item model you will have to
write a method similar to the below:
(pseudo-code)
def save(item_attributes, array_of_files)
#item = Item.new( -- add here only Item attributes --)
#item.save
# Here you are creating a model file for each file you are saving.
# So there will be N file model for 1 item.
for file in array_of_files
#file = File.new( #item.id, file)
#file.save
end
# here you can implement transaction in case a error occurs.
# and shows the error to your client through Item model
# google how to do this (transaction and Active Record class).
end
I know there are alot of these kinds of posts on SO, and I think Ive read and tried each of them, all without success.
I have Post and Image models that I need to work together with a many to one relationship.
class Post < ActiveRecord::Base
has_many :images
end
class Image < ActiveRecord::Base
belongs_to :post
mount_uploader :file, images_uploader
end
Here is the post_parms declaration in my posts controller, which includes all of the fields in my image model migration.
private
def post_params
params.require(:post).permit(:title, :content, image_attributes: [:id, :post_id, :file])
end
and here is my post creation form where, I hope to allow multiple image asset creation with each post.
<%= form_for #post, html: {class: "pure-form pure-form-stacked"} do |post| %>
<%= post.fields_for :image, :html => { :multipart => true } do |image| %>
<%= image.label :file, "Upload Image:" %>
<%= image.file_field :file, multiple: true %>
<% end %>
<fieldset class="post-form">
<%= post.label :title %>
<%= post.text_field :title %>
<%= post.label :content %>
<%= post.text_area :content, :class => "redactor", :rows => 40, :cols => 120 %>
</fieldset>
<div class = "button-box">
<%= post.submit class: "pure-button pure-button-primary" %>
<%= link_to "Cancel", posts_path, :class => "pure-button" %>
</div>
Despite repeated effort and reading every post I can find on this topic, I'm still getting:
Unpermitted parameters: image
The trouble here is that this error provides no clue where to start looking for the problem. Since Im not really sure where to look next I thought I would post it here looking for more professional opinions.
Update the Post model as below:
class Post < ActiveRecord::Base
has_many :images
accepts_nested_attributes_for :images ## Add this
end
This way upon form submission you would receive the image attributes in key images_attributes and not image which you are currently receiving which is causing the warning as Unpermitted parameters: image
As you have 1-M relationship between Post and Image
You need to update post_params as below:
def post_params
params.require(:post).permit(:title, :content, images_attributes: [:id, :post_id, :file])
end
Use images_attributes (Notice plural images) and NOT image_attributes (Notice singular image)
And change fields_for in your view as
<%= post.fields_for :images, :html => { :multipart => true } do |image| %>
Use images (Notice plural) and NOT image (Notice singular)
UPDATE
To resolve uninitialized constant Post::Image error
Update Image model as below:
class Image < ActiveRecord::Base
belongs_to :post
## Updated mount_uploader
mount_uploader :file, ImagesUploader, :mount_on => :file
end
Also, suggested to remove multiple: :true from
<%= ff.file_field :file, multiple: true %>
I'm attempting to build a recipe-keeper app with three primary models:
Recipe - The recipe for a particular dish
Ingredient - A list of ingredients, validated on uniqueness
Quantity - A join table between Ingredient and Recipe that also reflects the amount of a particular ingredient required for a particular recipe.
I'm using a nested form (see below) that I constructed using an awesome Railscast on Nested Forms (Part 1, Part 2) for inspiration. (My form is in some ways more complex than the tutorial due to the needs of this particular schema, but I was able to make it work in a similar fashion.)
However, when my form is submitted, any and all ingredients listed are created anew—and if the ingredient already exists in the DB, it fails the uniqueness validation and prevents the recipe from being created. Total drag.
So my question is: Is there a way to submit this form so that if an ingredient exists whose name matches one of my ingredient-name fields, it references the existing ingredient instead of attempting to create a new one with the same name?
Code specifics below...
In Recipe.rb:
class Recipe < ActiveRecord::Base
attr_accessible :name, :description, :directions, :quantities_attributes,
:ingredient_attributes
has_many :quantities, dependent: :destroy
has_many :ingredients, through: :quantities
accepts_nested_attributes_for :quantities, allow_destroy: true
In Quantity.rb:
class Quantity < ActiveRecord::Base
attr_accessible :recipe_id, :ingredient_id, :amount, :ingredient_attributes
belongs_to :recipe
belongs_to :ingredient
accepts_nested_attributes_for :ingredient
And in Ingredient.rb:
class Ingredient < ActiveRecord::Base
attr_accessible :name
validates :name, :uniqueness => { :case_sensitive => false }
has_many :quantities
has_many :recipes, through: :quantities
Here's my nested form that displays at Recipe#new:
<%= form_for #recipe do |f| %>
<%= render 'recipe_form_errors' %>
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<h3>Ingredients</h3>
<div id='ingredients'>
<%= f.fields_for :quantities do |ff| %>
<div class='ingredient_fields'>
<%= ff.fields_for :ingredient_attributes do |fff| %>
<%= fff.label :name %>
<%= fff.text_field :name %>
<% end %>
<%= ff.label :amount %>
<%= ff.text_field :amount, size: "10" %>
<%= ff.hidden_field :_destroy %>
<%= link_to_function "remove", "remove_fields(this)" %><br>
</div>
<% end %>
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %>
</div><br>
<%= f.label :description %><br>
<%= f.text_area :description, rows: 4, columns: 100 %><br>
<%= f.label :directions %><br>
<%= f.text_area :directions, rows: 4, columns: 100 %><br>
<%= f.submit %>
<% end %>
The link_to and link_to_function are there to allow the addition and removal of quantity/ingredient pairs on the fly, and were adapted from the Railscast mentioned earlier. They could use some refactoring, but work more or less as they should.
Update: Per Leger's request, here's the relevant code from recipes_controller.rb. In the Recipes#new route, 3.times { #recipe.quantities.build } sets up three blank quantity/ingredient pairs for any given recipe; these can be removed or added to on the fly using the "Add ingredient" and "remove" links mentioned above.
class RecipesController < ApplicationController
def new
#recipe = Recipe.new
3.times { #recipe.quantities.build }
#quantity = Quantity.new
end
def create
#recipe = Recipe.new(params[:recipe])
if #recipe.save
redirect_to #recipe
else
render :action => 'new'
end
end
You shouldn't put the logic of ingredients match into view - it's duty of Recipe#create to create proper objects before passing 'em to Model. Pls share the relevant code for controller
Few notes before coming to code:
I use Rails4#ruby2.0, but tried to write Rails3-compatible code.
attr_acessible was deprecated in Rails 4, so strong parameters are used instead. If you ever think to upgrade your app, just go with strong parameters from the beginning.
Recommend to make Ingredient low-cased to provide uniform appearance on top of case-insensitivity
OK, here we go:
Remove attr_accessible string in Recipe.rb, Quantity.rb and Ingredient.rb.
Case-insensitive, low-cased Ingredient.rb:
class Ingredient < ActiveRecord::Base
before_save { self.name.downcase! } # to simplify search and unified view
validates :name, :uniqueness => { :case_sensitive => false }
has_many :quantities
has_many :recipes, through: :quantities
end
<div id='ingredients'> part of adjusted form to create/update Recipe:
<%= f.fields_for :quantities do |ff| %>
<div class='ingredient_fields'>
<%= ff.fields_for :ingredient do |fff| %>
<%= fff.label :name %>
<%= fff.text_field :name, size: "10" %>
<% end %>
...
</div>
<% end %>
<%= link_to 'Add ingredient', "new_ingredient_button", id: 'new_ingredient' %>
We should use :ingredient from Quantity nested_attributes and Rails will add up _attributes-part while creating params-hash for further mass assignment. It allows to use same form in both new and update actions. For this part works properly association should be defined in advance. See adjusted Recipe#new bellow.
and finally recipes_controller.rb:
def new
#recipe = Recipe.new
3.times do
#recipe.quantities.build #initialize recipe -> quantities association
#recipe.quantities.last.build_ingredient #initialize quantities -> ingredient association
end
end
def create
#recipe = Recipe.new(recipe_params)
prepare_recipe
if #recipe.save ... #now all saved in proper way
end
def update
#recipe = Recipe.find(params[:id])
#recipe.attributes = recipe_params
prepare_recipe
if #recipe.save ... #now all saved in proper way
end
private
def prepare_recipe
#recipe.quantities.each do |quantity|
# do case-insensitive search via 'where' and building SQL-request
if ingredient = Ingredient.where('LOWER(name) = ?', quantity.ingredient.name.downcase).first
quantity.ingredient_id = quantity.ingredient.id = ingredient.id
end
end
end
def recipe_params
params.require(:recipe).permit(
:name,
:description,
:directions,
:quantities_attributes => [
:id,
:amount,
:_destroy,
:ingredient_attributes => [
#:id commented bc we pick 'id' for existing ingredients manually and for new we create it
:name
]])
end
In prepare_recipe we do the following things:
Find ID of ingredient with given name
Set foreign_key quantity.ingredient_id to ID
Set quantity.ingredient.id to ID (think what happens if you don't do that and change ingredient name in Recipe)
Enjoy!
I am working on a Rails app, wherein I have two models, i.e. a chef model and a dish model.
class Dish < ActiveRecord::Base
belongs_to :chef
attr_accessible :description, :photo, :price
validates :chef_id, presence: true
has_attached_file :photo
end
class Chef < ActiveRecord::Base
attr_accessible :name, :email, :mobile ,:password, :password_confirmation, :postcode
has_many :dishes
has_secure_password
end
I (chef) am trying to create to a dish by going to the /upload url, whose view is
<%= form_for(#dish) do |d| %>
<%= d.label :description, "Please name your dish..."%>
<%= d.text_field(:description)%>
<%= d.label :price, "What should the price of the dish be..."%>
<%= d.number_field(:price)%>
<%= d.submit "Submit this Dish", class: "btn btn-large btn-primary"%>
<% end %>
I want the created dish to appear on the show page of the chef,
<% provide(:title, #chef.name)%>
<div class = "row">
<aside class = "span4">
<h1><%= #chef.name %></h1>
<h2><%= #chef.dishes%></h2>
</aside>
<div>
<% end %>
And, dishes_controller is:
class DishesController < ApplicationController
def create
#dish = chef.dishes.build(params[:dish])
if #dish.save
redirect_to chef_path(#chef)
else
render 'static_pages/home'
end
But as soon as I try to create a dish from the /upload url, I get the following error in the dishes_controller:
NameError undefined local variable or method `chef' for #<DishesController:0x3465494>
app/controllers/dishes_controller.rb:5:in `create'
I think I have instantiated all variables, but the problem persists.
In this line:
#dish = chef.dishes.build(params[:dish])
The chef variable is not instantiated. You have to do something like this:
#chef = Chef.find(params[:chef_id])
#dish = #chef.dishes.build(params[:dish])
This way the #chef variable is populated before you use it.