Allowing both comma or dot in currency parameter - ruby-on-rails

I have this in my model:
monetize :advance_amount_cents, allow_nil: true
monetize :rental_amount_cents, allow_nil: true
I use AutoNumeric to display the currency. It sends it back to the controller like this in params:
'rental_amount' = "2050.12"
Which returns this error from the model:
activerecord.errors.models.rental_period.attributes.rental_amount.invalid_currency
It accepts the currency when I can get it to be sent with a comma instead of a dot as decimal. What is the best practise here? Ideally I would like for all attributes that are monetized to accept anything as decimal separator, comma or dot. That's also how Monetize seems to do it:
pry(main)> Monetize.parse "2050.12"
=> #<Money fractional:205012 currency:USD>
pry(main)> Monetize.parse "2050,12"
=> #<Money fractional:205012 currency:USD>
Which is perfect. How can I configure my model (or the Monetize gem in general) to accept both as params (dot or comma).

Hopefully this is of use to someone.
Model:
monetize :rental_amount_cents, allow_nil: true
View:
= f.input :rental_amount, label: 'Rental amount' do
.input-group
= text_field_tag :rental_amount, #rental_period.rental_amount, class: 'form-control', id: "#{#rental_period.new_record? ? '' : (#rental_period.id.to_s + '_')}rental_amount_rate_rendered"
= f.hidden_field :rental_amount, class: 'rate-input'
%span.input-group-addon €
Javascript setup:
$('[id$=rate_rendered]').add('.flex-rate').autoNumeric('init', settings.money_nosign).on('keyup', function() {
var $hid;
$hid = $(this).parent().find('input.rate-input');
if ($(this).autoNumeric('get') !== '') {
return $hid.val($(this).autoNumeric('get').replace('.', ','));
} else {
return $hid.val(0);
}
});
In settings I have (only relevant part:
window.settings = {
money_nosign: {
aDec: ',',
aSep: '.',
vMin: '-999999999.99'
}
};

Related

nested form fields using fields_for is giving error "hash is defined by Active Record... same name"

I'm fairly new to rails, so excuse my ignorance. But I currently have a controller which stores "Scan Results" for a theoretical virus scanner.
The Scan Model, has_many Detections, which is a record which holds a Sha256 hash and a type.
the code looks as follows
model/scan.rb
class Scan < ApplicationRecord
has_many :detections, class_name: "detection", foreign_key: "d_id", :dependent => :destroy
accepts_nested_attributes_for :detections
validates :hostname, presence: true, uniqueness: { case_sensitive: true }, length: {maximum: 50, minimum: 1}, if: :hostname_not_empty
def hostname_not_empty
if ( self.hostname != '' || self.hostname.nil? )
return true
end
return errors.add(:scansErr, "Hostname is empty")
end
end
model/detections
class Detection < ApplicationRecord
belongs_to :scan
validates :hash, presence: true, length: {maximum: 64, minimum:64}, format: { with: /\b[A-Fa-f0-9]{64}\b/ }
def new ()
end
def new_record
end
end
when I try creating a template for adding new scans to the db, I'm getting this error hash is defined by Active Record. Check to make sure that you don't have an attribute or method with the same name.
I'm using the following template to try and render it all.
views/dashboard/form.html.haml
...
= form_for :scans, url: dashboard_scan_create_path(params.to_unsafe_h.slice(:hostname)), :html => {:class => 'flex w-full flex-col scan-form' } do |s|
%div.flex.w-full.relative
#{s.text_field :hostname, :class => 'input border border-gray-400 appearance-none rounded w-full px-3 py-3 pt-8 pb-2 my-2 focus focus:border-indigo-600 focus:outline-none active:outline-none active:border-indigo-600' }
#{s.label :hostname, :class => 'label absolute mb-0 -mt-2 pt-4 pl-3 leading-tighter text-gray-400 text-base mt-2 cursor-text'}
%div.detections.flex.w-full.my-2{ 'data-controller' => 'detection-form' }
%div.text-xl.font-bold Detections
%template{"data-target" => "detection-form.template"}
= s.fields_for :detections, Detection.new, child_index: "NEW_RECORD" do |d_form|
= render "detection_fields", form: d_form
...
views/dashboard/detections-fields.html.haml
= content_tag :div, class: "detection-fields" do
.input.detection-field-input.d-flex.justtify-content-between.mb-2
.col-11.pl-0
= form.fields_for(:detection) do |detection_form|
= detection_form.text_field :type
= detection_form.text_field :hash
.col-1
= link_to "delete", "#", data: { action: "nested-form#remove_association" }
= form.hidden_field :_destroy, as: :hidden
can anyone help me figure out what I am doing wrong.
The error is clear in that ActiveRecord::Core has defined a method hash and your model, Detection, has a conflicting attribute called hash.
The action here is to rename the hash attribute of your model to something that isn't already implemented/reserved.
As an example, if you change the model attribute (and the associated code that uses it) to be sha256 or sha it will avoid the conflict.

Defining input types in graphql-ruby

I'm trying to implement an input type for filters with graphql-ruby and rails.
Base input type looks like:
module Types
class BaseInputObject < GraphQL::Schema::InputObject
end
end
My own input type looks like:
module Types
class PhotoFilterType < Types::BaseInputObject
argument :attribution, String, "Filters by submitter", required: false
argument :country, String, "Filters by country", required: false
argument :year, Int, "Filters by year", required: false
end
end
query_type's method header looks like:
field :filtered_photos, [Types::PhotoType], null: true do
argument :filters, Types::PhotoFilterType, 'filters for photo', required: true
end
And the query as follows:
const FILTER_QUERY = gql`
query getFilteredPhotos($filters: PhotoFilterType!) {
filteredPhotos(filters: $filters) {
id
title
width
height
}
}
`
Interacting with the backend using react-apollo as follows:
this.props.client.query({
query: FILTER_QUERY,
variables: {
filters: {
attribution: author.length > 0 ? author : '',
country: country.length > 0 ? country : '',
year: year ? year : 9999
}
}
})
I get the following error
{message: "PhotoFilterType isn't a defined input type (on $filters)",…}
But when I interact with rails console, I see:
irb(main):004:0> Types::PhotoFilterType
=> Types::PhotoFilterType
So I don't think the type being undefined is an issue.
Any idea what is going wrong and how to fix it? Thanks in advance.
Problem was solved by changing:
const FILTER_QUERY = gql`
query getFilteredPhotos($filters: PhotoFilter!) { // not PhotoFilterType
filteredPhotos(filters: $filters) {
id
title
width
height
}
}
`

Rails checkbox checked updates shipping address while using form object pattern

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_')

Adding new field in RoR simple-form gives ActionView::Template::Error

We are looking to add a new hidden field into our form but every time I add in a new field (even copy and pasting a field that works and just adding a number to the end) we get an error,
ActionView::Template::Error (undefined method `hidden_gclid' for #<Wizard::Lead::Step2:0x00000004d859d8>):
Please let me know which code you would like to see. I tried reading some documentation here and thought, I might need to add a database column but I'm not sure.
Thank you all for the help and I apologize for the delay. I didn't receive
notification on the comments. I have a new problem though. After getting the new field created we are getting this error,
F, [2019-01-09T05:08:28.887941 #26599] FATAL -- : [437ed38a-c36c-4121-9608-385c43d0449e]
F, [2019-01-09T05:08:28.888395 #26599] FATAL -- : [437ed38a-c36c-4121-9608-385c43d0449e] NameError (undefined local variable or method `hidden_gclid' for #<Lead:0x0000000527c4a0>):
F, [2019-01-09T05:08:28.888449 #26599] FATAL -- : [437ed38a-c36c-4121-9608-385c43d0449e]
F, [2019-01-09T05:08:28.888487 #26599] FATAL -- : [437ed38a-c36c-4121-9608-385c43d0449e] app/models/lead.rb:91:in `sales_force_info'
lead.rb
# This model represents an User's (customer) submission through the form
class Lead < ApplicationRecord
validates :address, :lat, :lng, presence: true
belongs_to :user, optional: true
has_and_belongs_to_many :characteristics
has_many :offers, inverse_of: :lead, dependent: :destroy
scope :newest_first, (-> { order(created_at: :desc) })
enum pool_type: %i[
in_ground above_ground none_or_community
]
enum kitchen_condition: %i[
great_kitchen typical_kitchen needs_work_kitchen
]
enum bathroom_condition: %i[
great_bathroom typical_bathroom needs_work_bathroom
]
enum timeline_to_sell: %i[
asap 2_4_weeks 4_6_weeks few_months just_curious
]
enum looking_for_another: %i[
yes already_found no
]
enum reasons_for_selling: %i[
upgrading relocating downsizing retiring selling_investment
]
enum offer_status: %i[
pending sent accepted declined closed
]
attr_accessor :hidden_gclid
def has_basic_information?
if bedrooms.present? || bathrooms.present? ||
built_surface.present? || pool_type.present? ||
kitchen_condition.present? || bathroom_condition.present? ||
renovated.present? || renovated_spent.present? ||
renovated_description.present?
return true
end
false
end
def street
(address.remove(', USA')&.split(",").count == 3) ? address.remove(', USA')&.split(",").first : "#{address.remove(', USA')&.split(",").first}, #{address.remove(', USA')&.split(",").second}"
end
def city
address.remove(', USA')&.split(",").second_to_last
end
def state_code
address.remove(', USA')&.split(",").last
end
def sales_force_info
form = { 'oid' => 'xxxxxx', # roganization id
'retURL' => 'http://placeholder.com',
'00No0000009e5Lb' => 'Cash Offer',
'country_code' => 'US',
'first_name' => user&.first_name,
'last_name' => user&.last_name,
'phone' => user&.phone,
'street' => street, #address
'city' => city,
'state_code' => state_code,
'zip' => zip,
'description' => "Check more lead info at www.placeholder.com/leads/#{id}",
'email' => user&.email,
'lead_source' => user&.how_about_us || "Web",
'00N1N00000Oko3t' => bedrooms,
'00N1N00000Oko3y' => bathrooms,
'00N1N00000Oko43' => built_surface,
'00N1N00000Oko48' => air_conditioner,
'00N1N00000Oko4N' => roof_age,
'00N1N00000Oko4h' => timeline_to_sell,
'00N1N00000Oko4m' => looking_for_another,
'00N1N00000Oko4r' => reasons_for_selling,
'00N1N00000Oko4S' => renovated,
'00N1N00000Oko4X' => renovated_spent,
'00N1N00000Oko4c' => renovated_description,
'00N1N00000Oko4w' => own_valuation,
'00N1N00000PMKAo' => hidden_gclid
}
form
end
def send_info_to_sales_force
puts "SENDING INFO TO SALES FORCE!"
url = URI('https://webto.salesforce.com/servlet/servlet.WebToLead?encoding=UTF-8')
form = sales_force_info
res = Net::HTTP.post_form(url, form)
puts res.body
end
end
form
:javascript
window.onload = function getGclid() {
document.getElementById("00N1N00000PMKAo").value = (name = new RegExp('(?:^|;\\s*)gclid=([^;]*)').exec(document.cookie)) ? name.split(",")[1] : ""; }
.block.block-fill-height.px-0
= render 'application/header', logo: 'logo-blue.png'
.container.mt-2.pt-5
.card.card-outline-primary
%h3.card-header
%ul.nav.nav-bordered
%li.nav-item
%a.nav-link{:href => "#"} Basics
%li.nav-item
%a.nav-link.active{:href => "#"} Sale
%li.nav-item
%a.nav-link{:href => "#"} Offer
.card-block
%h4.card-title Where should we send your offer?
%p.card-text
%strong Save your progress
and join thousands of home owners who work with us every month.
%hr
%h5.mt-5 Create your account below:
= simple_form_for User.new, url: '/users', method: 'POST', html: { class:'mt-4' } do |f|
.form-group
= f.label(:phone, 'Phone')
= f.input_field(:phone, class: 'form-control', placeholder: 'Phone', required: true)
.form-group
= f.label(:first_name, 'First Name')
= f.input_field(:first_name, class: 'form-control', placeholder: 'First Name')
.form-group
= f.label(:last_name, 'Last Name')
= f.input_field(:last_name, class: 'form-control', placeholder: 'Last Name')
.form-group
= f.label(:email, 'Email')
= f.input_field(:email, class: 'form-control', placeholder: 'Enter your Email')
.form-group
= f.label(:password, 'Password')
= f.input_field(:password, class: 'form-control', placeholder: '6 characters minimum')
.form-group
= f.label(:how_about_us, 'Where did you hear about us?')
%br
= f.select :how_about_us, ['TV Commercial', 'Word of Mouth', 'Radio', 'Web', 'Letter/Postcard/Doorhanger',
'Social Media', 'Telephone Call'], class: 'form-control', prompt: 'Please Select', required: true
%hr
= hidden_field_tag(:hidden_gclid, "", :id => "00N1N00000PMKAo")
= hidden_field_tag(:current_step, 'step3')
= f.submit 'Next', class: 'btn btn-success btn-lg'
It looks like we have the form going without any errors now but we need the hidden field ID 00N1N00000PMKAo to send to salesforce but it isn't.
Since simple_form relies on the model instance you have two options here:
add a database column to the corresponding table
use a virtual attribute
Looks like you don't want to store the value in the db, in this case just add attr_accessor :hidden_gclid to your model. After it you can use it like a normal attribute inside the controller action.

rails 4 simple form include_blank still sending empty attributes

I have the following form setup:
= simple_form_for(#job, url: job_payment_path, html: { id: 'payment-processor-form' }) do |j|
div[class='row']
div[class='col-md-12']
div[class='panel panel-default']
div[class='panel-heading']
h3[class='panel-title']
|Total Cost
div[class='panel-body']
h2[class='job-cost' data-initial = "#{job_base_price}"]
= number_to_currency(job_base_price)
div[class='panel-heading']
h3[class='panel-title']
|Have a coupon?
div[class='panel-body']
div[class='row-inline']
div[class='row-block row-block-one']
= j.simple_fields_for :coupon_attributes, #job.coupon do |c|
= c.input_field :code, maxlength: 50, id: 'coupon-code', class: 'form-control', data: { 'initial' => 0 }, include_blank: false
div[class='row-block']
button[type='button' class='btn btn-primary' id='coupon-verify' ]
|Verify
p[class='help-hint']
= t('simple_form.hints.coupon.code')
div[class='row']
div[class='col-md-12']
= j.button :button, type: 'button', class: 'btn-primary text-uppercase', id: 'purchase-job' do
= job_posting_button_step_label
When this form submits, I am seeing the following attributes:
This is not right.
I would expect the coupon code to be nil, not "".
Am I missing something here?
I see two things:
first:
:include_blank is an option for select fields, it skips the generation of a blank option tag at the beginning. code is an input field. If you want to force an input value, try required: true.
second:
form values are sent to the server as application/x-www-form-urlencoded. Empty imput fields are sent as i.e. job[coupon_attributes][code]=. There is no distinction between an empty string and no value.
The convention in Rails is to interpret all input values as string (and typecast later when assigning values to models). So an empty value is always returned as ''.

Resources