Rails checkbox checked updates shipping address while using form object pattern - ruby-on-rails

So I have a form that I'm trying to allow the user to copy over text from billing to shipping address. Unfortunately at the moment it's automatically saving billing address in the shipping address.
My form looks like the following (albeit truncated for massive amount of fields):
=form_for #customer, url: create_customer_path, html: {class: 'new-customer} do |f|
.row
.col-md-4
= f.label :first_name
.col-md-8
= f.text_field :first_name, class: 'form-control', required:true
.row
.col-md-4
= f.label :billing_address1
.col-md-8
= f.text_field :billing_address1, class: 'form-control', required:true
.row
.col-md-12
= f.check_box :shipping_is_billing
= f.label :shipping_is_billing, 'Same as billing address'
.row
.col-md-4
= f.label :shipping_address1
.col-md-8
= f.text_field :shipping_address1
CustomersController
class CustomersController < ApplicationController
def new
#customer = CustomerForm.new
end
def create
#customer = CustomerForm.new(customer_params)
if #customer.save
redirect_to customer_success_path
else
render 'new'
end
end
private
def customer_params
params.require(:customer_form).permit!.tap do |p|
p[:captcha_response] = params['g-recaptcha-response']
end
end
end
CustomerForm (truncated for massive fields)
class CustomerForm
include ActiveModel::Model
CUSTOMER_ATTRS = %w[
first_name
].freeze
ADDRESS_ATTRS = %w[
address1 address2 city state zip
].freeze
attr_accessor(*CUSTOMER_ATTRS)
attr_accessor(*ADDRESS_ATTRS.map { |attr| 'billing_' + attr })
attr_accessor(*ADDRESS_ATTRS.map { |attr| 'shipping_' + attr })
attr_accessor :confirm_email, :captcha_response, :shipping_is_billing
validates :first_name, presence: true
validates :billing_address1, presence: true
validates :shipping_address1, presence: true, unless: :shipping_is_billing
def save
return false unless valid?
persist!
end
private
def captcha_passes
captcha = Captcha.new
return if captcha.valid?(captcha_response)
errors.add(:captcha_response, 'is invalid')
end
def persist!
customer = Customer.new(attrs_for_customer)
customer.billing_address = CustomerAddress.new(attrs_for_address('billing_'))
customer.shipping_address = CustomerAddress.new(
attrs_for_address(shipping_is_billing ? 'billing_' : 'shipping_')
)
customer.save!
customer
end
def attrs_for_customer
Hash[
CUSTOMER_ATTRS.map { |attr| [attr, send(attr)] }
]
end
def attrs_for_address(prefix)
Hash[
ADDRESS_ATTRS.map { |attr| [attr, send(prefix + attr.to_s)] }
]
end
end
JS
app.newCustomer = () => {
function init() {
let check = document.querySelector('#customer_form_shipping_is_billing')
check.addEventListener('change', toggledUseBilling)
}
let toggledUseBilling = event => {
shippingFields().forEach(field => {
if(event.target.checked) {
field.value = null;
field.removeAttribute('required');
field.setAttribute('disabled', true);
} else {
field.setAttribute('required', true);
field.removeAttribute('disabled');
}
})
}
let shippingFields = () => {
let selectors = [
'#customer_form_shipping_address1',
'#customer_form_shipping_address2',
'#customer_form_shipping_city',
'#customer_form_shipping_state',
'#customer_form_shipping_zip',
]
return document.querySelectorAll(selectors.join(', '));
}
return init();
}
Under the persist! method I'm using the ternary operator on the checkbox to determine the attributes for the address to be billing/shipping. But it doesn't look like it's actually working. How do I grab from the form the checkbox being marked?
Here are the things I've tried:
Switched = f.check_box :shipping_is_billing to =check_box_tag :shipping_is_billing. Then I had to update my JS to make sure I was grabbing the right checkbox. This stores the shipping data but when checked the business data isn't being copied.
Attempted to switch attrs_for_address(shipping_is_billing ? 'billing_' : 'shipping_') to attrs_for_address(shipping_is_billing ? 'shipping_' : 'billing_'). If I use the check_box this will populate the data over to shipping but the checkbox becomes ineffectual.
Put a form_tag around the check_box_tag but this actually stripped out the check box
Changed the check_box to check_box_tag, updated my JS to look for the correct ID on the checkbox. Can save the shipping address if typed in but if checking the box it does not apply the billing address and I get prompts that the shipping address can't be blank

