I want to have a form to just edit one field for my user's model that is separate from the scaffold generated _form.erb.
The form will show but it will not save. When I modify the def in the controller with a respond_to block, the form is bypassed and I just get the record shown.
employee_supervisor_edit.html.erb has <%= render 'employee_supervisor_form' %>
routes.rb contains match '/employee_supervisor_edit/:id' => 'users#employee_supervisor_edit' , via: [:get, :post ]
the form is _employee_supervisor_form.erb
users_controller.rb has
def employee_supervisor_edit
#users = User.all
#user = User.find(params[:id])
respond_to do |format|
if #user.update(user_params)
format.html { redirect_to #user, notice: 'User was successfully updated.' }
format.json { render :show, status: :ok, location: #user }
else
format.html { render :edit }
format.json { render json: #user.errors, status: :unprocessable_entity }
end
end
end
I also have have the following in my users controller.
def user_params
# params.require(:user).permit(:login,
params.permit(:login,
:group_strings,
:name,
:ou_strings,
:email,
:active_employee,
:last_name,
:first_name,
:is_supervisor,
:#supervisor_id)
end
end
If I comment out the whole respond_to block, the form appears but no data is saved. If I put the respond_to block in, then the form is bypassed and it goes right to the show method.
I'm not sure if the problem is related to getting the following error if I use params.require(:user).permit(:login, instead of params.permit(:login,
ActionController::ParameterMissing in UsersController#employee_supervisor_edit
param is missing or the value is empty: user
Rails.root: C:/Users/cmendla/RubymineProjects/employee_observations
Application Trace | Framework Trace | Full Trace
app/controllers/users_controller.rb:134:in `user_params'
app/controllers/users_controller.rb:16:in `block in employee_supervisor_edit'
app/controllers/users_controller.rb:15:in `employee_supervisor_edit'
========== added ==============
I have the following associations in my user.rb
Class User < ActiveRecord::Base
has_many :subordinates, class_name: "User", foreign_key: "supervisor_id"
belongs_to :supervisor, class_name: "User"
======== added : =====================
<%= form_for(#user) do |f| %>
<% if #user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% #user.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
User Login: <%= #user.login %><br>
User Name: <%= #user.name %> <br>
<div class="field">
<%= f.label :active_employee %>
<%= f.check_box :active_employee %>
</div>
<div class="field">
<%= f.label :supervisor %>
<%= f.collection_select(:supervisor_id, User.order('name'), :id, :name, prompt: true)%>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The problem stemmed from my data. Since I am in the development process, I don't have all the error checking I need in place. Null fields or associations pointing to non existent records cause errors where it isn't always obvious (at least to me) that the problem is the data, not the code itself.
I went in with an sql editor and made sure that the contents causing the issues were not set to null and that the columns such as supervisor_id were pointing to actual existing records, not records that were deleted.
I changed params.permit(:login, back to params.require(:user).permit(:login, and now the form is saving as expected.
My next step will be to add validations for input and some error checking for the index and show methods. For the long term, I think that I need to become more proficient with testing as that might show areas that could cause these types of issues.
Related
I have a nested form which captures information for two models, Games and Teams.
My Models:
class Game < ApplicationRecord
has_many :teams
accepts_nested_attributes_for :teams
validates_associated :teams
validates :start_time, presence: true
end
class Team < ApplicationRecord
belongs_to :game
validates :name, presence: true, length: { maximum: 50 }
end
Before saving the records, the validations must be passed. If the save fails, the form should be re-rendered and the validation error messages displayed, as per the controller below. However, the error messages never get displayed.
My GamesController:
class GamesController < ApplicationController
def new
#game = Game.new
#team = #game.teams.build
end
def create
#game = Game.new(game_params)
unless #game.save
render 'new'
return
end
# Some other code that shouldn't run if the save fails, hence the 'return' above
end
end
My form (new.html.erb):
<%= render 'shared/error_messages' %>
<%= form_with model: #game do |f| %>
<%= f.fields_for :teams do |f_teams| %>
<%= f_teams.label :name %>
<%= f_teams.text_field :name, class: 'form-control'%>
<%= f.label :start_time, "Game day" %>
<%= f.date_field :start_time, id: "game_day", class: 'form-control' %>
<%= f.submit "Book now!", class: "btn btn-primary" %>
<% end %>
<% end %>
and finally, the error message partial:
<% if #game.errors.any? %>
<div id="error_explanation">
<div class="alert alertdanger">
The form contains <%= pluralize(#game.errors.count, "error") %>.
</div>
<ul>
<% #game.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
When I deliberately trip up the validations (e.g. by not including the game day) the error message partial doesn't run, presumably because the #game.errors.any? is false.
If I use byebug or if I go throug the rails console, I get the validation errors, e.g. start_time can't be blank.
What am I missing here?
EDIT
Chris's solution below worked for me. However, I wanted my controller to run JS if validations were met and save succeeded. So I went back and removed the suggested local: true and allowed the remote submission to happen. What I did to fix the issue is render html if save didn't succeed:
unless #game.save
respond_to do |format|
format.html { render 'new' }
end
return
end
This didn't work out of the box because turbolinks interferes. I therefore ended up adding gem 'turbolinks_render' to my Gemfile and voila everthing works great now.
Huge shoutout to Joel (https://joelc.io/ajax-ruby-on-rails-forms) for the walkthrough.
In your new.html.erb file, try
<%= form_with(model: #game, local: true) do |f| %>
Some good info for using the form_with tag here:
https://medium.com/#tinchorb/form-with-building-html-forms-in-rails-5-1-f30bd60ef52d
One Article has_many Images. When creating a new Article, Users can add 2 images max.
In my controller I run "build" for images only twice, but when I submit the form that has 3 image fields, it succeeds. Is there any need to run "build" at all? It seems pointless in this scenario, is there another way to better ensure only 2 images are accepted?
articles_controller.rb
def new
#article = Article.new
2.times { #article.images.build }
end
Note the "2.times" here.
def create
#article = Article.new(place_params)
#article.user = current_user
respond_to do |format|
if #review.save
params[:images][:image_file].each do |image_params|
#image = #article.images.create(image_file: image_params, user: current_user)
end
end
end
end
_form.html.erb
<%= form_with(model: article, url: create_article_path(#article), local: true) do |form| %>
<div class="field">
<%= form.label :title %>
<%= form.text_area :title %>
</div>
<%= form.fields_for :images, #image do |image| %>
<div class="field">
<%= image.label :image_file_1, "Image 1" %>
<%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_1 %>
</div>
<div class="field">
<%= image.label :image_file_2, "Image 2" %>
<%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_2 %>
</div>
<div class="field">
<%= image.label :image_file_3, "Image 3" %>
<%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_3 %>
</div>
<% end %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
SUCCESS (But why?)
In short -- Your build statement is prepping the view to have 2 child objects. But you're manually creating them, so you're rendering the build statement as useless. You don't have to do it this way, you can declare nested attributes in the model, then whitelist in the controller, then auto-add them in the view. (see code example below)
Build itself does change how many objects are instantiated, but you're overriding that.
You are also manually saving the images, which you do not have to do. There's a bit of rails magic that saves all the children for you, if you've built them properly.
CodeView
1 The Model
app/models/article.rb
class Article < ApplicationRecord
has_many :images
validates :images, length: {maximum: 2}
accepts_nested_attributes_for :images
end
2 bits of note here. Firstly, in your validation, only allow 2 object, if you try to save a third, it will fail. Secondly, accepting the attribute in the model allows you to create safe params in the controller, thus alleviating your need to manually create. (unless of course, you really want to)
2 The View
<%= form_with(model: article, url: article_path(#article), local: true) do |form| %>
<div class="field">
<%= form.label :title %>
<%= form.text_area :title %>
</div>
<%= form.fields_for :images do |image_form| %>
<div class="field">
<%= image_form.label "image_file_#{image_form.index + 1}" %>
<%= image_form.file_field :image_file %>
<%= image_form.hidden_field :user_id, value: current_user.id %>
</div>
<% end %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
The change here is a) I added the user directly to the form b.) because you are accepting attribute in the model and we'll whitelist the attribute in the controller, you don't need to pass an object to the field_for -- :images will do just fine. And because you will say to build it twice in your controller, you'll have 2 image objects in the form. Additionally, because you wanted a label of Image 1 and Image 2, with fields_for you automatically get access to the index of the object (just like you'd have with any array) by calling object.index.
3 The Controller - New Action
app/models/article.rb
Your action works perfectly well, keep it just as it is.
def new
#article = Article.new
2.times { #article.images.build }
end
4 The Controller - Strong Params
def article_params
params.require(:article).permit(:title, :body, images_attributes: [:id, :article_id,:user_id, :image_file])
end
Whitelisting your params altogether will save time and it's easier to read than permitting them in each controller, though you CAN do that if you need to, for instance if params are allowed in certain actions but not in others.
5 The Controller - Create Action
def create
#article = Article.new(article_params)
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
This will probably look similar if not identical to the default create action in a scaffold, and this is all you need. The child image objects will not be created unless the parent can be created, so you don't have to worry about adding them in an if save.
I have a model called Video, which takes in user_id, question, and video_cid.
The validates seem to have been set, as the form does not save if it doesn't meet requirements. However, the error messages partial shows no error messages :(.
Here's how the model looks like ->
# == Schema Information
#
# Table name: videos
#
# id :integer not null, primary key
# user_id :integer
# video_cid :string(255)
# question :string(255)
# created_at :datetime
# updated_at :datetime
#
class Video < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
validates :question, presence: true
validates :video_cid, presence: true
end
Here's how the videos/new view looks like ->
<% provide(:title, "Final step, Record a video of yourself") %>
<%= form_for #video do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :question %>
<%= select(:video, :question,
[
['Why would you be effective in a sales/business development role in China?',
'Why would you be a valuable addition to an international team in China? '],
['What is your most significant accomplishment or the best example of your leadership skills in China?',
'What is your most significant accomplishment or the best example of your leadership skills in China?'],
['How would you help solve the biggest challenges Chinese companies and investors face when doing business abroad?',
'How would you help solve the biggest challenges Chinese companies and investors face when doing business abroad? ']
]) %>
<%= render 'nimbb' %>
<%= f.hidden_field :video_cid, value: "" %>
<%= f.submit "Submit the Video", class: "button" %>
<% end %>
I use javascript to set the hidden value for :video_cid like so. The form should technically only pass if the user records a video of himself, and therefore updates the hidden value in the form ->
// Global variable to hold player's reference.
var _Nimbb;
// Global variable to hold the guid of the recorded video.
// Event: Nimbb Player has been initialized and is ready.
function Nimbb_initCompleted(idPlayer)
{
// Get a reference to the player since it was successfully created.
_Nimbb = document[idPlayer];
}
// Event: the video was saved.
function Nimbb_videoSaved(idPlayer)
{
document.getElementById('video_video_cid').value = _Nimbb.getGuid();
}
This is how the controller looks like -->
class VideosController < ApplicationController
before_action :signed_in_user
def new
if current_user.video.present?
redirect_to current_user
else
#video = current_user.build_video
end
end
def create
#video = current_user.build_video(video_params)
if #video.save
flash[:success] = "Video Created!"
redirect_to root_url
else
redirect_to new_video_path
end
end
private
def video_params
params.require(:video).permit(:video_cid,:question)
end
end
this is how the error messages partial looks like:
<% if object.errors.any? %>
<div id="error_explanation">
<div class="alert alert-error">
The form contains <%= pluralize(object.errors.count, "error") %>.
</div>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li>* <%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Change your create method to:
def create
#video = current_user.build_video(video_params)
if #video.save
flash[:success] = "Video Created!"
redirect_to root_url
else
render :new
end
end
The form now will be displayed directly after your application rejects form data, without redirection, so it has Video object instantiated in create action, with its errors. In your original form, you were redirecting user to new video path after video saving failure, so the new action was being fired again, with new "clean" Video instance.
Use
<%= form_for(#video, :validate => true) do |f| %>
instead of
<%= form_for #video do |f| %>
And in your video controller, your create method should be like this
def create
#video = current_user.build_video(video_params)
respond_to do |format|
if #video.save
flash[:success] = "Video Created!"
format.html {redirect_to( :controller => "controller_name", :action => "action_name" )}
else
format.html {render :action => "action_name"}
end
end
end
I'm using Mongoid, awesome_nested_fields gem and rails 3.2.8.
I have the functionality working on the client (adding multiple fields), but when I try to save I get a "undefined method `update_attributes' for nil:NilClass" error.
Here is all the relevant info:
profile.rb
class Profile
include Mongoid::Document
include Mongoid::Timestamps
has_many :skills, :autosave => true
accepts_nested_attributes_for :skills, allow_destroy: true
attr_accessible :skills_attributes
end
skill.rb
class Skill
include Mongoid::Document
belongs_to :profile
field :skill_tag, :type => String
end
View
<%= simple_form_for(#profile) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<div class="items">
<%= f.nested_fields_for :skills do |f| %>
<fieldset class="item">
<%= f.input :skill_tag, :label => 'Skills:' %>
Remove Skill
<%= f.hidden_field :id %>
<%= f.hidden_field :_destroy %>
</fieldset>
<% end %>
</div>
Add Skill
</div>
<div class="form-actions">
<%= f.button :submit, :class => 'btn' %>
</div>
<% end %>
profiles_controller.rb
# PUT /profiles/1
# PUT /profiles/1.json
def update
#profile = Profile.find(params[:id])
respond_to do |format|
if #profile.update_attributes(params[:profile])
format.html { redirect_to #profile, notice: 'Profile was successfully updated.' } # Notice
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #profile.errors, status: :unprocessable_entity }
end
end
end
*********** UPDATE ************
I ended up switching to Ryan's nested_form gem and it works like a charm.
Your message directly points to the line of execution where it failed, ergo the line:
if #profile.update_attributes(params[:profile])
The problem is that the #profile instance variable is nil and rails can not find the update_attributes method for a nil object.
You could easly check your server log for the params hash, to see what params[:id] (probably it isn't defined at all) by going in the terminal where you started your app.
Or you can check your development log when being in your app folder:
tail -n 1000 development.log | egrep params
I have an app where users can create courses, and each course has_one syllabus. How could I go about configuring my courses and syllabuses (I know it's Syllabi but apparently Rails doesn't) controller, and my routes, so on a course's page there is a link to create or show the course's syllabus, and a link back to the course from the show syllabus page?
In my routes I have:
resources :courses do
resources :syllabuses
member do
put :enroll #this is so users can enroll in the course
end
end
Currently , so the course_id will be saved in the syllabus table in my courses_controller, I have:
def create_syllabus
#course = Course.find(params[:id])
#syllabus = #course.build_syllabus(params[:syllabus])
if #syllabus.save
redirect_to #syllabus, notice: "Successfully created syllabus."
else
render :new
end
end
then in my courses show page I have:
<section>
<% if (current_user.courses.includes(#course) ||
current_user.coursegroups.find_by_course_id_and_role(#course.id, "admin")) %>
<%= render 'create_syllabus' %>
<% end %>
</section>
then in my create_syllabus form (in my courses views folder) I have tried starting it off with:
# I have #course = Course.find(params[:id]) defined in show in the
#courses_controller
<%= form_for #course.create_syllabus do |f| %>
<%= form_for #course.syllabus.create_syllabus do |f| %>
<%= form_for #course.syllabus.create do |f| %>
and I get an undefined method error for each of those.
If you want to create a new syllabus in your show action of a specific course, you can add this to your controllers and views:
courses_controller.rb
#course = Course.find(params[:id])
# Build a new #syllabus object, only if there is none for the current course
unless #course.syllabus
#syllabus = #course.build_syllabus
end
views/courses/show.html.erb
# Show the syllabus name if there is one, or show the form to create a new one
<% if #course.syllabus.name %>
<p>Syllabus: <%= #course.syllabus.name %></p>
<% else %>
<p>Create Syllabus:</p>
<%= form_for([#course, #syllabus]) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
<% end %>
syllabuses_controller.rb
def create
#course = Course.find(params[:course_id])
# Build new syllabus object based on form input
#syllabus = #course.build_syllabus(params[:syllabus])
if #syllabus.save
# redirect to /course/:id
redirect_to #course, notice: 'Syllabus was successfully created.' }
end
end
course.rb
class Course < ActiveRecord::Base
attr_accessible :name
has_one :syllabus
end
syllabus.rb
class Syllabus < ActiveRecord::Base
belongs_to :course
attr_accessible :name, :course_id
end
Some things that I left out but you should still include:
validations
rerendering form if something goes wrong
pulling things out into partials
fixing bad code like if #course.syllabus.name
pull out if/else logic into a helper
…