Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Record changes pend approval by a privileged user; Its like versioning combined with approvals

I have a requirement that certain attribute changes to records are not reflected in the user interface until those changes are approved. Further, if a change is made to an approved record, the user will be presented with the record as it exists before approval.

My first try...

was to go to a versioning plugin such as paper_trail, acts_as_audited, etc. and add an approved attribute to their version model. Doing so would not only give me the ability to 'rollback' through versions of the record, but also SHOULD allow me to differentiate between whether a version has been approved or not.

I have been working down this train of thought for awhile now, and the problem I keep running into is on the user side. That is, how do I query for a collection of approved records? I could (and tried) writing some helper methods that get a collection of records, and then loop over them to find an "approved" version of the record. My primary gripe with this is how quickly the number of database hits can grow. My next attempt was to do something as follows:

Version.
  where(:item_type => MyModel.name, :approved => true).
  group(:item_type).collect do |v|

  # like the 'reify' method of paper_trail
  v.some_method_that_converts_the_version_to_a_record 
end

So assuming that the some_method... call doesn't hit the database, we kind of end up with the data we're interested in. The main problem I ran into with this method is I can't use this "finder" as a scope. That is, I can't append additional scopes to this lookup to narrow my results further. For example, my records may also have a cool scope that only shows records where :cool => true. Ideally, I would want to look up my records as MyModel.approved.cool, but here I guess I would have to get my collection of approved models and then loop over them for cool ones would would result in the very least in having a bunch of records initialized in memory for no reason.

My next try...

involved creating a special type of "pending record" that basically help "potential" changes to a record. So on the user end you would lookup whatever you wanted as you normally would. Whenever a pending record is apply!(ed) it would simply makes those changes to the actual record, and alls well... Except about 30 minutes into it I realize that it all breaks down if an "admin" wishes to go back and contribute more to his change before approving it. I guess my only option would be either to:

  1. Force the admin to approve all changes before making additional ones (that won't go over well... nor should it).

  2. Try to read the changes out of the "pending record" model and apply them to the existing record without saving. Something about this idea just doesn't quite sound "right".

I would love someone's input on this issue. I have been wrestling with it for some time, and I just can't seem to find the way that feels right. I like to live by the "if its hard to get your head around it, you're probably doing it wrong" mantra.

And this is kicking my tail...

like image 725
Iamvery Avatar asked Jul 11 '11 22:07

Iamvery


1 Answers

How about, create an association:

class MyModel < AR::Base
  belongs_to :my_model
  has_one    :new_version, :class_name => MyModel

  # ...
end

When an edit is made, you basically clone the existing object to a new one. Associate the existing object and the new one, and set a has_edits attribute on the existing object, the pending_approval attribute on the new one.

How you treat the objects once the admin approves it depends on whether you have other associations that depend on the id of the original model.

In any case, you can reduce your queries to:

objects_pending_edits = MyModel.where("has_edits = true").all

then with any given one, you can access the new edits with obj.new_version. If you're really wanting to reduce database traffic, eager-load that association.

like image 144
Steve Ross Avatar answered Sep 23 '22 07:09

Steve Ross