Dynamic form with jquery and calculations server side - javascript

I have a rails app that generates quotes to rent out meeting rooms using forms. Each quote requires a number of inputs pulled from different models in the database (room, rate) and some other inputs specific to each quote (hours, extras) that are filled directly into the quote form.
With all that data I run some calculations in the model to get the prices of a particular meeting room for a specific day.
I'm trying to show the results of those calculations dynamically on the same page, next to the form.
For that, I have:
-> A form, with nested fields.
<%= form_for(#quote) do |f| %>
<%= f.text_field :hours, class: "form-control" calculation %>
<%= f.text_field :extras, class: "form-control calculation" %>
.
.
<%= f.fields_for :contact, #quote.contact do |contact_form| %>
<%= contact_form.text_field :email, class: "form-control"%>
<% end %>
.
.
<%= f.fields_for :room, #quote.room do |room_form| %>
<%= room_form.collection_select :room_id, Room.all, :id, :name, class: "form-control" %>
<% end %>
.
.
.
<%= f.submit class: "btn btn-default btn-primary" %>
<% end %>
-> Some logic in the Quote model
class Quote < ActiveRecord::Base
belongs_to :contact
belongs_to :client
belongs_to :room
has_one :booking
belongs_to :rate
accepts_nested_attributes_for :contact
accepts_nested_attributes_for :booking
validates :rate_id, presence: true
validates :contact_id, presence: true
validates :room_id, presence: true
def calculate_quote
(half_day_price +
full_day_price +
extended_day_price +
hours_price) /
calculate_discount
end
def calculate_discount
if self.rate
self.rate.discount_rate / 100
else
return 1
end
.
.
.
end
-> A method in the controller that should create a json response with the result of those calculations.
class QuotesController < ApplicationController
before_action :set_quote, only: [:show, :edit, :update, :destroy, :pricing]
def pricing
respond_to do |format|
format.json { render json: #quote, methods: [:calculate_quote, :half_day_price], only: [:id, :calculate_quote, :half_day_price, :full_day_price] }
end
end
.
.
.
-> And everything into params
def quote_params
params.require(:quote).permit(:half_days, :full_days, :extended_days, :extras, :rate, :discount, :rate_id, contact_attributes: [:id, :name, :email, :phone], booking_attributes: [:id, :start_date, :end_date, :start_time, :finish_time])
end
-> And a js file that should pick up data from that json:
$(function(){
$(".calculation").change(function(){
var rate = $("#quote_rate_id").val();
var room = $("#quote_room_room_id").val();
var half_days = $("#quote_half_days").val();
var full_days = $("#quote_full_days");
var extended_days = $("#quote_extended_days");
var extras = $("#quote_extras");
var discount = $("#quote_discount");
jQuery.getJSON(url? + "/pricing", { rate: rate, room: room, half_days: half_days, full_days: full_days, extended_days: extended_days, hours: hours, extras: extras, discount: discounti}, function(data){
$("#half-days-price").text(data.half_days);
$("full-days-price").text(data.full_days);
});
});
});
Following this approach I'm stuck trying to find the right routes and urls to have this form working under quotes/new while passing the id of the quote object in json before it's saved. And that makes me wonder, is this the right approach to begin with? Should I be following a different path?
I would appreciate some fresh ideas.

Related

How to set up a order form with options belonging to a product_category

Goal
I would like to set up an order form, where users can order one product. After having filled out the product_category, a user can select
the product belonging to the product_category
the quantity of each option belonging to the product_category.
Current state
The way I currently set up my code, causes issues when the form has to be built up again:
when a validation issue is triggered, (1) product_category, (2) product and (3) options are empty, but also the reservation still saves, thereby creating situation where a reservation is saved twice.
=> I know it's because I first save the reservation in the controller and then the options, but I don't know how to solve this (e.g. it is saved when the validation is triggered and when the user has filled in the form properly afterwards).
Code
models
class Order < ApplicationRecord
belongs_to :store
belongs_to :product
has_many :order_options, dependent: :destroy
has_many :options, through: :order_options
accepts_nested_attributes_for :order_options
end
class OrderOption < ApplicationRecord
belongs_to :option
belongs_to :order
accepts_nested_attributes_for :option
end
class Option < ApplicationRecord
belongs_to :product_category
has_many :order_options, dependent: :destroy
has_many :orders, through: :order_options
end
class ProductCategory < ApplicationRecord
belongs_to :store
has_many :products, dependent: :destroy
accepts_nested_attributes_for :products, allow_destroy: true
has_many :options, dependent: :destroy
accepts_nested_attributes_for :options, allow_destroy: true
end
order_controller
class OrdersController < ApplicationController
# skip_before_action :authenticate_user!
def new
#user = current_user
#store = Store.find(params[:store_id])
#order = Order.new
#order.build_order_contact
#product_category_list = #store.product_categories
#all_options = #store.options
#products = []
#options = []
if params[:product_category].present?
#products = ProductCategory.find(params[:product_category]).products
#options = ProductCategory.find(params[:product_category]).options
else
end
if request.xhr?
respond_to do |format|
format.json {
render json: {products: #products, options: #options}
}
format.js
end
end
authorize #order
end
def create
#user = current_user
#store = Store.find(params[:store_id])
#order = Order.new(order_params)
#order.store = #store
authorize #order
if #order.save
params[:order_options_attributes].each do |order_option|
if #option = Option.find_by(id: order_option[:option_id])
#option_quantity = order_option[:option_quantity]
#order.options << #option
order_option = #order.order_options.where(option: #option)
order_option.update(option_quantity: #option_quantity)
end
end
redirect_to store_path(#store)
else
#product_category_list = #store.product_categories
render 'new'
end
end
views/orders/new.js
$("#product_options").html("<%= escape_javascript(render partial: 'option_fields', collection: #options) %>");
$("#dynamic-products").empty();
<% #products.each do |pro| %>
$("#dynamic-products").append('<option value="<%= pro.id %>"><%= pro.name %></option>')
<% end %>
views/orders/new.html.erb
<%= simple_form_for [#store, #order] do |f|%>
<%= f.simple_fields_for :products do |product| %>
<%= product.input :product_category, collection: #product_category_list, prompt: "Select type of product", label:false,
input_html:{
id: "product_category"
}%>
<%= f.association :product, collection: #products, input_html:{
value: #products.object_id,
id: "dynamic-products"
} %>
<div class="product_category-options" id="product_options">
</div>
<% end %>
<% end %>
<script >
// dynamic products and options for change category
$(document).on("change", "#product_category", function(){
var product_category = $(this).val();
$.ajax({
url: "/stores/<%= #store.id %>/orders/new",
method: "GET",
// dataType: "json",
dataType: "script",
data: {product_category: product_category},
error: function (xhr, status, error) {
console.error('AJAX Error: ' + status + error);
},
success: function (response) {
}
});
});
// dynamic products and option for releading form (e.g. new)
$(document).ready(function(){
var product_category = $("#product_category").val();
$.ajax({
url: "/stores/<%= #store.id %>/orders/new",
method: "GET",
dataType: "json",
data: {product_category: product_category},
error: function (xhr, status, error) {
console.error('AJAX Error: ' + status + error);
},
success: function (response) {
}
});
});
</script>
views/orders/_option_fields.html.erb
<div class="product_option order-form-quantity-row border-bottom col col-sm-10">
<div class="product_option_name order-form-quantity-name">
<strong> <%= option_fields.name %></strong>
</div>
<div class="order-form-input">
<%= hidden_field_tag("order_options_attributes[]option_id", option_fields.id ) %>
<%= select_tag("order_options_attributes[]option_quantity", options_for_select((0..9)), {class:'form-control col col-sm-12'} ) %>
</div>
</div>
This is very over-complicated and missguided. All you really need is something like:
<%= simple_form_for([#store, #order]) do |f| %>
<% f.simple_fields_for(:order_options) do |ff| %>
<%= ff.association :option %>
<%= ff.input :option_quantity %>
<% end %>
# ...
<% end %>
class OrdersController
# Use callbacks to DRY your code
before_action :set_store, only: [:new, :create, :index]
def new
#order = #store.order.new
# seed the record to create the inputs
5.times { #order.order_options.build }
authorize #order
end
def create
#order = #store.orders.new(order_params) do |order|
order.user = current_user
end
if #order.save
redirect_to #order.store
else
render :new
end
end
def set_store
#store = Store.find(params[:store_id])
end
def order_params
params.require(:order)
.permit(:foo, :bar,
order_options_attributes: [:option_id, :option_quantity]
)
end
end
You don't need to accept nested attributes for the option unless you are letting users create them on the fly which does not seem like a good idea since you already have 100 levels too much of complexity in a single component.
You also don't need to ever do params[:order_options_attributes].each do |order_option| and iterate through the nested attributes. Really DON'T ever do this as it defeats the whole purpose of using nested attributes in the first place.
When you use the order_options_attributes= setter created by accepts_nested_attributes Rails will handle assigning the attributes to new instances of order_options and will do it before the record is saved. When you call save it will persist everything at once in a transaction which avoids most of the issues you have.
You can use validates_associated to trigger the validations on the order_options before saving.
If you then want to to use AJAX to spruce it up feel free. But you should really start by just setting up something simple and synchronous so that you understand how nested attributes work.
In general this code seems to be suffering from going to fast. Start by setting up just the basics (ie just creating an order for a product). Test it - refactor - and then add more features. If you try to do everything at once you usually end up with a dumpster fire.

When I try to register for an account on my website, I am returned with the error "Plan must exist"

After submitting an email and password through my RoR site, I am prompted with an error message stating that
"the plan doesn't exist. "
User Class
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
belongs_to :plan
attr_accessor :stripe_card_token
def save_with_subscription
if valid?
customer = Stripe::Customer.create(description: email, plan: plan_id, card: stripe_card_token)
self.stripe_customer_token = customer.id
save!
end
end
end
Contacts Controller
class ContactsController < ApplicationController
# Get request to /contact-us
# show new contact form
def new
#contact = Contact.new
end
# Post request /contacts
def create
# Mass assignment of form fields into Contact object
#contact = Contact.new(contact_params)
# save the Contact object to the database
if #contact.save
# Store form fields via parameters. into variables
name = params[:contact][:name]
email = params[:contact][:email]
body = params[:contact][:comments]
# Plug variables into Contact Mailer email method and send email
ContactMailer.contact_email(name, email, body).deliver
# Store success message in flash hash
# redirect to the new action
flash[:success] = "Message sent."
redirect_to new_contact_path
else
# If Contact object doesnt save,
# store errors in a flash hash
# and redirect to the new action
flash[:danger] = #contact.errors.full_messages.join(", ")
redirect_to new_contact_path
end
end
private
# To collect data from form, we need to use
# strong parameters and whitelist the form fields
def contact_params
params.require(:contact).permit(:name, :email, :comments)
end
end
Registrations
class Users::RegistrationsController < Devise::RegistrationsController
def create
super do |resource|
if params[:plan]
resource.plan_id = params[:plan]
if resource.plan_id == 2
resource.save_with_subscription
else
resource.save
end
end
end
end
end
Basic Form
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= devise_error_messages! %>
<div class="field form-group">
<%= f.label :email %>
<%= f.email_field :email, autofocus: true, class: 'form-control' %>
</div>
<div class="field form-group">
<%= f.label :password %>
<% if #minimum_password_length %>
<em>(<%= #minimum_password_length %> characters minimum)</em>
<% end %>
<%= f.password_field :password, autocomplete: "off", class: 'form-control' %>
</div>
<div class="field form-group">
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control' %>
</div>
<div class="actions form-group">
<%= f.submit "Sign up", class: 'btn btn-success' %>
</div>
<% end %>
I am unsure what else I may need to add. I am doing the upskill course for fun. Before adding the plans ids using the stripe plugin I was able to create an account just fine. I tried searching around on stackoverflow for a solution, but nothing came about. I am kindof at a dead end and cannot proceed until I finish this part out. The premium subscription part of my site also does not work, but that can be fixed after I do this.
If anyone has any reccomendations on what I can do to fix this it would be greatly appriciated, or what files I will add in order for you to reach your answer.
Thank you.
plan.rb
class Plan < ActiveRecord::Base
has_many :users
end
This is the issue
customer = Stripe::Customer.create(description: email, plan: plan_id, card: stripe_card_token)
Seems like the plan_id you are sending doesn't exist on Stripe.
Have you created those plans on stripe?

select user and then dropdown of his challenges

A user has_many challenges.
When a user is selected...
<%= f.select :user_id, options_for_select(#challengers.collect { |challenger| [challenger.full_name] }) %>
... how can we show another dropdown with a list of his challenges?
<%= f.select :challenge_id, options_for_select(#challenger_challenges.collect { |challenged| [challenged.full_challenge]}) %>
In other words, how can we make "#challenger_challenges = the selected user's challenges"?
As it stand I get an error undefined method 'collect' for nil:NilClass since #challenger_challenges is nil.
OPTION 1
In challenges_controller I could do this:
#challengers = User.all
#challenger = User.find(params[:challenger_selected]) if (params[:challenger_selected]).present?
#challenger_challenges = #challenger.challenges
And then I would just need a way to refresh the page once a user is selected so that the user ID is passed in the params as :challenger_selected
OPTION 2
Achieve the aim of this question without the need of a page refresh. *Preferable
UPDATE
Based upon the comments below I realize I need to elaborate.
A user has_many challenges.
A user can create a duel.
In a duel there are two duelers.
The creator of the duel selects his own :challenge_id and then he selects the other dueler as well as one of his :challenge_id and then sets the #duel.consequence the dueler will have to do if he fails his challenge. The other dueler will get a duel request notification and then has the choice to accept or decline the conditions of the duel.
challenges.show.html.erb
<%= render 'duels/form' %>
duels/_form.html.erb
<%= simple_form_for(#duel) do |f| %>
<%= f.fields_for :duelers do |dueler| %>
<%= f.hidden_field :challenge_id, :value => #challenge.id %>
<%= #challenge.full_challenge %>
<% end %>
<%= f.fields_for :duelers do |dueler| %>
<%= render 'duels/dueler_fields', :f => dueler %>
<% end %>
<%= button_tag(type: 'submit', class: "btn", id: "challenge-create-save") do %>
Request Duel
<% end %>
<% end %>
duels/_dueler_fields.html.erb
<%= f.select :user_id, options_for_select(#challengers.collect { |challenger| [challenger.id] }) %>
# Trying to make this responsive to the user that is selected above
<%= render 'challenges/select', :f => f %>
<script>
$('#duel_duelers_attributes_1_user_id').change(function () {
var challenger_id = $(this).find(":selected").val();
var address = "<%= select_path %>".concat(challenger_id);
$.get(address, function(data) {
$("#duel_duelers_attributes_1_challenge_id").html(data);
});
});
</script>
routes
get 'challenges/select/:id' => 'challenges#select', as: 'select'
challenges/_select.html.erb
<%= f.select :challenge_id, options_for_select(#challenger_challenges.collect { |challenged| [challenged.full_challenge]}) %>
challenges_controller
def select
if (params[:challenger_id]).present?
#challenger = User.find(params[:challenger_id])
else
#challenger = User.find(1)
end
#challenger_challenges = #challenger.challenges
end
Credit for this should go to #Fallenhero - I am just explaining it in more detail.
You need to be able to identify the select tag.
<%= f.select ..., :html => {:id => :first} %>
You also need somewhere to put the second one.
<div id="second"></div>
Using jQuery:
$('#first').change(function () {
var challenger_id = $(this).find(":selected").val();
var address = "<%= [prints address to new select tag] %>".concat(challenger_id);
$.get(address, function(data) {
$("#second").html(data);
});
});
The address Ruby prints out should look something like challenges/select/ depending on how you want to design it. The / at the end is important.

Prevent form being submitted until final button - avoid edit page

I know there are a lot of similar questions on SO but I haven't found one that fits what I'm trying to do...(because I'm not 100% sure it is even possible to do what I am trying to do!!)
At the moment I have a page that creates several form_tags based on how many 'questions' there are in the 'test', like this:
<div class="jumbotron">
<% #test.questions.each do |question| %>
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">Question <%= question.question %></h3>
</div>
<div class="panel-body">
<p>
<%= image_tag question.image.url(:medium) %>
</p>
<%= form_tag edit_test_testsession_path(#test, question.testsessions), id: 'submit_answer' do %>
<%= radio_button_tag :answer, "A" %> A
<%= radio_button_tag :answer, "B" %> B
<%= radio_button_tag :answer, "C" %> C
<%= radio_button_tag :answer, "D" %> D
<%= submit_tag 'Submit', class: "btn btn-success", id: 'single_submit' %>
<% end %>
</div>
</div>
<% end %>
<br/>
<%= link_to "See Test Results!", results_path(#test), class: "btn btn-lg btn-info btn-block", id: "submit_all" %>
</div>
At the moment I have disabled the 'submit' buttons because I don't want the forms to actually be submitted until all of them are completed so I have this Javascript:
$(document).ready(function() {
$('.btn.btn-success').click(function(e) {
e.preventDefault();
this.value="Resubmit";
});
$('#submit_all').click(function(){
$('#submit_answer').each(function(){
$(this).submit();
});
});
});
It does attempt to submit the answers but still tries to take the user to an edit page, but of several [:id], which obviously doesn't work...
In my TestsessionsController I have this:
class TestsessionsController < ApplicationController
def new
#test = Test.find(params[:test_id])
#testsession = Testsession.new
redirect_to action: :create
end
def create
#test = Test.find(params[:test_id])
#test.questions.each do |question|
#test.testsessions.create user: current_user, question: question
end
redirect_to action: :index
end
def index
#test = Test.find(params[:test_id])
#questions = #test.questions
# #testsession = Testsession.find(params[:id])
end
def show
#testsession = Testsession.find(params[:id])
#test = Test.find(params[:test_id])
#questions = #test.questions
end
def update
#testsession = Testsession.find(params[:id])
# #question = Question.find(params[:question_id])
#testsession.update(params.require(:testsession).permit(:answer))
redirect_to action: :results
end
def edit
#testsession = Testsession.find(params[:id])
#question = Question.find(params[:question_id])
#testsession.update(params.require(:testsession).permit(:answer))
redirect_to action: :results
end
def results
#test = Test.find(params[:id])
#testsession = Testsession.find(params[:id])
end
end
Is there any way to force the last link to take it directly to the results page, but still submit the information from the form_tags?
At the moment it is just giving me this error:
No route matches [POST] "/tests/1/testsessions/%23%3CTestsession::ActiveRecord_Associations_CollectionProxy:0x007fe197a72e40%3E/edit"
and only when I hit the back button it takes me to the results page...
EDIT
config/routes file:
Rails.application.routes.draw do
devise_for :users
root 'welcome#index'
get 'tests/:id/testsessions/new' => 'testsessions#create'
get 'tests/:id/testsessions/results' => 'testsessions#results', as: :results
resources :tests do
resources :questions
resources :testsessions
end
end
Upd I think what you was meaning is:
<%= form_tag edit_test_testsession_path(#test, #testsession), id: 'submit_answer' do %>
The problem seems to be here:
<%= form_tag edit_test_testsession_path(#test, question.testsessions), id: 'submit_answer' do %>
You are passing a collection of all associated testsessions instead of single object, and I doubt it's what you intended to do.
Please show a part of your config/routes.rb related to the controller

Rails 3 javascript: How to render a partial with parameters

I'm still getting the hang of Rails. Here I'm using Rails 3 and the goal basically is to have an AJAX call triggered when I click the subscribe button the post_form partial is rendered beneath for the topic I have just subscribed to. The button then becomes an unsubscibe button and the post_form partial is removed. The toggling of the button alone works (i.e: by removing the second line in the two immediately following snippets), but the rendering of the *post_form* partial does not.
The problem is I can't seem to get the right syntax and/or passing of parameters in the two following partials. The topic object is just not passed and I get an invalid model_name for NilClass error when clicking on the subscribe or unsubscribe button. If I refresh the page manually, the partial is rendered or hidden the correct way, so it's really just the AJAX part that isn't working right.
views/subscription/create.js.erb
$("#subscription_form").html("<%= escape_javascript(render('users/unsubscribe')) %>");
$("#post_form").html("<%= escape_javascript(render('shared/post_form', :topic => #topic)) %>");
views/subscription/destroy.js.erb
$("#subscription_form").html("<%= escape_javascript(render('users/subscribe')) %>");
$("#post_form").html("<%= escape_javascript(render('shared/post_form', :topic => #topic)) %>");
views/users/_subscription_form.html.erb
<% unless current_user?(#user) %>
<div id="subscription_form">
<% if current_user.subscribed?(#topic) %>
<%= render 'users/unsubscribe', :topic => #topic %>
<% else %>
<%= render 'users/subscribe', :topic => #topic %>
<% end %>
</div>
<% end %>
controllers/subscriptions_controller.rb
class SubscriptionsController < ApplicationController
before_filter :signed_in_user
respond_to :html, :js
def create
#topic = Topic.find(params[:subscription][:topic_id])
current_user.subscribe!(#topic)
respond_with #topic
end
def destroy
#topic = Subscription.find(params[:id]).topic
current_user.unsubscribe!(#topic)
respond_with #topic
end
end
views/shared/_post_form.html.erb
<%= form_for(#post) do |f| %>
<div class="field">
<%= f.hidden_field :topic_id, :value => #topic.id %>
<%= f.text_area :content, placeholder: "Tell us about it ..." %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
If it is of any help, the relationships are:
post -> belongs_to -> topic and topic -> has_many -> posts
Looks like you're using the variable "#post" in the "views/_post_form.html.erb" file.
<%= form_for(#post) do |f| %>
Since you aren't setting that variable anywhere in your actions you would get a null reference error.
You would need to do something like this:
def create
#post = Post.find(the_post_id)
#topic = Topic.find(params[:subscription][:topic_id])
current_user.subscribe!(#topic)
respond_with #topic
end
Also you are passing in the "topic" variable as a local but accessing it as an instance variable. You should change the your _post_form.html.erb file to look like this:
<%= form_for(#post) do |f| %>
<div class="field">
<%= f.hidden_field :topic_id, :value => topic.id %>
<%= f.text_area :content, placeholder: "Tell us about it ..." %>
</div>
<%= f.submit "Post", class: "btn btn-large btn-primary" %>
<% end %>
I don't have my ruby environment readily available so I can't verify that this will solve your problem but I think it should move you in the right direction.

Categories