ActiveModel::ForbiddenAttributesError for polymorphic association - ruby-on-rails

There are the following models:
class Discount < ActiveRecord::Base
belongs_to :content, polymorphic: true
validates :value, presence: true
end
class TimeDiscount < ActiveRecord::Base
has_one :discount, as: :content, dependent: :destroy
validates :start_time, :end_time, presence: true
accepts_nested_attributes_for :discount
end
And the following controller:
class Admin::TimeDiscountsController < ApplicationController
def new
#time_discount = TimeDiscount.new
end
def create
#time_discount = TimeDiscount.new(time_discount_params)
if #time_discount.save
redirect_to root_path
else
render 'new'
end
end
private
def time_discount_params
params.require(:time_discount).permit.tap do |whitelisted|
whitelisted[:start_time] = params[:time_discount][:start_time]
whitelisted[:end_time] = params[:time_discount][:end_time]
whitelisted[:discount_attributes] = params[:time_discount][:content]
end
end
end
Form:
= form_for #time_discount, url: admin_time_discounts_path do |f|
.row
= f.label :start_time
= f.text_field :start_time
.row
= f.label :end_time
= f.text_field :end_time
= f.fields_for :content do |discount|
.row
= discount.label :value
= discount.text_field :value
.row
= f.submit "Добавить"
But 'create' action generates 'ActiveModel::ForbiddenAttributesError' in TimeDiscount.new line. I use Rails 4. How can I fix it? Thanks.

def time_discount_params
params.require(:time_discount).permit(:start_time, :end_time, content: [:id, :start_time, :end_time])
end
This is probably what you want instead of that method you have defined in the above. This is assuming you are creating it via something like this:
= form_for #time_discount, url: admin_time_discounts_path do |f|
.row
= f.label :start_time
= f.text_field :start_time
.row
= f.label :end_time
= f.text_field :end_time
= f.fields_for :content, #time_discount.content.build do |discount|
.row
= discount.label :value
= discount.text_field :value
.row
= f.submit "Добавить"
give that a go.

Related

Update nested object on create

