aws / aws-sdk-ruby-record

Official repository for the aws-record gem, an abstraction for Amazon DynamoDB.
Apache License 2.0
318 stars 41 forks source link

Derived Column Values #58

Closed IbottaBaier closed 2 years ago

IbottaBaier commented 7 years ago

Question: is there a good way to have columns whose values are derived from other columns and to have those values be correctly populate for all interfaces? For example:

class MyRecord
  include Aws::Record

  integer_attr :pid, hash_key: true
  integer_attr :shd, derived_from: :pid { |pid| pid % 100 }

Derived from would take an array of fields and would execute the block when any referenced column is changed, passing their values to the block either separately or as a hash.

Then one would expect:

record = MyRecord.new(pid: 1234)
record.shd #=> 34

My work-around for this was along the lines of:

def pid=(value)
  set_attribute(:pid, value) # copied from Aws::Record generated method since 'super' doesn't exist.
  self.shd = pid % 100
  value
end

Which works for the simple create/save case:

MyRecord.new(pid: 1234).save!
record = MyRecord.find(pid: 1234)
record.shd #=> 34

But, as I just discovered, does not work for updates (because no instance is ever involved):

MyRecord.update(pid: 3456) # upserts a new record
record = MyRecord.find(pid: 3456)
record.shd #=> nil

Would love to see a generalized way of doing this; seems pertinent for dynamically generating GSIs and having everything play nicely...

Current work around will be (though now I need a base record class rather than module so super will actually work):

def update(opts)
  inject_default_opts!(opts)
  super(opts)
end
awood45 commented 7 years ago

I think at first pass that a derived_from option feels too specific, that writing the logic into the class would be better. Is the issue that you want better hooks?

I'll have to think on this for a bit - I see the problem you're getting at, would like to think of a way to solve in a generalized manner.

IbottaBaier commented 7 years ago

Yes, sort of like ActiveRecord's before_commit hook.

Update poses a different problem (posting a second issue to discuss because my solution is copy pasting your code).

IbottaBaier commented 7 years ago

Though an after_commit hook would imply it only happens once a save operation actually occurs, I would really want an after_change for any attr, i.e.:

integer_attr :partition, hash_key: true
integer_attr :shard
after_change :partition do |record|
  record.shard = record.partition % 100
end
# OR without specifying the column to listen to
after_change do |record|
  record.shard = record.partition % 100
end

This is nice because I can read back values before saving:

record = MyRecord.new(partition: 123123)
record.shard #=> 23
mullermp commented 2 years ago

Apologies for the late response on this. We agree that this should be behavior that you define on the class. The code samples provided will already accomplish this! Though there appears to be a minor bug in the sample. If you use @data.set_attribute(:shd, value % 100) in your pid=(value) method, it will correctly set shd even on update.