Avoid encrypt + decrypt on attribute assignment

Prior to this commit, `EncryptedAttributeType` inherited its `cast`
method from `ActiveModel::Type::Helpers::Mutable`.  `Mutable#cast` is
implemented as a `serialize` + `deserialize`.  For
`EncryptedAttributeType`, this means `encrypt` + `decrypt`.  This
overhead can be avoided by simply delegating `cast` to the `cast_type`.

**Before**

  ```irb
  irb> Post.encrypts :body, encryptor: MyLoggingEncryptor.new

  irb> post = Post.create!(body: "Hello")
  ! encrypting "Hello"
  ! decrypted "Hello"
  ! encrypting "Hello"
    TRANSACTION (0.1ms)  begin transaction
    Post Create (1.0ms)  INSERT INTO "posts" ...
  ! encrypting "Hello"
    TRANSACTION (76.7ms)  commit transaction

  irb> post.update!(body: "World")
  ! decrypted "Hello"
  ! encrypting "World"
  ! decrypted "World"
  ! decrypted "Hello"
  ! encrypting "World"
    TRANSACTION (0.1ms)  begin transaction
    Post Update (0.6ms)  UPDATE "posts" ...
  ! encrypting "World"
    TRANSACTION (100.0ms)  commit transaction
  ```

**After**

  ```irb
  irb> Post.encrypts :body, encryptor: MyLoggingEncryptor.new

  irb> post = Post.create!(body: "Hello")
  ! encrypting "Hello"
    TRANSACTION (0.1ms)  begin transaction
    Post Create (0.8ms)  INSERT INTO "posts" ...
  ! encrypting "Hello"
    TRANSACTION (97.0ms)  commit transaction

  irb> post.update!(body: "World")
  ! decrypted "Hello"
  ! decrypted "Hello"
  ! encrypting "World"
    TRANSACTION (0.1ms)  begin transaction
    Post Update (0.9ms)  UPDATE "posts" ...
  ! encrypting "World"
    TRANSACTION (85.4ms)  commit transaction
  ```
This commit is contained in:
Jonathan Hefner 2022-10-16 11:21:42 -05:00
parent 0eb42cae30
commit 9ba037471d

@ -28,6 +28,10 @@ def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type:
@default = default @default = default
end end
def cast(value)
cast_type.cast(value)
end
def deserialize(value) def deserialize(value)
cast_type.deserialize decrypt(value) cast_type.deserialize decrypt(value)
end end