I have 3 models :
user.rb
class User < ApplicationRecord
has_one :profile, inverse_of: :user, dependent: :destroy
accepts_nested_attributes_for :profile
has_many :bookings, inverse_of: :user, dependent: :destroy
has_many :owner_bookings, foreign_key: 'owner_id', class_name: 'Booking'
end
profile.rb
class Profile < ApplicationRecord
# mount_uploader :avatar, AvatarUploader
belongs_to :user, inverse_of: :profile
accepts_nested_attributes_for :user
end
booking.rb
class Booking < ApplicationRecord
belongs_to :user
belongs_to :owner, class_name: :User
belongs_to :poi
end
After a create booking, I would like to update the user profile (if some changes)
my form :
<%= simple_form_for [#poi.poitable, #booking] do |f| %>
<%= f.simple_fields_for #user.profile do |profile| %>
<%= profile.input :first_name,
input_html: { value: #user.profile.first_name } %>
<%= profile.input :last_name, input_html: { value: #user.profile.last_name } %>
<% end %>
<%= f.input :owner_id, input_html: { value: #poi.user.id } %>
<%= f.input :poi_id, input_html: { value: #poi.id } %>
<%= f.input :user_id, input_html: { value: #user.id } %>
<%= f.submit %>
<% end %>
booking_controller :
def new
#user = current_user
#poi = Sleep.friendly.find(params[:sleep_id]).poi
#booking = Booking.new
end
def create
#user = current_user
#poi = Sleep.friendly.find(params[:sleep_id]).poi
#booking = Booking.new(booking_params)
if #booking.save
#booking.user.profile.update_attribute(booking_params[:user_profile_attributes])
end
private
def booking_params
params.require(:booking).permit(
:poi_id,
:owner_id,
:user_id,
user_profile_attributes: [:user_id, :first_name, :first_name],
)
end
booking_params return only :poi_id, :owner_id, user_id, how can I get user_profile params to update #user.profile ?

how to save data into has_many_through table while saving parent object with nested_form rails

i have job model, skill model and job_skill model
class Job < ActiveRecord::Base
has_many :job_skills
has_many :skills ,through: :job_skills
accepts_nested_attributes_for :job_skills
class Skill < ActiveRecord::Base
has_many :job_skills
has_many :jobs ,through: :job_skills
accepts_nested_attributes_for :job_skills
class JobSkill < ActiveRecord::Base
belongs_to :skill
belongs_to :job
accepts_nested_attributes_for :job
accepts_nested_attributes_for :skill
And jobs controller
class JobsController < ApplicationController
def new
#job = Job.new
#job.job_skills.build
end
def create
#job = Job.new(job_params)
#job.save
end
private
def job_params
params.require(:job).permit(:occupation, :industry, :location,job_skills_attributes:[])
end
and job form is
= form_for #job do |f|
= f.label :location
= f.text_field :location
.clear
= f.label "industry*"
= f.text_field :industry
.clear
= f.label :occupation
= f.text_field :occupation
.clear
= f.label "Skill Required*"
= f.fields_for(:job_skills) do |s|
= s.select :skill_id, Skill.all.collect{|p| [p.skill, p.id]},{}, {:multiple => true, :class=> "chosen-select multiple-select-skills"}
= f.submit "submit"
only job get save. jobs_skills doesnt save. in job params i get only jobs data. what could be the reason. please help.!!
Update your form_for to nested_form_for
= nested_form_for #job do |f|
= f.label :location
= f.text_field :location
.clear
= f.label "industry*"
= f.text_field :industry
.clear
= f.label :occupation
= f.text_field :occupation
.clear
= f.label "Skill Required*"
= f.fields_for(:job_skills) do |s|
= s.select :skill_id, Skill.all.collect{|p| [p.skill, p.id]},{}, {:multiple => true, :class=> "chosen-select multiple-select-skills"}
In this case accepts_nested_attributes_for is not required.
Your models should be
class Job < ActiveRecord::Base
has_many :job_skills
has_many :skills ,through: :job_skills
class Skill < ActiveRecord::Base
has_many :job_skills
has_many :jobs ,through: :job_skills
class JobSkill < ActiveRecord::Base
belongs_to :skill
belongs_to :job
And jobs controller
class JobsController < ApplicationController
def new
#job = Job.new
##job.job_skills.build #=> No need of buid
end
def create
#job = Job.new(job_params)
#job.save
end
private
# Add skill_ids to job_params
def job_params
params.require(:job).permit(:occupation, :industry, :location, skill_ids:[])
end
and job form
= form_for #job do |f|
= f.label :location
= f.text_field :location
.clear
= f.label "industry*"
= f.text_field :industry
.clear
= f.label :occupation
= f.text_field :occupation
.clear
= f.label "Skill Required*"
= s.select :skill_ids, Skill.all.collect{|p| [p.skill, p.id]},{}, {:multiple => true, :class=> "chosen-select multiple-select-skills"}
= f.submit "submit"
Hope this will be helpful

Setting Default Value in .build method

Im trying to submit a nested form when you create a school, you can also create a admin with it. When I try to save the form with the default value of owner set to true and status set to active, it saves the form but doesnt save those values in the database
class SchoolsController < ApplicationController
before_filter :authenticate_admin!, except: [:index, :new, :create]
def show
#school = School.friendly.find(params[:id])
end
def new
#school = School.new
#admin = #school.admins.build(:owner => true, :status => "active")
end
def create
#school = School.new(school_params)
if #school.save
redirect_to new_admin_session_path
else
render 'new'
end
end
def edit
#school = School.find_by_slug(params[:id])
end
def update
#school = School.find_by_slug(params[:id])
if #school.update_attributes(school_params)
redirect_to :action => 'show', :id => #school
else
render :action => 'edit'
end
end
def team
#teams = Admin.all
end
private
def school_params
params.require(:school).permit(:school_name, :latitude, :longitude, :radius, admins_attributes: [ :first_name, :last_name, :email, :password, :password_confirmation, :image ])
end
end
class School < ActiveRecord::Base
has_many :admins
accepts_nested_attributes_for :admins
extend FriendlyId
friendly_id :school_name, use: :slugged
def should_generate_new_friendly_id?
school_name?
end
# Validation
validates :school_name, presence: true, uniqueness: true
validates :latitude, presence: true
validates :longitude, presence: true
validates :radius, presence: true, numericality: { only_integer: true }
end
%nav.navbar.navbar-default.navbar-fixed-top{role:"navigation"}
%div.container
%div.navbar-header
%button.navbar-toggle.collapsed{"data-toggle" => "collapse", "data-target" => "#navbar", "aria-expanded" => "false", "aria-controls" => "navbar"}
%span.sr-only Toggle Navigation
%span.icon-bar
%span.icon-bar
%span.icon-bar
%a.navbar-brand{"href" => "/", "id"=> "brand"} QuickAlert
%div#navbar.collapse.navbar-collapse
%ul.nav.navbar-nav.main-menu
%li.active
%a{"href" => "#"} Home
%li
%a{"href" => "#about"} About
%li
%a{"href" => "#contact"} Contact
%div.container-fluid
%div.row
%div.school-create
- if #school.errors.any?
%ul
- #school.errors.full_messages.each do |msg|
%li
= msg
%div#map-check
%h2.header School Info
= form_for #school do |f|
= f.label :school_name, "School Name"
= f.text_field :school_name, :class => 'form-control'
= f.label :latitude, "Latitude"
= f.text_field :latitude, :class => 'form-control', :id => "latitude"
= f.label :longitude, "Longitude"
= f.text_field :longitude, :class => 'form-control', :id => "longitude"
= f.label :radius, "Radius"
= f.text_field :radius, :class => 'form-control', :id => "radius"
%div.admin-fields
%h2.header Admin Info
= f.fields_for :admins do |ff|
= ff.label :first_name
= ff.text_field :first_name, :class => 'form-control'
= ff.label :last_name
= ff.text_field :last_name, :class => 'form-control'
= ff.label :email
= ff.text_field :email, :class => 'form-control'
= ff.label :password
= ff.password_field :password, :class => 'form-control'
= ff.label :password_confirmation
= ff.password_field :password_confirmation, :class => 'form-control'
= ff.file_field :image
= f.submit :class => 'submit-button btn btn-primary'

Can't mass-assign protected attributes: _destroy

I'm getting this really weird error when I submit my nested form.
Can't mass-assign protected attributes: _destroy
Any idea why this may be? It's a bit of a concern as I'm having to remove the 'destroy' hidden_field with javascript temporarily until i figure out what it is, meaning i can't delete anything!
_form.html.erb
<%= nested_form_for(#post, :html=> {:multipart => true, :class=> "new_blog_post", :id=> "new_blog_post"}) do |f| %>
<%= field do %>
<%= f.text_field :title, placeholder: "Give your post a title", :class=>"span12" %>
<% end %>
<%= field do %>
<%= f.text_area :body, placeholder: "Write something here...", :id=>"blog-text", :class=>"span12" %>
<% end %>
<%= f.label :search_locations, "Add locations to your post" %>
<%= text_field_tag :name,"",:class=>"localename", :id=>"appendedInput", :placeholder=> "Name of the location", :autocomplete => "off" %>
<%= f.link_to_add "Add a location", :locations %>
<%= actions do %>
<%= f.submit "Submit", :class=>"btn", :disable_with => 'Uploading Image...' %>
<% end end%>
_posts_controller.rb_
class PostsController < ::Blogit::ApplicationController
...
def new
#post = current_blogger.blog_posts.new(params[:post])
#location = #post.locations.build
end
def edit
#post = Post.find(params[:id])
##post = current_blogger.blog_posts.find(params[:id]) removed so any use can edit any post
#location = #post.locations.build
end
def create
location_set = params[:post].delete(:locations_attributes) unless params[:post][:locations_attributes].blank?
#post = current_blogger.blog_posts.new(params[:post])
#post.locations = Location.find_or_initialize_location_set(location_set) unless location_set.nil?
if #post.save
redirect_to #post, notice: 'Blog post was successfully created.'
else
render action: "new"
end
end
def update
#post = current_blogger.blog_posts.find(params[:id])
if #post.update_attributes(params[:post])
redirect_to #post, notice: 'Blog post was successfully updated.'
else
render action: "edit"
end
end
def destroy
#post = current_blogger.blog_posts.find(params[:id])
#post.destroy
redirect_to posts_url, notice: "Blog post was successfully destroyed."
end
location.rb
class Location < ActiveRecord::Base
after_save { |location| location.destroy if location.name.blank? }
has_many :location_post
has_many :posts, :through => :location_post
has_many :assets
attr_accessible :latitude, :longitude, :name, :post_id, :notes, :asset, :assets_attributes
accepts_nested_attributes_for :assets, :allow_destroy => true
include Rails.application.routes.url_helpers
def self.find_or_initialize_location_set(location_set)
locations = []
locations = locations.delete_if { |elem| elem.flatten.empty? }
location_set.each do |key, location|
locations << find_or_initialize_by_name(location)
end
locations
end
end
EDIT:
Snippet of rendered form in new.html.erb
<div class="row span locsearch">
<div class="input-append span3">
<input autocomplete="off" class="localename" id="appendedInput" name="name" placeholder="Name of the location" type="text" value="">
<span class="add-on"><input id="post_locations_attributes_0__destroy" name="post[locations_attributes][0][_destroy]" type="hidden" value="false"><i class="icon-trash"></i></span> </div>
<div class="latlong offset3 span4"> <p class="help-block">Enter the name of the town or city visited in this blog entry.</p>
</div>
<input class="LegNm" id="post_locations_attributes_0_name" name="post[locations_attributes][0][name]" type="hidden" value="Dresden">
<input class="long" id="post_locations_attributes_0_longitude" name="post[locations_attributes][0][longitude]" type="hidden" value="13.7372621">
<input class="lat" id="post_locations_attributes_0_latitude" name="post[locations_attributes][0][latitude]" type="hidden" value="51.0504088">
</div>
</div>
EDIT2:
post.rb
class Post < ActiveRecord::Base
require "acts-as-taggable-on"
require "kaminari"
acts_as_taggable
self.table_name = "blog_posts"
self.paginates_per Blogit.configuration.posts_per_page
# ==============
# = Attributes =
# ==============
attr_accessible :title, :body, :tag_list, :blogger_id, :coverphoto, :locations_attributes
# ===============
# = Photo Model =
# ===============
has_attached_file :coverphoto,
:styles => {
:coverbar => "600x300>", :medium => "250x250^" , :thumb => "100x100^"},
#:source_file_options => {:all => '-rotate "-90>"'},
:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml",
:bucket => "backpackbug",
:path => "/:style/:id/:filename"
# ===============
# = Validations =
# ===============
validates :title, presence: true, length: { minimum: 6, maximum: 66 }
validates :body, presence: true, length: { minimum: 10 }
validates :blogger_id, presence: true
# =================
# = Associations =
# =================
belongs_to :blogger, :polymorphic => true
has_many :location_post
has_many :locations, :through => :location_post
belongs_to :profile
accepts_nested_attributes_for :locations, :allow_destroy => true, :reject_if => proc { |attributes| attributes['name'].blank? }
end end
This was solved with a combination of this and another answer, found here:
How can I fix this create function?
A short term fix is to add attr_accessible :_destroy and attr_accessor :_destroy.
Thanks both!
Add :_destroy to your attr_accessible list
attr_accessible ...., :_destroy
you should write to post.rb,
attr_accessible: locations_atributes
and
accepts_nested_attributes_for :locations, :allow_destroy => true
as you are updating the location object via post object.
replace f.hidden_field it must stay thus(line 2)
module ApplicationHelper
def link_to_remove_fields(name, f)
text_field_tag(:_destroy) + link_to_function(name, "remove_fields(this)")
end

Connection between three controllers rails

I have three controllers - users, stories, categories. I want when I am logged in as a administrator to create categories and then to write stories for every category. I did part of the task but in DB in table Stories category_id is empty and I cannot understand how to fix it. Here is part of my code:
stories/new.html.erb:
<%= form_for(#story) do |f| %>
<div class="field">
<%= f.label :title %><br />
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :category %><br />
<%= f.collection_select :category_id, #categories, :id, :title %>
</div>
<div class="field">
<%= f.label :content %><br />
<%= f.text_area :content %>
</div>
<div class="actions">
<%= f.submit "Add" %>
</div>
<% end %>
stories_controller.rb:
class StoriesController < ApplicationController
before_filter :admin_user
def new
#story = Story.new
#categories = Category.all
#title = "Add news"
end
def create
#categories = Category.all
#story = current_user.stories.build(params[:story])
if #story.save
flash[:success] = "Successfullly added news"
redirect_to #story
else
#title = "Add news"
render 'new'
end
end
private
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
story.rb:
class Story < ActiveRecord::Base
attr_accessible :title, :content
belongs_to :user
belongs_to :category
validates :title, :presence => true
validates :content, :presence => true
validates :user_id, :presence => true
default_scope :order => 'stories.created_at DESC'
end
category.rb:
class Category < ActiveRecord::Base
attr_accessible :title
has_many :stories
validates :title, :presence => true,
:length => { :within => 6..40 }
end
user.rb:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
has_many :stories
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => true
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
private
def encrypt_password
self.salt = make_salt unless has_password?(password)
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
You need add attr_accessible :category_id to your story model
Its preventing mass assignment in your controllers create method. Alternatively you could pull out the category_id from your params hash and assign it on a separate line.

Resources