Use removable models in Rails nested attributes
Tags: rails, programming
Published: 2023-08-24 13:03:29

In many Rails based systems we can find models that should not be destroyed but should be tagged as removed/disabled.
This is a simple problem to solve but it becomes complicated when we want to use `accepts_nested_attributes_for ... , allow_destroy: true`

I want to share with you my latest thoughts about this problem. Maybe you have some fancy solution.

Usually I use simple `ActiveSupport::Concern` to satisfy the bussines

```ruby
module Removable
  extend ActiveSupport::Concern

  included do
    scope :removed, -> { where(removed: true) }
    scope :active, -> { where(removed: false) }
  end

  def removed?
    removed
  end

  def remove(user)
    update!({
              removed: true,
              removed_by: user,
              removed_date: DateTime.now
            })
    # other bussines logic for remove
  end

  def remove_nested(user)
    self.removed = true
    self.removed_by = user
    self.removed_date: DateTime.now
    # other bussines logic for remove
  end

  def restore
    # bussines logic for restore
  end
```

No matter how you implemented nested attributes ([Stimulus](https://www.stimulus-components.com/docs/stimulus-rails-nested-form/), [Old Rails Way](http://railscasts.com/episodes/196-nested-model-form-revised), [cocoon](https://github.com/nathanvda/cocoon), etc..), the rails magic is the same. As always we need to permit controller parameters for our nested attributes: `id` and `_destroy`:
```ruby
def todo_params
  params.require(:todo).permit(:name, subtasks_attributes: %i[ title content id _destroy])
end
```

At this point rails magic should **destroy** nested models. 

## Important notice
It destroy record due to [Active Record Autosave Association](https://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html) `#marked_for_destruction`

> Marks this record to be destroyed as part of the parent's save transaction. This does not actually destroy the record instantly, rather child record will be destroyed when parent.save is called.


Using default controller action like `@todo.update(todo_params)` we don't have any option to access and change destroy behaivior to use `Removable#remove_nested`. 

## My current solution

Lets provide `before_validation :mark_nested_removed` to take care about almost destroyed records. 


```ruby
  def mark_nested_removed
    subtasks.filter(&:marked_for_destruction?).each do |subtask|
      subtask.reload
      subtask.remove_nested(edited_by)
    end
  end
```

- we use `subtask.reload` to clear [marked_for_destruction flag](https://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html#method-i-reload), that prevent destroying record. 
- we also use `subtask.remove_nested` to set params to model instead of make update! immediately


### What do YOU think about it? 
__
If you found these helpful, share them with your mates.


References:
- [Active Record Nested Attributes](https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for)
- [Active Record Autosave Association](https://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html)
- [active_record_autosave_association.rb- CODE](https://github.com/rails/rails/blob/main/activerecord/lib/active_record/autosave_association.rb)