Did a binding.pry within the persist! method. Looks like shipping_is_billing is pulling a string which is hitting truthy. Changed to
customer.shipping_address = CustomerAddress.new(
attrs_for_address(shipping_is_billing == "1" ? 'billing_' : 'shipping_')

Related

Convert string into integer in rails

I'm creating a form_for in which one of the field fetches the drop-down list from the database. I'm interpolating the data to display the string but I want to store its id back into other database which is linked with my form.
class FlightsController < ApplicationController
def new
#flight = Flight.new
#airplane = #flight.airplane
#options = Airport.list
end
def create
#flight = Flight.new(flight_params)
if #flight.save!
flash[:success] = "Flight created successfully."
redirect_to #flight
else
flash[:danger] = "Flight not created."
redirect_to :new
end
end
private
def flight_params
params.require(:flight).permit(:name, :origin, :destination, :depart, :arrive, :fare, :airplane_id)
end
end
<%= form_for(#flight) do |f| %>
...
<div class="row">
<div class="form-group col-md-6">
<%= f.label :origin %>
<%= f.select :origin, grouped_options_for_select(#options), { include_blank: "Any", class: "form-control selectpicker", data: { "live-search": true } } %>
</div>
</div>
...
<% end %>
class Airport < ApplicationRecord
def self.list
grouped_list = {}
includes(:country).order("countries.name", :name).each do |a|
grouped_list[a.country.name] ||= [["#{a.country.iso} #{a.country.name}", a.country.iso]]
grouped_list[a.country.name] << ["#{a.iata} #{a.name} (#{a.city}, #{a.country.name})", a.id]
end
grouped_list
end
end
class Flight < ApplicationRecord
belongs_to :origin, class_name: "Airport"
belongs_to :destination, class_name: "Airport"
belongs_to :airplane
has_many :bookings, dependent: :destroy
has_many :passengers, through: :bookings
end
The following error is showing,
Airport(#69813853361360) expected, got "43" which is an instance of String(#47256130076180)
The output of Airport.list when run in a console is shown below:
=> {"India"=>[["IN India", "IN"], ["AGX Agatti Airport (Agatti, India)", 3], ["IXV Along Airport (Along, India)", 5], ["AML Aranmula International Airport (Aranmula, India)", 6], ["IXB Bagdogra International Airport (Siliguri, India)", 50]]}
Parameters: {"utf8"=>"✓", "authenticity_token"=>"+Z8+rkrJkkgaTznnwyTd/QjEoq3kR4ZmoUTp+EpM+320fNFg5rJm+Izx1zBODo/H7IIm3D+yg3ysnVUPmy7ZwQ==", "flight"=>{"name"=>"Indigo", "origin"=>"49", "destination"=>"11", "depart"=>"2019-02-21T21:30", "arrive"=>"2019-02-22T01:30", "fare"=>"2500", "airplane_id"=>"3"}, "commit"=>"Create Flight"}
I tried using to_i but it didn't work.
if you're interpolating a string with space delimiter you can try this.
'1 one'.split(' ').first.to_i
grouped_options_for_select is sending a.id as string value. Convert it to integer in your create action.
def create
#flight = Flight.new(flight_params)
#flight.origin = #flight.origin.to_i ## <== add this line
if #flight.save!
...
Convert string into integer in rails:
user_id = "123"
#user_id = user_id.to_i
puts #user_id
#user_id = 123
Convert integer into string in rails:
user_id = 456
#user_id = user_id.to_s
puts #user_id
#user_id = "456"
Convert column type string into integer in rails migration :
def change
change_column :webinars, :user_id, :integer, using: 'user_id::integer'
end
Convert column type integer into string in rails migration:
def change
change_column :webinars, :user_id, :string, using: 'user_id::string'
end
Your problem is not integer versus string, your problem is (and the error is telling you) the field is expecting an Airport object, but it's getting an airport id...
<%= f.select :origin, grouped_options_for_select(#options), { include_blank: "Any", class: "form-control selectpicker", data: { "live-search": true } } %>
You're trying to select origin which is an airport object. You really are just returning the ID of an airport object (origin_id).
Change it to
<%= f.select :origin_id, grouped_options_for_select(#options), { include_blank: "Any", class: "form-control selectpicker", data: { "live-search": true } } %><%= f.select :origin, grouped_options_for_select(#options), { include_blank: "Any", class: "form-control selectpicker", data: { "live-search": true } } %>
And don't forget to update your flight_params
def flight_params
params.require(:flight).permit(:name, :origin_id, :destination, :depart, :arrive, :fare, :airplane_id)
end
I would guess you'll have a similar problem with destination

Ruby on Rails error when validates_uniqueness_of using collection_select

First, sorry for my bad English. I'm still learning.
I have 3 tables in my DB:
Problem
has_many :registers
has_many :solutions, through : :registers
Solution
has_many :problems
has_many :problems, through : :registers
Register
belongs_to: problem
belongs_to :solution
The system is working well. I am able to insert new data in all of the 3 tables.
In the views for the table/model Register, to select problems and solutions, I make use of collection_select, like this:
= form_for #register do |f|
.field
= f.label :problem_id
= collection_select( :register, :problem_id, #problems, :id, :name, {}, { :multiple => false })
.field
= f.label :solution_id
= collection_select( :register, :solution_id, #courses, :id, :name, {}, { :multiple => false })
.field
= f.label :entry_at
= f.datetime_select :entry_at
.actions = f.submit
The problem only appears when I try to add this validation to Register:
validates_uniqueness_of :student_id , scope: :course_id
Then I get:
> undefined method `map' for nil:NilClass
> = collection_select( :register, :problem_id, #problems, :id, :name, {}, { :multiple => false })
And I dont know why.
So, I tried to do the validation by the controller:
def create
#register = Register.new(register_params)
problem_id = #register.problem_id
solution_id = #register.solution_id
if Register.exists?(['problem_id LIKE ? AND solution_id LIKE ?', problem_id, solution_id ])
render 'new'
else
#register.save
respond_with(#register)
end
end
But the error remains.
I believe that the cause is the collection_select, but I don't know how to solve it.
Saying one more time, I am able to persist date in all the 3 DB tables. But when I try to avoid duplication, the error appears.
This is how I solve this problem:
def create
#register = register.new(register_params)
#if #register.save
# respond_with(#register)
#else
# #register = register.all
# render :new
#end
problem_id = #register.problem_id
solution_id = #register.solution_id
if register.exists?(['problem_id LIKE ? AND solution_id LIKE ?', problem_id, solution_id ])
#register = register.new
#solutions = Solution.all
#problems = Problem.all
flash[:error] = "Problem alread in the register for this solution"
render 'new'
else
#register.save
respond_with(#register)
end
end

Bootstrap 3 Datetimepicker with Rails form_for

I am a new Rails developer and I am having trouble using the bootstrap3 datetimepicker together with my rails form_for to save to a DateTime attribute in my ScheduledAccess Model. The one I am using is from http://eonasdan.github.io/bootstrap-datetimepicker/
Code:
.form-group
= f.label :start_time
datetimepicker1.input-group.date
= f.text_field :start_time, class:"form-control", id:"scheduled_access_start_time"
span.input-group-addon
span.glyphicon.glyphicon-calendar
.form-group
= f.label :end_time
datetimepicker2.input-group.date
= f.text_field :start_time, class:"form-control", id:"scheduled_access_start_time"
span.input-group-addon
span.glyphicon.glyphicon-calendar
javascript:
$(function () {
$('#datetimepicker1').datetimepicker(
{
sideBySide: true
});
$('#datetimepicker2').datetimepicker(
{
sideBySide: true
});
});
_fields.html.slim
Do note that I am able to save to my ScheduledAccess model when I use the default Rails/HTML5 datetime as shown below.
Code:
.form-group
= f.label :start_time
= f.datetime_local_field :start_time, class: "form-control"
.form-group
= f.label :end_time
= f.datetime_local_field :end_time, class: "form-control"
The following code shows how I code my ScheduledAccess controller which might be needed.
Code:
class ScheduledAccessesController < ApplicationController
before_filter :authenticate_user!
def create
#access = ScheduledAccess.new(scheduledaccess_params)
#access.user_id = current_user.id
# generate 5-digit random pin
randompin = rand(10**5)
#access.pin = randompin
if #access.save
redirect_to home_path
else
flash[:alert] = "There was some unexpected error! Please Retry!"
redirect_to new_scheduled_access_path
end
end
def destroy
end
def edit
#access = current_access
end
def new
#access = ScheduledAccess.new
end
private
def scheduledaccess_params
params.require(:scheduled_access).permit(:name,:pin, :phoneno, :start_time, :end_time, :remarks)
end
def current_access
ScheduledAccess.find(params[:id])
end
end
scheduled_accesses_controller.rb
When I use the bootstrap3 datetimepicker and try to save. I always get the flash messsage.
Any help would be appreciated! =)

Passing dynamic subject to Rails mail_form

I am using gem mail_form to handle contact in a Rails app, and I have 6 different contact forms.
I put a hidden_field_tag in the form and pass the desired subject as a variable. In the html, the value is there but the email arrives with (no subject). What can I be doing wrong?
In controllers
def where_to_buy
#contact = Contact.new
#the_subject = "Where to buy"
end
In contact form
= form_for #contact do |f|
= render "form", f: f
= f.text_area :message
.hide
= f.text_field :nickname, hint: 'Leave this field empty!'
= hidden_field_tag :mail_subject, #the_subject
= f.submit "Send Message"
In model
class Contact < MailForm::Base
attribute :mail_subject
attribute :first_name, validate: true
attribute :last_name, validate: true
attribute :message, validate: true
attribute :nickname, captcha: true
def headers
{
subject: %(#{mail_subject}),
to: "jorge#email123.com",
from: %("#{first_name} #{last_name}" <#{email}>)
}
end
end
Output html in chrome:
<input type="hidden" name="mail_subject" id="mail_subject" value="Where to buy">
Instead of:
= hidden_field_tag :mail_subject, #the_subject
You will want to use:
= f.hidden_field :mail_subject, value: #the_subject
If you inspect the parameters that are logged into your development.log you will see why.
When you use hidden_field_tag the mail_subject is defined as its own independent parameter and will not be included in the contact hash. You'll have something like:
params = { "contact" => { "message" => "text here", ... }, "mail_subject => "Where to buy" }
But when you use f.hidden_field the mail_subject will be included in the contact hash. You'll have something like:
params = { "contact" => { "message" => "text here", "mail_subject => "Where to buy", ... } }
And then when you call Contact.new(params[:contact]) the new contact object will get the mail_subject value.

form_for without ActiveRecord, form action not updating

I'm using an API instead of a database, so I'm not using ActiveRecord but ActiveModel (I mostly did like here: railscasts.com/episodes/219-active-model)
Thing is, when I try to edit an item (in my case a parking), the action of the form still remains the action of the create and not update.
so when I go on /parkings/2/edit to edit a parking, the form is still:
<form accept-charset="UTF-8" action="/parkings" class="form-horizontal" id="new_parking" method="post">
when it should be more like that with the put hidden field and the parkings/2 as the action:
<form accept-charset="UTF-8" action="/parkings/2" class="form-horizontal" id="edit_parking" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="✓" /><input name="_method" type="hidden" value="put" />
Anybody knows where the method & action of the form_for is set according to the route? What I'm trying to do is be as close as if I was using ActiveRecord with a database.
Here is some code :
_form.html.erb
<%= form_for(#parking, :html => { :class => "form-horizontal" }) do |f| %>
...
<% end %>
edit.html.erb & new.html.erb, simply has
<%= render 'form' %>
Controller
class ParkingsController < ApplicationController
def index
#parkings = Parse.get("Parking")
respond_to do |format|
format.html
format.json { render :json => #parking }
end
end
def new
#parking = Parking.new
respond_to do |format|
format.html
format.json { render :json => #parking }
end
end
def edit
#parking = Parking.find(params[:id])
respond_to do |format|
format.html
format.json { render :json => #parking }
end
end
def create
#parking = Parking.new(params[:parking])
if (#parking.save)
flash[:success] = "Parking was just added!"
redirect_to :action => "new"
else
render :action => "new"
end
end
def update
# Testing
parking = Parse.get("Parking", params[:id])
parking.delete("updatedAt")
parking["name"] = params[:parking][:name]
parking.save
redirect_to :action => "index"
end
Model
class Parking
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :address, :city, :longitude, :latitude, :contributor_name, :contributor_email
validates_presence_of :name, :address, :city, :longitude, :latitude
#id = nil
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def self.find(id)
#id = id
raw = Parse.get("Parking", #id.to_s)
parking = Parking.new
parking.name = raw["name"]
parking.address = raw["address"]
parking.city = raw["city"]
parking.longitude = raw["location"]["longitude"]
parking.latitude = raw["location"]["latitude"]
parking.contributor_name = raw["contributorName"]
parking.contributor_email = raw["contributorEmail"]
return parking
end
def save
if (!valid?)
return false
else
parking = Parse::Object.new("Parking")
data =
{
:longitude => longitude.to_f,
:latitude => latitude.to_f
}
point = Parse::GeoPoint.new(data)
parking["location"] = point
parking["name"] = name
parking["address"] = address
parking["city"] = city
parking["contributorName"] = contributor_name
parking["contributorEmail"] = contributor_email
if (parking.save)
return true
end
end
end
def persisted?
false
end
end
Please note that the create is working and if I add the id of my parking in the form action="" using the Web Inspector or Firebug, and add :method => "put" in my form_for, my record successfully update.
The real problem here is really the form_for action & method who doesn't get updated when I'm editing a parking and remains like if I was adding a new one.
I'm still learning Rails, so sorry if some infos aren't clear!
Thank you!
--- SOLUTION ---
persisted? shouldn't only return false, and my model needed to define a method that returns the id of the object (so they can update the action="") so here's is my updated model:
class Parking
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :objectId, :name, :address, :city, :longitude, :latitude, :contributor_name, :contributor_email
validates_presence_of :name, :address, :city, :longitude, :latitude
#id = nil
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def self.find(id)
raw = Parse.get("Parking", id.to_s)
parking = Parking.new
parking.objectId = id
parking.name = raw["name"]
parking.address = raw["address"]
parking.city = raw["city"]
parking.longitude = raw["location"]["longitude"]
parking.latitude = raw["location"]["latitude"]
parking.contributor_name = raw["contributorName"]
parking.contributor_email = raw["contributorEmail"]
return parking
end
def save
if (!valid?)
return false
else
parking = Parse::Object.new("Parking")
data =
{
:longitude => longitude.to_f,
:latitude => latitude.to_f
}
point = Parse::GeoPoint.new(data)
parking["location"] = point
parking["name"] = name
parking["address"] = address
parking["city"] = city
parking["contributorName"] = contributor_name
parking["contributorEmail"] = contributor_email
if (parking.save)
return true
end
end
end
def update_attributes(aParking)
parking = Parse.get("Parking", #id.to_s)
parking.delete("updatedAt")
parking["name"] = aParking["name"]
parking.save
return true
end
def destroy
parking = Parse.get("Parking", #id)
#parking.parse_delete
end
def id
return self.objectId
end
def persisted?
!(self.id.nil?)
end
end
I think your problem is in your model's persisted? method. Since it always returns false, Rails always thinks it's building a form for a newly created record, so it uses POST and submits to the collection URL.
You need some sort of logic in that method so that existing records return true and new records return false.
Hi friend you can to tell the form builder which method to use.So try
<%= form_for(#parking, :method => ["new", "create"].include?(action_name) ? :post : :put,
:html => { :class => "form-horizontal" }) do |f| %>
...
<% end %>
If you are not using ActiveRecord you should use 'form_tag' instead 'form_for'

Resources