ForbiddenAttributesError error when building from a has_many relationship - ruby-on-rails

New updates
Moved params permit responsability from model to controller and used comment_attributes instead of comments as #vinodadhikary pointed me
Using better_errors REPL, I traced the problem down to sanitize_for_mass_assignment method. When doing attributes.permitted? it returns false. But doing attributes.permit(:article_id, :name, :email, :body) returns me exactly que entry parameters!:
>> attributes
=> {"name"=>"Commenter", "email"=>"commenter#mail.com", "body"=>"Here is the comment >> body!! :D"}
>> attributes.permit(:article_id, :name, :email, :body)
=> {"name"=>"Commenter", "email"=>"commenter#mail.com", "body"=>"Here is the comment body!! :D"}
>> attributes.permitted?
=> false
Context and code
Trying to get in touch with Rails 4, I encountered a problem with (I think) strong parameters use.
I have an Article class which can have many Comments. When creating a new comment doing:
#comment = #article.comments.build(params[:comment])
I get the following error (pointing this line):
ActiveModel::ForbiddenAttributesError at /articles/1/comments
The models are the following:
class Article < ActiveRecord::Base
validates_presence_of :title, :content
validates_uniqueness_of :title
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments
end
Comments:
class Comment < ActiveRecord::Base
belongs_to :article
validates_presence_of :article_id, :author, :body, :content
end
Article controller have this in the private section:
def article_params
params.require(:article).permit(:title, :content, comments_attributes: [:article_id, :name, :email, :body])
end
Comments controller code is:
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.build(params[:comment]) # <--- It fails here
respond_to do |format|
if #comment.save
format.html { redirect_to #comment, notice: 'Comment was successfully created.' }
format.json { render action: 'show', status: :created, location: #comment }
else
format.html { render action: 'new' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end

The methods article_params and comment_params that you have in the models belong in their respective controllers not in models. The idea is to filter the parameters passed to the model in the controller rather than in the model. Take a read on http://edgeapi.rubyonrails.org/classes/ActionController/StrongParameters.html, on how to allow attributes for nested attributes.
You models should be as follows:
# Articles.rb
class Article < ActiveRecord::Base
validates_presence_of :title, :content
validates_uniqueness_of :title
has_many :comments, :dependent => :destroy
accepts_nested_attributes_for :comments
end
# Comment.rb
class Comment < ActiveRecord::Base
belongs_to :article
validates_presence_of :article_id, :author, :body, :content
end
Then move the strong parameters to Articles Controller as follows:
#ArticlesController.rb
def create
#article = Article.find(params[:article_id])
#comment = #article.comments.build(params[:comment])
respond_to do |format|
if #comment.save
format.html { redirect_to #comment, notice: 'Comment was successfully created.' }
format.json { render action: 'show', status: :created, location: #comment }
else
format.html { render action: 'new' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
private
def article_params
params.require(:article).permit(:title, :content, comments_attributes: [:article_id, :author, :email, :body, :content])
end

permit params method name should be same as model/controller
e.g if model name is "recent_post" then permit method name should be
def recent_post_params
..............
end

Related

Rails create multiple records from comma delimited form param

I'm having an issue creating multiple option_values from a form param field called "name" that has a value that looks like this: a1,a2,b2,c4. What I would like to do is create an option_value for each one of those entries but I'm not sure how to do it. I know that I need to split the value but I'm just not sure where to do that exactly.
Controller:
class Admin::OptionValuesController < Admin::ApplicationController
before_action :set_option_value, only: [:show, :edit, :update, :destroy]
# GET /option_values
# GET /option_values.json
def index
#option_values = OptionValue.all
end
# GET /option_values/1
# GET /option_values/1.json
def show
end
# GET /option_values/new
def new
#option_value = OptionValue.new
end
# GET /option_values/1/edit
def edit
end
# POST /option_values
# POST /option_values.json
def create
#option_value = OptionValue.new(option_value_params)
respond_to do |format|
if #option_value.save
format.html { redirect_to #option_value, notice: 'Option value was successfully created.' }
format.json { render :show, status: :created, location: #option_value }
else
format.html { render :new }
format.json { render json: #option_value.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /option_values/1
# PATCH/PUT /option_values/1.json
def update
respond_to do |format|
if #option_value.update(option_value_params)
format.html { redirect_to #option_value, notice: 'Option value was successfully updated.' }
format.json { render :show, status: :ok, location: #option_value }
else
format.html { render :edit }
format.json { render json: #option_value.errors, status: :unprocessable_entity }
end
end
end
# DELETE /option_values/1
# DELETE /option_values/1.json
def destroy
#option_value.destroy
respond_to do |format|
format.html { redirect_to option_values_url, notice: 'Option value was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_option_value
#option_value = OptionValue.find(params[:id])
end
# Only allow a list of trusted parameters through.
def option_value_params
params.require(:option_value).permit(:option_type_id, :name)
end
end
Form. Within the main product form this allows me to create an option_type and also at that time create multiple option_values which belong to the option_type: This is what I am doing.
<div class="grid md:grid-cols-1 row-gap-6 col-gap-4 lg:grid-cols-3 mb-4">
<%= form.fields_for :option_types, OptionType.new do |options| %>
<div>
<%= options.label "Option Type Name", class: "text-gray-700" %>
<%= options.text_field :name, class: 'w-full mt-2 px-4 py-2 block rounded bg-gray-200 text-gray-800 border border-gray-300 focus:outline-none focus:bg-white' %>
</div>
<div data-controller="nested-form">
<%= options.fields_for :option_values, OptionValue.new do |ov| %>
<%= render "admin/option_types/option_values_fields", form: ov %>
<% end %>
</div>
<% end %>
</div>
Models:
product:
class Product < ApplicationRecord
has_many_attached :images, :dependent => :delete_all
has_one_attached :main_image, :dependent => :delete_all
has_many :product_option_types, dependent: :destroy, inverse_of: :product
has_many :option_types
has_many :option_values
accepts_nested_attributes_for :option_types
accepts_nested_attributes_for :option_values
has_many :variants, inverse_of: :product, dependent: :destroy
end
Option_type
class OptionType < ApplicationRecord
belongs_to :product
has_many :option_values, dependent: :destroy
accepts_nested_attributes_for :option_values, reject_if: :all_blank, allow_destroy: true
has_many :product_option_types, dependent: :destroy
end
option_value
class OptionValue < ApplicationRecord
belongs_to :option_type
has_many :option_value_variants, dependent: :destroy
has_many :variants, through: :option_value_variants
validates_presence_of :name
validates_uniqueness_of :name, scope: :option_type_id, case_sensitive: false
def create_from_csv(comma_separated_string)
comma_separated_string.split(',').map do |val|
create(name: val)
end
end
end
You can create a method in your OptionType called create_with_values
In it you'd do something like
def self.create_with_values(type, names)
type_instance = create(name: type)
names.split(',').map do |val|
type_instance.option_values.create(name: val)
end
end
Instead of your OptionValuesController, your OptionTypesController's create method should be
def create
OptionType.create_with_values(type: params[:type], values: params[:name])
redirect_to appropriate_path
end
A more railsy way to do this would be with nested attributes but you will have to setup your create_with_values method to transform the incoming comma separated strings into something that the nested attributes can accept.
Look at https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html for more information

Rails form params not being saved

I'm very new to Ruby on Rails and I'm struggling with my scaffolded controller.
I made a nested resource where my comments are placed in posts.
class Post < ActiveRecord::Base
validates :name, :presence => true
validates :title, :presence => true, :length => { :minimum => 5 }
has_many :comments
end
class Comment < ActiveRecord::Base
validates :commenter, :presence => true
validates :body, :presence => true
belongs_to :post
end
A simplified version of the controller is
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
# omited new, index, show...
# POST /comments
# POST /comments.json
def create
post = Post.find(params[:post_id])
#comment = post.comments.create(params[:comment].permit(:name, :title, :context))
respond_to do |format|
if #comment.save
format.html { redirect_to([#comment.post, #comment], notice: 'Comment was successfully created.') }
format.json { render action: 'show', status: :created, location: #comment }
else
format.html { render action: 'new' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /comments/1
# PATCH/PUT /comments/1.json
def update
post = Post.find(params[:post_id])
#comment = post.comments.find(params[:comment])
respond_to do |format|
if #comment.update(comment_params)
format.html { redirect_to #comment, notice: 'Comment was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #comment.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comment
#comment = Comment.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comment_params
params.require(:comment).permit(:commenter, :body, :post)
end
end
When i filled in the form, i get this exception:
2 errors prohibited this comment from being saved:
Commenter can't be blank
Body can't be blank
I tried this guide but i think it isn't 100% compatible with Rails 4.
You are reading from params attributes for a Post (:name, :title, :context), but you need to read Comments attributes (:commenter, :body)
replace this:
#comment = post.comments.create(params[:comment].permit(:name, :title, :context))
with
#comment = post.comments.create(params[:comment].permit(:commenter, :body))
or, much better, with:
#comment = post.comments.create(comment_params)
It appears that you are explicitly permitting different set of attributes in your create action.
You should update your create action to use comment_params like you've done in update action. The reason being your Comment is definitely expecting Commenter and Body and not :name, :title, :context which you've permitted in your create action.
Update controller's create action as:
# POST /comments
# POST /comments.json
def create
...
#comment = post.comments.create(comment_params)
...
end

Rails 3.2 Associations and :include

I have some troubles to make an association between 2 tables.
I have Users who can write Posts
Here is my migration file :
class LinkUsersAndPosts < ActiveRecord::Migration
def change
add_column :posts, :user_id, :integer
add_index :posts, :user_id
end
end
My models :
class Post < ActiveRecord::Base
attr_accessible :content, :title
belongs_to :user
end
class User < ActiveRecord::Base
attr_accessible :email, :login, :password, :password_confirmation, :rights
has_many :posts
end
My controller :
class PostsController < ApplicationController
respond_to :json
def index
#posts = Post.includes(:user).all
respond_with #posts
end
def create
#post = Post.new(params[:post])
#post.user = current_user
if #post.save
render json: #post, status: :created, location: #post
else
render json: #post.errors, status: :unprocessable_entity
end
end
end
The posts is correctly created, the user_id is set all is fine.
The problem is when i want to retrieve the list of posts including user, the list of posts is retrieve, but i havn't any data on the user except his id.
Response :
[
{
"content":"jksdjd",
"created_at":"2013-08-31T09:03:01Z",
"id":11,"title":"kdjs",
"updated_at":"2013-08-31T09:03:01Z",
"user_id":4
},
{
"content":"tez2",
"created_at":"2013-08-31T09:16:45Z",
"id":12,
"title":"test2",
"updated_at":"2013-08-31T09:16:45Z",
"user_id":4
}
]
By default a JSON response won't return any associated models. You need to specify the other models you want returned. So in your case, you can do this:
render json: #post => #post.to_json(:include => :user), status: :created, location: #post
Also see:
http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
Rails Object Relationships and JSON Rendering

Rails behaving strange

I have rails version 3.2.13 and ruby version 1.9.3.
I have caught into very strange and interesting situation.
In my application have a model 'Product' with custom validator.
product.rb
class Product < ActiveRecord::Base
attr_accessible :description, :name, :price, :short_description, :user_id
validates :name, :short_description, presence: true
validates :price, :numericality => {:greater_than_or_equal_to => 0}
validate :uniq_name
belongs_to :user
belongs_to :original, foreign_key: :copied_from_id, class_name: 'Product'
has_many :clones, foreign_key: :copied_from_id, class_name: 'Product', dependent: :nullify
def clone?
self.original ? true : false
end
private
#Custom validator
def uniq_name
return if clone?
user_product = self.user.products.unlocked.where(:name => self.name).first
errors[:name] << "has already been taken" if user_product && !user_product.id.eql?(self.id)
end
end
In products controller's create action when I am trying to create new product
def create
#product = current_user.products.new(params[:product])
respond_to do |format|
if #product.save
format.html { redirect_to #product, notice: 'Product was successfully created.' }
format.json { render json: #product, status: :created, location: #product }
else
#product.errors[:image] = "Invalid file extension" if #product.errors[:image_content_type].present?
format.html { render action: "new" }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
Custom validator is being called when this line executed #product = current_user.products.new(params[:product]) and line # 2 of custom validator giving me error
undefined method `products' for nil:NilClass
I have inspected product object in custom validator but user_id is nil.
Why user_id is not being autoassigned?
Your help will be appreciated :)
So ... bypassing your question. Why aren't you just validating the uniqueness of name?
validates_uniqueness_of :name, :unless => :clone?
try to change .new to .build
#product = current_user.products.build(params[:product])
and be sure that you have relation in your User model
Class User < ActiveRecord::Base
has_many :products

Rails: am I nesting incorrectly?

Hi I'm currently working on my first project, and am trying to build the functionality first before doing the login/sessions. I'm trying to create a picture album website, where users have many albums (that contain many pictures), and album access is shared among friends. However, I'm noticing that after my albums#create
http://localhost:3000/users/18/albums/new (no problem here)
I am redirected to albums#show:
http://localhost:3000/albums/20 (problem!!)
shouldn't there be a user_id in the URL as well?? Or does it not have a user_id attached to the URL because it belongs to multiple users? Here are my routes:
Pholder::Application.routes.draw do
resources :users do
resources :albums
end
resources :albums do
resources :pictures
end
root :to => "users#index"
Here are my models in case:
user model
class User < ActiveRecord::Base
has_secure_password
attr_accessible :email, :name, :password, :password_confirmation
validates_presence_of :password, :on => :create
validates_format_of :name, :with => /[A-Za-z]+/, :on => :create
validates_format_of :email, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
validates_length_of :password, :minimum => 5, :on => :create
has_many :user_albums
has_many :albums, :through => :user_albums
accepts_nested_attributes_for :albums
end
album model
class Album < ActiveRecord::Base
attr_accessible :avatar, :name, :description
has_many :user_albums
has_many :users, :through => :user_albums
has_many :photos
end
photo album
class Photo < ActiveRecord::Base
belongs_to :album
end
albums controller
class AlbumsController < ApplicationController
def index
#albums = Albums.all
respond_to do |format|
format.html
format.json { render json: #albums }
end
end
def show
#albums = Album.all
#album = Album.find(params[:id])
#photo = Photo.new
end
def update
end
def edit
end
def create
# #user = User.find(params[:albums][:user_id])
# #users = User.all
#album = Album.new(params[:album])
# #album.user_id = #user.id
respond_to do |format|
if #album.save
format.html { redirect_to #album, notice: 'Album was successfully created.' }
format.json { render json: #album, status: :created, location: #album}
else
format.html { render action: "new" }
format.json { render json: #album.errors, status: :unprocessable_entity }
end
end
end
def new
#user = User.find(params[:user_id])
#album = Album.new
end
def destroy
end
end
Let me know if you need any other files.
The line redirect_to #album makes you redirect to the show action of the #album in question.
Changing this piece of code to something like redirect_to users_path will make the app redirect to the index action of users_controller and so on.
It depends on whatever behavior you want after the save.
Reading this should be helpful too: http://guides.rubyonrails.org/routing.html

Resources