0

I have a model that I'll call Parent. When this model is updated, I register which User did this last update on the parent model. I do this at the ParentsController like this:

def update
  @parent = Parent.find(params[:id])
  @parent.assign_attributes(params[:parent])
  @parent.last_editor = current_user

  if @parent.save
    # continue....

This works well. The controller tells the model who is the current_user. The concerns are separated well.

But the Parent model has many different child models and I want to update the last_editor of the Parent when its childs are updated. (The last_editor is a User).

I could do this:

class ChildOnesController
  # ...
  def create
    @child_one = ChildOne.new(params[:child_one])

    if @child_one.save
      @child_one.parent.update_attribute(last_editor: current_user)
    else
      # ...
    end
  end

  def update 
    @child_one = ChildOne.find(params[:id])

    if @child_one.update_attributes(params[:child_one])
      @child_one.parent.update_attribute(last_editor: current_user)
      # continue....
    end
  end

  def destroy  
    # Destroy...then
    @child_one.parent.update_attribute(last_editor: current_user)
    # ...
  end

The main problem is: As my Parent model has various child models, I'll have to add those lines to every child model controller.

Is there a better, more concise way to do this?

Thanks a lot for any hint!!!

Mauricio Moraes
  • 6,889
  • 5
  • 34
  • 54

2 Answers2

1

You could use a mixin and name the instance variable you assign a child to consistently in all controllers.

module ChildControllerMixin
  def update_last_editor
    @child.parent.update_attribute(last_editor: current_user) if @child.valid?
  end
end

class ChildOnesController
  include ChildControllerMixin
  after_filter :update_last_editor, only: [:create, :update, :destroy]
end

I don't know how great of an option it is since it seems a little fragile but it might help you.

Edit: you may want to reconsider why you have a separate controller for each child. Are they similar enough that there is only a need for one controller?

kcdragon
  • 1,599
  • 14
  • 22
  • Thanks for your answer! Can I guarantee that after the :destroy method I will always have a valid child object? – Mauricio Moraes Aug 15 '16 at 12:48
  • Ah. Good point. There will probably still be a reference to the child instance variable but I'm not sure you can call parent on it and be able up persist the parent. – kcdragon Aug 15 '16 at 13:40
0

Using such approach, your data will be messed up soon. You will be forced to manage each action in controller, so last_editor is set properly. I recommend you to change it like this:

# user.rb

def self.current_user=(user)
  Thread.current[:current_user] = user
end

def self.current_user
  Thread.current[:current_user]
end

Now current_user will be available using User.current_user in your models. All we need is just set it in your application controller.

# application_controller.rb

before_filter :set_current_user

def set_current_user
  User.current_user = current_user
end

From this moment we can create callback in model to set last_editor on every save.

# parent.rb

before_save :set_last_editor

...

def set_last_editor
  self.last_editor = User.current_user
end

And last moment for your children models. You need just resave parent on every change in children models.

# every_children_model.rb

after_save :update_parent

...

def update_parent
  # ... get parent
  parent.save!
end

Now any action in your system, no matter which controller you use, will handle last_editor problem.

Pavel Tkackenko
  • 933
  • 7
  • 19
  • I understand your solution. Thanks for answering! I took a look at this question and got a little aware of using thread locals: http://stackoverflow.com/questions/7896298/safety-of-thread-current-usage-in-rails. Furthermore, I would save putting my line to update the parent model, but would still have to remember to save it everywere its needed. What do you think? – Mauricio Moraes Aug 15 '16 at 12:42