Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allowing commas in a numerical field

This is such a dumb question that I feel like I must be missing something simple. I have a form with a quantity field. People keep typing commas when they enter the quantity (for example, they type 12,000 to indicate twelve thousand) so I would like to strip out the commas before saving the quantity (12000 in the example) into an integer column in the database.

So far I have tried two methods. Overriding the setter method as suggested in this SO question

  def quantity=(num)
    num.gsub!(',', '') if num.is_a?(String)
    self[:quantity] = num.to_i
  end

This works in a sense. If I type 12,000 in the quantity field and submit the form, I get 12000 in the database. The problem is that it also bypasses all quantity validations. No longer can I validate the presence of a value for quantity for example. This is not good.

I have also tried to use a before validation callback (instead of overriding the setter):

  before_validation :sanitize_quantity

  def sanitize_quantity
    # Doesn't wok because quantity.to_i already called by this point.
  end

This doesn't work because by the time the quantity has reached the callback method, Rails has already called to_i on it. This means that 12,000 will be truncated to 12 by the time it reaches the callback. Once again, not good.

What else can I try aside from stripping the commas on the client-side?

As a final note, I am aware that this is essentially a dumb thing to do because certain countries use periods and commas in different ways. I still need to do it anyway.

like image 943
David Tuite Avatar asked Nov 30 '22 23:11

David Tuite


2 Answers

Use Type Coercions

If I understand your question correctly, you're trying to coerce strings into numbers. If so, you can just use an explicit cast like so:

validates :quantity, presence: true, numericality: true

def quantity=(num)
  self[:quantity] = num.to_s.scan(/\b-?[\d.]+/).join.to_f
end

Tests and Examples

See see how this works, you can try the following at the console.

# String as input.
number = '12,956.05'
number.to_s.scan(/\b-?[\d.]+/).join.to_f
=> 12956.05

# Float as input.
number = 12956.05
number.to_s.scan(/\b-?[\d.]+/).join.to_f
=> 12956.05

# Using an ActiveRecord object.
q = Quantity.new quantity: '12,956.05'
=> #<Quantity id: nil, quantity: 12956.05, created_at: nil, updated_at: nil>
q.save
=> true
like image 74
Todd A. Jacobs Avatar answered Dec 14 '22 23:12

Todd A. Jacobs


I actually figured this out just as I was reading over my question.

The key is to use a virtual attribute.

class Job < ActiveRecord::Base
  def flexible_quantity
    quantity
  end

  def flexible_quantity=(quantity)
    self.quantity = quantity.gsub(',', '') unless quantity.blank?
  end
 end

Then in the form, use the virtual attribute.

<%= f.text_field :flexible_quantity %>

Now validate the presence of the flexible_quantity value instead.

class Job < ActiveRecord::Base
  validates :flexible_quantity, presence: true

  # The cool this is that we can leave the numericality validation on the quantity field.
  validates :quantity, presence: true, numericality: { only_integer: true }
end
like image 26
David Tuite Avatar answered Dec 14 '22 23:12

David Tuite