This project is archived and is in readonly mode.
ActiveModel transformations
Reported by Mike Perham | February 27th, 2011 @ 05:34 AM
I'd like to propose adding the notion of transformations and transformers to ActiveModel. I've found that basically every web project I've ever worked on has needed functionality to strip and clean form inputs, e.g. stripping non-numeric characters from a credit card number field.
So I'd like to implement something similar to ActiveModel's validations, except that it would happen before the validation stage. The API is completely open at this point; I was thinking something like this:
class CreditCard
# attributes: number, name
transform :number do |num|
num.gsub(/\D/, '')
end
transform :name do |num|
num.strip
end
end
cc = CreditCard.new(:number => '4111-1111-1111-1111', :name => ' Mike Perham ')
cc.save!
[cc.number, cc.name] # => ['4111111111111111', 'Mike Perham']
but I'm happy to discuss use cases and what the API might look like at this point. Some other possibilities: type coercion (String -> Date, e.g.) and what might we supply as common, built-in transformers.
Comments and changes to this ticket
-
thoefer February 27th, 2011 @ 03:28 PM
Hey Mike,
I like your idea basically as this is really default functionality! The first thing that comes to my mind was that these transformers should easily be chainable e.g. like rack-middleware. Output of the first transformer could be treated as input to the next transformer. Furthermore I think it´s important to implement the API in a way that custom transformers can easily be added.
What do you think about this:
validates :name, :presence => true, :transformer => [:remove_whitespace, :remove_invalid_chars]
This way the entire chain of transformers is grouped with the validator and easily verifyable.
-
thoefer February 27th, 2011 @ 03:34 PM
And I think it´s important to really distinguish between what´s is implemented as a transformer (and therefore before the validation stage) and what should be a validator.
-
Josep M. Bach February 27th, 2011 @ 03:58 PM
Hi Mike,
I gave it a shot and tried to implement a first basic draft.
https://gist.github.com/846276
I've got it green on ActiveModel master w/ Ruby 1.9.2. There's some stuff pending (inheriting transformations for example).
Somehow I like better the idea of having validations and transformations as two separate sets of callbacks, since they are inherently different concepts. I've mirrored the structure of ActiveModel::Validations, creating a new type of :transform callbacks and all.
The only thing I don't like with this approach is that transformers are not chainasble, and, as @thoefer says, intuitively it would be a nice feature to have.
-
thoefer February 28th, 2011 @ 06:52 PM
You convinced me Josep. I think it´s better to separate validations and transformations. I think the possibility to chain transformers is almost a must-have. Therefore I tried to implement the basic library in this way, as you can see in https://gist.github.com/847786 (please scroll down for the actual AR-model and transformer usage).
It basically looks like this:
class Asset < ActiveRecord::Base include ActiveModel::Transformers # Usage examples for chainable transformer API. UseCases: # - builtin transformer without customized options # - builtin transformer with customized options # - custom transformer (as a lambda) # API for builtin transformers relying on default-options transform :age, :digit, :strip # API for builtin transformer with customized options and a custom transformer transform :name do with :strip, :l => false with lambda {|value| "custom transformer: ...#{value}..."} end end
@Trevor: You´re absolutely right with your proposal. Nontheless it would be helpful to have a couple of basic transformers available for filtering.
Comments?
-
thoefer February 28th, 2011 @ 06:56 PM
forgot to mention, sorry: output of one transformer is treated as input value for the next one.
-
Mike Perham February 28th, 2011 @ 07:09 PM
After weekend consideration, I'm now wondering if this shouldn't just be a before_validation and after_validation block for each model. I'm not convinced having separate transform blocks is any cleaner or more useful.
-
thoefer February 28th, 2011 @ 08:45 PM
You´re right, this could be implemented with validation hook also. Nonetheless I think it would be helpful to have some builtin highlevel-transformer available rather than having to deal with low-level-filter e.g. regular expressions.
-
Oriol Gual March 1st, 2011 @ 12:49 PM
@thoefer you may want to look at mdeering's attribute normalizer
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile »
<h2 style="font-size: 14px">Tickets have moved to Github</h2>
The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>