effective_trash 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []