effective_trash 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 473e805a96b7c73933733dff9e8342b0f6e8eb3c
4
+ data.tar.gz: 4b4c0294ef97d0ed8545925cecb4810437c362f2
5
+ SHA512:
6
+ metadata.gz: dcfad56fc484fee2bf983b521011a3e54dbc28855fd0e500b7639b539022038642b65cf04320991f905ec40ec2765e79caf34cf6edd6b4367399d046dc172e52
7
+ data.tar.gz: 66704561125458f4a8c17ff18db6d8c06d5634dd7179160f345bcde95f6a1cb625aa83793272e7550bbf6eb319204483fb17b810cd5d4e0e9fda36fcb123f130
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Code and Effect Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # Effective Trash
2
+
3
+ Trash and Restore any Active Record object.
4
+
5
+ ## Getting Started
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem 'effective_trash'
11
+ ```
12
+
13
+ Run the bundle command to install it:
14
+
15
+ ```console
16
+ bundle install
17
+ ```
18
+
19
+ Then run the generator:
20
+
21
+ ```ruby
22
+ rails generate effective_trash:install
23
+ ```
24
+
25
+ The generator will install an initializer which describes all configuration options and creates a database migration.
26
+
27
+ If you want to tweak the table name (to use something other than the default 'trash'), manually adjust both the configuration file and the migration now.
28
+
29
+ Then migrate the database:
30
+
31
+ ```ruby
32
+ rake db:migrate
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ Add to your model:
38
+
39
+ ```ruby
40
+ class Post < ActiveRecord::Base
41
+ acts_as_trashable
42
+ end
43
+ ```
44
+
45
+ and to your contoller:
46
+
47
+ ```ruby
48
+ class ApplicationController < ActionController::Base
49
+ before_action :set_effective_trash_current_user
50
+ end
51
+ ```
52
+
53
+ ## How it works
54
+
55
+ The `acts_as_trashable` mixin sets up `before_destroy` and serializes the resource's attributes to an `Effective::Trash` object.
56
+
57
+ It also serializes the attributes of any `belongs_to`, `has_one`, and `has_many` with `accepts_nested_attributes` related resources.
58
+
59
+ Restoring only works with the single base object right now.
60
+
61
+ ## Routes
62
+
63
+ Visit `/trash`, or `/admin/trash` for an interface to view and restore Trash.
64
+
65
+ ```ruby
66
+ link_to 'Trash', effective_trash.trash_index_path # /trash
67
+ link_to 'Admin Trash', effective_trash.admin_trash_index_path # /admin/trash
68
+ ```
69
+
70
+ ## Permissions
71
+
72
+ Add the following permissions (using CanCan):
73
+
74
+ ```ruby
75
+ can :manage, Effective::Trash, user_id: user.id
76
+
77
+ # Admin
78
+ can :manage, Effective::Trash
79
+ can :admin, :effective_trash
80
+ ```
81
+
82
+ The user must be permitted to to `:update` an `Effective::Trash` in order to restore the trashed item.
83
+
84
+ ## License
85
+
86
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
87
+
88
+ ## Contributing
89
+
90
+ 1. Fork it
91
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
92
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
93
+ 4. Push to the branch (`git push origin my-new-feature`)
94
+ 5. Bonus points for test coverage
95
+ 6. Create new Pull Request
96
+
@@ -0,0 +1,27 @@
1
+ # This copies the permissions of The Logs controller
2
+
3
+ module Admin
4
+ class TrashController < ApplicationController
5
+ respond_to?(:before_action) ? before_action(:authenticate_user!) : before_filter(:authenticate_user!) # Devise
6
+
7
+ layout (EffectiveTrash.layout.kind_of?(Hash) ? EffectiveTrash.layout[:admin_trash] : EffectiveTrash.layout)
8
+
9
+ helper EffectiveTrashHelper
10
+
11
+ def index
12
+ @datatable = Effective::Datatables::Trash.new()
13
+ @page_title = 'Trash'
14
+
15
+ EffectiveTrash.authorized?(self, :index, Effective::Trash)
16
+ EffectiveTrash.authorized?(self, :admin, :effective_trash)
17
+ end
18
+
19
+ def show
20
+ @trash = Effective::Trash.all.find(params[:id])
21
+ @page_title = "Trash item - #{@trash.to_s}"
22
+
23
+ EffectiveTrash.authorized?(self, :show, @trash)
24
+ EffectiveTrash.authorized?(self, :admin, :effective_trash)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,44 @@
1
+ module Effective
2
+ class TrashController < ApplicationController
3
+ if respond_to?(:before_action) # Devise
4
+ before_action :authenticate_user!
5
+ else
6
+ before_filter :authenticate_user!
7
+ end
8
+
9
+ # This is the User index event
10
+ def index
11
+ @datatable = Effective::Datatables::Trash.new(user_id: current_user.id)
12
+ @page_title = 'Trash'
13
+
14
+ EffectiveTrash.authorized?(self, :index, Effective::Trash.new(user_id: current_user.id))
15
+ end
16
+
17
+ # This is the User show event
18
+ def show
19
+ @trash = Effective::Trash.where(user_id: current_user.id).find(params[:id])
20
+ @page_title = "Trash item - #{@trash.to_s}"
21
+
22
+ EffectiveTrash.authorized?(self, :show, @trash)
23
+ end
24
+
25
+ def restore
26
+ @trash = Effective::Trash.all.find(params[:id])
27
+ EffectiveTrash.authorized?(self, :update, @trash)
28
+
29
+ Effective::Trash.transaction do
30
+ begin
31
+ @trash.restore_trash!
32
+ @trash.destroy!
33
+ flash[:success] = "Successfully restored #{@trash}"
34
+ rescue => e
35
+ flash[:danger] = "Unable to restore: #{e.message}"
36
+ raise ActiveRecord::Rollback
37
+ end
38
+ end
39
+
40
+ redirect_back(fallback_location: effective_trash.trash_path)
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,69 @@
1
+ module EffectiveTrashHelper
2
+ ALLOWED_TAGS = ActionView::Base.sanitized_allowed_tags.to_a + ['table', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th']
3
+ ALLOWED_ATTRIBUTES = ActionView::Base.sanitized_allowed_attributes.to_a + ['colspan', 'rowspan']
4
+
5
+ def render_trash(trash)
6
+ render(partial: 'effective/trash/trash', locals: {trash: trash})
7
+ end
8
+
9
+ # Call me with :th => true, :sub_th => false
10
+ # Any other options are sent to the table tag
11
+ def tableize_hash(hash, options = {})
12
+ if hash.present? && hash.kind_of?(Hash)
13
+ content_tag(:table, options) do
14
+ hash.map do |k, v|
15
+ content_tag(:tr) do
16
+ content_tag((options[:th] ? :th : :td), k) +
17
+ content_tag(:td) do
18
+ if v.kind_of?(Hash)
19
+ tableize_hash(v, options.merge({:class => 'table table-bordered', :th => (options.key?(:sub_th) ? options[:sub_th] : options[:th])}))
20
+ elsif v.kind_of?(Array)
21
+ '[' + v.join(', ') + ']'
22
+ else
23
+ v.to_s
24
+ end
25
+ end
26
+ end
27
+ end.join('').html_safe
28
+ end.html_safe
29
+ else
30
+ hash.to_s.html_safe
31
+ end
32
+ end
33
+
34
+ def format_trash_details_value(trash, key)
35
+ value = trash.details[key]
36
+
37
+ if value.kind_of?(Hash)
38
+ tableize_hash(value, :class => 'table', :th => true)
39
+ elsif value.kind_of?(Array)
40
+ value.map { |value| effective_trash_simple_format(value) }.join.html_safe
41
+ else
42
+ value = value.to_s
43
+
44
+ open = value.index('<!DOCTYPE html') || value.index('<html')
45
+ close = value.rindex('</html>') if open.present?
46
+ return effective_trash_simple_format(value) unless (open.present? && close.present?)
47
+
48
+ before = value[0...open]
49
+ after = value[(close+7)..-1]
50
+ divide = before.sub!('<hr>', '').present?
51
+
52
+ [
53
+ h(before).gsub("\n", '<br>'),
54
+ (content_tag(:hr) if divide),
55
+ content_tag(:iframe, '',
56
+ src: '#',
57
+ style: 'frameborder: 0; border: 0; width: 100%; height: 100%;',
58
+ onload: "this.style.height=(this.contentDocument.body.scrollHeight + 30) + 'px';",
59
+ scrolling: 'no'),
60
+ h(after).gsub("\n", '<br>')
61
+ ].compact.join.html_safe
62
+ end
63
+ end
64
+
65
+ def effective_trash_simple_format(value)
66
+ simple_format(sanitize(value.to_s, :tags => ALLOWED_TAGS, :attributes => ALLOWED_ATTRIBUTES), {}, :sanitize => false)
67
+ end
68
+
69
+ end
@@ -0,0 +1,46 @@
1
+ module ActsAsTrashable
2
+ extend ActiveSupport::Concern
3
+
4
+ module ActiveRecord
5
+ def acts_as_trashable(*options)
6
+ @acts_as_trashable_options = options.try(:first) || {}
7
+
8
+ unless @acts_as_trashable_options.kind_of?(Hash)
9
+ raise ArgumentError.new("invalid arguments passed to (effective_trash) acts_as_trashable. Expecting no options.")
10
+ end
11
+
12
+ include ::ActsAsTrashable
13
+ end
14
+ end
15
+
16
+ included do
17
+ has_one :trash, as: :trashed, class_name: Effective::Trash
18
+
19
+ before_destroy do
20
+ trash = Effective::Trash.new(
21
+ trashed: self,
22
+ user: EffectiveTrash.current_user,
23
+ trashed_to_s: to_s,
24
+ trashed_extra: (trashed_extra if respond_to?(:trashed_extra)),
25
+
26
+ details: EffectiveTrash::ActiveRecordLogger.new(self, acts_as_trashable_options).attributes
27
+ ).save!
28
+ end
29
+
30
+ # Parse Options
31
+ acts_as_trashable_options = {
32
+ only: Array(@acts_as_trashable_options[:only]).map { |attribute| attribute.to_s },
33
+ except: Array(@acts_as_trashable_options[:except]).map { |attribute| attribute.to_s },
34
+ additionally: Array(@acts_as_trashable_options[:additionally]).map { |attribute| attribute.to_s }
35
+ }
36
+
37
+ self.send(:define_method, :acts_as_trashable_options) { acts_as_trashable_options }
38
+ end
39
+
40
+ module ClassMethods
41
+ end
42
+
43
+ # Regular instance methods
44
+
45
+ end
46
+
@@ -0,0 +1,17 @@
1
+ unless defined?(Effective::AccessDenied)
2
+ module Effective
3
+ class AccessDenied < StandardError
4
+ attr_reader :action, :subject
5
+
6
+ def initialize(message = nil, action = nil, subject = nil)
7
+ @message = message
8
+ @action = action
9
+ @subject = subject
10
+ end
11
+
12
+ def to_s
13
+ @message || I18n.t(:'unauthorized.default', :default => 'Access Denied')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,53 @@
1
+ if defined?(EffectiveDatatables)
2
+ module Effective
3
+ module Datatables
4
+ class Trash < Effective::Datatable
5
+ include EffectiveTrashHelper
6
+
7
+ datatable do
8
+ default_order :created_at, :desc
9
+
10
+ table_column :created_at, label: 'Destroyed at'
11
+ table_column :id, visible: false
12
+
13
+ unless attributes[:user_id] || attributes[:user] || (attributes[:user] == false)
14
+ table_column :user, label: 'Destroyed by', visible: false
15
+ end
16
+
17
+ table_column :trashed_type, label: 'Type'
18
+ table_column :trashed_id, label: 'Original Id', visible: false
19
+ table_column :trashed_to_s, label: 'Item'
20
+
21
+ table_column :details, visible: true, sortable: false do |trash|
22
+ tableize_hash(trash.details, th: true, sub_th: false, width: '100%')
23
+ end
24
+
25
+ unless attributes[:actions] == false
26
+ actions_column partial: 'admin/trash/actions', partial_local: :trash
27
+ end
28
+ end
29
+
30
+ # A nil attributes[:log_id] means give me all the top level log entries
31
+ # If we set a log_id then it's for sub trash
32
+ def collection
33
+ collection = Effective::Trash.all.includes(:user)
34
+
35
+ if attributes[:user_id].present?
36
+ collection = collection.where(user_id: attributes[:user_id])
37
+ end
38
+
39
+ if attributes[:user].present?
40
+ collection = collection.where(user: attributes[:user])
41
+ end
42
+
43
+ if attributes[:trashed_id] && attributes[:trashed_type]
44
+ collection = collection.where(trashed_id: attributes[:trashed_id], trashed_type: attributes[:trashed_type])
45
+ end
46
+
47
+ collection
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,39 @@
1
+ module Effective
2
+ class Trash < ActiveRecord::Base
3
+ self.table_name = EffectiveTrash.trash_table_name.to_s
4
+
5
+ belongs_to :trashed, polymorphic: true # The original item type and id. Note that this object will never exist as it's deleted.
6
+ belongs_to :user, optional: true # The user that destroyed the original resource
7
+
8
+ # Attributes
9
+ # trashed_type :string
10
+ # trashed_id :integer
11
+ # trashed_to_s :string
12
+ # trashed_extra :string
13
+
14
+ # details :text
15
+ # timestamps
16
+
17
+ serialize :details, Hash
18
+
19
+ default_scope -> { order(updated_at: :desc) }
20
+
21
+ def to_s
22
+ [trashed_type, trashed_id].join(' ').presence || 'New Trash item'
23
+ end
24
+
25
+ def details
26
+ self[:details] || {}
27
+ end
28
+
29
+ # So this is a Trash item
30
+ # When we delete ourselves, we restore this trash item first
31
+ def restore_trash!
32
+ raise 'no attributes to restore from' unless details.kind_of?(Hash) && details[:attributes].present?
33
+ trashed_type.constantize.new(details[:attributes]).save!
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+
@@ -0,0 +1,15 @@
1
+ :ruby
2
+ show_path =
3
+ if datatables_active_admin_path?
4
+ admin_effective_trash_path(trash)
5
+ elsif datatables_admin_path?
6
+ effective_trash.admin_trash_path(trash)
7
+ else
8
+ effective_trash.trash_path(trash)
9
+ end
10
+
11
+ = link_to show_path, title: 'view' do
12
+ %span.glyphicon.glyphicon-eye-open
13
+
14
+ = link_to effective_trash.restore_trash_path(trash), title: 'Restore', data: {confirm: 'Restore this item?'} do
15
+ %span.glyphicon.glyphicon-retweet
@@ -0,0 +1,3 @@
1
+ %h1.effective-admin-heading= @page_title
2
+
3
+ = render_datatable(@datatable)
@@ -0,0 +1,4 @@
1
+ %h1.effective-admin-heading= @page_title
2
+
3
+ = render_trash(@trash)
4
+
@@ -0,0 +1,30 @@
1
+ .panel.panel-default
2
+ .panel-body
3
+ %p
4
+ %span.label.label-default= trash
5
+ = trash.trashed_to_s
6
+
7
+ - if trash.trashed_extra.present?
8
+ %p= trash.trashed_extra
9
+
10
+ .row
11
+ .col-sm-6
12
+ %strong Destroyed at:
13
+ = trash.created_at.strftime("%Y-%m-%d %H:%M:%S")
14
+ = '(' + time_ago_in_words(trash.created_at) + ' ago)'
15
+
16
+ .col-sm-6
17
+ - if trash.user.present?
18
+ %strong Destroyed by:
19
+ = trash.user.to_s
20
+
21
+ %br
22
+
23
+ - trash.details.each do |key, value|
24
+ - next unless value.present?
25
+ .row
26
+ .col-lg-12
27
+ %p
28
+ %strong= "#{key.to_s.titleize}:"
29
+ = format_trash_details_value(trash, key)
30
+
@@ -0,0 +1,6 @@
1
+ %h1.effective-heading= @page_title
2
+
3
+ - if @datatable.present?
4
+ = render_datatable(@datatable)
5
+ - else
6
+ %p You have no previously trashed items
@@ -0,0 +1,3 @@
1
+ %h1.effective-heading= @page_title
2
+
3
+ = render_trash(@trash)
@@ -0,0 +1,36 @@
1
+ EffectiveTrash.setup do |config|
2
+ # Configure Database Table
3
+ config.trash_table_name = :trash
4
+
5
+ # Authorization Method
6
+ #
7
+ # This method is called by all controller actions with the appropriate action and resource
8
+ # If the method returns false, an Effective::AccessDenied Error will be raised (see README.md for complete info)
9
+ #
10
+ # Use via Proc (and with CanCan):
11
+ # config.authorization_method = Proc.new { |controller, action, resource| can?(action, resource) }
12
+ #
13
+ # Use via custom method:
14
+ # config.authorization_method = :my_authorization_method
15
+ #
16
+ # And then in your application_controller.rb:
17
+ #
18
+ # def my_authorization_method(action, resource)
19
+ # current_user.is?(:admin)
20
+ # end
21
+ #
22
+ # Or disable the check completely:
23
+ # config.authorization_method = false
24
+ config.authorization_method = Proc.new { |controller, action, resource| authorize!(action, resource) } # CanCanCan
25
+
26
+ # Admin Screens Layout Settings
27
+ config.layout = 'application' # All EffectiveTrash controllers will use this layout
28
+
29
+ # config.layout = {
30
+ # trash: 'application',
31
+ # admin_trash: 'admin',
32
+ # }
33
+
34
+ # Enable the /trash, /admin/trash and /trash/:id/restore routes. Doesn't affect acts_as_trashable itself.
35
+ config.routes_enabled = true
36
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,21 @@
1
+ EffectiveTrash::Engine.routes.draw do
2
+ scope :module => 'effective' do
3
+
4
+ if EffectiveTrash.routes_enabled
5
+ resources :trash, only: [:index, :show] do
6
+ member { get :restore }
7
+ end
8
+ end
9
+ end
10
+
11
+ if EffectiveTrash.routes_enabled
12
+ namespace :admin do
13
+ resources :trash, only: [:index, :show]
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ Rails.application.routes.draw do
20
+ mount EffectiveTrash::Engine => '/', :as => 'effective_trash'
21
+ end
@@ -0,0 +1,26 @@
1
+ class CreateEffectiveTrash < ActiveRecord::Migration
2
+ def self.up
3
+ create_table <%= @trash_table_name %> do |t|
4
+ t.integer :user_id
5
+
6
+ t.string :trashed_type
7
+ t.integer :trashed_id
8
+
9
+ t.string :trashed_to_s
10
+ t.string :trashed_extra
11
+
12
+ t.text :details
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ add_index <%= @trash_table_name %>, :user_id
18
+ add_index <%= @trash_table_name %>, [:trashed_type, :trashed_id]
19
+ add_index <%= @trash_table_name %>, :trashed_id
20
+ add_index <%= @trash_table_name %>, :trashed_extra
21
+ end
22
+
23
+ def self.down
24
+ drop_table <%= @trash_table_name %>
25
+ end
26
+ end
@@ -0,0 +1,160 @@
1
+ module EffectiveTrash
2
+ class ActiveRecordLogger
3
+ attr_accessor :resource, :logger, :depth, :options
4
+
5
+ BLANK = "''"
6
+
7
+ def initialize(resource, options = {})
8
+ raise ArgumentError.new('options must be a Hash') unless options.kind_of?(Hash)
9
+
10
+ @resource = resource
11
+ @logger = options.delete(:logger) || resource
12
+ @depth = options.delete(:depth) || 0
13
+ @options = options
14
+
15
+ unless @logger.respond_to?(:logged_changes) || @logger.respond_to?(:trash)
16
+ raise ArgumentError.new('logger must respond to logged_changes or trash')
17
+ end
18
+ end
19
+
20
+ # execute! is called when we recurse, otherwise the following methods are best called individually
21
+ def execute!
22
+ if resource.new_record?
23
+ created!
24
+ elsif resource.marked_for_destruction?
25
+ destroyed!
26
+ else
27
+ changed!
28
+ end
29
+ end
30
+
31
+ # before_destroy
32
+ def trashed!
33
+ log_trash((resource.to_s rescue ''), details: applicable(attributes))
34
+ end
35
+
36
+ # before_destroy
37
+ def destroyed!
38
+ log('Deleted', details: applicable(attributes))
39
+ end
40
+
41
+ # after_commit
42
+ def created!
43
+ log('Created', details: applicable(attributes))
44
+ end
45
+
46
+ # after_commit
47
+ def updated!
48
+ log('Updated', details: applicable(attributes))
49
+ end
50
+
51
+ # before_save
52
+ def changed!
53
+ applicable(changes).each do |attribute, (before, after)|
54
+ if resource.respond_to?(:log_changes_formatted_value)
55
+ before = resource.log_changes_formatted_value(attribute, before) || before
56
+ after = resource.log_changes_formatted_value(attribute, after) || after
57
+ end
58
+
59
+ attribute = if resource.respond_to?(:log_changes_formatted_attribute)
60
+ resource.log_changes_formatted_attribute(attribute)
61
+ end || attribute.titleize
62
+
63
+ if after.present?
64
+ log("#{attribute} changed from #{before.presence || BLANK} to #{after.presence || BLANK}", details: { attribute: attribute, before: before, after: after })
65
+ else
66
+ log("#{attribute} set to #{before || BLANK}", details: { attribute: attribute, value: before })
67
+ end
68
+ end
69
+
70
+ # Log changes on all accepts_as_nested_parameters has_many associations
71
+ (resource.class.try(:reflect_on_all_autosave_associations) || []).each do |association|
72
+ child_name = association.name.to_s.singularize.titleize
73
+
74
+ Array(resource.send(association.name)).each_with_index do |child, index|
75
+ ActiveRecordLogger.new(child, options.merge(logger: logger, depth: (depth + 1), prefix: "#{child_name} ##{index+1}: ")).execute!
76
+ end
77
+ end
78
+ end
79
+
80
+ def attributes
81
+ attributes = { attributes: resource.attributes }
82
+
83
+ # Collect to_s representations of all belongs_to associations
84
+ (resource.class.try(:reflect_on_all_associations, :belongs_to) || []).each do |association|
85
+ attributes[association.name] = resource.send(association.name).to_s.presence || 'nil'
86
+ end
87
+
88
+ # Collect to_s representations for all has_one associations
89
+ (resource.class.try(:reflect_on_all_associations, :has_one) || []).each do |association|
90
+ next if association.name == :trash && resource.respond_to?(:acts_as_trashable_options) # We skip our own association
91
+ attributes[association.name] = resource.send(association.name).to_s.presence || 'nil'
92
+ end
93
+
94
+ # Collects attributes for all accepts_as_nested_parameters has_many associations
95
+ (resource.class.try(:reflect_on_all_autosave_associations) || []).each do |association|
96
+ attributes[association.name] = {}
97
+
98
+ Array(resource.send(association.name)).each_with_index do |child, index|
99
+ attributes[association.name][index+1] = ActiveRecordLogger.new(child, options.merge(logger: logger)).attributes
100
+ end
101
+ end
102
+
103
+ attributes
104
+ end
105
+
106
+ def changes
107
+ changes = resource.changes
108
+
109
+ # Log to_s changes on all belongs_to associations
110
+ (resource.class.try(:reflect_on_all_associations, :belongs_to) || []).each do |association|
111
+ if (change = changes.delete(association.foreign_key)).present?
112
+ changes[association.name] = [association.klass.find_by_id(change.first), resource.send(association.name)]
113
+ end
114
+ end
115
+
116
+ changes
117
+ end
118
+
119
+ private
120
+
121
+ def log(message, details: {})
122
+ logger.logged_changes.build(
123
+ user: EffectiveTrash.current_user,
124
+ status: EffectiveTrash.log_changes_status,
125
+ message: "#{"\t" * depth}#{options[:prefix]}#{message}",
126
+ details: details
127
+ ).tap { |log| log.save }
128
+ end
129
+
130
+ def log_trash(message, details: {})
131
+ logger.build_trash(
132
+ user: EffectiveTrash.current_user,
133
+ status: EffectiveTrash.trashable_status,
134
+ message: "#{"\t" * depth}#{options[:prefix]}#{message}",
135
+ details: details
136
+ ).tap { |log| log.save }
137
+ end
138
+
139
+ # TODO: Make this work better with nested objects
140
+ def applicable(attributes)
141
+ atts = if options[:only].present?
142
+ attributes.slice(*options[:only])
143
+ elsif options[:except].present?
144
+ attributes.except(*options[:except])
145
+ else
146
+ attributes.except(:updated_at, :created_at)
147
+ end
148
+
149
+ (options[:additionally] || []).each do |attribute|
150
+ value = (resource.send(attribute) rescue :effective_trash_nope)
151
+ next if attributes[attribute].present? || value == :effective_trash_nope
152
+
153
+ atts[attribute] = value
154
+ end
155
+
156
+ atts
157
+ end
158
+
159
+ end
160
+ end
@@ -0,0 +1,29 @@
1
+ module EffectiveTrash
2
+ class Engine < ::Rails::Engine
3
+ engine_name 'effective_trash'
4
+
5
+ config.autoload_paths += Dir["#{config.root}/lib/"]
6
+
7
+ # Set up our default configuration options.
8
+ initializer "effective_trash.defaults", before: :load_config_initializers do |app|
9
+ eval File.read("#{config.root}/config/effective_trash.rb")
10
+ end
11
+
12
+ # Include acts_as_trashable concern and allow any ActiveRecord object to call it with acts_as_trashable()
13
+ initializer 'effective_trash.active_record' do |app|
14
+ ActiveSupport.on_load :active_record do
15
+ ActiveRecord::Base.extend(ActsAsTrashable::ActiveRecord)
16
+ end
17
+ end
18
+
19
+ # Register the log_page_views concern so that it can be called in ActionController or elsewhere
20
+ initializer 'effective_trash.action_controller' do |app|
21
+ Rails.application.config.to_prepare do
22
+ ActiveSupport.on_load :action_controller do
23
+ ActionController::Base.include(EffectiveTrash::SetCurrentUser::ActionController)
24
+ end
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ module EffectiveTrash
2
+ module SetCurrentUser
3
+ module ActionController
4
+
5
+ def set_effective_trash_current_user
6
+ if respond_to?(:current_user)
7
+ EffectiveTrash.current_user = current_user
8
+ else
9
+ raise "(effective_trash) set_effective_trash_current_user expects a current_user() method to be available"
10
+ end
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,3 @@
1
+ module EffectiveTrash
2
+ VERSION = '0.2.0'.freeze
3
+ end
@@ -0,0 +1,34 @@
1
+ require 'haml-rails'
2
+ require 'effective_trash/engine'
3
+ require 'effective_trash/version'
4
+
5
+ module EffectiveTrash
6
+
7
+ # The following are all valid config keys
8
+ mattr_accessor :trash_table_name
9
+
10
+ mattr_accessor :authorization_method
11
+ mattr_accessor :layout
12
+ mattr_accessor :routes_enabled
13
+
14
+ def self.setup
15
+ yield self
16
+ end
17
+
18
+ def self.authorized?(controller, action, resource)
19
+ if authorization_method.respond_to?(:call) || authorization_method.kind_of?(Symbol)
20
+ raise Effective::AccessDenied.new() unless (controller || self).instance_exec(controller, action, resource, &authorization_method)
21
+ end
22
+ true
23
+ end
24
+
25
+ # This is set by the "set_effective_trash_current_user" before_filter.
26
+ def self.current_user=(user)
27
+ @effective_trash_current_user = user
28
+ end
29
+
30
+ def self.current_user
31
+ @effective_trash_current_user
32
+ end
33
+
34
+ end
@@ -0,0 +1,29 @@
1
+ module EffectiveTrash
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ include Rails::Generators::Migration
5
+
6
+ desc 'Creates an EffectiveTrash initializer in your application.'
7
+
8
+ source_root File.expand_path('../../templates', __FILE__)
9
+
10
+ def self.next_migration_number(dirname)
11
+ if not ActiveRecord::Base.timestamped_migrations
12
+ Time.new.utc.strftime("%Y%m%d%H%M%S")
13
+ else
14
+ "%.3d" % (current_migration_number(dirname) + 1)
15
+ end
16
+ end
17
+
18
+ def copy_initializer
19
+ template ('../' * 3) + 'config/effective_trash.rb', 'config/initializers/effective_trash.rb'
20
+ end
21
+
22
+ def create_migration_file
23
+ @trash_table_name = ':' + EffectiveTrash.trash_table_name.to_s
24
+
25
+ migration_template ('../' * 3) + 'db/migrate/01_create_effective_trash.rb.erb', 'db/migrate/create_effective_trash.rb'
26
+ end
27
+ end
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: effective_trash
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Code and Effect
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 3.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 3.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: effective_datatables
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: coffee-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: devise
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: haml-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Trash and Restore any Active Record object.
84
+ email:
85
+ - info@codeandeffect.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - MIT-LICENSE
91
+ - README.md
92
+ - app/controllers/admin/trash_controller.rb
93
+ - app/controllers/effective/trash_controller.rb
94
+ - app/helpers/effective_trash_helper.rb
95
+ - app/models/concerns/acts_as_trashable.rb
96
+ - app/models/effective/access_denied.rb
97
+ - app/models/effective/datatables/trash.rb
98
+ - app/models/effective/trash.rb
99
+ - app/views/admin/trash/_actions.html.haml
100
+ - app/views/admin/trash/index.html.haml
101
+ - app/views/admin/trash/show.html.haml
102
+ - app/views/effective/trash/_trash.html.haml
103
+ - app/views/effective/trash/index.html.haml
104
+ - app/views/effective/trash/show.html.haml
105
+ - config/effective_trash.rb
106
+ - config/routes.rb
107
+ - db/migrate/01_create_effective_trash.rb.erb
108
+ - lib/effective_trash.rb
109
+ - lib/effective_trash/active_record_logger.rb
110
+ - lib/effective_trash/engine.rb
111
+ - lib/effective_trash/set_current_user.rb
112
+ - lib/effective_trash/version.rb
113
+ - lib/generators/effective_trash/install_generator.rb
114
+ homepage: https://github.com/code-and-effect/effective_trash
115
+ licenses:
116
+ - MIT
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 2.4.5.1
135
+ signing_key:
136
+ specification_version: 4
137
+ summary: Trash and Restore any Active Record object.
138
+ test_files: []