This project is archived and is in readonly mode.

#6477 new
Mike Perham

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

    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

    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

    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.

  • Trevor Turk

    Trevor Turk February 27th, 2011 @ 11:21 PM

    Why not just override the setter method?

  • thoefer

    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

    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

    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

    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

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>

People watching this ticket

Pages