Rails 6.1.3.2 Unpermitted parameter and User must exist - ruby-on-rails

Few years ago I develop aps in Rails 4 and now many things change.
I user Shire GEM in this example to upload photos:
Ok, my models:
Photo model:
class Photo < ApplicationRecord
include ImageUploader::Attachment(:image)
belongs_to :user
end
User model ( i put only few lines):
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one :profile, :dependent => :destroy
has_many :photos, :dependent => :destroy
end
PhotosController:
class PhotosController < ApplicationController
before_action :set_photo, only: %i[ show edit update destroy ]
before_action :authenticate_user!
def create
#photo = Photo.new(photo_params)
respond_to do |format|
if #photo.save
format.html { redirect_to #photo, notice: "Photo was successfully created." }
format.json { render :show, status: :created, location: #photo }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #photo.errors, status: :unprocessable_entity }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_photo
#photo = Photo.find(params[:id])
end
# Only allow a list of trusted parameters through.
def photo_params
params.require(:photo).permit(:title, :image_data, :image, :user_id)
end
end
UsersController:
class UsersController < ApplicationController
private
# Only allow a list of trusted parameters through.
def set_user
#user = User.find(params[:id])
end
def user_params
params.require(:user).permit(:id)
end
end
And now last thing, form for add photos in view:
<%= form_with(model: photo) do |form| %>
<% if photo.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(photo.errors.count, "error") %> prohibited this photo from being saved:</h2>
<ul>
<% photo.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :title %>
<%= form.text_field :title %>
</div>
<div class="field">
<%= form.label :image %>
<%= form.file_field :image %>
</div>
<%= current_user.id %> // SHOWS USER ID
//// THIS GENERATE ERROR ////
<%#= form.hidden_field :user_id => current_user.id %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
And where I have problem. When I to it in that way I see in logs unpermited parameters :user_id.
But when I submit form I see error "User must exist".
I found solution and change in PhotosController this line:
def create
#photo = Photo.new(photo_params)
to:
def create
#photo = current_user.photos.new(photo_params)
and it start working. Photo have user_id in table. But I want to know why I can't add user_id for photos in form in view like:
<%= form.hidden_field :user_id => current_user.id %>

Never pass the user id as plaintext through the parameters. Get it from the session instead. Its trivial for any malicous user to use the web inspector and simply fill in the hidden input and then upload a unseemly picture as ANY user.
The session cookie is encrypted and much more difficult to tamper with.
class PhotosController < ApplicationController
before_action :authenticate_user!
def create
# build the record off the current_user
#photo = current_user.photos.new(photo_params)
respond_to do |format|
if #photo.save
format.html { redirect_to #photo, notice: "Photo was successfully created." }
format.json { render :show, status: :created, location: #photo }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #photo.errors, status: :unprocessable_entity }
end
end
end
private
# Only allow a list of trusted parameters through.
def photo_params
params.require(:photo).permit(:title, :image_data, :image)
end
end
But I want to know why I can't add user_id for photos in form in view like:
<%= form.hidden_field :user_id => current_user.id %>
Because you're calling the method with the wrong arguments. The signature is hidden_field(object_name, method, options = {}) but as already stated its a bad idea.

Related

Rails .build is not building has_many :options

I have a Poll app with 3 models.
Poll.rb
class poll < ApplicationRecord
validates_presence_of :user, :title
belongs_to :user
has_many :questions, dependent: :destroy
has_many :options, through: :questions
accepts_nested_attributes_for :questions
end
Question.rb
class Question < ApplicationRecord
validates_presence_of :poll_id, :question_id, :title
belongs_to :poll
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['title'].blank? }
end
Option.rb
class Option < ApplicationRecord
validates_presence_of :question_id, :title
belongs_to :question
belongs_to :poll
end
I want the question form to have a field for adding options so I've added this to the question _form.
<%= form.fields_for :option do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
I can now see an option block which is good. Although I wish to have 3 possbile options so in the questions_controller.rb I've added the following:
def new
#question = #poll.questions.build
3.times { #question.options.build } # 3 different options
end
Despite this I'm only seeing one option block instead of the 3. Why is this the case and how do i fix? Additionally I'm not seeing new entries into the options postgresql table.
Full questions_controller.rb
class QuestionsController < ApplicationController
before_action :set_question, only: %i[ show edit update destroy ]
before_action :set_poll
# GET /questions or /questions.json
def index
#questions = Question.all
end
# GET /questions/1 or /questions/1.json
def show
end
# GET /questions/new
def new
# #question = Question.new
#question = #poll.questions.build
3.times { #question.options.build } # 5 different options
end
# GET /questions/1/edit
def edit
end
# POST /questions or /questions.json
def create
#question = Question.new(question_params)
respond_to do |format|
if #question.save
format.html { redirect_to polls_question_url(#question), notice: "Question was successfully created." }
format.json { render :show, status: :created, location: #question }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1 or /questions/1.json
def update
respond_to do |format|
if #question.update(question_params)
format.html { redirect_to polls_question_url(#question), notice: "Question was successfully updated." }
format.json { render :show, status: :ok, location: #question }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1 or /questions/1.json
def destroy
poll_id = Question.find_by(params[:poll_id])
session[:return_to] ||= request.referer
#question.destroy
respond_to do |format|
format.html { redirect_to session.delete(:return_to), notice: "Question was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
#question = Question.find(params[:id])
end
# Only allow a list of trusted parameters through.
def question_params
params.require(:question).permit(:poll_id, :question_type, :title, :description, :randomize_selection, :voter_abstain, { option_attributes: [:question_id, :poll_id, :party_id, :title, :description] } )
end
def set_poll
#poll = poll.find_by(params[:poll_id])
end
end
routes.rb
resources :users do
resources :polls
end
resource :polls do
resources :questions
end
resource :questions do
resources :options
end
Edit:
Here is my questions form partial.
_form.html.erb
<%= form_with(model: [#Poll, question] ) do |form| %>
<% if question.errors.any? %>
<div style="color: red">
<h2><%= pluralize(question.errors.count, "error") %> prohibited this question from being saved:</h2>
<ul>
<% question.errors.each do |error| %>
<li><%= error.full_message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.hidden_field :poll_id %>
</div>
<div>
<%= form.label :question_type, style: "display: block" %>
<%= form.text_field :question_type %>
</div>
<div>
<%= form.label :title, style: "display: block" %>
<%= form.text_field :title %>
</div>
<div>
<%= form.label :description, style: "display: block" %>
<%= form.text_area :description %>
</div>
<div>
<%= form.label :randomize_selection, style: "display: block" %>
<%= form.check_box :randomize_selection %>
</div>
<div>
<%= form.label :voter_abstain, style: "display: block" %>
<%= form.check_box :voter_abstain %>
</div>
<div>
<%= form.fields_for :options do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
Here is the poll's show where I am rendering the forms.
show.html.erb
<p style="color: green"><%= notice %></p>
<p>
<strong>Poll Title:</strong>
<%= #poll.title %>
<%= render #poll %>
</p>
<div>
<%= link_to "Edit this poll", edit_user_poll_path(#poll) %> |
<%= link_to "Back to polls", user_polls_path %> |
<%= link_to "Destroy this poll", user_poll_path(#poll), method: :delete %>
</div>
<% if #poll.questions.any? %>
<hr>
<h2>Questions:</h2>
<%= render #poll.questions %>
<% end %>
<hr>
<h2>Add a new Question:</h2>
<%= render "questions/form", question: #poll.questions.build %>
The argument you pass to fields_for has to match the name of the assocation on the model:
<%= form.fields_for :options do |o| %>
<div>
<%= o.label "Option", style: "display: block" %>
<%= o.text_field :title, placeholder: "Enter Option here" %>
</div>
<% end %>
Pay very careful attention to plurization in Rails. Its a huge part of getting Convention over Configuration to work for you instead of against you.
However there are a quite a few other problems with this code.
Constants should always be CamelCase or UPPERCASE in Ruby - you need to change class poll to class Poll and fix all the references to the class. This isn't just a matter of style since the interpreter treats identifiers that start with an uppercase letter completely differently.
You're not nesting it properly. You have a nested route but you're still treating it like a non-nested resource in your controller and docstrings.
You're passing the parent id in your params whitelist. :poll_id and :question_id should not be whitelisted. Do not pass the parent id with a hidden input. The question id is assigned by Rails - you should not trust the user to pass it.
The option should not need a poll_id. Use an indirect has_one assocation to go up the tree. This could cause a edge case where a question and its options belong to different polls.
First lets fix the models:
class Poll < ApplicationRecord
# belongs_to assocations are required by default
# adding validations will just cause duplicate error messages
validates_presence_of :title
belongs_to :user
has_many :questions, dependent: :destroy
has_many :options, through: :questions
accepts_nested_attributes_for :questions
end
class Question < ApplicationRecord
validates_presence_of :title
belongs_to :poll
has_many :options
accepts_nested_attributes_for :options, reject_if: proc { |attributes| attributes['title'].blank? }
end
class Option < ApplicationRecord
validates_presence_of :title
belongs_to :question
has_one :poll, through: :question
end
Then I would recommend that you use shallow nesting
resource :polls do
resources :questions, shallow: true
end
This creates the questions member routes (show, edit, delete) without the /polls/:poll_id prefix while the collection routes (index, create, new) are nested.
And that you set controller up as:
class QuestionsController < ApplicationController
before_action :set_question, only: %i[ show edit update destroy ]
before_action :set_poll, only: %i[ new create index ]
# GET /polls/1/questions or /polls/1/questions.json
def index
#questions = #poll.questions.all
end
# GET /questions/1 or /polls/1/questions/1.json
def show
end
# GET /polls/1/questions/new
def new
# build is just an alias of new for legacy compatibility with Rails 2...
# its about time that we ditch it
#question = #poll.questions.new
3.times { #question.options.new } # 5 different options
end
# GET /questions/1/edit
def edit
end
# POST /polls/1/questions or /polls/1/questions.json
def create
#question = #poll.questions.new(question_params)
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: "Question was successfully created." }
format.json { render :show, status: :created, location: #question }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1 or /questions/1.json
def update
respond_to do |format|
if #question.update(question_params)
format.html { redirect_to #question, notice: "Question was successfully updated." }
format.json { render :show, status: :ok, location: #question }
else
format.html { render :edit, status: :unprocessable_entity }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1 or /questions/2.json
def destroy
session[:return_to] ||= request.referer
#question.destroy
respond_to do |format|
format.html { redirect_to session.delete(:return_to), notice: "Question was successfully destroyed." }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
#question = Questions.find(params[:id])
end
# Only allow a list of trusted parameters through.
def question_params
# do not write this in a single unreadable line
params.require(:question).permit(
:question_type,
:title,
:description,
:randomize_selection,
:voter_abstain,
# do not wrap hash arguments in brackets
# as it will break if/when the `permit` method is changed to use real keyword arguments
# for has_many assocations the key naming convention is also plural_attributes
options_attributes: [
:party_id,
:title,
:description
]
)
end
def set_poll
#poll = Poll.find_by(params[:poll_id])
end
end
The key difference here is that you should look up the poll by the parameter in the URL for the nested routes and create the question off the poll instance (which sets poll_id).
Added:
You're not actually using the model you initialized in your controller. If you want to render the form from a completely different action you need to initialize the instance variable there:
class PollsController < ApplicationController
def show
#question = #poll.questions.new
3.times { #question.options.new } # 5 different options ???
end
# ...
end
<%= render "questions/form", question: #question %>
And in your partial you have a sneaky little bug. Ruby is case sensitive so #poll and #Poll are actually different variables.
irb(main):049:0> #foo = "bar" => "bar"
irb(main):050:0> #Foo
=> nil
Since instance variables are auto-vivified you're just get an unexpected nil instead of an error. What you actually want is:
<%= form_with(model: [#poll, question] ) do |form| %>

Nested attributes in rails 5.0 errored while saving the record

I have User model (for devise ) and then i have member which references User and then portfolio which references member .
i have created a user while signingup .
Now i want the signed up user to update his deatails which is members and portfolio tables .
The portfolio model is :-
class Portfolio < ApplicationRecord
belongs_to :member
validates_presence_of title:
end
class Member < ApplicationRecord
belongs_to :user
has_one :portfolio
accepts_nested_attributes_for :portfolio
end
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_one :member
validates_presence_of :name
validates :email, uniqueness: true
after_create :create_member_portfolio
def create_member_portfolio
puts "Test "
end
end
in my members_controller the code i have is :-
class MembersController < ApplicationController
before_action :set_member, only: [:show, :edit, :update, :destroy]
# GET /members
# GET /members.json
def index
#members = Member.all
end
# GET /members/1
# GET /members/1.json
def show
end
# GET /members/new
def new
#member = setup_member(Member.new)
end
# GET /members/1/edit
def edit
end
# POST /members
# POST /members.json
def create
p = member_params
byebug
#member = Member.new(p)
byebug
#member.user_id = current_user.id unless current_user.nil?
respond_to do |format|
if #member.save
format.html { redirect_to #member, notice: 'Member was successfully created.' }
format.json { render :show, status: :created, location: #member }
else
format.html { render :new }
format.json { render json: #member.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /members/1
# PATCH/PUT /members/1.json
def update
respond_to do |format|
if #member.update(member_params)
format.html { redirect_to #member, notice: 'Member was successfully updated.' }
format.json { render :show, status: :ok, location: #member }
else
format.html { render :edit }
format.json { render json: #member.errors, status: :unprocessable_entity }
end
end
end
# DELETE /members/1
# DELETE /members/1.json
def destroy
#member.destroy
respond_to do |format|
format.html { redirect_to members_url, notice: 'Member was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_member
#member = Member.find(params[:id])
end
def setup_member(member)
member.portfolio ||= Portfolio.new
member
end
# Only allow a list of trusted parameters through.
def member_params
# slice(*filter.keys).each{|k,v| puts "#{k}:#{v}"}
#byebug
params.require(:member).permit(
:dob,
:email,
:phone,
portfolio_attributes: [ :title, :subtitle ]
)
end
end
And the html.erb is as given below :-
<%= form_for(#member) do |f| %>
<% if #member.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(member.errors.count, "error") %> prohibited this member from being saved:</h2>
<ul>
<% #member.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-control">
<%= f.label :dob %>
<%= f.datetime_select :dob %>
</div>
<div class="form-control">
<%= f.label :email %>
<%= f.text_field :email %>
</div>
<div class="form-control">
<%= f.label :phone %>
<%= f.text_field :phone %>
</div>
<div class="col-md-12">
<h2>Profile Details :</h2>
<div>
<%= f.fields_for :portfolio do |ff| %>
<div class="form-control">
<%= ff.label :title %>
<%= ff.text_field :title %>
</div>
<div class="form-control">
<%= ff.label :subtitle %>
<%= ff.text_field :subtitle %>
</div>
<% end %>
</div>
</div>
<div class="form-group">
<%= f.submit 'Save Member Item', class: 'btn btn-primary btn-block' %>
</div>
<% end %>
But when in the create controller method on save of member object it fails and says that Portfolio.member does not exist .
Although i an getting the portfolio_attributes as key with title and subtitle in the passed in params and those are validated too , but fails on save . Could you please let me know what did i do wrong here ?
The error i am gettng here adter member.save is called is as shown below :-
byebug) #member.errors
#<ActiveModel::Errors:0x00007fa011bceba8 #base=#<Member id: nil, fname: "asdsa", lname: "asdfsda", dob: "2020-06-16 18:59:00", email: "asddfsda#asdfdsa.com", religion_id: nil, phone: "", created_at: nil, updated_at: nil, user_id: 1>, #messages={:"portfolio.member"=>["must exist"]}, #details={:"portfolio.member"=>[{:error=>:blank}]}>
(byebug)
You might be able to isolate which field is causing the error by checking:
#member = Member.new(p)
#member.valid?
#member.errors
For more information, see the valid? and invalid? section of the Active Record Validations and Callbacks documentation.
It seems there is a bug in version rails 5.0 . and to move away from the default behavior(not allowing to save entry without the parent entry ) we'll have to add
class Portfolio < ApplicationRecord
belongs_to :member, optional: true
end
which should do the trick . To follow check this link :-
https://github.com/rails/rails/issues/18233

Rails 'required: true' does not work fully

I have a simple_form with a grouped collection select and two input fields. I have a required: true on both fields, but it still allows empty input through. The little 'required' asterisk appears next to the field name, but that's it. Is there any way I can prevent empty input from going through the form?
new.rb
<h1>New Article</h1>
<%= render 'form', article: #article %>
<%= link_to 'Back', articles_path(category_id: params[:category_id]) %>
_form.rb
<%= simple_form_for(article, html: {class: 'form-vertical'}) do |f| %>
<% if article.errors.any? %>
<div id="error_explanation">
<h4><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h4>
<ul>
<% article.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%# field being selected, parent collection, subcollection, displayed, key, value %>
<%= f.grouped_collection_select :subcategory_id, Category.all,:subcategories,:name, :id,:name, {required: true} %>
<%= f.input :title, required: true %>
<%= f.input :content, input_html: { rows: 20 }, required: true%>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
articles_controller.rb
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :edit, :update, :destroy]
# GET /articles
# GET /articles.json
def index
if params[:category_id].blank? && params[:subcategory_id].blank?
#articles = Article.all.order("created_at DESC")
elsif params[:subcategory_id].blank?
#articles = Article.where(category_id: params[:category_id])
else
#articles = Article.where(subcategory_id: params[:subcategory_id]).order("created_at DESC")
end
end
# GET /articles/1
# GET /articles/1.json
def show
end
# GET /articles/new
def new
#article = Article.new
end
# GET /articles/1/edit
def edit
end
# POST /articles
# POST /articles.json
def create
#parameters = article_params
#parameters[:category_id] = Subcategory.find(#parameters[:subcategory_id]).category_id
#article = Article.new(#parameters)
respond_to do |format|
if #article.save
format.html { redirect_to #article, notice: 'Article was successfully created.' }
format.json { render :show, status: :created, location: #article }
else
format.html { render :new }
format.json { render json: #article.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /articles/1
# PATCH/PUT /articles/1.json
def update
respond_to do |format|
if #article.update(article_params)
format.html { redirect_to #article, notice: 'Article was successfully updated.' }
format.json { render :show, status: :ok, location: #article }
else
format.html { render :edit }
format.json { render json: #article.errors, status: :unprocessable_entity }
end
end
end
# DELETE /articles/1
# DELETE /articles/1.json
def destroy
#article.destroy
respond_to do |format|
format.html { redirect_to root_path, notice: 'Article was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_article
#article = Article.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def article_params
params.require(:article).permit(:title,:content,:subcategory_id)
end
end
As per simple documentation of rails. Your model should look like this(suppose i wanted to validate presence of first_name last_name and compnay_name):
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates :company_name, presence: true
validates :first_name, presence: true
validates :last_name, presence: true
end
Now after this you are ready to go, presence: true will validate data before saving it to the database. A good practice is to add a validation to the front end too. which is add require: true to your form_for helper.
<div class="field">
<%= f.label :last_name %><br />
<%= f.text_field :last_name, autofocus: true, required: true ,autocomplete: "email", class: "border p-3 w-100 my-2" %>
</div>
You need to use validations, that logic is done inside the model.
Make sure you read and undertand the ActiveRecord Validations Guide because it will save you a lot of time.
Example taken from the guide:
class Person < ApplicationRecord
validates :name, presence: true
end
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false
Basically when the object is saved, it runs the validations and if it's not valid it will fill the errors variable with the messages.
Simple form documentation states...
By default all inputs are required. When the form object has presence
validations attached to its fields, Simple Form tells required and
optional fields apart. For performance reasons, this detection is
skipped on validations that make use of conditional options, such as
:if and :unless.
Which means that all you need to do is add into your model/article.rb file
class Article < ActiveRecord::Base
validates :title, presence: true
validates :content, presence: true

Rails 4 controller actions for fields_for nested resources

I am trying to understand Rails' field_for, specifically what should go into the controller for nested resources. My issue is that when I create a comic with comic pages through the Comic form, the page's image are not saved.
I have Users, Comics, and ComicPages. Here are the models:
class User < ActiveRecord::Base
has_many :comics
has_many :comic_pages, through: :comics
end
class Comic < ActiveRecord::Base
belongs_to :user
has_many :comic_pages, :dependent => :destroy
accepts_nested_attributes_for :comic_pages
end
class ComicPage < ActiveRecord::Base
belongs_to :comic
end
Here is the form for Comic, where I also want to add comic_pages:
<%= form_for ([#user, #comic]) do |f| %>
<%= f.text_field :title %>
<%= f.fields_for :comic_pages do |comic_page| %>
<%= comic_page.file_field :comic_page_image %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I am confused about the comics_controller (new and create actions). How can I pass comic_page params to this controller???
def new
#user = current_user
#comic = #user.comics.new
#comic.comic_pages.build
end
def create
#user = current_user
#comic = #user.comics.new(comic_params)
#comic.comic_pages.build
respond_to do |format|
if #comic.save
format.html { redirect_to #user, notice: 'Comic was successfully created.' }
format.json { render action: 'show', status: :created, location: #user }
else
format.html { render action: 'new' }
format.json { render json: #comic.errors, status: :unprocessable_entity }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_comic
#comic = Comic.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def comic_params
params.require(:comic).permit(:title, :synopsis)
end
def comic_page_params
params.require(:comic_page).permit(:comic_page_image, :comic_image_file_name)
end
Many thanks!
--- EDIT ---
After the answer for the params, I used it to create the following create action:
def create
#user = current_user
#comic = #user.comics.new(comic_params)
i = 0
until i = 1
#comic_page = #comic.comic_pages.new(comic_params[:comic_pages_attributes]["#{i}"])
#comic_page.save
i += 1
end
respond_to do |format|
if #comic.save
...
end
end
end
You need to permit those fields from comic_pages that you want to save through in the comic_params section of your controller
params.require(:comic).permit(:title, :synopsis, comic_pages_attributes: [:comic_page_image])

How to allow users to comment

So, I'd like users to be able to comment. At the present moment anyone can comment just by typing in a arbitrary name in the name field.
But I'd like to associate a comment with a user. So there will no longer be a need for a name field in the comments form as it will be the users name.
How can this be done?
I've followed Ryan Bates railscast but he never associates comments with users.
comments_controller.rb
class CommentsController < ApplicationController
before_action :set_comment, only: [:show, :edit, :update, :destroy]
def index
#comments = Comment.where("song_id IS NOT ?", nil)
end
def show
end
# GET /comments/new
def new
end
# GET /comments/1/edit
def edit
end
# POST /comments
# POST /comments.json
def create
#comment = Comment.new(comment_params)
respond_to do |format|
if #comment.save
format.html { redirect_to song_url(#comment.song_id), 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
respond_to do |format|
if #comment.update(comment_params)
format.html { redirect_to song_url(#comment.song_id), 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
# DELETE /comments/1
# DELETE /comments/1.json
def destroy
#comment.destroy
redirect_to song_url(#comment.song_id)
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(:song_id, :author_name, :site_url, :content, :user_id)
end
end
user.rb
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable,
# :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :songs
has_many :comments
acts_as_voter
end
comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :song
end
comments#form.html.erb
<%= form_for #comment do |f| %>
<% if #comment.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#comment.errors.count, "error") %> prohibited this comment from being saved:</h2>
<ul>
<% #comment.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div id="comment_form">
<div class="field">
<%= f.hidden_field :song_id %>
<p>
<%= f.text_field :author_name, placeholder: "Name" %>
</p>
<p>
<%= f.text_area :content, :rows => '12', :cols => 35, placeholder: "Leave a comment..." %>
</p>
<p><%= f.submit "Submit" %></p>
<% end %>
<br /><br />
</div></div>
Your Comment table should have a column named user_id if it does not already. Then you can assign the user_id two different ways. These assume you have a current_user method. If you do not, then you will have to fill in the user_id from whatever session store or method you are using.
You could create a hidden_field in your form to assign it.
<%= f.hidden_field :user_id, value: current_user.id %>
but as noted by #rmagnum2002 this could be a security concern due to a user could edit this.
You could assign it during the create action:
def create
#comment = Comment.new(comment_params)
#comment.user_id = current_user.id
respond_to do |format|
if #comment.save
format.html { redirect_to song_url(#comment.song_id), 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
Assigning this in the controller create action is probably best.

Resources