Managing child relationships with a nested attribute form
-
11-09-2019 - |
Question
What I'm trying to do is the following:
At anyone time a user can have 1 active profile. This active profile must be authorized by an administrator to make sure that it is compliant with the rules and regulations of the site. When a user edits their profile their public profile is unaffected until the administrator signs off their changes. If they make an edit while their profile is in review, their edits are applied to the outstanding profile for review and is pushed to the back of the queue.
My models look something like this:
class Profile < AR:B
belongs_to :user
end
class User < AR:B
has_many :profiles do
def active
...
end
def latest
end
end
def profile
self.profiles.active
end
end
There is one small twist... the user should not be able to edit the profile directly, as the profiles collection is not exposed. Instead they edit their user and the profile fields are shown there.
What's the best way to manage this? Currently I'm using:
accepts_nested_attributes_for :profiles
In the user, but that seems quite hacky. Ideally most of this logic would live in the model, but the other thing I'm flirting with is the use of a presenter.
Any thoughts would be greatly appreciated, let me know if you need more information as a comment and I'll update this post appropriately.
Solution
Maybe you should try having two relationships from user to profile. One is the one they can edit through your User interface, and the other is the one that is approved by the administrator.
It could work something like:
class User < AB:B
has_one :profile #the user-editable one one
has_one :active_profile, :class_name=>"profile" #the one shown
end
Each changes on the User profile through the form would then show for the Admin (using and observer or maybe just and "after_save" filter). When it aproves it, the changes are then dumped to the active_profile one, and shown somewhere.
That way, you can have a clean form interface, and whenever they edit it again, they see the latest (but not approved) profile. You can also order the Queue using the updated_at column to gain the "their edits are applied to the outstanding profile for review and is pushed to the back of the queue" funcionality.
OTHER TIPS
I'd go about this by having the user Model have a relationship with two profiles as suggested above as well. One "Approved" profile, and the one for editing that goes into your admin queue.
However, in order to handle transitions between "pending" profiles and "approved" profiles i'd suggest possibly adding in a State Machine to handle the transitions. The AASM gem has been good for me in a recent project. (http://github.com/rubyist/aasm/tree/master), and I believe Edge Rails has just baked State Machinage right in as well. (http://github.com/rails/rails/commit/aad5a30bf25d8a3167afd685fc91c99f4f09cc57)
Your model could look something like this:
class User < AR:B
has_one :active_profile
has_one :pending_profile
include ActiveRecord:: StateMachine
state_machine do
state :approved
state :pending
state :rejected
event :update_profile_pending do
transitions :to => :pending, :from => [:approved], :on_transition => :send_to_admin_queue
end
event :update_profile_approved do
transitions :to => :approved, :from => [:pending], :on_transition => :update_current_profile
end
event :update_to_rejected do
transitions :to => :rejected, :from => [:pending]
end
end
def send_to_admin_queue
//method to handlesending pending profiles to admin for approval
end
def update_current_profile
//method to handle updated profiles with changes
end
end
You could then call User.update profile pending! or User.update profile approved! to transition between your profile states and use the transition callbacks to handle sending the editing data between your active profile and pending profile.
As far as using the nested_attributes_for with your actual form, I don't think it's a hack, I've used it as well to update nested attributes and it'd work fine. In this case though you may not need too since you have 2 profiles (one public, one pending).
Just an idea! Thinking out loud here!