param is missing or the value is empty error - ruby-on-rails

I am having trouble with the params.require().permit(). I have a User model from devise and a group model which represents a class (as in an academic class). When the user is on the groups index view I want them to be able to click join and become part of that group.
<%= form_for #student, :url => students_path(#student), method: :post do %>
<%= hidden_field :student_id, :value => current_user.id %>
<%= hidden_field :course_id, :value => group.id %>
<%= submit_tag "+ Join", :class => "btn btn-primary pull-right join-button" %>
<% end %>
My thinking was I would pass the current users id as well as the group ID that they click join for as hidden values so there would just be a join button.
My group controllers index method looks like this
def index
#groups = Group.all
#student = Student.new
end
My Student Controller looks like this:
class StudentsController < ApplicationController
before_action :set_student, only: [:show, :edit, :update, :destroy]
def index
##students = Student.all
#students = Student.where(:student_id => current_user.id)
#respond_with(#students)
end
def show
#respond_with(#student)
#students = Student.find(params[:id])
end
def new
#student = Student.new
#respond_with(#student)
end
def edit
end
def create
#student = Student.new(student_params)
#student.save
#respond_with(#student)
end
def update
#student.update(student_params)
#respond_with(#student)
end
def destroy
#student.destroy
#respond_with(#student)
end
private
def set_student
#student = Student.find(params[:id])
end
def student_params
params.require(:student).permit(:course_id, :student_id)
end
end
Whenever I try and submit the form (aka click the join button) I get an error saying:
param is missing or the value is empty: student
I also get this info which is correct as far as the student and group ID's
Request
Parameters:
{"authenticity_token"=>"/bJYZBGr6lfAzb9mYnvRfMZII+QS8iskd0MRuHh+RnE=",
"course_id"=>"10",
"student_id"=>"4"}
This also does insert into the database, but the student_id and course_id are nil. I am guessing this has something to do with strong params but I am not sure what I am doing wrong.

It looks like your form_builder object is missing from the form_for block. Try this:
<%= form_for #student, :url => students_path(#student), method: :post do |f| %>
<%= f.hidden_field :student_id, :value => current_user.id %>
<%= f.hidden_field :course_id, :value => group.id %>
<%= submit_tag "+ Join", :class => "btn btn-primary pull-right join-button" %>
<% end %>
without the builder I don't believe rails will provide a student hash in the submitted params

in the before _action set_student
you are usin param[:id] , while your params don't have it .. I think you should use param[:student_id] instead

