deleted_at 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 08cd630b8b6125e3e1ba1d36f0cf5175b14e414c
4
- data.tar.gz: 5b3f9fa3fdb83fda8a9ffe436ec5a30028e329e4
3
+ metadata.gz: ee93e0bf9a77ac398902a8c79ba43f1561b86033
4
+ data.tar.gz: 8029a0f75a4537d12133c31a8558ab5cc2efcf6b
5
5
  SHA512:
6
- metadata.gz: 544cd3f0b558a4b3435139051dd954377750cd14ccfc74e2f5479a8fce5574af09ee61def9985dc2d47ac2ec07ce2600c4f0cdae921f4c7c405f0f2233c9616c
7
- data.tar.gz: d653993d66d89e4447dab9dbaa05c6eb9d1ec68254fc4a71f624b44b2c7a1184656339d6634f3288fdd718e8c94f78b7f73ff32a43acec1c9430786bf13655ea
6
+ metadata.gz: 54813d5862acc4864dc9daeada10f7bce78aea8e7f3488cb69cf29e8c50ea97141a7a32eed712aa5b4ec21bd97b7fc1f17f5c929709d4cfcfec278376a369885
7
+ data.tar.gz: b14aefcb95ebac5d43d76230b0cde74b485e1120d0bbfd7f1e5d698238966a57152cf1e253060fc1bf1d3f711e800b2cc8735eefd8208bff4d3cf2e4e7473e16
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.13.6
@@ -0,0 +1,74 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, gender identity and expression, level of experience,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ * Using welcoming and inclusive language
18
+ * Being respectful of differing viewpoints and experiences
19
+ * Gracefully accepting constructive criticism
20
+ * Focusing on what is best for the community
21
+ * Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ * The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ * Trolling, insulting/derogatory comments, and personal or political attacks
28
+ * Public or private harassment
29
+ * Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ * Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at dale@getnotion.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at [http://contributor-covenant.org/version/1/4][version]
72
+
73
+ [homepage]: http://contributor-covenant.org
74
+ [version]: http://contributor-covenant.org/version/1/4/
@@ -1,22 +1,21 @@
1
- Copyright (c) 2014 Twilight Coders
1
+ The MIT License (MIT)
2
2
 
3
- MIT License
3
+ Copyright (c) 2017 Dale Stevens
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
12
11
 
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
15
14
 
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # DeletedAt
2
2
 
3
- Provides functionality to ActiveRecord::Base to delete records while keeping them in the database.
3
+ Deleting data is never good. A common solution is to use `default_scope`, but conventional wisdom (and for good reason) deams this a bad practice. So how do we achieve the same effect with minimal intervention. What we're looking for is the cliche "clean" solution.
4
+
5
+ DeletedAt leverages the power of SQL views to achieve the same effect. It also takes advantage of Ruby's flexibility.
4
6
 
5
7
  ## Installation
6
8
 
@@ -20,12 +22,25 @@ Or install it yourself as:
20
22
 
21
23
  ## Usage
22
24
 
23
- Coming soon.
25
+ Using `DeletedAt` is very simple. It follows a familiar pattern seen throughout the rest of the Ruby/Rails community.
26
+
27
+ ```
28
+ class User < ActiveRecord::Base
29
+ # Feel free to include/extend other modules before or after, as you see fit...
30
+
31
+ with_deleted_at
32
+
33
+ # the rest of your model code...
34
+ end
35
+
36
+ ## Development
37
+
38
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
24
39
 
25
40
  ## Contributing
26
41
 
27
- 1. Fork it ( https://github.com/TwilightCoders/deleted_at/fork )
28
- 2. Create your feature branch (`git checkout -b my-new-feature`)
29
- 3. Commit your changes (`git commit -am 'Add some feature'`)
30
- 4. Push to the branch (`git push origin my-new-feature`)
31
- 5. Create a new Pull Request
42
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/deleted_at. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
43
+
44
+ ## License
45
+
46
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,2 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
2
3
 
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "deleted_at"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ require "pry"
10
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -6,18 +6,35 @@ require 'deleted_at/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "deleted_at"
8
8
  spec.version = DeletedAt::VERSION
9
- spec.authors = ["Dale Stevens"]
10
- spec.email = ["dale@twilightcoders.net"]
11
- spec.summary = %q{Provides functionality to ActiveRecord::Base to delete records while keeping them in the database.}
12
- # spec.description = %q{TODO: Write a longer description. Optional.}
9
+ spec.authors = ['Dale Stevens']
10
+ spec.email = ['dale@twilightcoders.net']
11
+
12
+ spec.summary = %q{Soft delete your data, but keep it clean.}
13
+ spec.description = %q{Default scopes are bad. Don't delete your data. DeletedAt, I choose you!}
13
14
  spec.homepage = "https://github.com/TwilightCoders/deleted_at"
14
15
  spec.license = "MIT"
15
16
 
16
- spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
21
+ else
22
+ raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
+ f.match(%r{^(test|spec|features)/})
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
30
  spec.require_paths = ["lib"]
31
+ spec.required_ruby_version = '>= 2.0'
32
+
33
+ spec.add_dependency "activerecord", "~> 4.2.0"
34
+ spec.add_dependency 'request_store', '>= 1.1.0'
20
35
 
21
- spec.add_development_dependency "bundler", "~> 1.6"
36
+ spec.add_development_dependency "bundler", "~> 1.13"
22
37
  spec.add_development_dependency "rake", "~> 10.0"
38
+ spec.add_development_dependency "rspec", "~> 3.0"
39
+ spec.add_development_dependency "pry", "~> 0.10.0"
23
40
  end
@@ -1,5 +1,16 @@
1
1
  require "deleted_at/version"
2
+ require 'deleted_at/railtie' if defined? ::Rails::Railtie
2
3
 
3
4
  module DeletedAt
4
- # Your code goes here...
5
+
6
+ def self.install(model)
7
+ DeletedAt::Views.create_deleted_view(model)
8
+ DeletedAt::Views.create_present_view(model)
9
+ end
10
+
11
+ def self.uninstall(model)
12
+ DeletedAt::Views.destroy_deleted_view(model)
13
+ DeletedAt::Views.destroy_present_view(model)
14
+ end
15
+
5
16
  end
@@ -0,0 +1,143 @@
1
+ require 'deleted_at/views'
2
+ require 'deleted_at/active_record/relation'
3
+
4
+ module DeletedAt
5
+ module ActiveRecord
6
+ module Base
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :original_table_name,
11
+ :deleted_at_column, :deleted_by_column, :deleted_by_class, :deleted_by_primary_key
12
+
13
+ class << self
14
+ [:archive_with_deleted_at?, :archive_with_deleted_by?].each do |sym|
15
+ define_method(sym) do
16
+ false
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ module ClassMethods
23
+
24
+ def with_deleted_at(options={})
25
+
26
+ parse_options(options)
27
+
28
+ # Just gotta ask for it once, so the class knows it before inheritence
29
+ # NOTE: This needs to happen regardless of whether the views have been installed
30
+ # yet or not. This is because to install the views, this needs to have been done!
31
+ primary_key = self.primary_key
32
+ self.original_table_name = self.table_name
33
+
34
+ unless ::DeletedAt::Views.present_view_exists?(self) && ::DeletedAt::Views.deleted_view_exists?(self)
35
+ return warn("You're trying to use `with_deleted_at` on #{name} but you have not installed the views, yet.")
36
+ end
37
+
38
+ unless columns.map(&:name).include?(deleted_at_column)
39
+ return warn("Missing `#{deleted_at_column}` in `#{name}` when trying to employ `deleted_at`")
40
+ end
41
+
42
+ [:archive_with_deleted_at?, :archive_with_deleted_by?].each do |sym|
43
+ class_eval <<-BBB
44
+ def self.#{sym}
45
+ true
46
+ end
47
+ BBB
48
+ end
49
+
50
+ setup_class_views
51
+ with_deleted_by
52
+
53
+ self.table_name = ::DeletedAt::Views.present_view(self)
54
+
55
+ end
56
+
57
+ private
58
+
59
+ def parse_options(options)
60
+ self.deleted_at_column = (options.dig(:deleted_at, :column) || :deleted_at).to_s
61
+ self.deleted_by_column = (options.dig(:deleted_by, :column) || :deleted_by).to_s
62
+ self.deleted_by_class = (options.dig(:deleted_by, :class) || User)
63
+ self.deleted_by_primary_key = (options.dig(:deleted_by, :primary_key) || deleted_by_class&.primary_key).to_s
64
+ end
65
+
66
+ def deleted_by_class_is_delete_at(klass)
67
+ klass && (klass.archive_with_deleted_at? || self == klass)
68
+ end
69
+
70
+ def with_deleted_by
71
+ return unless (deleted_by_column && columns.map(&:name).include?(deleted_by_column) && deleted_by_class < ActiveRecord::Base)
72
+ self.deleted_by_class = self.deleted_by_class.const_get(:All) if deleted_by_class_is_delete_at(self.deleted_by_class)
73
+
74
+ unless reflect_on_association(:destroyer)
75
+ class_eval do
76
+ belongs_to :destroyer, foreign_key: deleted_by_column, primary_key: deleted_by_primary_key, class_name: deleted_by_class.name
77
+ end
78
+ end
79
+ end
80
+
81
+ def setup_class_views
82
+
83
+ self.const_set(:All, Class.new(self) do |klass|
84
+ class_eval <<-AAA
85
+ self.table_name = '#{self.original_table_name}'
86
+ AAA
87
+ end)
88
+
89
+ self.const_set(:Deleted, Class.new(self) do |klass|
90
+ class_eval <<-AAA
91
+ self.table_name = '#{::DeletedAt::Views.deleted_view(klass)}'
92
+ AAA
93
+ end)
94
+ end
95
+
96
+ end
97
+
98
+ [:archive_with_deleted_at?, :archive_with_deleted_by?].each do |sym|
99
+ class_eval <<-BBB
100
+ def #{sym}
101
+ self.class.#{sym}
102
+ end
103
+ BBB
104
+ end
105
+
106
+ def destroy
107
+ if archive_with_deleted_at?
108
+ with_transaction_returning_status do
109
+ run_callbacks :destroy do
110
+ update_columns(deleted_at_attributes)
111
+ self
112
+ end
113
+ end
114
+ else
115
+ super
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ def deleted_at_attributes
122
+ attributes = {
123
+ deleted_at_column => Time.now.utc
124
+ }
125
+
126
+ # attributes.merge({
127
+ # deleted_by_column => DeletedAt::who_by
128
+ # }) if by_who?
129
+
130
+ attributes
131
+ end
132
+
133
+ def deleted_at_column
134
+ self.class.deleted_at_column
135
+ end
136
+
137
+ def deleted_by_column
138
+ self.class.deleted_by_column
139
+ end
140
+
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,89 @@
1
+
2
+ module DeletedAt
3
+ module ActiveRecord
4
+ # = Active Record Relation
5
+ module Relation
6
+
7
+ def deleted_at_attributes
8
+ { klass.deleted_at_column => Time.now.utc }
9
+ end
10
+
11
+ # Deletes the records matching +conditions+ without instantiating the records
12
+ # first, and hence not calling the +destroy+ method nor invoking callbacks. This
13
+ # is a single SQL DELETE statement that goes straight to the database, much more
14
+ # efficient than +destroy_all+. Be careful with relations though, in particular
15
+ # <tt>:dependent</tt> rules defined on associations are not honored. Returns the
16
+ # number of rows affected.
17
+ #
18
+ # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
19
+ # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
20
+ # Post.where(person_id: 5).where(category: ['Something', 'Else']).delete_all
21
+ #
22
+ # Both calls delete the affected posts all at once with a single DELETE statement.
23
+ # If you need to destroy dependent associations or call your <tt>before_*</tt> or
24
+ # +after_destroy+ callbacks, use the +destroy_all+ method instead.
25
+ #
26
+ # If an invalid method is supplied, +delete_all+ raises an ActiveRecord error:
27
+ #
28
+ # Post.limit(100).delete_all
29
+ # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit
30
+ def delete_all(conditions = nil)
31
+ if archive_with_deleted_at?
32
+ where(conditions).update_all(deleted_at_attributes)
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ # Deletes the row with a primary key matching the +id+ argument, using a
39
+ # SQL +DELETE+ statement, and returns the number of rows deleted. Active
40
+ # Record objects are not instantiated, so the object's callbacks are not
41
+ # executed, including any <tt>:dependent</tt> association options.
42
+ #
43
+ # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
44
+ #
45
+ # Note: Although it is often much faster than the alternative,
46
+ # <tt>#destroy</tt>, skipping callbacks might bypass business logic in
47
+ # your application that ensures referential integrity or performs other
48
+ # essential jobs.
49
+ #
50
+ # ==== Examples
51
+ #
52
+ # # Delete a single row
53
+ # Todo.delete(1)
54
+ #
55
+ # # Delete multiple rows
56
+ # Todo.delete([2,3,4])
57
+ def delete(id_or_array)
58
+ where(primary_key => id_or_array).delete_all
59
+ end
60
+
61
+ # Destroy an object (or multiple objects) that has the given id. The object is instantiated first,
62
+ # therefore all callbacks and filters are fired off before the object is deleted. This method is
63
+ # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
64
+ #
65
+ # This essentially finds the object (or multiple objects) with the given id, creates a new object
66
+ # from the attributes, and then calls destroy on it.
67
+ #
68
+ # ==== Parameters
69
+ #
70
+ # * +id+ - Can be either an Integer or an Array of Integers.
71
+ #
72
+ # ==== Examples
73
+ #
74
+ # # Destroy a single object
75
+ # Todo.destroy(1)
76
+ #
77
+ # # Destroy multiple objects
78
+ # todos = [1,2,3]
79
+ # Todo.destroy(todos)
80
+ def destroy(id)
81
+ if id.is_a?(Array)
82
+ id.map { |one_id| destroy(one_id) }
83
+ else
84
+ find(id).destroy
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,18 @@
1
+ require 'rails/railtie'
2
+ require 'action_controller'
3
+ require 'action_controller/railtie'
4
+ require 'deleted_at/active_record/base'
5
+ require 'deleted_at/active_record/relation'
6
+
7
+ module DeletedAt
8
+ class Railtie < ::Rails::Railtie
9
+
10
+ initializer 'deleted_at.install' do
11
+ ActiveSupport.on_load(:active_record) do
12
+ ::ActiveRecord::Relation.send :prepend, DeletedAt::ActiveRecord::Relation
13
+ ::ActiveRecord::Base.send :include, DeletedAt::ActiveRecord::Base
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -1,3 +1,3 @@
1
1
  module DeletedAt
2
- VERSION = "0.0.0"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,61 @@
1
+ module DeletedAt
2
+ module Views
3
+
4
+ def self.create_present_view(model)
5
+ table_name = present_view(model)
6
+ model.connection.execute <<-eos
7
+ CREATE OR REPLACE VIEW "#{table_name}"
8
+ AS SELECT * FROM "#{model.original_table_name}" WHERE #{model.deleted_at_column} IS NULL;
9
+ eos
10
+ # AS SELECT #{cols.join(', ')} FROM "#{model.original_table_name}" WHERE #{model.deleted_at_column} IS NULL;
11
+ return table_name
12
+ end
13
+
14
+ def self.create_deleted_view(model)
15
+ table_name = deleted_view(model)
16
+ model.connection.execute <<-eos
17
+ CREATE OR REPLACE VIEW "#{table_name}"
18
+ AS SELECT * FROM "#{model.original_table_name}" WHERE #{model.deleted_at_column} IS NOT NULL;
19
+ eos
20
+ return table_name
21
+ end
22
+
23
+ def self.present_view_exists?(model)
24
+ query = model.connection.execute <<-eos
25
+ SELECT EXISTS (
26
+ SELECT 1
27
+ FROM information_schema.tables
28
+ WHERE table_name = '#{present_view(model)}'
29
+ );
30
+ eos
31
+ query.first['exists'] == 't'
32
+ end
33
+
34
+ def self.deleted_view_exists?(model)
35
+ query = model.connection.execute <<-eos
36
+ SELECT EXISTS (
37
+ SELECT 1
38
+ FROM information_schema.tables
39
+ WHERE table_name = '#{deleted_view(model)}'
40
+ );
41
+ eos
42
+ query.first['exists'] == 't'
43
+ end
44
+
45
+ def self.present_view(model)
46
+ "#{model.original_table_name}/present"
47
+ end
48
+
49
+ def self.deleted_view(model)
50
+ "#{model.original_table_name}/deleted"
51
+ end
52
+
53
+ def self.destroy_present_view(model)
54
+ model.connection.execute("DROP VIEW IF EXISTS \"#{present_view(model)}\"")
55
+ end
56
+
57
+ def self.destroy_deleted_view(model)
58
+ model.connection.execute("DROP VIEW IF EXISTS \"#{deleted_view(model)}\"")
59
+ end
60
+ end
61
+ end
metadata CHANGED
@@ -1,29 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deleted_at
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dale Stevens
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2014-08-27 00:00:00.000000000 Z
11
+ date: 2017-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.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: 4.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: request_store
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.1.0
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: bundler
15
43
  requirement: !ruby/object:Gem::Requirement
16
44
  requirements:
17
45
  - - "~>"
18
46
  - !ruby/object:Gem::Version
19
- version: '1.6'
47
+ version: '1.13'
20
48
  type: :development
21
49
  prerelease: false
22
50
  version_requirements: !ruby/object:Gem::Requirement
23
51
  requirements:
24
52
  - - "~>"
25
53
  - !ruby/object:Gem::Version
26
- version: '1.6'
54
+ version: '1.13'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: rake
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -38,7 +66,35 @@ dependencies:
38
66
  - - "~>"
39
67
  - !ruby/object:Gem::Version
40
68
  version: '10.0'
41
- description:
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.10.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.10.0
97
+ description: Default scopes are bad. Don't delete your data. DeletedAt, I choose you!
42
98
  email:
43
99
  - dale@twilightcoders.net
44
100
  executables: []
@@ -46,17 +102,27 @@ extensions: []
46
102
  extra_rdoc_files: []
47
103
  files:
48
104
  - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - CODE_OF_CONDUCT.md
49
108
  - Gemfile
50
109
  - LICENSE.txt
51
110
  - README.md
52
111
  - Rakefile
112
+ - bin/console
113
+ - bin/setup
53
114
  - deleted_at.gemspec
54
115
  - lib/deleted_at.rb
116
+ - lib/deleted_at/active_record/base.rb
117
+ - lib/deleted_at/active_record/relation.rb
118
+ - lib/deleted_at/railtie.rb
55
119
  - lib/deleted_at/version.rb
120
+ - lib/deleted_at/views.rb
56
121
  homepage: https://github.com/TwilightCoders/deleted_at
57
122
  licenses:
58
123
  - MIT
59
- metadata: {}
124
+ metadata:
125
+ allowed_push_host: https://rubygems.org
60
126
  post_install_message:
61
127
  rdoc_options: []
62
128
  require_paths:
@@ -65,7 +131,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
65
131
  requirements:
66
132
  - - ">="
67
133
  - !ruby/object:Gem::Version
68
- version: '0'
134
+ version: '2.0'
69
135
  required_rubygems_version: !ruby/object:Gem::Requirement
70
136
  requirements:
71
137
  - - ">="
@@ -73,9 +139,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
139
  version: '0'
74
140
  requirements: []
75
141
  rubyforge_project:
76
- rubygems_version: 2.2.2
142
+ rubygems_version: 2.5.1
77
143
  signing_key:
78
144
  specification_version: 4
79
- summary: Provides functionality to ActiveRecord::Base to delete records while keeping
80
- them in the database.
145
+ summary: Soft delete your data, but keep it clean.
81
146
  test_files: []