Mass Assignment in nested form - ruby-on-rails

I am following railscasts 196 episode and i try exactly what he has, but i am having the following error
ActiveModel::MassAssignmentSecurity::Error in SurveysController#create
Can't mass-assign protected attributes: questions_attributes
Rails.root: /home/jean/rail/surveysays
Here my code so far
Survey Form
<%= form_for(#survey) do |f| %>
<% if #survey.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#survey.errors.count, "error") %> prohibited this survey from being saved:</h2>
<ul>
<% #survey.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<%= f.fields_for :questions do |bf|%>
<%= bf.label :content, "Question" %><br />
<%= bf.text_area :content, :rows=> 3 %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Survey Relationship
class Survey < ActiveRecord::Base
has_many :questions, :dependent => :destroy
accepts_nested_attributes_for :questions
attr_accessible :name
end
Question Relationship
class Question < ActiveRecord::Base
belongs_to :survey
attr_accessible :content, :survey_id
end
New Controller
def new
#survey = Survey.new
3.times {#survey.questions.build }
respond_to do |format|
format.html # new.html.erb
format.json { render json: #survey }
end
end

change your class to this
class Survey < ActiveRecord::Base
has_many :questions, :dependent => :destroy
accepts_nested_attributes_for :questions
attr_accessible :name, :questions_attributes
end
look for topic "Using with attr_accessible" here for more information

Related

Rails 5 - has_many through: and nested fields_for in forms

I am new to RoR (using rails 5) and have problems with a has_many through: association.
I want to create Categories with different labels for different Languages.
Here is my model:
class Language < ApplicationRecord
has_many :category_infos
has_many :categories, through: :category_infos
end
class Category < ApplicationRecord
has_many :category_infos
has_many :languages, through: :category_infos
accepts_nested_attributes_for :category_infos
end
class CategoryInfo < ApplicationRecord
belongs_to :language
belongs_to :category
accepts_nested_attributes_for :language
end
The controller:
class CategoriesController < ApplicationController
def new
#category = Category.new
#languages = Language.all
#languages.each do |language|
#category.category_infos.new(language:language)
end
end
def create
#category = Category.new(category_params)
if #category.save
redirect_to #category
else
render 'new'
end
end
private
def category_params
params.require(:category).permit(:name, category_infos_attributes:[:label, language_attributes: [:id, :language]])
end
end
The form:
<%= form_with model: #category, local: true do |form| %>
<% if #category.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#category.errors.count, "error") %>:
</h2>
<ul>
<% #category.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<br/>
<% end %>
<p>
<%= form.label :name %>
<%= form.text_field :name %>
</p>
<p>
Labels:
</p>
<table>
<% #category.category_infos.each do |category_info| %>
<tr>
<td>
<%= category_info.language.name %>
</td>
<td>
<%= form.fields_for :category_infos, category_info do |category_info_form| %>
<%= category_info_form.fields_for :language, category_info.language do |language_form| %>
<%= language_form.hidden_field :id, value: category_info.language.id %>
<%= language_form.hidden_field :name, value: category_info.language.name %>
<% end %>
<%= category_info_form.text_field :label %>
<% end %>
</td>
</tr>
<% end %>
</table>
<p>
<%= form.submit %>
</p>
<% end %>
When I create a new category, I get this error :
Couldn't find Language with ID=1 for CategoryInfo with ID=
on Line :
#category = Category.new(category_params)
However I already have registered several languages in database (1 = English, 2 = French etc...)
How do I need to write the form so that I can create a Category and its CategoryInfos in English, French etc... at the same time?
Thanks in advance for your answers
You're making a classic newbie misstake and using fields_for when you just want to create an association by passing an id.
<%= form_with model: #category, local: true do |f| %>
# ...
<%= f.fields_for :category_infos do |cif| %>
<%= cif.collection_select(:language_id, Language.all, :name, :id) %>
<%= cif.text_field :label %>
<% end %>
<% end %>
While you could also pass the attributes to let a users create languages at the same time its very much an anti-pattern as it adds a crazy amount of responsibilities to a single controller. It will also create an authorization problem if the user is allowed to create categories but not languages.
Use ajax to send requests to a seperate languages controller instead if you need the feature.

Rails 5 Nested form, parent doesn't saved

I have hard time with this one. I have an album Model and a Track Model, Track belongs to album and album has many tracks. When I try to create an album with a track(the nested form below) it fails to save and renser the 'new' form with this message:
1 error prohibited this album from being saved: Tracks album must
exist
Albums Controller
class Admin::AlbumsController < AdminController
def new
#album = Album.new
#album.tracks.build
end
def create
#album = Album.new(album_params)
if #album.save
redirect_to admin_album_path(#album)
else
render 'new'
end
end
private
def album_params
params.require(:album).permit(:title, :kind, :release, tracks_attributes: [:id, :title, :time, :order])
end
end
Album Model
class Album < ApplicationRecord
has_many :tracks
accepts_nested_attributes_for :tracks
end
Track Model
class Track < ApplicationRecord
belongs_to :album
end
Form
<%= form_for [:admin, #album] do |f| %>
<% if #album.errors.any? %>
<div id="error_explanation">
<h2>
<%= pluralize(#album.errors.count, "error") %> prohibited this album from being saved:
</h2>
<ul>
<% #album.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<h5>Album</h5>
<p>
<%= f.label :title %><br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :kind %><br>
<%= f.text_field :kind %>
</p>
<p>
<%= f.label :release %><br>
<%= f.text_field :release %>
</p>
<br><br><br>
<h5>Track</h5>
<%= f.fields_for :tracks do |tracks_form| %>
<p>
<%= tracks_form.label :title %>
<%= tracks_form.text_field :title %>
</p>
<p>
<%= tracks_form.label :time %>
<%= tracks_form.text_field :time %>
</p>
<p>
<%= tracks_form.label :order %>
<%= tracks_form.text_field :order %>
</p>
<% end %>
<%= f.submit class: "waves-effect waves-light btn" %>
<% end %>
I think album doesn’t saved so track can’t get the album id.
Could you help me to figure out what really happens?
When Rails attempts to save the track, the album has not yet been committed into the database. In order for this to to work you need to have the
:inverse_of
Try this
class Album < ApplicationRecord
has_many :tracks, inverse_of: :album
accepts_nested_attributes_for :tracks
end
class Track < ApplicationRecord
belongs_to :album, inverse_of: :tracks
validates_presence_of :album
end

Saving :content of first post upon topic creation, with Post and Topic models

The new topic form has fields for title, description, and first post content.
Upon submission, a topic with values for title, description, user id, and forum id should be created, along with a post with values for content, user id, and topic id. However, the post's :content is not getting saved to the table, though user id and topic id are.
views/topics/_form.html.erb
<%= form_for(#topic) do |f| %>
<% if #topic.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#topic.errors.count, "error") %> prohibited this topic from being saved:</h2>
<ul>
<% #topic.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<% if params[:forum] %><input type="hidden" id="topic_forum_id" name="topic[forum_id]" value="<%= params[:forum] %>" /><% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="field">
<textarea name="post[content]" class="form-control" cols="80" rows="20"><%= #post.content %></textarea>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
controllers/topics_controller.rb
def new
#topic = Topic.new
#post = Post.new
end
def create
user_id = current_user.id || 1 # temporary assignment until guest account generated
#topic = Topic.new(title: params[:topic][:title], description: params[:topic][:description], forum_id: params[:topic][:forum_id], user_id: user_id)
if #topic.save
#post = Post.new(content: params[:post][:content], topic_id: #topic.id, user_id: user_id)
if #post.save
flash[:notice] = "Successfully created topic."
redirect_to "/topics/#{#topic.id}"
else
render action: 'new'
end
else
render action: 'new
end
end
models/post.rb
class Post < ActiveRecord::Base
belongs_to :topic
belongs_to :user
has_many :replies, :dependent => :nullify
validates :content, presence: true
attr_accessor :content
end
models/topic.rb
class Topic < ActiveRecord::Base
belongs_to :forum
belongs_to :user
has_many :posts, :dependent => :destroy
validates :title, presence: true, length: { maximum: 255 }
validates :user_id, presence: true
validates :forum_id, presence: true
end
I would implement the concept of accepsts_nested_attributes like below to handle your situation.
#topic.rb
class Topic < ActiveRecord::Base
belongs_to :forum
belongs_to :user
has_many :posts, :dependent => :destroy
accepts_nested_attributes_for :posts
validates :title, presence: true, length: { maximum: 255 }
validates :user_id, presence: true
validates :forum_id, presence: true
end
#topics_controller.rb
def new
#topic = Topic.new
#post = #topic.posts.build
end
def create
user_id = current_user.id || 1 # temporary assignment until guest account generated
#topic = Topic.new(params[:topic])
if #topic.save
#post = Post.new(params[:post])
#post.topic_id = #topic.id
#post.user_id = user_id
if #post.save
flash[:notice] = "Successfully created topic."
redirect_to #topic
else
render action: 'new'
end
else
render action: 'new'
end
end
#form
<%= form_for(#topic) do |f| %>
<% if #topic.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#topic.errors.count, "error") %> prohibited this topic from being saved:</h2>
<ul>
<% #topic.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<% if params[:forum] %><input type="hidden" id="topic_forum_id" name="topic[forum_id]" value="<%= params[:forum] %>" /><% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<%= f.fields_for #post do |p| %>
<div class="field">
<%= p.text_area :content, cols: 80, rows: 20, class: "form-control" %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>

Rails 4 fields_for not displaying field

I have two models that I would like to create with one form. I tried following this railscasts tutorial, but I just can't get the nested fields to display on the form. How can I make these nested fields appear?
Models
class Poll < ActiveRecord::Base
has_many :poll_answers, :dependent => :destroy
accepts_nested_attributes_for :poll_answers, allow_destroy: true
end
class PollAnswer < ActiveRecord::Base
belongs_to :poll
end
Controller
class PollsController < ApplicationController
def new
#poll = Poll.new
2.times { #poll.poll_answers.build }
end
private
def poll_params
params.require(:poll).permit([
:question,
poll_answers_attributes: [:answer]
])
end
end
View
<%= form_for(#poll) do |f| %>
<div class="field">
<%= f.label :question %><br>
<%= f.text_field :question %>
</div>
<% f.fields_for :poll_answers do |pa| %>
<p>Hello
<%= pa.text_field :answer %>
</p>
<% end %>
<%= debug #poll.poll_answers %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
You missed an =
<%= f.fields_for :poll_answers do |pa| %>
<p>Hello
<%= pa.text_field :answer %>
</p>
<% end %>

How to save tag with many to many relationship in ActiveRecord

I'm trying to create a video with many-to-many tags. So, each tag would be each row in Tag table. I'm not sure I have to do it myself or Rails has some magic that could do that?
Here's my model.
class Video < ActiveRecord::Base
belongs_to :user
has_many :video_tags
has_many :tags, through: :video_tags
end
class Tag < ActiveRecord::Base
end
class VideoTag < ActiveRecord::Base
belongs_to :video
belongs_to :tag
end
Here's my form
<%= form_for(#video, html: { class: "directUpload" }, multipart: true) do |f| %>
<% if #video.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#video.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% #video.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :title %><br>
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :path %><br>
<%= f.file_field :path%>
</div>
<div class="field">
<%= f.label :tags %><br>
<%= f.text_field :tags %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
And here's my Controller
class VideosController < ApplicationController
def show
#videos = Video.where(user_id: params[:user_id])
end
def new
#video = Video.new
#s3_direct_post = S3_BUCKET.presigned_post(key: "uploads/#{SecureRandom.uuid}/${filename}", success_action_status: 201, acl: :public_read)
end
def create
#video = Video.new(video_params)
#video.user_id = 1
if #video.save
redirect_to #video
end
end
private
# Use callbacks to share common setup or constraints between actions.
# Never trust parameters from the scary internet, only allow the white list through.
def video_params
params.require(:video).permit(:title, :path, :tags)
end
end
But then I got this error which I thought I must have missed something. I just want to have tag separated by space.
Started POST "/videos" for ::1 at 2015-04-07 00:21:11 -0400
Processing by VideosController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"authenticity_token",
"video"=>{"title"=>"asdfasdf",
"path"=>"//s3.amazonaws.com/uploads/token/57203191c21df0cacf98f3fa9340f4.mp4",
"tags"=>"test "}, "commit"=>"Create Video"}
Completed 500 Internal Server Error in 3ms
NoMethodError (undefined method `each' for "test ":String):
app/controllers/videos_controller.rb:14:in `create'
My following answer will help you
https://stackoverflow.com/questions/22611372/rails-4-paperclip-and-polymorphic-association
you can use accept_nested_attributes_for and,
<%= f.fields_for :tags, #_your_tags do |t| %>
instead of
<div class="field">
<%= f.label :tags %><br>
<%= f.text_field :tags %>
</div>
Your association would be like this:
class Video < ActiveRecord::Base
belongs_to :user
has_many :video_tags
has_many :tags, through: :video_tags
end
class VideoTag < ActiveRecord::Base
belongs_to :video
belongs_to :tag
end
class Tag < ActiveRecord::Base
has_many :video_tags
has_many :videos, through :video_tags
end
I found this wiki pretty helpful. And yes rails has a special way to handle this. These are called nested models or you can also user nested_form gem

Resources