storage_unit 0.0.1

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: f7e3ecd85e04df0d54954b250d3d1f2de3bedcdd
4
+ data.tar.gz: bf0e2597da2ac992fff271875a22540b697baf9e
5
+ SHA512:
6
+ metadata.gz: 83ea81551a3f79d5ea0f513fddd36b5cf5561bc493a02753ecd4b5d104961a0af2600fa9e8b7270c35cf128e68b19cb62f5ca4217c7c1ddac250a33f8fa58079
7
+ data.tar.gz: ac2e270f5056529bd2c37368f0db25d67c486947ce910679bd6eb533babaa019e107d92cd27d5fd7ceb041edcb5f9a74c1402e3830606ab7e2b642a0e1ffbdf1
data/.coveralls.yml ADDED
@@ -0,0 +1,2 @@
1
+ service_name: travis-ci
2
+ repo_token: NLvtwz6L7nW32aBVsJK4ph3MelAV48otV
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ .bundle/
2
+ log/*.log
3
+ pkg/
4
+ spec/dummy/config/database.yml
5
+ spec/dummy/log/*.log
6
+ spec/dummy/tmp/
7
+ spec/dummy/.sass-cache
8
+ spec/dummy/public/uploads/*
9
+ tmp/*
10
+ coverage/*
11
+ *.gem
12
+ Gemfile.lock
13
+ spec/dummy/db/test.sqlite3
14
+ gemfiles/*.lock
15
+ .ruby-gemset
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.1
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ cache: bundler
3
+ script: bundle exec rspec
4
+ notifications:
5
+ email: false
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 Department of Better Technology
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,110 @@
1
+ storage_unit
2
+ =========
3
+
4
+ [![Travis][0]](https://travis-ci.org/dobtco/storage_unit)
5
+ [![Code Climate][1]](https://codeclimate.com/github/dobtco/storage_unit)
6
+ [![Coveralls][2]](https://coveralls.io/r/dobtco/storage_unit)
7
+ [![RubyGem][3]](http://rubygems.org/gems/storage_unit)
8
+
9
+ :recycle: Soft deletion for Rails 4.1+, done right.
10
+
11
+ ## Goals
12
+
13
+ - Standard set of "soft deletion" methods (`trash`, `recover`, `trashed?`)
14
+ - Explicit storage_unit dependencies (automatically trash associated records)
15
+ - Low-overhead (minimize queries)
16
+ - No validations on `recover`. (If your records became invalid after they were trashed, check for this yourself)
17
+ - Small, readable codebase
18
+
19
+ ## Non-goals
20
+
21
+ - Targeting anything less than Rails 4.1
22
+ - Reflection on Rails' associations
23
+ - Generally, anything weird or complex happening behind the scenes
24
+
25
+ ## Installation
26
+
27
+ ```ruby
28
+ # In your Gemfile:
29
+ gem 'storage_unit'
30
+
31
+ # In a migration:
32
+ add_column :posts, :deleted_at, :datetime
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ```ruby
38
+ class Post < ActiveRecord::Base
39
+ has_storage_unit
40
+ end
41
+
42
+ post = Post.create
43
+ Post.all # => [post]
44
+ post.trashed? # => false
45
+
46
+ post.trash!
47
+ post.trashed? # => true
48
+ Post.all # => []
49
+ Post.with_deleted.all # => [post]
50
+
51
+ post.recover!
52
+ post.trashed? # => false
53
+ Post.all # => []
54
+ ```
55
+
56
+ ### Cascading trashes
57
+
58
+ ```ruby
59
+ class User < ActiveRecord::Base
60
+ has_storage_unit, cascade: [:posts]
61
+ has_many :posts
62
+ end
63
+
64
+ class Post < ActiveRecord::Base
65
+ has_storage_unit
66
+ end
67
+
68
+ user = User.create
69
+ post = Post.create
70
+
71
+ user.trash!
72
+ user.trashed? # => true
73
+ post.trashed? # => true
74
+
75
+ user.recover!
76
+ user.trashed? # => false
77
+ post.trashed? # => false
78
+ ```
79
+
80
+ ### Callbacks
81
+
82
+ ```ruby
83
+ class Post < ActiveRecord::Base
84
+ has_storage_unit
85
+ after_recover :ensure_record_is_still_valid
86
+
87
+ private
88
+ def ensure_record_is_still_valid
89
+ if !valid?
90
+ # i dunno, trash! this post again? delete it entirely? inform the user? shit is hard.
91
+ end
92
+ end
93
+ end
94
+ ```
95
+
96
+ ### Use a different column
97
+
98
+ ```ruby
99
+ class Post < ActiveRecord::Base
100
+ has_storage_unit column: :trashed_at
101
+ end
102
+ ```
103
+
104
+ ## License
105
+ MIT.
106
+
107
+ [0]: https://img.shields.io/travis/dobtco/storage_unit.svg
108
+ [1]: https://img.shields.io/codeclimate/github/dobtco/storage_unit.svg
109
+ [2]: https://img.shields.io/coveralls/dobtco/storage_unit.svg
110
+ [3]: https://img.shields.io/gem/v/storage_unit.svg
@@ -0,0 +1,3 @@
1
+ require 'storage_unit/setup'
2
+ require 'storage_unit/core'
3
+ ActiveRecord::Base.send :include, StorageUnit::Setup
@@ -0,0 +1,59 @@
1
+ module StorageUnit
2
+ module Core
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ default_scope { where(with_deleted_scope_sql) }
7
+ define_model_callbacks :trash
8
+ define_model_callbacks :recover
9
+ end
10
+
11
+ module ClassMethods
12
+ def with_deleted_scope_sql
13
+ all.table[storage_unit_opts[:column]].eq(nil).to_sql
14
+ end
15
+
16
+ # lifted from acts_as_paranoid, works around https://github.com/rails/rails/issues/4306
17
+ # with this in place Post.limit(10).with_deleted, will work as expected
18
+ def with_deleted
19
+ scope = self.all
20
+ scope.where_values.delete(with_deleted_scope_sql)
21
+ scope
22
+ end
23
+ end
24
+
25
+ def trashed?
26
+ send(storage_unit_opts[:column]).present?
27
+ end
28
+
29
+ def trash!
30
+ run_callbacks :trash do
31
+ update_columns trash_hash(DateTime.now)
32
+ trash_dependents
33
+ end
34
+ end
35
+
36
+ def trash_dependents
37
+ storage_unit_opts[:cascade].each do |x|
38
+ send(x).update_all trash_hash(DateTime.now)
39
+ end
40
+ end
41
+
42
+ def recover!(opts = {})
43
+ run_callbacks :recover do
44
+ update_columns trash_hash(nil)
45
+ recover_dependents
46
+ end
47
+ end
48
+
49
+ def recover_dependents
50
+ storage_unit_opts[:cascade].each do |x|
51
+ send(x).with_deleted.update_all trash_hash(nil)
52
+ end
53
+ end
54
+
55
+ def trash_hash(value)
56
+ {}.tap { |h| h[storage_unit_opts[:column]] = value }
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,21 @@
1
+ module StorageUnit
2
+ module Setup
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ end
7
+
8
+ module ClassMethods
9
+ def has_storage_unit(opts = {})
10
+ cattr_accessor :storage_unit_opts
11
+
12
+ self.storage_unit_opts = {
13
+ column: :deleted_at,
14
+ cascade: []
15
+ }.merge(opts)
16
+
17
+ include StorageUnit::Core
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ module StorageUnit
2
+ VERSION = '0.0.1'
3
+ end
data/spec/setup.rb ADDED
@@ -0,0 +1,14 @@
1
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
2
+
3
+ ActiveRecord::Schema.define(:version => 1) do
4
+ create_table :users do |t|
5
+ t.datetime :deleted_at
6
+ t.datetime :trashed_at
7
+ end
8
+
9
+ create_table :notes do |t|
10
+ t.integer :user_id
11
+ t.datetime :deleted_at
12
+ t.text :body
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ Coveralls::SimpleCov::Formatter,
6
+ SimpleCov::Formatter::HTMLFormatter
7
+ ]
8
+
9
+ require 'rails/all'
10
+ require 'storage_unit'
11
+ require 'rspec/rails'
12
+ require_relative 'setup'
13
+
14
+ RSpec.configure do |c|
15
+ c.use_transactional_fixtures = true
16
+ end
@@ -0,0 +1,165 @@
1
+ require 'spec_helper'
2
+
3
+ class User < ActiveRecord::Base
4
+ self.table_name = 'users'
5
+ has_storage_unit
6
+ end
7
+
8
+ class UserWithTrashedAtColumn < ActiveRecord::Base
9
+ self.table_name = 'users'
10
+ has_storage_unit column: :trashed_at
11
+ end
12
+
13
+ class TrashableNote < ActiveRecord::Base
14
+ self.table_name = 'notes'
15
+ has_storage_unit
16
+ validates :body, presence: true
17
+ end
18
+
19
+ class UserWithNote < ActiveRecord::Base
20
+ self.table_name = 'users'
21
+ has_many :notes, class_name: 'NoteForUser', foreign_key: 'user_id'
22
+ has_storage_unit cascade: [:notes]
23
+ end
24
+
25
+ class NoteForUser < TrashableNote
26
+ belongs_to :user, class_name: 'UserWithNote'
27
+ end
28
+
29
+ describe 'Options' do
30
+ describe 'column' do
31
+ let!(:user) { UserWithTrashedAtColumn.create }
32
+
33
+ it 'functions properly' do
34
+ expect(UserWithTrashedAtColumn.count).to eq 1
35
+ user.trash!
36
+ expect(UserWithTrashedAtColumn.count).to eq 0
37
+ end
38
+ end
39
+
40
+ describe 'cascade' do
41
+ let!(:user) { UserWithNote.create }
42
+ let!(:note) { NoteForUser.create(body: 'foo', user: user) }
43
+
44
+ describe '#trash!' do
45
+ it 'trashes associated records' do
46
+ expect(user.deleted_at).to be_blank
47
+ expect(note.deleted_at).to be_blank
48
+ user.trash!
49
+ expect(user.reload.deleted_at).to be_present
50
+ expect(note.reload.deleted_at).to be_present
51
+ end
52
+ end
53
+
54
+ describe '#recover!' do
55
+ before do
56
+ user.update_attributes deleted_at: Time.now
57
+ note.update_attributes deleted_at: Time.now
58
+ end
59
+
60
+ it 'recovers associated records' do
61
+ expect(user.deleted_at).to be_present
62
+ expect(note.deleted_at).to be_present
63
+ user.recover!
64
+ expect(user.reload.deleted_at).to be_blank
65
+ expect(note.reload.deleted_at).to be_blank
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ describe 'Default scope' do
72
+ let!(:user) { User.create }
73
+
74
+ it 'excludes trashed objects' do
75
+ expect(User.count).to eq 1
76
+ user.update deleted_at: Time.now
77
+ expect(User.count).to eq 0
78
+ end
79
+
80
+ it 'can be overridden' do
81
+ expect(User.count).to eq 1
82
+ user.update deleted_at: Time.now
83
+ expect(User.with_deleted.count).to eq 1
84
+ end
85
+ end
86
+
87
+ describe '#trashed?' do
88
+ let!(:user) { User.create }
89
+ subject { user }
90
+
91
+ it { should_not be_trashed }
92
+
93
+ context 'when trashed' do
94
+ before { user.update deleted_at: Time.now }
95
+ it { should be_trashed }
96
+ end
97
+ end
98
+
99
+ describe '#trash!' do
100
+ let!(:user) { User.create }
101
+
102
+ it 'functions properly' do
103
+ expect(user.deleted_at).to be_blank
104
+ user.trash!
105
+ expect(user.deleted_at).to be_present
106
+ expect(user.reload.deleted_at).to be_present
107
+ end
108
+
109
+ context 'with callbacks' do
110
+ before do
111
+ User.before_trash :do_before_trash
112
+ User.after_trash :do_after_trash
113
+ User.around_trash :do_around_trash
114
+ end
115
+
116
+ it 'calls them' do
117
+ expect(user).to receive(:do_before_trash)
118
+ expect(user).to receive(:do_after_trash)
119
+ expect(user).to receive(:do_around_trash)
120
+ user.trash!
121
+ end
122
+ end
123
+ end
124
+
125
+ describe '#recover!' do
126
+ let!(:user) { User.create(deleted_at: Time.now) }
127
+
128
+ it 'functions properly' do
129
+ expect(user.deleted_at).to be_present
130
+ user.recover!
131
+ expect(user.deleted_at).to be_blank
132
+ expect(user.reload.deleted_at).to be_blank
133
+ end
134
+
135
+ context 'with callbacks' do
136
+ before do
137
+ User.before_recover :do_before_recover
138
+ User.after_recover :do_after_recover
139
+ User.around_recover :do_around_recover
140
+ end
141
+
142
+ it 'calls them' do
143
+ expect(user).to receive(:do_before_recover)
144
+ expect(user).to receive(:do_after_recover)
145
+ expect(user).to receive(:do_around_recover)
146
+ user.recover!
147
+ end
148
+ end
149
+
150
+ context 'when invalid' do
151
+ let!(:note) do
152
+ note = TrashableNote.new(deleted_at: Time.now)
153
+ note.save(validate: false)
154
+ note
155
+ end
156
+
157
+ it 'does not care' do
158
+ expect {
159
+ note.recover!
160
+ }.to_not raise_error
161
+
162
+ expect(note).to_not be_valid
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,27 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ # Maintain your gem's version:
4
+ require "storage_unit/version"
5
+
6
+ # Describe your gem and declare its dependencies:
7
+ Gem::Specification.new do |s|
8
+ s.name = "storage_unit"
9
+ s.version = StorageUnit::VERSION
10
+
11
+ s.required_ruby_version = Gem::Requirement.new('>= 2.0.0')
12
+ s.authors = ['Adam Becker']
13
+ s.summary = 'Soft deletion for Rails 4, done right.'
14
+ s.email = 'adam@dobt.co'
15
+ s.license = 'MIT'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {features,spec}/*`.split("\n")
19
+
20
+ s.homepage = 'http://github.com/dobtco/storage_unit'
21
+
22
+ s.add_dependency 'rails', '~> 4.1', '>= 4.1.0'
23
+
24
+ s.add_development_dependency 'coveralls'
25
+ s.add_development_dependency 'rspec-rails'
26
+ s.add_development_dependency 'sqlite3'
27
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: storage_unit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Adam Becker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-03 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: '4.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 4.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '4.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 4.1.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: coveralls
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec-rails
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: sqlite3
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ description:
76
+ email: adam@dobt.co
77
+ executables: []
78
+ extensions: []
79
+ extra_rdoc_files: []
80
+ files:
81
+ - ".coveralls.yml"
82
+ - ".gitignore"
83
+ - ".ruby-version"
84
+ - ".travis.yml"
85
+ - Gemfile
86
+ - LICENSE.md
87
+ - README.md
88
+ - lib/storage_unit.rb
89
+ - lib/storage_unit/core.rb
90
+ - lib/storage_unit/setup.rb
91
+ - lib/storage_unit/version.rb
92
+ - spec/setup.rb
93
+ - spec/spec_helper.rb
94
+ - spec/storage_unit_spec.rb
95
+ - storage_unit.gemspec
96
+ homepage: http://github.com/dobtco/storage_unit
97
+ licenses:
98
+ - MIT
99
+ metadata: {}
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: 2.0.0
109
+ required_rubygems_version: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ requirements: []
115
+ rubyforge_project:
116
+ rubygems_version: 2.2.2
117
+ signing_key:
118
+ specification_version: 4
119
+ summary: Soft deletion for Rails 4, done right.
120
+ test_files:
121
+ - spec/setup.rb
122
+ - spec/spec_helper.rb
123
+ - spec/storage_unit_spec.rb