Module: Sequel::Plugins::Paranoid

Defined in:
lib/sequel/plugins/paranoid.rb

Class Method Summary collapse

Class Method Details

.configure(model, options = {}) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/sequel/plugins/paranoid.rb', line 5

def self.configure(model, options = {})
  options = {
    :deleted_at_field_name      => :deleted_at,
    :deleted_by_field_name      => :deleted_by,
    :enable_deleted_by          => false,
    :deleted_scope_name         => :deleted,
    :non_deleted_scope_name     => :present,
    :ignore_deletion_scope_name => :with_deleted,
    :enable_default_scope       => false,
    :deleted_column_default     => nil
  }.merge(options)

  model.instance_eval do
    #
    # Inject the scopes for the deleted and the existing entries.
    #

    dataset_module do
      # scope for deleted items
      define_method(options[:deleted_scope_name]) do
        send(options[:ignore_deletion_scope_name]).exclude(Sequel.qualify(model.table_name, options[:deleted_at_field_name]) => options[:deleted_column_default])
      end

      # scope for non-deleted items
      define_method(options[:non_deleted_scope_name]) do
        filter(Sequel.qualify(model.table_name, options[:deleted_at_field_name]) => options[:deleted_column_default])
      end

      # scope for both
      define_method(options[:ignore_deletion_scope_name]) do
        unfiltered
      end
    end

    #
    # Overwrite the "_destroy_delete" method which is used by sequel to
    # delete an object. This makes sure, we run all the hook correctly and
    # in a transaction.
    #
    define_method("destroy") do |*args|
      # Save the variables threadsafe (because the locks have not been
      # initialized by sequel yet).
      Thread.current["_paranoid_destroy_args_#{self.object_id}"] = args

      super(*args)
    end

    define_method("_destroy_delete") do
      # _destroy_delete does not take arguments.
      destroy_options = Thread.current["_paranoid_destroy_args_#{self.object_id}"].first
      Thread.current["_paranoid_destroy_args_#{self.object_id}"] = nil

      # set the deletion time
      self.send("#{options[:deleted_at_field_name]}=", Time.now)

      # set the deletion author
      if options[:enable_deleted_by] && destroy_options && destroy_options[:deleted_by]
        self.send("#{options[:deleted_by_field_name]}=", destroy_options[:deleted_by])
      end

      self.save
    end

    #
    # Method for undeleting an instance.
    #

    define_method("recover") do
      self.send("#{options[:deleted_at_field_name]}=".to_sym, options[:deleted_column_default])

      if options[:enable_deleted_by] && self.respond_to?(options[:deleted_by_field_name].to_sym)
        self.send("#{options[:deleted_by_field_name]}=", nil)
      end

      self.save
    end

    #
    # Check if an instance is deleted.
    #

    define_method("deleted?") do
      send(options[:deleted_at_field_name]) != options[:deleted_column_default]
    end

    #
    # Inject the default scope that filters deleted entries.
    #

    if options[:enable_default_scope]
      set_dataset(self.send(options[:non_deleted_scope_name]))
    end
  end
end