Reputation: 43
I'm making a application by using Rails 4.
Basically, I have two models, Card and CardCollection. CardCollections has_many Cards. I implemented this relationship with nested attributes.
class Card < ActiveRecord::Base
belongs_to :card_collection
end
class CardCollection < ActiveRecord::Base
has_many :uploaded_cards, class_name: 'Card', dependent: :destroy
accepts_nested_attributes_for :uploaded_cards, allow_destroy: true
end
And form is like below.
<%= form_for @card_collection, options do |f| %>
<%= f.fields_for :uploaded_cards do |ff| %>
<%= render 'cards/card_form', f: ff %>
<% end %>
<% end %>
When I put some data in the form and press the submit button, I can see that parameters are passing correctly to the server. (According to the server log)
Parameters: {"utf8"=>"✓", "authenticity_token"=>"YFn7RsQzR4Sq1bjR+mOhCASWmeqAxw9B30oAHCL
9qzk=", "card_collection"=>{"collectible_id"=>"16", "collectible_type"=>"Scrapbook", "uplo
aded_cards_attributes"=>{"0"=>{"_destroy"=>"false", "image_url"=>"https://s3-ap-northeast-
1.amazonaws.com/bucketplace/uploads/image_content/src/2833/2b67142422.jpg", "place"=>"1",
"style"=>"0", "description"=>"생각을 짓다......", "id"=>"97"}, "1"=>{"image_url"=>"http://
hodoli.s3.amazonaws.com/uploads%2F14_06_02_17_17_56_62276930_468703135.jpg", "place"=>"7",
"style"=>"1", "description"=>"123"}}}, "project_cover_image_url"=>"", "commit"=>"확인", "
id"=>"16"}
But when I really print 'params' variable in program code, params[:card_collection][:uploaded_cards_attributes] hash(nested attributes part) is suddenly missing. So the printed params value is like below.
"params= {\"utf8\"=>\"✓\", \"_method\"=>\"patch\", \"authenticity_token\"=>\"YFn7RsQzR4Sq
1bjR+mOhCASWmeqAxw9B30oAHCL9qzk=\", \"card_collection\"=>{\"collectible_id\"=>16, \"collec
tible_type\"=>\"Scrapbook\"}, \"project_cover_image_url\"=>\"\", \"commit\"=>\"확인\", \"a
ction\"=>\"update\", \"controller\"=>\"card_collections\", \"id\"=>\"16\"}"
Unpermitted parameters: collectible_id, collectible_type
I don't know why this happen. I think it doesn't have relationship with 'Strong Parameter' because problem is in the Rails-side parameter parsing part. (as I think) Please help me to find the exact problem and solution.
EDIT
this is my controller code
class CardCollectionsController < ApplicationController
before_action :set_card_collection, only: [:do_upload, :edit, :update]
before_action :authenticate_user!, only: [:upload, :do_upload, :edit, :update]
before_action :authenticate_card_collection_owner!, only: [:do_upload, :edit, :update]
# GET /card_collections/upload_select
def upload_select
end
# GET /card_collections/upload
def upload
@card_collection = CardCollection.new
end
def do_upload
respond_to do |format|
p card_collection_params
if @card_collection.update(card_collection_params)
format.html do
@card_collection.collectible.update(cover_image_url: params[:project_cover_image_url]) unless params[:project_cover_image_url].blank?
redirect_to @card_collection.collectible, notice: 'CardCollection was successfully updated.'
end
format.json { head :no_content }
else
@card_collection = CardCollection.new(card_collection_params)
format.html { render 'upload', alert: '에러' }
format.json { render json: @card_collection.errors, status: :unprocessable_entity }
end
end
end
# GET /card_collections/1/edit
def edit
end
# PATCH/PUT /card_collections/1
# PATCH/PUT /card_collections/1.json
def update
respond_to do |format|
if @card_collection.update!(card_collection_params)
format.html do
@card_collection.collectible.update(cover_image_url: params[:project_cover_image_url]) unless params[:project_cover_image_url].blank?
redirect_to @card_collection.collectible, notice: 'CardCollection was successfully updated.'
end
format.json { head :no_content }
else
format.html { render 'edit', alert: '에러' }
format.json { render json: @card_collection.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_card_collection
if params[:id]
@card_collection = CardCollection.find(params[:id])
params[:card_collection] = {collectible_id: @card_collection.collectible_id, collectible_type: @card_collection.collectible_type}
elsif params[:card_collection][:collectible_id].blank?
@card_collection = CardCollection.new(card_collection_params)
flash.now[:alert] = '스크랩북을 선택해 주세요'
render 'upload'
else
@card_collection = CardCollection.find_by_collectible_id_and_collectible_type(params[:card_collection][:collectible_id], params[:card_collection][:collectible_type])
end
end
# Never trust parameters from the scary internet, only allow the white list through.
def card_collection_params
params.require(:card_collection).permit(CardCollection.param_names + [uploaded_cards_attributes: Card.param_names])
end
def authenticate_card_collection_owner!
redirect_to previous_url if current_user != @card_collection.user
end
end
class CardCollection < ActiveRecord::Base
def self.param_names
[:id, :name, :description]
end
end
class Card < ActiveRecord::Base
def self.param_names
[:id, :image_url, :description, :place, :style, :source, :width, :height]
end
end
Solved Finally, I solved the problem thanks to @Rich Peck. The real problem was about route part of Rails. I was identifying specific record by two ways in one controller as you can see in 'set_card_collection' method.
resources :card_collection, only: [:edit, :update] do
get :upload, on: :collection
patch :do_upload, on: :collection
end
Original route.rb file was look like above. I structured my route file (do not specify CardCollection id when user connect to upload page) like that to give flexibility to user choosing CardCollection dinamically in upload page. But to connect edit page, user had to specify record id such as /card_collection/1/edit because it is Rails's default behavior.
Then I customized 'set_card_collection' method in the controller.
if params[:id]
@card_collection = CardCollection.find(params[:id])
params[:card_collection] = {collectible_id: @card_collection.collectible_id, collectible_type: @card_collection.collectible_type}
elsif params[:card_collection][:collectible_id].blank?
@card_collection = CardCollection.new(card_collection_params)
flash.now[:alert] = '스크랩북을 선택해 주세요'
render 'upload'
else
@card_collection = CardCollection.find_by_collectible_id_and_collectible_type(params[:card_collection][:collectible_id], params[:card_collection][:collectible_type])
Still, I don't know the EXACT cause, but using default :edit and :update route setting caused the problem that nested parameters would not be passed correctly. So after I changed my route.rb file like below as unifying the way identifying a record, the problem was solved.
resources :card_collections, only: [] do
get :upload_select, on: :collection
get :upload, on: :collection
post :upload, on: :collection, to: :do_upload
get :edit, on: :collection
patch :update, on: :collection
put :update, on: :collection
end
Upvotes: 4
Views: 8426
Reputation: 1809
In my case I had to add the MIME type in order to see all params in Rails. The Rails logs were kind of warning me about this by making me aware, that the request came in as "application/vnd.api+json".
I needed to add the following line to my config/initializers/mime_types.rb
:
Mime::Type.register "application/vnd.api+json", :json
See https://github.com/rails/rails/issues/28564 for this issue.
Upvotes: 0
Reputation: 76774
There are a number of issues which could cause this
--
Error
I see your system is correctly generating the uploaded_cards_attributes
, which is the biggest hurdle to cross
This means the problem is either going to be with your strong params
(which I doubt because the params
hash is available in its entirety in your controller (it's the model where you want to restrict access); or how you're managing the params inside the controller
I don't know how to fix your problem, but it's likely not going to be a Rails issue
--
Remedy
I would initially look at how you're passing the params
Looks like you're using the edit
/ update
method (by use of the PATCH
verb). This could be a problem, as Rails may not pass the data for an already-created object.
I would initially test using a new
form - to see if you can create a new object with nested attributes. If you can do this, I would then test using nested resources
in your routes & edit the CardCollection
object directly:
#config/routes.rb
resources :card_collections do
resources :card_uploads
end
This will allow you to do something like:
#app/controllers/card_collections_controller.rb
class CardCollectionsController < AppicationController
def edit
@card_collection = CardCollection.find params[:card_collection_id]
@card_upload = @card_collectiom.new #-> might need to change
end
end
#app/views/card_collections/edit.html.erb
<%= form_for @card_upload do |f| %>
<%= f.file_field :image %>
<%= f.submit %>
<% end %>
Bear in mind, that is a test - so I will not use that in production; I just want to see why you're not receiving the params
.
--
Attributes
btw we use the attribute_names
method to make our Strong Params
dry:
def your_params
parameters = Model.attribute_names - %w(id created_at updated_at)
params.require(:parent).permit(parameters)
end
Upvotes: 1