Since you're passing #student isn't the path just student_path(#student) rather than what you had, which is students_path(#student)?

Related

Rails - Passing a variable while using Simple Form

When creating a new model object using simple form, I need to pass the id of one object to the new object as it relies on the id for its URL. The routing is:
resources :coins do
resources :questions
end
I am attempting to do this using a hidden field tag but its not working. The ID is not passed and as a result, the new object does not save.
<%= simple_form_for #question, url: coin_questions_path(#coin.id) do |f| %>
<%= f.input :ques_num %>
<%= f.input :content %>
<%= hidden_field_tag(:coin_id, #coin.id) %>
<%= f.button :submit, 'Submit' %>
<% end %>
Prior to this, I was using collection_select in simple form to manually enter the ID and it worked, however I need it to happen automatically. Is there a better way of doing this that will do what I am looking for?
Question model:
class QuestionsController < ApplicationController
before_action :find_question, only: [:show, :edit, :update, :destroy ]
before_action :find_coin
before_action :authenticate_user!, except: [:index, :show]
def index
#questions = Question.where(coin_id: #coin.id).order("created_at DESC")
end
def show
end
def new
#coin
#question = current_user.questions.build
end
def create
#question = current_user.questions.build(question_params)
if #question.save
redirect_to coin_question_path(#coin.id, #question.id)
else
render 'new'
end
end
.
.
.
private
def find_question
#question = Question.find(params[:id])
end
def find_coin
#coin = Coin.find(params[:coin_id])
end
def question_params
params.require(:question).permit(:content, :ques_num, :coin_id)
end
end
You could easily use hidden_field with a value - read more
For example:
<%= f.hidden_field :coin_id, value: #coin.id %>
And then the value will be in the params[:question][:coin_id] on create, so the rest should work as it is now. :)
<%= hidden_field_tag(:coin_id, #coin.id) %> will generate a field with a name of just "coin_id"; you want "question[coin_id]"
You should, in turn, assign this value to the object in your controller. #question.coin_id = #coin.id, or #question = Question.new(:user => current_user, :coin => #coin) or #question = current_user.questions.new(:coin => #coin)
<%= f.input :coin_id, :as => :hidden %>

Edit page to display all category names with edit inputs on each

I have categories nested inside of guides. I'm building an app to learn rails better and I'm trying to make a page that will display all categories that belong to a guide and have edit inputs under them and a save button next to it so the user can edit the names of the categories they want to change.
Bit stuck on how exactly how get this done.
here is the category_item_keys controller
def edit
#guide = Guide.friendly.find(params[:guide_id])
#category = Category.friendly.find(params[:category_id])
#key = #category.category_item_keys
end
def update
#guide = Guide.friendly.find(params[:guide_id])
#category = Category.friendly.find(params[:category_id])
#key = #category.category_item_keys.friendly.find(key_params) # no idea what to make this equal because it isn't one set key being edited on the page
if #key = #category.category_item_keys.update_attributes(key_params)
flash[:success] = "Key updated"
redirect_to #guide
else
render 'edit'
end
end
private
def key_params
params.require(:category_item_key).permit(:key, :slug)
end
routes
match '/guides/:guide_id/:category_id/keys/edit' => 'category_item_keys#edit', :via => :get
match '/guides/:guide_id/:category_id/keys/' => 'category_item_keys#update', :via => :post, as: :category_item_keys_update
edit.html.erb
<ul>
<% #key.each do |key| %>
<li><%= key.key #just shows key name %><br>
<%= form_for([#category, #keys], url: category_item_keys_create_path) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :key, "Key name" %>
<%= f.text_field :key %>
<%= f.submit "Save" %>
<% end %>
</li>
<% end %>
</ul>
This just gives me an error of:
undefined method `to_key' for #<CategoryItemKey::ActiveRecord_Associations_CollectionProxy:0x007fe20a86b480>
Later I plan on using an in-place editor gem but i would like to learn how this can be done fist.
EDIT:
Fixed the error ( changed form_for([#category, #keys] to form_for([#category, key] and turns out this way works for displaying and allowing all categories to be edited... to an extent.
I get another error when i submit a form
undefined method 'update_attribute'
EDIT 2
slowly getting there. I Changed the update #key variable to #key = #category.category_item_keys.all to fix the error. But this line is now giving me problems
if #key = #category.category_item_keys.update_attributes(key_params)'
THIRD EDIT
ERROR
Couldn't find CategoryItemKey without an ID
on line
#key = #category.category_item_keys.find params[:id]
paramaters:
{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"egj/OebdSbxxaoaTkr46WVIOIIu4Ezijzu45kqxLT0krjFWHqi67SRJDSgV7bcL6SeoGpUSYsrolspylCXBu9g==",
"category_item_key"=>{"name"=>"def1111"},
"commit"=>"Save",
"guide_id"=>"dbz",
"category_id"=>"characters"}
Here's how to clean up the code:
#config/routes.rb
resources :guides do
resources :categories, path: "" do
resources :category_item_keys, path: "keys", only: [:update] do
get :edit, on: :collection #-> url.com/guides/:guide_id/:category_id/keys/edit
end
end
end
#app/controllers/keys_controller.rb
class KeysController < ApplicationController
def edit
#guide = Guide.find params[:guide_id]
#category = Category.find params[:category_id]
#keys = #category.category_item_keys
end
def update
#guide = Guide.find params[:guide_id]
#category = Category.find params[:category_id]
#key = #category.category_item_keys.find params[:id]
if #key.update key_params
redirect_to #guide, success: "Key Updated"
else
render 'edit'
end
end
private
def key_params
params.require(:category_item_key).permit(:key)
end
end
#app/views/keys/edit.html.erb
<% #keys.each do |key| %>
<%= form_for [#guide, #category, key] do |f| %>
<%= f.text_field :key %>
<%= f.submit %>
<% end %>
<% end %>
If you wanted to use an in-place editor gem, I'd recommend looking at X-Editable, as we've applied it here (its only a demo app, just sign up for free and go to profile):
Looks like you are trying to do update_attributes on a collection instead of an object. Try to first fetch the key object
#key = #category.category_item_keys.friendly.find(params[:id])
and then try to update its attributes
if #key.update_attributes(key_params)
...
end
use nested forms available in rails 4.

Trying to have 2 forms pass to 2 different controllers from one view

I have 2 forms in one view one is displayed if the user is a moderator and the other if it is a normal user and they both send the information to 2 different controllers. My problem is that if its a normal user, the form that is displayed for them uses the wrong controller.
Here is the coding
categories/new.html.erb
<% if current_user.mod_of_game? #guide %>
<%= form_for([#guide, #category], url: guide_categories_path) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name, "Category name" %>
<%= f.text_field :name %>
<%= f.submit "Next" %>
<% end %>
<% else %>
<%= form_for([#guide, #check_category], url: check_category_post_path) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name, "Category name" %>
<%= f.text_field :name %>
<%= f.submit "Next" %>
<% end %>
<% end %>
Categories controller
before_action :mod_checker, only: [:create]
def new
#guide = Guide.friendly.find(params[:guide_id])
#category = Guide.friendly.find(#guide.id).categories.new
#check_category = CheckCategory.new
end
def create
#guide = Guide.friendly.find(params[:guide_id])
#category = Guide.friendly.find(#guide.id).categories.new(category_params)
if ((#category.save) && (current_user.mod_of_game? #guide))
flash[:info] = "guide category added succesfully!"
redirect_to #guide
else
render 'new'
end
end
private
def category_params
params.require(:category).permit(:name)
end
def mod_checker
#guide = Guide.friendly.find(params[:guide_id])
unless current_user.mod_of_game? #guide
flash[:danger] = "Sorry something went wrong!"
redirect_to root_path
end
end
check_categories controller
def new
end
def create
if #check_category.save
flash[:info] = "Game category added successfully. A mod will apporve it shortly."
redirect_to #guide
else
render 'new'
end
end
private
def check_category_params
params.require(:check_category).permit(:name)
end
and the routes
resources :guides do
resources :categories, only: [:new, :create, :edit, :update]
end
resources :check_categories, only: [:new, :edit, :update]
match 'guides/:guide_id/categories/' => 'check_categories#create', :via => :post, as: :check_category_post
sorry the coding is a bit messy, the 4 spaces to put it in a code block was spacing my coding weird.
When i have a non moderator user submit the form, the before action in the categories controller is run and I'm redirected to the homepage. I don't know why it does this because the submit path should go to the check_categories controller for non moderator users, the check_categories controller doesn't have the before filter.
Why does it use the before filter in the controller I'm not using for that form? How can I fix it?
Building this app to learn rails better. So I can only assume lack of rails knowledge is causing me to do something wrong.
Bad practice to have two forms with identical code (apart from the path) - goes against DRY Don't Repeat Yourself.
As mentioned by #Akash, this sounds like a job for authorization.
Further, it also denotes that you have issues with the underlying structure of your code. Specifically, you have an antipattern with CheckCategory (you can put it all into the Category model):
#config/routes.rb
resources :guides do
resources :categories, only: [:new, :create, :edit, :update] do
patch :approve, on: :member
end
end
#app/models/category.rb
class Category < ActiveRecord::Base
before_action :set_guide
def new
#category = current_user.categories.new
flash[:notice] = "Since you are not a moderator, this will have to be approved." unless current_user.mod_of_game? #guide
end
def create
#category = current_user.categories.new category_params
#category.guide = #guide
#category.save
end
def approve
#category = #guide.categories.find params[:id]
#category.approve
end
private
def set_guide
#guide = Guide.find params[:guide_id]
end
end
#app/views/categories/new.html.erb
<%= form_for [#guide, #category] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name, "Category name" %>
<%= f.text_field :name %>
<%= f.submit "Next" %>
<% end %>
The above will solve most of your structural issues.
--
To fix the authorization issue, you'll be best denoting whether the category is "approved" in the model:
#app/models/category.rb
class Category < ActiveRecord::Base
enum status: [:pending, :approved]
belongs_to :user
belongs_to :guide
validates :user, :guide presence: true
before_create :set_status
def approve
self.update status: "approved"
end
private
def set_status
self[:status] = "approved" if self.user.mod_of_game? self.guide
end
end
--
If I understand correctly, you want to allow anyone to create a category, but none-mods are to have their categories "checked" by a moderator.
The code above should implement this for you.
You will need to add a gem such as CanCan CanCanCan to implement some authorization:
#app/views/categories/index.html.erb
<% #categories.each do |category| %>
<%= link_to "Approve", guide_category_approve_path(#guide, category) if category.waiting? && can? :update, Category %>
<% end %>
Use "Cancan" Gem and give authorization

Routing issue after update

So I'm still a Rails noob so I may be completely going at this wrong but I have two controllers. A Question Controller and an Answer Controller. I am trying to build a grading function that allows an admin user to assign points to essay questions. I am using the /answer/:id to be where the :id is the id of the question and then rendering a partial to iterate through all of the answers for that id. Clear as mud I'm sure...
My problem: Within the partial where the user's answer is displayed, I have a form that allows the admin to fill out the number of points for that answer and submit. Ideally, I'd like it to move to the next page (using will_paginate), but at a minimum, I'd like to stay on the same page. I am able to get the form working but it keeps going to /answers/:id but where :id is the id of the individual answer, so not what I'm hoping.
answers_controller.rb
class AnswersController < ApplicationController
def index
#user = current_user
#questions = Question.all
end
def show
#user = current_user
#question = Question.find(params[:id])
#answers = Answer.where("question_id = ?", #question.id).paginate(:page => params[:page], :per_page => 1)
#answer = Answer.where("question_id =? AND user_id = ?", #question.id, #user.id)
end
def edit
#answer = Answer.find(params[:id])
end
def update
#answer = Answer.find(params[:id])
if #answer.update_attributes(grade_params)
flash[:success] = "Answer Graded"
else
flash[:warning] = "Not Graded"
end
end
private
def grade_params
params.require(:answer).permit(:points_earned)
end
end
_essay_grades.html.erb (partial that is being rendered on the show page that contains the form)
<% #answers.each do |answer| %>
<p>User: <%= answer.user_id %></p>
<%= answer.answer %><br>
<%= #question.value %>
<br>
<%= form_for(answer) do |f| %>
<%#= f.radio_button :points_earned, #question.value %><br>
<%#= f.radio_button :points_earned, 0 %> <br>
<%= f.text_field :points_earned %> Points<br>
<br>
<%= f.submit "Award Points" %>
<% end %>
<% end %>
<br>
<br>
<%= will_paginate #answers, renderer: BootstrapPagination::Rails %>
routes.rb
Rails.application.routes.draw do
resources :admins, :answers, :static_pages, :questions
devise_for :users, :controllers => { registrations: 'registrations' },
:path => '', :path_names =>
{ :sign_in => "login", :sign_up => "register" }
root "static_pages#index"
end
I'm sure there's a simple solution here (or maybe it's changing how I have things set up...). Any help is greatly appreciated!
AFTER FEEDBACK:
Added the grades model and set up a through relationship with questions.
answer_controller.rb
class AnswersController < ApplicationController
def show
#user = current_user
#question = Question.find(params[:id])
#answers = Answer.where("question_id = ?", #question.id).paginate(:page => params[:page], :per_page => 1)
#answer = Answer.where("question_id =? AND user_id = ?", #question.id, #user.id)
end
def update
#user = current_user
#question = Question.find(params[:question_id])
#answer = #question.answers.find(params[:id])
#grade = #question.grades.new(grade_params)
if #grade.save
flash[:success] = "Answer Graded"
redirect_to #question
end
end
private
def grade_params
params.require(:grade).permit(:user_id, :answer_id, :points_earned, :graded_by, :comment)
end
end
_answer.html.erb
<%= answer.user_id %>
<%= form_tag [#question, answer], method: :put do %>
<%= hidden_field_tag :graded_by, current_user.id %>
<%= hidden_field_tag :answer_id, answer.id %>
<%= number_field_tag :points_earned %>
<%= submit_tag "Submit Grade" %>
<% end %>
routes.rb
Rails.application.routes.draw do
resources :questions do
resources :answers, only: [:update]
end
resources :admins, :static_pages
questions/show.html.erb
...
<h3>Show answers</h3>
<%= render #answers, locals: {question: #question} %>
<%= will_paginate #answers, renderer: BootstrapPagination::Rails %>
You will have to use the following in your form so the update does not load a new page, but still submits your update. Use Chrome / Firefox developer tools to view requests / responses.
<%= form_for(answer), :remote => true do |f| %>
Then, alter the update action in the answers controller to load the 'next unrated answer':
def update
rated_answer = Answer.find(params[:id])
if rated_answer.update_attributes(grade_params)
flash[:success] = "Answer Graded"
else
flash[:warning] = "Not Graded"
end
#answer = get_next_unrated_answer(rated_answer.question_id)
end
private
def get_next_unrated_answer(question_id)
# I am making a couple of assumptions on your model here, but get an answer that has not been rated yet for this question
next_answer = Answer.where("question_id = ? and points_earned = ?", question.id, nil)
#returned automatically
end
Then you will have to create app/views/answers/update.js.erb to load the new answer to your page with the following line:
$('#main_div').html('<%= escape_javascript(render partial: 'whatever_partial_you_have_created_to_display_the_next_unrated_answer') %>');
Just go and create a new partial that displays your answer and form correctly for the next unrated answer. Or ideally load your initial 'show.html.erb' with the relevant partials and reuse them.
This is the simple way to do it, but if I were you I would probably rename these new functions to not use 'update' or 'show' but rather call it something like 'rate' and even 'rate_show' so you can use update and show in its original form (for updating and answer or showing an answer) if required later in your project.
From what I understood of your question, I think you'd be best looking into nested routes:
#config/routes.rb
resources :questions do
resources :answers, only: [:update]
end
#app/controllers/questions_controller.rb
class QuestionsController < ApplicationController
def show
#question = Question.find params[:id]
end
end
#app/views/questions/show.html.erb
<%= #question.title %>
<%= render #question.answers, locals: {question: #question} %>
#app/views/questions/_answer.html.erb
<%= answer.title %>
<%= form_tag [question, answer], method: :put do %>
<%= text_field_tag :grade %>
<%= submit_tag %>
<% end %>
The above will give you what you have already (just to clarify your "clearly mad" remark is not the case at all!).
-
The following is where the nested resources come in.
At the moment, it seems you're having a problem associating an answer to a question:
where :id is the id of the individual answer, so not what I'm hoping
A remedy for this is as follows:
#app/controllers/answers_controller.rb
class AnswersController < ApplicationController
def update
#question = Question.find params[:question_id]
#answer = #question.answers.find params[:id]
#grade = #answer.grades.new grade_params
redirect_to #question if #grade.save
end
private
def grade_params
params.permit(:points_earned) #-> will probably have to refactor this
end
end
This will create a new grade (which you should have in an associated model), for that specific answer. Because the answer has been associated to a question, it will allow you to use the nested routes to load both.
In terms of your setup, I'd personally add a Grade model, so that you can have multiple grades per answer. This is against your current schema, but works well to ensure you have the functionality necessary to facilitate multiple grades:
#app/models/grade.rb
class Grade < ActiveRecord::Base
belongs_to :answer
belongs_to :user
end
#app/models/answer.rb
class Answer < ActiveRecord::Base
has_many :grades
end

Form_for data not accessible in custom action

I have a form_for #user to update a column in user model .
I have given html method as get and submitting to action look like
#user = User.find(params[:id])
#user_update_attribute(:phno,params[:phno])
and in view its look like
<%= form_for :#user, url: addphno_addphno_path , html: { method: :get } %>
The issue is I am not able to get data in controller action.the error is
could not find record of "id="
If you wanted to have a separate "phoneno" action:
#config/routes.rb
resources :users do
match :addphone, via: [:get, :post]
end
#app/views/users/add_phone_no.html.erb
<%= form_for #user, user_addphone_path(#user) do |f| %>
<%= f.text_field :number %>
<%= f.submit %>
<% end %>
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def addphone
#user = User.find params[:id]
#user.update( update_params ) if request.post?
end
private
def update_params
params.require(:user).permit(:phno)
end
end
If you wanted to use the update action (as is convention):
#config/routes.rb
resources :users
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def edit
#user = User.find params[:id]
end
def update
#user = User.find params[:id]
#user.update user_params
end
private
def user_params
params.require(:user).permit(:phno)
end
end
This will allow you to call:
#app/views/users/edit.html.erb
<%= form_for #user do |f| %>
<%= f.text_field :phno %>
<%= f.submit %>
<% end %>
Bottom line is that you should be using the second batch of code if you're updating your #user object.
I originally thought you wanted to add a phone number as associative data to your #user... but it seems that you just wish to add a phone number for the user. To do this, the above code will suffice.

Resources