So, I have a Ruby on Rails application where the user have a list of ingredients. When the user adds a new item to the list of ingredients I need to refresh the page so the ingredient are shown in the list. I want this to be done dynamically using Ajax, I looked up to the internet for solutions but none worked :/
Here is my HTML code(index.html.erb):
<%= form_for(:ingredientList, :url => {:controller => 'ingredients', :action => 'index'}, :remote => true) do |f| %>
<div class ="modal-text col-md-6">
<div class="model-subtitle">Ingrediente: </div>
<%= f.select :value, options_for_select(#ingredient.map{ |ing| [ing.name, ing.id]}, html_options = { class: 'form-control', required: true })%>
</div>
<div class="modal-text col-md-6">
<div class="model-subtitle">Quantidade: </div>
<%= f.text_field :quantity, class: 'form-control', placeholder: "quantidade", autofocus: true %>
</div>
<%= f.submit :Salvar, class: 'btn btn btn-success center-block'%>
My controller:
def index
#user = User.find_by id: session[:user_id]
#ingredients = Ingredient.joins(:ingredients_users).where("user_id = ?", session[:user_id]).select("*")
#user_id = session[:user_id]
#ingredient = Ingredient.all
respond_to do |format|
format.js
format.html
if params[:ingredientList]
#userIngredient = IngredientsUser.new
#userIngredient.ingredient_id = params[:ingredientList][:value]
#userIngredient.quantity = params[:ingredientList][:quantity]
#userIngredient.user_id = #user.id
if #userIngredient.save
flash[:success] = "Ingredient saved!"
else
#userIngredient.errors.full_messages.each do |error|
flash[:alert] = error
end
end
end
end
end
I don't know if I make something wrong(probably as is not working) or if I forgot to add something. Do any of you know how to solve this?
I say this without knowing what your app/views/ingredients/index.js file looks like, but I think you have it almost correct.
As soon as you call format.js and format.html in your controller, it will go ahead and render the corresponding view (eg: app/views/ingredients/index.js). Any code that comes after format.js and format.html will be executed only AFTER the rendering is complete.
So in your case, what happens is that the rendering is done prior to actually processing the input (initializing the ingredient, trying to save it, ...). Try moving format.js and format.html to the end of the action code:
def index
#user = User.find_by id: session[:user_id]
#ingredients = Ingredient.joins(:ingredients_users).where("user_id = ?", session[:user_id]).select("*")
#user_id = session[:user_id]
#ingredient = Ingredient.all
respond_to do |format|
if params[:ingredientList]
#userIngredient = IngredientsUser.new
#userIngredient.ingredient_id = params[:ingredientList][:value]
#userIngredient.quantity = params[:ingredientList][:quantity]
#userIngredient.user_id = #user.id
if #userIngredient.save
flash[:success] = "Ingredient saved!"
else
#userIngredient.errors.full_messages.each do |error|
flash[:alert] = error
end
end
end
format.js
format.html
end
end
All of that said, you're "violating" the Rails conventions. An index action should primarily be used to get a series of objects. Saving data should be either via the create (if it's a new record) or update action (if it's a change to an existing record).
Related
hope your having a wonderful day drinking some coffee and responding to some forms.
Problem:
As my title states, I am trying to create 2 forms on one view. I am new to ruby on rails.
My controller functions:
Controller name is border_rotation:
def create
if params[:export_submit]
#border_rotation_export = BorderRotationExport.new(border_rotation_export_params)
respond_to do |format|
if #border_rotation_export.save
flash[:success] = "Export successfully created"
format.html { render :new }
else
flash[:error] = "Export was not created."
end
end
else
#border_rotation_import = BorderRotationImport.new(border_rotation_import_params)
respond_to do |format|
if #border_rotation_import.save
flash[:success] = "Export successfully created"
format.html { render :new }
else
flash[:error] = "Export was not created."
end
end
end
end
def new
#border_rotation_export = BorderRotationExport.new
#border_rotation_import = BorderRotationImport.new
end
private
def border_rotation_export_params
params.require(:border_rotation_export).permit(:exporter_name,:vehicle_color,:rot_num,:current_date,:current_time,:goods_description,:license_num,:entry)
end
def border_rotation_import_params
params.require(:border_rotation_import).permit(:importer_name,:vehicle_color,:rot_num,:current_date,:current_time,:goods_description,:license_num,:entry)
end
My new View form:
It has 2 forms and is enclosed in bootstrap tabs
<%= form_for #border_rotation_export, url: rotation_create_path, method: :post do |f|%>
<lable>Importer Name: </lable><%= f.text_field :importer_name, class: "form-control", placeholder: "Importer Name"%>
<lable>Vehicle Color: </lable><%= f.text_field :vehicle_color, class: "form-control", placeholder: "Vehicle Color"%>
**its fields**
<% end %>
and
<%= form_for #border_rotation_import, url: rotation_create_path, method: :post do |f|%>
<lable>Exporter Name: </lable><%= f.text_field :exporter_name, class: "form-control", placeholder: "Exporter Name"%>
<lable>Vehicle Color: </lable><%= f.text_field :vehicle_color, class: "form-control", placeholder: "Vehicle Color"%>
**its fields**
<% end %>
The error in my new.html.rb
First argument in form cannot contain nil or be empty
Displays this in red highlighted
<%= form_for #border_rotation_export, url: rotation_create_path, method: :post do |f|%>
My guess is that it submits both forms but only has the parameters for one form with the input data. Once I submit, it saves to the database but it gives me the error
**Routes **
get 'rotation/create', to: 'border_rotation#create'
post 'rotation/create', to: 'border_rotation#create'
Request
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"Cu52CIDgrY0b7Yk6edkd7+RTl5yR4qSEqPPrqWtM0nIQVDvw7eYDF36zduJPLjI+vVNqCfgtLcMDUEkW6qDOdQ==",
"border_rotation_import"=>
{"importer_name"=>"john",
"vehicle_color"=>"red",
"rot_num"=>"11sssfeeea",
"current_date"=>"2021-09-22",
"current_time"=>"09:37",
"goods_description"=>"yogurt",
"license_num"=>"c-11223",
"entry"=>"c1223"},
"import_submit"=>"Submit"}
Thank you in advance
You can setup the controller with a lot less redundancy:
# config/routes.rb
resources :rotations, only: [:new, :create]
class BorderRotationsController < ApplicationController
# GET /rotations/new
def new
populate_forms
end
# POST /rotations
def create
resource = model_class.new(create_params)
set_ivar(resource) # sets #border_rotation_export or #border_rotation_import
if resource.save
flash[:success] = "#{humanized} successfully created"
redirect_to action: :new
else
populate_forms
flash[:error] = "#{humanized} could not be created - please try again."
render :new
end
end
private
# gets the model class via params[:subtype]
def model_class
#model_class ||= begin do
if params[:border_rotation_export].present?
BorderRotationExport
else
BorderRotationImport
end
end
end
def humanized
model_class == BorderRotationExport ? 'Export' : 'Import'
end
def set_ivar(value)
instance_variable_set(
"##{param_key}",
value
)
end
# just sets up the instance variables for the form
def populate_forms
#border_rotation_export ||= BorderRotationExport.new
#border_rotation_import ||= BorderRotationImport.new
end
# border_rotation_export or border_rotation_import
def param_key
model_class.model_name.param_key
end
def create_params
require(param_key).permit(
:exporter_name, :vehicle_color, :rot_num,
:current_date,:current_time, :goods_description,
:license_num, :entry
)
end
And then use partials so that you can resuse the same form:
# app/views/border_rotations/new.html.erb
<%= render partial: 'form',
locals: { border_rotation: #border_rotation_export } %>
<%= render partial: 'form',
locals: { border_rotation: #border_rotation_import } %>
# app/views/border_rotations/new.html.erb
<%= form_with model: border_rotation, url: rotations_path do |f| %>
<div class="field">
<%= f.label :importer_name %>
<%= f.text_field :importer_name, class: "form-control" %>
</div>
<div class="field">
<%= f.label :importer_name %>
<%= f.text_field :importer_name, class: "form-control" %>
</div>
# ...
<% end %>
If the requirements diverge use two separate routes/controllers and inheritance instead of blooming out in tons of new code paths.
Apologies in advance for a noob question/bad formatting. Its my first post. I will try to explain clearly. In my app I have two objects quiz and quiz_question.
I want to create a quiz that has 10 questions associated with it, but want to create them over 10 'pages' rather than one long form on one page. I am trying to update the quiz object but when the update method is calls, it adds all the questions again, so the number of objects increases rapidly 1, 3, 7 etc.
quiz has many quiz questions
this is my controller:
def new
#quiz = Quiz.new
1.times{#quiz.quiz_questions.new}
end
def create
# return render json:params
#quiz = Quiz.new(quiz_params)
respond_to do |format|
if #quiz.save
format.html{ redirect_to edit_quiz_path(#quiz.id)}
else
format.html{ render :new}
end
end
end
def edit
# return render json:params
#quiz = Quiz.find params[:id]
1.times{#quiz.quiz_questions.new}
end
def update
#return render json:params
#quiz = Quiz.find params[:id]
#quiz.update(quiz_params)
respond_to do |format|
if #quiz.save && #quiz.quiz_questions.count < 10
format.html{ redirect_to edit_quiz_path(#quiz.id)}
elsif #quiz.save && #quiz.quiz_questions.count > 10
format.html{ redirect_to dashboard_teachers_path, notice: "Quiz Created Successfully"}
else
format.html{ render :edit}
end
end
end
And this is my edit view
<%= simple_form_for(#quiz, :defaults => { :input_html => { :class => "hello" } }) do |f| %>
<%= f.error_notification %>
<%= f.simple_fields_for :quiz_questions do |builder| %>
<p><%= builder.input :question %></p>
<p><%= builder.input :correct_answer %></p>
<p><%= builder.input :incorrect_answer1 %></p>
<p><%= builder.input :incorrect_answer2 %></p>
<p><%= builder.input :incorrect_answer3 %></p>
<% end %>
<%= f.button :submit, class: "btn btn-default" %>
<% end %>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$(window).load(function () {
$('input[type=text], textarea').each(function () {
// Cache pointer to selected dom element.
// Don't need to parse entire html each time you need that.
var input = $(this);
// .val() will return empty string if there is no value
// 0 means false in this case don't need to use equality check
if (!input.val()) {
input.parent().css("display", "block");
} else {
input.parent().css("display", "none");
}
});
})
</script>
EDIT: For clarity. I want to advance through the edit action 10 times, each time adding a new quiz_question object and assigning it to the quiz object.
if you want to call the update action, you need to use PUT:
<%= simple_form_for(#quiz, :method => :put, :defaults => { :input_html => { :class => "hello" } }) do |f| %>
Video objects have tags (via acts-as-taggable-on gem). The update function works for all of the video attributes except for tags. I'm not sure why. Any insight would be greatly appreciated.
views/videos/_new.html.erb
<div class="">
<%= f.label(:tag_list, "Tags (separated by commas)") %></br >
<%= f.text_field :tag_list, multiple: true, class: "form-control" %>
</div>
videos_controller.rb
def edit
#video = Video.find(params[:id])
#post = #video.post
end
def update
#video = Video.find(params[:id])
#post = #video.post
#video.update_attributes(video_params)
if current_user == #video.user
if #video.save
flash[:notice] = "Video successfully updated!"
redirect_to video_path(#video)
else
flash[:notice] = "Video was not updated."
#errors = #video.errors.full_messages.join(", ")
flash[:alert] = #errors
render action: "edit"
end
else
flash[:notice] = "Only OP can edit video"
refresh :edit
end
end
private
def video_params
params.require(:video).permit(
:title,
:url,
:tag_list,
)
end
Looks like your video_params is filtering it.
Try
params.require(:video).permit(
:title,
:url,
tag_list: []
)
I'm trying to add time_select with include_blank. I'm doing this:
<%= f.time_select :start_at, include_blank: true, ampm: true %><br>
What I'd like to do is to delete value (save nil?) if blank is selected in view.
Although I tried the following posts, it didn't work for me.
time_select blank field saves a default time when form is submitted
Optional time_select with allow_blank defaults to 00:00
1) When I try as below, no error is appeared, but 00:00:00 is saved.
controller
def update
#event = Event.find(params[:id])
if event_params["start_at(4i)"].blank? or event_params["start_at(5i)"].blank?
#event.start_at = nil
end
if #event.update(event_params)
flash[:success] = "event updated!"
redirect_to root_url
else
render 'edit'
end
end
2) When I try as below (change if clause), no error is appeared, but 00:00:00 is saved.
controller
def update
#event = Event.find(params[:id])
if params[:id]["start_at(4i)"].blank? or params[:id]["start_at(5i)"].blank?
#event.start_at = nil
end
if #event.update(event_params)
flash[:success] = "event updated!"
redirect_to root_url
else
render 'edit'
end
end
3) When I try as below (add before_action), no error is appeared, but 00:00:00 is saved.
controller
before_action :blank_time, only: [:update]
def update
#event = Event.find(params[:id])
if #event.update(event_params)
flash[:success] = "event updated!"
redirect_to root_url
else
render 'edit'
end
end
private
def blank_time
if params[:id]["start_at(4i)"].blank? or params[:id]["start_at(5i)"].blank?
params[:id]['start_at(1i)'] = ""
params[:id]["start_at(2i)"] = ""
params[:id]["start_at(3i)"] = ""
params[:id]["start_at(4i)"] = ""
params[:id]["start_at(5i)"] = ""
end
end
4) When I try as below (use nil instead of ""), error is appeared.
error
IndexError (string not matched):
app/controllers/events_controller.rb:106:in `[]='
app/controllers/events_controller.rb:106:in `blank_time'
controller
before_action :blank_time, only: [:update]
def update
#event = Event.find(params[:id])
if #event.update(event_params)
flash[:success] = "event updated!"
redirect_to root_url
else
render 'edit'
end
end
private
def blank_time
if params[:id]["start_at(4i)"].blank? or params[:id]["start_at(5i)"].blank?
params[:id]['start_at(1i)'] = nil
params[:id]["start_at(2i)"] = nil
params[:id]["start_at(3i)"] = nil
params[:id]["start_at(4i)"] = nil
params[:id]["start_at(5i)"] = nil
end
end
It would be appreciated if you could give me any advice.
UPDATE
Although I change the edit in events_controller.rb as below, the error ActiveModel::MissingAttributeError (can't write unknown attribute 'start_at(4i)'): is displayed.
def edit
#room = Room.find(params[:room_id])
#event = #room.events.find(params[:id])
#event['start_at(4i)'] = #event.start_at.split(':')[0] #the error occur here
#event['start_at(5i)'] = #event.start_at.split(':')[1]
end
My idea works like this:
Migration:
class CreateTests < ActiveRecord::Migration[5.0]
def change
create_table :tests do |t|
t.string :time
t.timestamps
end
end
end
Form:
<%= form_for(test) do |f| %>
<% if test.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(test.errors.count, "error") %> prohibited this test from being saved:</h2>
<ul>
<% test.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :time %>
<%= f.time_select :time, include_blank: true, ampm: false %><br>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Controller:
This will save : when value send are null where you can use conditional to check if parameters are null or and set value of time. //It's consuming much time and I skipped for you to complete.
def create
#test = Test.new(test_params)
#time = (params[:test]['time(4i)']).to_s
#time_ampm = (params[:test]['time(5i)']).to_s
#test.time = #time+":"+ #time_ampm
respond_to do |format|
if #test.save
format.html { redirect_to #test, notice: 'Test was successfully created.' }
format.json { render :show, status: :created, location: #test }
else
format.html { render :new }
format.json { render json: #test.errors, status: :unprocessable_entity }
end
end
end
For updating
def edit
#test = Test.find(params[:id])
#test['time(4i)'] = #test.time.split(':')[0]
#test['time(5i)'] = #test.time.split(':')[1]
end
def update
#test = Test.find(params[:id])
#time = (params[:test]['time(4i)']).to_s
#time_ampm = (params[:test]['time(5i)']).to_s
#test.time = #time+":"+ #time_ampm
#test.update(test_params)
end
Assigning #event.starts_at to nil does nothing as the attributes in #event_params is used when calling #update, overwriting your initial assignment.
Overwriting the starts_at attribute in your params should work instead.
def update
#event = Event.find(params[:id])
if event_params["start_at(4i)"].blank? or event_params["start_at(5i)"].blank?
event_params = event_params.reject { |k, v| k.starts_with? 'starts_at' }
.merge(starts_at: nil)
end
if #event.update(event_params)
flash[:success] = "event updated!"
redirect_to root_url
else
render 'edit'
end
end
The following line finds and remove the parameters for starts_at(1i) to starts_at(5i), then sets the whole starts_at attribute to be nil:
event_params.reject { |k, v| k.starts_with? 'starts_at' }.merge(starts_at: nil)
My form needs to customize before submitting to the rails. SO, I use ajax to submit the form, but every time, rails doubles records. Anyone has any idea how it happens.
my controller here:
def create
#school_current = School.find_by_id(params[:school_id])
#quiz = Quiz.new(params[:quiz])
#quiz.from_params(params)
questions = params[:quiz][:questions_attributes]
questions.each do |index, question|
#quiz.questions.build(question)
end
respond_to do |format|
if #quiz.save
format.html { render nothing: true }
else
format.html { render action: "new" }
end
end
end
def from_params(params)
self.name = params[:quiz][:name]
self.school_id = params[:school_id]
self.description = params[:quiz][:description]
end
form:
<div id="quizzes">
<%= form_for([#school_current, #quiz], :remote => true) do |f| %>
<div class="form-inputs" id="quiz_body">
<div class="form-group">
<label>Quiz Name (required)</label>
<%= f.text_field :name, class: "form-control", placeholder: "Enter quiz name here .." %>
</div>
<div class="form-group">
<label>Quiz Description (optional)</label>
<%= f.text_area :description, :rows => 10 , :cols => 10, class: "form-control", placeholder: "Enter quiz description here .." %>
</div>
</div>
<div id="question_list">
<ol>
<%= f.fields_for :questions do |builder| %>
<li class="question_field_item"><%= render "question_fields", f: builder %></li>
<% end %>
</ol>
</div>
<div class="form-actions">
<%= f.submit "save quiz", :data => {disable_with: "Saving ..."},class: "btn btn-primary text-uppercase", id: "save_quiz" %>
</div>
<% end %>
</div>
Make sure, single AJAX request is hitting the server
the following code block might be the culprit.
respond_to do |format|
if #quiz.save
format.html { render nothing: true }
else
format.html { render action: "new" }
end
end
Try extracting the saving from respond_to's block.
if #quiz.save
respond_to do |format|
format.html { render nothing: true }
end
else
respond_to do |format|
format.html { render action: "new" }
end
end
i figured out the problem. Call question.build twice in both new and create. That's why rails duplicates the record. Remove these lines of code. It will work.
questions.each do |index, question|
#quiz.questions.build(question)
end