ctilley79
ctilley79

Reputation: 2195

Ruby Regex for price

This one fails when a zero is at the end

12.12 passes 5.51 passes 12.50 fails 12.60 fails

 price_regex = /^\d+(\.\d{2})?$/

why? and how do I fix it?

Some more info

in _form.html.erb

  <p>
    <%= f.label :price %><br />
    <%= f.text_field :price %>
  </p>

in menu_item.rb

  price_regex = /^\d+(\.\d{2})?$/
  validates :price, :presence => true,
                :format => {  :with => price_regex  }

in menu_items_controller.rb

  def create
    @menu_item = MenuItem.new(params[:menu_item])

    if @menu_item.save
     respond_with @menu_item, :location => menu_items_url
   else
     flash[:notice] = "Not Saved"
       end
   end

price is a decimal in the database with a precision of 2.

Upvotes: 2

Views: 2313

Answers (3)

Isaac Betesh
Isaac Betesh

Reputation: 3000

I'm using Rails 3 with the client_side_validations gem, which means I need a Regexp that works both in Ruby and Javascript. I also have a clear delineation between frontend and backend format--The user should never be able to enter "$12.5", but once it hits the server, I don't care about the trailing 0.

My solution was to add a core extension (in my case, for Float, but BigDecimal would probably be more appropriate in most cases):

class Float
  def can_convert_to_i_with_no_loss_of_precision
    (self % 1).zero?
  end
  alias_method :to_s_with_loss_of_trailing_zeroes, :to_s
  def to_s
    if can_convert_to_i_with_no_loss_of_precision
      to_i.to_s
    else
      "#{to_s_with_loss_of_trailing_zeroes}#{0 if (self * 10 % 1).zero?}"
    end
  end
end

Now I can use this in a Model, and it plays nicely on the front end (Javascript doesn't convert it to a Float, so the user will always be forced to enter 2 digits after the decimal) and on the backend (where ActiveModel's FormatValidator will call to_s and the core extension will know when to add the trailing 0):

validates :price, :format => { :with => /^\d+(\.\d{2})?$/, :allow_blank => true }

Upvotes: 1

mu is too short
mu is too short

Reputation: 434785

You say that price is "a decimal in the database with a precision of 2". That means that price is being represented as a BigDecimal in Ruby and the regex test will be done on the string form of that BigDecimal. A little bit of experimentation will clarify things:

> p = BigDecimal.new('12.50')
 => #<BigDecimal:12a579e98,'0.125E2',18(18)> 
> p.to_s
 => "12.5" 

And so your regex will fail. You shouldn't be using a regex for this at all, regexes are meant for strings but you're checking a number. You should be able to keep using your regex if you allow for the conversion:

/^\d+(\.\d{1,2})?$/

Upvotes: 3

ankit
ankit

Reputation: 3358

The regex looks fine to me. I tested it at Rubular with the inputs you mentioned and a few more, and it captures all of them correctly.

The problem is likely with some other part of the code. Maybe you are using price = <regex>, whereas you should be using price =~ <regex> to match a string with a regex.

Upvotes: 0

Related Questions