views:

274

answers:

5

I feel bad asking this question, as I thought I knew enough about Activerecord to answer this myslef. But such is the way of having SO available ...

I'm trying to remove the commas from a field in a model of mine, I want the user to be able to type a number , ie 10,000 and that number be stored in the database as 10000. I was hoping that I could do some model-side normalization to remove the comma. I don't want to depend on the view or controller to properly format my data.

I tried ;

before_validation :normalize

def normalize 
 self['thenumber'] = self['thenumber'].to_s.gsub(',','')
end

no worky :(

A: 

Does ruby let you interchange between a . and [''] ? I don't know, I'll try later, but I think you are supposed to use .

self.thenumber = self.thenumber.to_s.gsub(',','')
phillc
Nope- tried that way the first time.
Bill
A: 

The problem with doing it that way is that for a while, the non-normalized stuff will exist in the object; if you have code that works on the attributes before stuff gets normalised, then that will be a problem.

You could define a setter:

def thenumber=(value)
  # normalise stuff here, call write_attribute
end

Unfortunately I think a lot of the Rails form stuff writes the attributes directly, which is one of the reasons I don't tend to use it.

Or you could normalise the params in the controller before you pass them through.

Simon Russell
Yes, I've resigned myself to normalize the params in the controller before they get into AR at all...
Bill
+2  A: 

I think you're doing it right. This test passes:

test "should remove commas from thenumber" do
  f = Foo.new(:thenumber => "10,000")
  f.save
  f = Foo.find(f.id)
  assert f.thenumber == "10000"    
end

And I used your code.

 class Foo < ActiveRecord::Base
  before_validation :normalize

  def normalize 
    self['thenumber'] = self['thenumber'].to_s.gsub(',','')
  end

 end

Now, my schema is set up for thenumber to be a string though, not an integer.

Started
.
Finished in 0.049666 seconds.

1 tests, 1 assertions, 0 failures, 0 errors

If you wanted to store this in the db as an integer, then you definitely need to override the setter:

 def thenumber=(value)
   self['thenumber'] = value.to_s.gsub(',','').to_i
 end

If you do it your way, with an integer column, it gets truncated by AR....

>> f.thenumber = "10,000"
=> "10,000"
>> f.thenumber
=> 10

That's a little-known thing with Ruby and integers... it auto-casts by truncating anything that's no longer an integer.

irb(main):004:0> i = "155-brian-hogan".to_i
=> 155

Can be cool for things like

/users/155-brian-hogan

@user = User.find_by_id(params[:id])

But not so cool for what you're doing.

So either change the col to a string and use the filter, or change the setter :)

Good luck!

Brian Hogan
Thank you for the very detailed response!
Bill
A: 

You should return true from your before_validation method, otherwise if the expression being assigned to self['thenumber'] ends up being nil or false, the data will not be saved, per the Rails documention:

If a before_* callback returns false, all the later callbacks and the associated action are cancelled.

Ostensibly, you are trying to normalize here then check the result of the normalization with your Rails validations, which will decide if nil/false/blank are okay or not.

before_validation :normalize

def normalize 
  self['thenumber'] = self['thenumber'].to_s.gsub(',','')
  return true
end
MikeJ
A: 

http://github.com/mdeering/attribute_normalizer looks like a promising solution to this common problem. Here are a few examples from the home page:

  # By default it will strip leading and trailing whitespace
  # and set to nil if blank.
  normalize_attributes :author, :publisher

  # Using one of our predefined normalizers.
  normalize_attribute  :price, :with => :currency

  # You can also define your normalization block inline.
  normalize_attribute :title do |value|
    value.is_a?(String) ? value.titleize.strip : value
  end

So in your case you might do something like this:

  normalize_attribute :title do |value|
    value.to_s.gsub(',', '')
  end
Tyler Rick