Search code examples
ruby-on-railsruby

Rails: how to disable persistence of object temporarily?


As in, freeze disables ability to update object values (to a degree). How might I build a method User.disable_persistence that would disable ability to create/save/update on that object and associated objects, both called directly (User.save) and indirectly (User.children << child).

Is there a gem, or a simple method, like:

class User < ...
  def disable_persistence
    # magic here (nullify save, and other methods, prevent callbacks etc.)
    class_eval :before_save do 
      errors.add(:base, "Persistence has been disabled for this object")
    end
  end
end

Solution

  • The problem seems simple. The tricky part though is the and indirectly (User.children << child) part of it. This can be dealt with easily when the parent object (User) is new record. But not that easily if it is not. This is because a statement like user#children << child saves the parent record and the children when record of user is new but does not do the same when it is not. On latter case it saves only the child. This problem is not solved on this project, automatically, at least for now. Developer has to disable the persistence on the child object first in order to achieve this on this latter case.

    See the author_spec.rb file. It is very helpful to tell you the whole story.

    The whole project that I developed as task to answer your SOW question is here: https://github.com/pmatsinopoulos/disable_persistence

    Anyone that wants to contribute on that, feel free.

    The code that does the whole trick is quoted here too for readers convenience:

    The disable_persistence.rb file:

    module DisablePersistence
      extend ActiveSupport::Concern
    
      def disable_persistence
        @persistence_disabled = true
      end
    
      def enable_persistence
        @persistence_disabled = false
      end
    
      module ClassMethods
        def disable_persistence
          @@class_persistence_disabled = true
        end
    
        def enable_persistence
          @@class_persistence_disabled = false
        end
    
        def persistence_disabled?
          @@class_persistence_disabled ||= false
        end
    
        def persistence_disabled
          persistence_disabled?
        end
      end
    
      included do
        attr_reader :persistence_disabled
        alias :persistence_disabled? :persistence_disabled
    
        before_save :can_persist?
    
        after_initialize do |base|
          base.instance_variable_set(:@persistence_disabled, false)
        end
    
        def can_persist?
          !persistence_disabled? && !self.class.persistence_disabled?
        end
    
        protected :can_persist?
      end
    end
    
    ActiveRecord::Base.send :include, DisablePersistence
    

    Notes:

    A. The instances will be responding to:

    1. #disable_persistence
    2. #enable_persistence
    3. #persistence_disabled?

    B. The class will be responding to:

    1. #disable_persistence
    2. #enable_persistence
    3. #persistence_disabled?

    C. There is a protected before_save method that checks whether the instance can persist. It check that both instance and class persistence are enabled. If any is disabled, does not allow the instance to persist.

    D. The functionality is automatically included in all ActiveRecord::Base classes. This is the last line above. You may not want that. If you do not want that, you have to call include DisablePersistence on all your ActiveRecord::Base classes that you want this feature on.

    E. In the rails project that I link to, I have an initializer that requires the file that contains this code. Look into the config/initializers. Otherwise, you will have to require it yourself.

    Some examples of usage (Assume an author and their books):

    First example:

    author = Author.new
    author.disable_persistence
    author.save # will return false and nothing will be saved
    author.enable_persistence
    author.save # will return true and author will be saved
    

    Second example:

    author = Author.new
    author.disable_persistence
    book = Book.new
    author.books << book
    author.save # false and nothing will be saved
    

    Third example:

    author = Author.new
    author.save
    book = Book.new
    book.disable_persistence
    author.books << book # nothing will be saved
    

    Fourth example:

    author = Author.new
    author.save
    book = Book.new
    author.disable_persistence
    author.books << book # will be saved indeed, because the book has enabled persistency
    

    Fifth example:

    author = Author.new
    Author.disable_persistence
    author.save # will return false and will not save
    

    I hope the above answers your question, or at least is helpful somehow.