Unpermitted parameters nested attributes - rails - ruby-on-rails

I'm trying to submit a form to 2 tables but somehow I got this syntax error unexpected '\n' at this line joins: ['sources'], :landslide_id and found unpermitted parameter: sources in landslide params. Here's all the files
Models
class Landslide < ApplicationRecord
has_many :sources, dependent: :destroy
accepts_nested_attributes_for :sources
class Source < ApplicationRecord
belongs_to :landslide
end
landslides_controller.rb
def new
#landslide = Landslide.new
#landslide.sources.build
end
# POST /landslides
def create
#landslide = Landslide.new(landslide_params)
#landslide.save
end
private
# Use callbacks to share common setup or constraints between actions.
def set_landslide
render json: Landslide.find(params[:total_id]),
joins: ['sources'], :landslide_id
end
# Only allow a trusted parameter "white list" through.
def landslide_params
params.require(:landslide).permit(:start_date, :continent, :country, :location, :landslide_type, :lat, :lng, :mapped, :trigger, :spatial_area, :fatalities, :injuries, :notes, sources_attributes: [ :url, :text ])
end
sources_controller.rb
def set_source
#source = Source.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def source_params
params.require(:source).permit(:url, :text)
end
_form.html.haml
= form_for :landslide, :url => {:controller => 'landslides', :action => 'create'} do |f|
#something
%fieldset
%legend Source
= f.fields_for :sources do |s|
.form-group.row
%label.col-sm-2.col-form-label{for: "textinput"}URL
.col-sm-10
= s.text_field :url, class: "form-control"
.form-group.row
%label.col-sm-2.col-form-label{for: "textinput"}Text
.col-sm-10
= s.text_field :text, class: "form-control"
Request
{"utf8"=>"✓",
"authenticity_token"=>"W3m2dLTGyuPCbP6+pStWDfgpIbPzGdl4tvf01vMAbyozzkimqlXH4B/RtwBcsLb+iiBqms7EHagY+Anbpo4zNg==",
"landslide"=>
{"start_date(3i)"=>"27",
"start_date(2i)"=>"4",
"start_date(1i)"=>"2017",
"continent"=>"Africa",
"country"=>"Country",
"location"=>"Location",
"landslide_type"=>"1",
"lat"=>"1",
"lng"=>"1",
"mapped"=>"False",
"spatial_area"=>"1",
"fatalities"=>"1",
"injuries"=>"1",
"notes"=>"1",
"trigger"=>"1",
"sources"=>{"url"=>"url", "text"=>"text"}},
"button"=>""}

found unpermitted parameter: sources
Based on your form, it looks like sources are inside a param called sources rather than sources_attributes. Edit your landslide_params, changing sources_attributes to sources.
May I ask what set_landslide is trying to render, or correct me if I am wrong below? Placing joins on a new line causes the error. I am thinking you're trying to do something like:
landslide = Landslide.find(params[:total_id])
render json: landslide.to_json(:include => { :sources => { landslide_params[:sources] }})
Which would give you a json with the landslide object and a sources array. The landslide id should be within the landslide object. This of course assumes that's what you were going for.

Related

Nested fields causing rollback

