Reputation: 141
I am working on web app development using ruby on rails. I want to enable users to upload images for their favorite food. I have food_item model and a food_image model. In the food_item model:
has_many :food_images
has_many :food_portions #this is the work done by another teammate
I also define in the food_item controller:
def food_item_params
params.require(:food_items).permit(:name, :category, :description, food_images_attributes: [:id, :food_item_id, :avatar]).tap do |whitelisted|
whitelisted[:portion] = params[:food_items][:portion]
whitelisted[:price] = params[:food_items][:price]
end
Upvotes: 4
Views: 20214
Reputation: 76774
There are two issues:
The first step is to ensure you're using this with the correct routes etc.
You shouldn't have an add_images
method in your controller - you could achieve what you need with edit
/update
:
#config/routes.rb
resources :food_items do
resources :images #-> if necessary, this should be its own controller rather than adding needless methods to your other controller
end
You should use the following controller setup:
#app/models/food_item.rb
class FoodItem < ActiveRecord::Base
accepts_nested_attributes_for :food_images
end
#app/controllers/food_items_controller.rb
class FoodItemsController < ApplicationController
def edit
@food_item = FoodItem.find params[:id]
@food_item.food_images.build
end
def update
@food_item = FootItem.find params[:id]
respond_to do |format|
if @food_item.update food_item_params
...
end
end
end
private
def food_item_params
params.require(:food_items).permit(:name, :category, :description, food_images_attributes: [:id, :food_item_id, :avatar]) #-> this is enough (no need to "whitelist")
end
end
This will give you the ability to load the following:
#url.com/food_items/:id/edit
#app/views/food_items/edit.html.erb
<%= form_for @food_item do |f| %>
= form_for @food_item, html: { :multipart => true } do |f|
= f.label :title
= f.text_field :title
= f.fields_for :food_images do |p|
= p.label :avatar
= p.file_field :avatar
.actions
= f.submit
<% end %>
This will submit to the "update" method, which should save the required object for you.
You should only upload one file at a time
If you need to upload multiple files, you'll need to use a gem such as cocoon
to add them. Rails is great but not magical -- it has to build a single object with each fields_for
.
I can explain more about this if required.
--
To give you context on why you should be using the edit
/ update
methods for this, you need to look up the resourceful principle for object orientated programming.
This is built on the "resources" principle put forward at the inception of HTTP -- a standardized set of technologies which allow browsers to send and retrieve data from servers.
In short, it means there are certain conventions you should abide by to keep your app extensible.
Because Ruby/Rails is object orientated, everything you do in a well-tailored application should revolve around objects. Like resources, these allow you to create a system which is both flexible and extensible if done properly.
Thus, with your code, you have to remember that you're trying to add an image to the food items object. You should therefore be editing
the food items object, updating it with the extra image; which the above code will help you achieve.
Upvotes: 5
Reputation: 814
You need to change food_item_params method :
From :
def food_item_params
params.require(:food_items).permit(food_images_attributes: [:id, :food_item_id, :avatar])
end
To :
def food_item_params
params.require(:food_item).permit(food_images_attributes: [:id, :food_item_id, :avatar])
end
Also, you can improve your code :
Add accepts_nested_attributes_for :food_images in food_item model, then make changes in controller's method
def add_image
@food_item = FoodItem.find(params[:id])
respond_to do |format|
if @food_item.update_attributes(food_item_params)
format.html { redirect_to @food_item, notice: 'Images uploaded successfully' }
else
format.html { redirect_to add_image_food_item_path}
end
end
end
Upvotes: 0
Reputation: 4164
Like Badheka said: "why are you querying and just saving the @food_item?
Anyways, Notice that when you try to create food_images, you did:
food_item_params[:food_image]['avatar'].each do |a|
@food_image = @food_item.food_images.create!(:avatar=>a)
end
You were looping through the avatar
in food_image
attributes of food_item_params
However, if you check your food_item_params
, you have:
def food_item_params
params.require(:food_items).permit(food_images_attributes: [:id, :food_item_id, :avatar])
end
With this, in all possibility, the structure of the data that this params is expecting will be something like:
{
food_items:
{
food_images_attributes:
{
id: "",
food_item_id: "",
avatar: ""
}
}
}
while what is being returned by your food_item_params
will be as follow:
{
food_images_attributes:
{
id: "",
food_item_id: "",
avatar: ""
}
}
There are a number of issues with this:
1) The error you are getting: param is missing or the value is empty: food_items
this suggests that the param that is coming in is not in the format specified above. the parent attribute food_items
is missing, so that is the first part of your code that you need to rectify from inside the view where you are sending the avatar, or conform your food_item_params
method to meet the structure of what is coming in through the params.
2) There is no food_item_params[:food_image]['avatar']
to loop on, since thee is no food_image
in your params. What you have is food_image_attributes
, so your loop will be on food_item_params[:food_image_attributes][:avatar]
3) Looping through the food_item_params[:food_image_attributes][:avatar]
suggests that it is an array. If so, then the way you are permitting it is wrong too. You will have to specify that it is an array from inside the food_item_params
method as follow:
def food_item_params
params.require(:food_items).permit(food_images_attributes: [:id, :food_item_id, avatar: []])
end
If however, as I suspect, what is coming through your param is as follow:
food_images_attributes:
{
id: "",
food_item_id: "",
avatar: []
}
Then your food_item_params
method should be as follows:
def food_item_params
params.require(:food_images_attributes).permit(:id, :food_item_id, avatar: [])
end
I'll advise you check your params and paste what it looks like here.
And as a last word, check what you are sending from the view, and set your controller accordingly, or check what you want the controller to expect, and send same from the view.
Upvotes: 0