So I am facing this problem where nested field is causing a rollback on submit. I am using rails 5.
Here is the new and create actions of the controller
def new
#match = Match.new
#match.heros.build
end
def create
#match = cur_user.matches.build(matches_params)
#match.save
end
Here are the params
def matches_params
params.require(:match).permit(:map, heros_attributes: [:id, :hero])
end
Simplified form_for
= form_for(#match) do |f|
= f.label :map, value: "Map Played:"
= f.select "map",
[["Select Map", 0]
= f.label :heros, value: "Hero Played:"
= f.fields_for :heros do |h|
= h.select "hero",
[["Select Hero", 0]
= f.submit "Submit"
In match.rb I have
has_many :heros, dependent: :destroy
accepts_nested_attributes_for :heros
and in hero.rb I have
belongs_to :match
I get a rollback on pressing submit and on running #match.errors.full_messages I get ["Heros match must exist"]
Any help would be greatly appreciated.
Edit: Views are in haml.
The plural of hero is heroes, and not heros. Change your code so that it specifies heroes instead of heros, and retry.

Rails - Issue with permitting params & require

I'm having an issue with submitting my params,
portrait and portrait_tag are passed through my form and are siblings,
how would I go permitting both of these?
Output
{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"", "portrait"=>{"artist_image"= "", #original_filename="rubytraining.jpg", #content_type="image/jpeg"}, "portrait_tag"=>{"tag_ids"=>["", "1", "2", "3", "4"]}, "commit"=>"Save changes", "controller"=>"admin/portraits", "action"=>"update", "id"=>"72"}
I've tried the following
private
def portrait_params
params.require(:portrait).permit(:id, :artist_image)
params.require(:portrait_tag).permit(:id, :tag => [])
end
These work separately but overwrite one another when added together
controller
def update
#portrait = Portrait.where(artist_id: 33, id: params[:id]).take
if #portrait.update(portrait_params)
redirect_to :edit_admin_portrait, flash: {notice: "Successfully updated your information"}
else
flash[:system] = #portrait.errors.full_messages
p #portrait.errors.full_messages
render :edit
end
end
private
def portrait_params
params.require(:portrait).permit(:id, :artist_image)
params.require(:portrait_tag).permit(:id, :tag => [])
end
Edit Form
%h1 Edit Portrait
= form_for [:admin, #portraits] do |f|
- if flash[:system].present?
- flash[:system].each do |e|
%div= e
- if flash[:notice].present?
%div= flash[:notice]
= f.file_field :artist_image
= collection_select :portrait_tag, :tag_ids, Tag.all, :id, :name, {:selected => #portraits.tag_ids}, { :multiple => true}
= f.submit "Save changes", class: "btn btn-primary"
the tags are an association of the portrait (portrait_tags).
Finally, the core of the problem. In this case, you should be using accepts_nested_attributes_for. It will allow you to post attributes for tags inside your :portrait params.
So your strong params method would look like this:
def portrait_params
params.require(:portrait).permit(:id, :artist_image, portrait_tags_attributes: [:id, :tag])
end
Of course, amend your forms accordingly.
You are returning only last line of portrait_params.
params.require(:portrait_tag).permit(:id, :tag => [])
I'd do it like this:
Make two private methods with params(portrait, portrait_tag) , they will return you Hash'es, then you can merge them. It is not clear to have method portrait_params which return you
portrait_tag params if they are not in nested attributes.
If they are you can use but you have to add this to model
method accepts_nested_attributes_for
and then you can do it like this
def portrait_params
params.require(:portrait).permit(:id, :artist_image, portrait_tags_attributes: [:id, :tag]))
end
You can use nested_attributes to update parent and child same time. But you have to made some changes, for instance you need to change params structure.
Ex:
params.require(:portrait).permit(:attr1,:att2,..., portrait_attributes: [:attr1, :attr2])
Ref: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Strong params, nested attributes: Can't mass-assign

I'm using Rails 4.0.2 and can't get strong parameters to work with nested attributes.
# Model
has_one :availability
accepts_nested_attributes_for :availability
# Controller
def base_content_params
params.require("base_content").permit(:enabled, :language, :title, :description,
availability_attributes: [:duration, :slots])
end
# View
form_for [app, base_content] do |form|
form.fields_for :availability do | a |
a.select 'duration', duration_values
end
form.fields_for :availability do | a |
a.select 'slots', [*(1..10)]
end
But I keep getting:
Can't mass-assign protected attributes for BaseContent: availability_attributes
>> base_content_params
=> {"enabled"=>"false", "title"=>"test", "description"=>"", availability_attributes"=>{"duration"=>"30", "slots"=>"10"}}
# request parameters
{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"---", "base_content"=>{"enabled"=>"false", "language_id"=>"12938", "title"=>"test", "description"=>"", "content"=>"", "terms"=>"", "category"=>"product", "category_mode"=>"appy_booking", "responder_email"=>"", "price"=>"111.00", "price_per"=>"unit", "availability_attributes"=>{"start_at(5i)"=>"17:45:00", "id"=>"1", "duration"=>"30", "slots"=>"10"}, "reward_points"=>"100", "hash_tags"=>"", "lat"=>"", "lng"=>""}, "commit"=>"Save & Continue edit", "geocoder_lat"=>"0.0", "geocoder_lng"=>"0.0", "pac-input"=>"", "action"=>"update", "controller"=>"backend/base_contents", "app_id"=>"1898", "id"=>"16108"}
What am I missing here?
EDIT
# BaseContent Model
class BaseContent < ActiveRecord::Base
attr_accessible :enabled, :price, :price_per, :app, :menu,
# App Model
class App < ActiveRecord::Base
attr_accessible :name, :allow_search, :display_logo_badge, #... etc
This should be a comment, but it was too long.
From your comments, it's now clearer what the problem is. If you're using Rails 4.0.2, you have to switch to using strong_params in your controller:
#app/controllers/your_controller.rb
class YourController < ApplicationController
def create
#model = Model.new model_params
#model.save
end
private
def model_params
params.require(:model).permit(:attribute1, :attribute2)
end
end
I would strongly recommend you go through your models, remove any attr_accessible references, get rid of the protected_attributes gem and rebuild the functionality for strong params.
--
Another issue I can see is the way you're calling your form:
form_for [app, base_content] do |form|
Why are you nesting base_content (which should be an instance variable) under app? If anything, I'd expect something along the lines of...
form_for #base_content do |form|

has_one nested attributes not saving

I have two models Project and ProjectPipeline.
I want to create a Project form that also has fields from the ProjectPipeline model. I have created the form successfully but when I hit save the values aren't stored on the database.
project.rb
class Project < ActiveRecord::Base
has_one :project_pipeline
accepts_nested_attributes_for :project_pipeline
self.primary_key = :project_id
end
projectpipeline.rb
class ProjectPipeline < ActiveRecord::Base
belongs_to :project, autosave: :true
validates_uniqueness_of :project_id
end
I don't always want a project pipeline but under the right conditions based on a user viewing a project. I want the project pipeline fields to be built, but only saved if the user chooses to save/populate them.
So when the project is shown I build a temporary project pipeline using the project_id: from params[:id] (not sure if I really need to do this). Then when the project is saved I use the create_attributes. But if it has already been created or built I just want to let the has_one and belongs_to association kick in and then use update_attributes.
My issue is when I am trying to save, I am either hitting a 'Forbidden Attribute' error if I use params[:project_pipeline] or having nothing saved at all if I used project_params. I have checked and rechecked all my fields are in project_params and even tried using a project_pipeline_params but that didn't feel right.
It is driving me nuts and I need to sleep.
projects_controller.rb
def show
#project = Project.find(params[:id])
if #project.project_pipeline
else
#project.build_project_pipeline(project_id: params[:id])
end
autopopulate
end
def update
#project = Project.find(params[:id])
if #project.project_pipeline
else
#project.build_project_pipeline(project_id: params[:id], project_type: params[:project_pipeline][:project_type], project_stage: params[:project_pipeline][:project_stage])
end
if #project.update_attributes(project_params)
flash[:success] = "Project Updated"
redirect_to [#project]
else
render 'edit'
end
end
def project_params
params.require(:project).permit(:user_id, project_pipeline_attributes:[:project_id,:project_type,:project_stage,
:product_volume,:product_value,:project_status,:outcome, :_destroy])
end
show.html.haml
- provide(:title, "Show Project")
%h1= #project.project_title
= simple_form_for(#project) do |f|
= f.input :id, :as => :hidden, :value => #project, :readonly => true
= f.input :user_id, label: 'Assigned to Account Manager', :collection => #account_managers, :label_method => lambda { |r| "#{r.first_name} #{r.last_name}" }
= f.input :project_id, :readonly => true
= f.input :status, :readonly => true
= f.input :project_stage, :readonly => true
- if #project.project_codename = "project pipeline"
= simple_fields_for #project.project_pipeline do |i|
%h2 Project Pipeline
- if #project.user_id == current_user.id
= i.input :project_volume, label: 'Project Status', collection: #project_status
= i.input :project_value, label: 'Project Status', collection: #project_status
= i.input :project_status, label: 'Project Status', collection: #project_status
= i.input :outcome, label: 'Outcome', collection: #outcome
= f.submit 'Save'
If you've gotten this far I sincerely thank you.
Solution
You need to change few things here. Firstly:
= simple_fields_for #project.project_pipeline do |i|
When you pass the object, rails have no idea it is to be associated with the parent object and as a result will create a field named project[project_pipeline] instead of project[project_pipeline_attributes]. Instead you need to pass the association name and call this method on the form builder:
= f.simple_fields_for :project_pipeline do |i|
This will check find out that you have defined project_pipeline_attributes= method (using accept_nested_attributes_for` and will treat it as association. Then in your controller change your show action to:
def update
#project = Project.find(params[:id])
#project.assign_attributes(project_params)
if #project.save
flash[:success] = "Project Updated"
redirect_to #project
else
render 'edit'
end
end
And all should work. As a separate note, since you are allowing :_destroy attribute in nested params, I am assuming you want to be able to remove the record using nested attributes. If so, you need to add allow_destroy: true to your accepts_nested_attributes_for call.
Now a bit of styling:
You can improve your show action a bit. First of all, I've noticed you are building an empty pipeline in every single action if none has been declared yet. That mean that you probably should move this logic into your model:
class Project < AR::Base
after_initalize :add_pipeline
private
def add_pipeline
project_pipeline || build_project_pipeline
end
end
You also have the mysterious method prepopulate - most likely it should be model concern as well.
Another point: This syntax:
if something
else
# do sth
end
is somehow quite popular and makes the code unreadable as hell. Instead, use:
if !something
# do something
end
or (preferred)
unless something
# do something
end
I'm not sure from your description if this is the problem, but one the thing is that a update_attributes with a has_one, by default, will rebuild the children(!), so you would lose the attributes you initialised. You should provide de update_only: true option to accepts_nested_attributes_for.
You can find more on this here, in the rails docs. The line would be this:
accepts_nested_attributes_for :project_pipeline, update_only: true
Considering the after_initialize, that would result in every project always having a pipeline. While that could be desirable, it isn't necessarily, depending on your domain, so I'd be a bit careful with that.
Cheers,
Niels

Trouble setting param values in controller for form

I have a form that lets users add a new blocked tv show to their list of blocked shows. The form is not taking the param values (:user_id, :title, :image) that I tried to set in the controller. I'm a beginner, so I'm guessing the syntax is the problem.
Also I am getting a Couldn't find Tvshow without Id error when trying to use the #tvshow instance variable to set the param values of :title and :image. Each Blocked show should have the same title and image as the tvshow that the user selects in the collection_select. Is there an easier way to do this?
View
<%= form_for #blockedshow do |b| %>
<%= b.label :tvshow_id, "Add a Blocked TV Show " %><br/>
<%= collection_select(:blockedshow, :tvshow_id, Tvshow.all, :id, :title, prompt: true) %>
<%= submit_tag 'Add' %>
<% end %>
Controller
class BlockedshowsController < ApplicationController
def new
#blockedshow = Blockedshow.new
end
def create
#tvshow = Tvshow.find params[:blockedshow][:id]
#blockedshow = Blockedshow.new(safe_blockedshow)
params[:user_id] = current_user.id
params[:title] = #tvshow.title
params[:image] = #tvshow.image
if #blockedshow.save
flash[:notice] = "New Blocked TV Show added successfully"
redirect_to tv_show_index_path
else
render 'new'
end
end
private
def safe_blockedshow
params.require(:blockedshow).permit(:title, :user_id, :tvshow_id, :image)
end
end
Blockedshow model
class Blockedshow < ActiveRecord::Base
has_many :phrases
has_many :tvshows
belongs_to :user
end
Tvshow model
class Tvshow < ActiveRecord::Base
has_many :phrases
belongs_to :blockedshow
def self.search_for (query)
where('title LIKE :query', query: "%#{query}%")
end
end
Routes
resources :blockedshows
post 'blockedshows', to:'blockedshows#create#[:id]'
you are getting the issue because params[:blockedshow][:id] is not passed, if your trying to access the Tvshow id selected by from the drop-list you can do the following
#tvshow = Tvshow.find params[:blockedshow][:tvshow_id]
Just fixed by changing the controller to this:
def create
#tvshow = Tvshow.find params[:blockedshow][:tvshow_id]
#blockedshow = Blockedshow.new(
:user_id =>current_user.id,
:title=> #tvshow.title,
:image=> #tvshow.image,
:tvshow_id=>#tvshow.id
)

Resources