zombie_record 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8fffbb5b3d7304492d0999ef54eb7db4fc783cb5
4
+ data.tar.gz: 121cc7d9627e7450500d076f025b501b52f31687
5
+ SHA512:
6
+ metadata.gz: e45075a3ee932ef550c34120d0be0d92d601e7839a16e06d418e949e4fba0673738a7f8e830e6d4be35aaca08f3ea5aa6bd95d66db594a22fd09258d794222a1
7
+ data.tar.gz: 38833db59a21a4bba05c96ba4d31fb34aefea3cd4d85912b9e5cbc1a3a71fa2dd3bb5d8c6217f82aa39d764a0b03f5c6144daf34d0f03bac67ad773d4b730dd3
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
@@ -0,0 +1,9 @@
1
+ ###### v0.4.0
2
+
3
+ * Allow accessing associated records on deleted objects, even if they themselves
4
+ are deleted, e.g.
5
+
6
+ # The post, comments, and category are all deleted.
7
+ post = Post.with_deleted.find(...)
8
+ post.comments
9
+ post.category
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in zombie_record.gemspec
4
+ gemspec
5
+
6
+ gem "byebug"
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Daniel Schierbeck
2
+
3
+ MIT License
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:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
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.
@@ -0,0 +1,51 @@
1
+ # Zombie Record
2
+
3
+ Allows restoring your Active Records from the dead!
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'zombie_record'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install zombie_record
18
+
19
+ ## Usage
20
+
21
+ Simply include the `ZombieRecord::Restorable` in your model class:
22
+
23
+ ```ruby
24
+ class Book < ActiveRecord::Base
25
+ include ZombieRecord::Restorable
26
+ end
27
+ ```
28
+
29
+ Zombie Record assumes the model's table has a `deleted_at` column with the `timestamp` type.
30
+
31
+ You can now delete and restore Book records:
32
+
33
+ ```ruby
34
+ book = Book.find(42)
35
+ book.destroy
36
+
37
+ Book.find(42) # raises ActiveRecord::RecordNotFound.
38
+
39
+ book = Book.deleted.find(42)
40
+ book.restore!
41
+
42
+ Book.find(42) # returns the Book record.
43
+ ```
44
+
45
+ ## Contributing
46
+
47
+ 1. Fork it
48
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
49
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
50
+ 4. Push to the branch (`git push origin my-new-feature`)
51
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,7 @@
1
+ require 'active_support'
2
+ require 'zombie_record/version'
3
+
4
+ module ZombieRecord
5
+ end
6
+
7
+ require 'zombie_record/restorable'
@@ -0,0 +1,195 @@
1
+ module ZombieRecord
2
+ module Restorable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ default_scope { where(deleted_at: nil) }
7
+
8
+ define_callbacks :restore
9
+ end
10
+
11
+ # Override Rails' #destroy for soft-delete functionality
12
+ # When changing to Rails 4, override #destroy_row with a one-liner instead.
13
+ def destroy
14
+ run_callbacks :destroy do
15
+ destroy_associations
16
+
17
+ if persisted?
18
+ time = current_time_from_proper_timezone
19
+ update_column(:deleted_at, time)
20
+
21
+ if self.class.column_names.include?("updated_at")
22
+ update_column(:updated_at, time)
23
+ end
24
+ end
25
+
26
+ @destroyed = true
27
+ freeze
28
+ end
29
+ end
30
+
31
+ # Restores a destroyed record.
32
+ #
33
+ # Returns nothing.
34
+ def restore!
35
+ if frozen?
36
+ raise "cannot restore an object that has been destroyed directly; " <<
37
+ "please make sure to load it from the database again."
38
+ end
39
+
40
+ run_callbacks :restore do
41
+ update_column(:deleted_at, nil)
42
+
43
+ restore_associated_records!
44
+ end
45
+ end
46
+
47
+ # Whether the record has been destroyed.
48
+ #
49
+ # Returns true if the record is deleted, false otherwise.
50
+ def deleted?
51
+ !deleted_at.nil?
52
+ end
53
+
54
+ # Allows accessing deleted associations from the record.
55
+ #
56
+ # Example
57
+ #
58
+ # book = Book.first.with_deleted_associations
59
+ #
60
+ # # Even deleted chapters are returned!
61
+ # book.chapters #=> [...]
62
+ #
63
+ # Returns a wrapped ActiveRecord::Base object.
64
+ def with_deleted_associations
65
+ if deleted?
66
+ WithDeletedAssociations.new(self)
67
+ else
68
+ self
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def restore_associated_records!
75
+ self.class.reflect_on_all_associations.each do |association|
76
+ # Only restore associations that are automatically destroyed alongside
77
+ # the record.
78
+ next unless association.options[:dependent] == :destroy
79
+
80
+ # Don't try to restore models that are not restorable.
81
+ next unless association.klass.ancestors.include?(Restorable)
82
+
83
+ records = deleted_records_for_association(association)
84
+
85
+ records.each do |record|
86
+ record.restore!
87
+ end
88
+ end
89
+ end
90
+
91
+ def deleted_records_for_association(association)
92
+ if association.macro == :has_one
93
+ foreign_key = association.foreign_key
94
+ association.klass.deleted.where(foreign_key => id)
95
+ elsif association.macro == :has_many
96
+ public_send(association.name).deleted
97
+ elsif association.macro == :belongs_to
98
+ associated_id = public_send(association.foreign_key)
99
+ return [] unless associated_id.present?
100
+ association.klass.deleted.where(:id => associated_id)
101
+ else
102
+ raise "association type #{association.macro} not supported"
103
+ end
104
+ end
105
+
106
+ # Wraps a deleted record and makes sure that any associated record is
107
+ # available even if it is deleted. This is done by intercepting the method
108
+ # calls, checking if the method is an association access, and then ensuring
109
+ # that deleted records are included in the resulting query.
110
+ class WithDeletedAssociations < BasicObject
111
+ def initialize(record)
112
+ @record = record
113
+ end
114
+
115
+ def method_missing(name, *args, &block)
116
+ delegate_to_record(name) { @record.public_send(name, *args, &block) }
117
+ end
118
+
119
+ # We want *all* methods to be delegated.
120
+ BasicObject.instance_methods.each do |name|
121
+ define_method(name) do |*args, &block|
122
+ @record.public_send(name, *args, &block)
123
+ end
124
+ end
125
+
126
+ private
127
+
128
+ def delegate_to_record(name, &block)
129
+ if reflection = reflect_on(name)
130
+ with_deleted_associations(reflection, &block)
131
+ else
132
+ block.call
133
+ end
134
+ end
135
+
136
+ def reflect_on(name)
137
+ reflection = @record.class.reflect_on_association(name)
138
+
139
+ if reflection && restorable_reflection?(reflection)
140
+ reflection
141
+ end
142
+ end
143
+
144
+ def associated_record_class(reflection)
145
+ # Polymorphic associations don't have an easy way to access the class,
146
+ # so we'll have to do it ourselves.
147
+ if reflection.options[:polymorphic]
148
+ @record.public_send(reflection.foreign_type).constantize
149
+ else
150
+ reflection.klass
151
+ end
152
+ end
153
+
154
+ def restorable_reflection?(reflection)
155
+ associated_record_class(reflection).ancestors.include?(Restorable)
156
+ end
157
+
158
+ def with_deleted_associations(reflection, &block)
159
+ case reflection.macro
160
+ when :has_one, :belongs_to
161
+ associated_record_class(reflection).unscoped(&block).with_deleted_associations
162
+ when :has_many
163
+ block.call.with_deleted
164
+ else
165
+ raise "invalid macro #{reflection.macro.inspect}"
166
+ end
167
+ end
168
+ end
169
+
170
+ module WithDeletedAssociationsWrapper
171
+ def to_a
172
+ super.map(&:with_deleted_associations)
173
+ end
174
+ end
175
+
176
+ module ClassMethods
177
+
178
+ # Scopes the relation to only include deleted records.
179
+ #
180
+ # Returns an ActiveRecord::Relation.
181
+ def deleted
182
+ with_deleted.where("#{quoted_table_name}.deleted_at IS NOT NULL")
183
+ end
184
+
185
+ # Scopes the relation to include both active and deleted records.
186
+ #
187
+ # Returns an ActiveRecord::Relation.
188
+ def with_deleted
189
+ scoped.
190
+ tap {|relation| relation.default_scoped = false }.
191
+ extending(WithDeletedAssociationsWrapper)
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,3 @@
1
+ module ZombieRecord
2
+ VERSION = "0.4.0"
3
+ end
@@ -0,0 +1,179 @@
1
+ require 'spec_helper'
2
+
3
+ describe ZombieRecord::Restorable do
4
+ context "when the record is deleted" do
5
+ it "allows accessing a deleted has_one association" do
6
+ book = Book.create!
7
+ cover = Cover.create!(book: book)
8
+
9
+ book.destroy
10
+ book = Book.with_deleted.first
11
+
12
+ book.cover.should == cover.reload
13
+ end
14
+
15
+ it "allows accessing deleted belongs_to associations" do
16
+ book = Book.create!
17
+ chapter = book.chapters.create!
18
+
19
+ book.destroy
20
+ chapter = Chapter.with_deleted.first
21
+
22
+ chapter.book.should == book
23
+ end
24
+
25
+ it "allows accessing deleted polymorphic belongs_to associations" do
26
+ book = Book.create!
27
+ tag = Tag.create!(name: "jelly", taggable: book)
28
+
29
+ tag.destroy
30
+ tag = Tag.with_deleted.first
31
+
32
+ tag.taggable.should == book
33
+ end
34
+
35
+ it "ensures deleted associations themselves allow access to deleted records" do
36
+ book = Book.create!
37
+ chapter = book.chapters.create!
38
+ book.bookmarks.create!
39
+
40
+ book.destroy
41
+ bookmark = Bookmark.with_deleted.first
42
+
43
+ bookmark.book.chapters.should == [chapter]
44
+ chapter = bookmark.book.chapters.first
45
+
46
+ chapter.book.should == book
47
+ end
48
+
49
+ it "forwards normal method calls" do
50
+ book = Book.create!(title: "The Odyssey")
51
+ book.destroy
52
+ book = Book.with_deleted.first
53
+
54
+ book.title.should == "The Odyssey"
55
+ end
56
+ end
57
+
58
+ describe ".deleted" do
59
+ it "scopes the query to only deleted records" do
60
+ book1 = Book.create!
61
+ book2 = Book.create!
62
+
63
+ book1.destroy
64
+
65
+ Book.deleted.should == [book1]
66
+ end
67
+
68
+ it "respects associations" do
69
+ author = Author.create!
70
+ book = Book.create!(author: author)
71
+ other_book = Book.create!
72
+
73
+ book.destroy
74
+ other_book.destroy
75
+
76
+ author.books.deleted.should == [book]
77
+ end
78
+ end
79
+
80
+ describe "#destroy" do
81
+ it "sets #updated_at if it is defined" do
82
+ book = Timecop.travel(2.days.ago) { Book.create! }
83
+ updated_at = book.updated_at
84
+
85
+ book.destroy
86
+
87
+ book.updated_at.should_not == updated_at
88
+ end
89
+
90
+ it "does not set #updated_at if it is not defined" do
91
+ bookmark = Timecop.travel(2.days.ago) { Bookmark.create! }
92
+ expect { bookmark.destroy }.to_not raise_exception
93
+ end
94
+ end
95
+
96
+ describe "#restore!" do
97
+ let(:book) { Book.create! }
98
+ let(:deleted_book) { Book.deleted.find(book.id) }
99
+
100
+ it "restores the record" do
101
+ book.destroy
102
+
103
+ deleted_book.restore!
104
+ deleted_book.deleted_at.should be_nil
105
+ end
106
+
107
+ it "also restores restorable has_many associated records" do
108
+ chapter = book.chapters.create!
109
+
110
+ book.destroy
111
+ deleted_book.restore!
112
+
113
+ deleted_chapter = Chapter.with_deleted.find(chapter.id)
114
+ deleted_chapter.deleted_at.should be_nil
115
+ end
116
+
117
+ it "also restores restorable has_one associated records" do
118
+ cover = book.create_cover!
119
+
120
+ book.destroy
121
+ deleted_book.restore!
122
+
123
+ deleted_cover = Cover.with_deleted.find(cover.id)
124
+ deleted_cover.deleted_at.should be_nil
125
+ end
126
+
127
+ it "also restores restorable belongs_to associated records" do
128
+ author = Author.create!
129
+ book.update_attribute(:author, author)
130
+
131
+ book.destroy
132
+ deleted_book.restore!
133
+
134
+ deleted_author = Author.with_deleted.find(author.id)
135
+ deleted_author.deleted_at.should be_nil
136
+ end
137
+
138
+ it "does not restore hard deleted associated records" do
139
+ note = book.notes.create!
140
+
141
+ book.destroy
142
+ deleted_book.restore!
143
+
144
+ Note.where(id: note.id).should_not exist
145
+ end
146
+
147
+ it "does not restore an association if it is not destroy dependent" do
148
+ library = Library.create!
149
+ book.update_attribute(:library, library)
150
+
151
+ book.destroy
152
+ library.destroy
153
+ deleted_book.restore!
154
+
155
+ library_after_deletion = Library.with_deleted.find(library.id)
156
+ library_after_deletion.deleted_at.should_not be_nil
157
+ end
158
+
159
+ it "fails if the object itself has been destroyed" do
160
+ book.destroy
161
+
162
+ expect { book.restore! }.to raise_exception(RuntimeError)
163
+ end
164
+ end
165
+
166
+ describe "#deleted?" do
167
+ let(:book) { Book.create! }
168
+
169
+ it "returns true if the record is deleted" do
170
+ book.destroy
171
+
172
+ book.should be_deleted
173
+ end
174
+
175
+ it "returns false if the record is not deleted" do
176
+ book.should_not be_deleted
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,138 @@
1
+ require 'bundler/setup'
2
+ require 'active_record'
3
+ require 'timecop'
4
+ require 'byebug'
5
+
6
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
7
+ require 'zombie_record'
8
+
9
+ class Book < ActiveRecord::Base
10
+ include ZombieRecord::Restorable
11
+
12
+ belongs_to :library
13
+ belongs_to :author, dependent: :destroy
14
+ has_one :cover, dependent: :destroy
15
+
16
+ has_many :chapters, dependent: :destroy
17
+ has_many :bookmarks, dependent: :destroy
18
+ has_many :notes, dependent: :destroy
19
+ end
20
+
21
+ class Chapter < ActiveRecord::Base
22
+ include ZombieRecord::Restorable
23
+
24
+ belongs_to :book
25
+ end
26
+
27
+ class Bookmark < ActiveRecord::Base
28
+ include ZombieRecord::Restorable
29
+
30
+ belongs_to :book
31
+ end
32
+
33
+ class Note < ActiveRecord::Base
34
+ belongs_to :book
35
+ end
36
+
37
+ class Author < ActiveRecord::Base
38
+ include ZombieRecord::Restorable
39
+
40
+ has_many :books
41
+ end
42
+
43
+ class Tag < ActiveRecord::Base
44
+ include ZombieRecord::Restorable
45
+
46
+ belongs_to :taggable, polymorphic: true, dependent: :destroy
47
+ end
48
+
49
+ class Cover < ActiveRecord::Base
50
+ include ZombieRecord::Restorable
51
+
52
+ belongs_to :book
53
+ end
54
+
55
+
56
+ class Library < ActiveRecord::Base
57
+ include ZombieRecord::Restorable
58
+
59
+ has_many :book
60
+ end
61
+
62
+ RSpec.configure do |config|
63
+ config.around do |example|
64
+ ActiveRecord::Base.transaction do
65
+ example.run
66
+ raise ActiveRecord::Rollback
67
+ end
68
+ end
69
+
70
+ config.before :suite do
71
+ ActiveRecord::Base.establish_connection(
72
+ adapter: "mysql2",
73
+ username: "root",
74
+ host: "127.0.0.1",
75
+ port: 3306,
76
+ password: ""
77
+ )
78
+
79
+ ActiveRecord::Base.connection.create_database("zombie_record")
80
+ ActiveRecord::Base.connection.execute("use zombie_record;")
81
+
82
+ ActiveRecord::Schema.define do
83
+ self.verbose = false
84
+
85
+ create_table :books do |t|
86
+ t.integer :author_id
87
+ t.integer :library_id
88
+ t.timestamps
89
+ t.string :title
90
+ t.timestamp :deleted_at
91
+ end
92
+
93
+ create_table :chapters do |t|
94
+ t.integer :book_id
95
+ t.timestamps
96
+ t.timestamp :deleted_at
97
+ end
98
+
99
+ create_table :bookmarks do |t|
100
+ t.integer :book_id
101
+ t.timestamp :created_at
102
+ t.timestamp :deleted_at
103
+ end
104
+
105
+ create_table :notes do |t|
106
+ t.integer :book_id
107
+ t.timestamps
108
+ end
109
+
110
+ create_table :tags do |t|
111
+ t.string :name
112
+ t.string :taggable_type
113
+ t.integer :taggable_id
114
+ t.timestamp :deleted_at
115
+ end
116
+
117
+ create_table :covers do |t|
118
+ t.integer :book_id
119
+ t.timestamps
120
+ t.timestamp :deleted_at
121
+ end
122
+
123
+ create_table :authors do |t|
124
+ t.timestamps
125
+ t.timestamp :deleted_at
126
+ end
127
+
128
+ create_table :libraries do |t|
129
+ t.timestamps
130
+ t.timestamp :deleted_at
131
+ end
132
+ end
133
+ end
134
+
135
+ config.after :suite do
136
+ ActiveRecord::Base.connection.drop_database("zombie_record") rescue nil
137
+ end
138
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'zombie_record/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "zombie_record"
8
+ spec.version = ZombieRecord::VERSION
9
+ spec.authors = ["Daniel Schierbeck"]
10
+ spec.email = ["dasch@zendesk.com"]
11
+ spec.description = %q{Allows restoring your Active Records from the dead!}
12
+ spec.summary = %q{Allows restoring your Active Records from the dead!}
13
+ spec.homepage = "https://github.com/dasch/zombie_record"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activerecord", "~> 3.2.15"
22
+ spec.add_dependency "mysql2"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "timecop", "~> 0.7.0"
28
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zombie_record
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Schierbeck
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-26 00:00:00.000000000 Z
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: 3.2.15
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.15
27
+ - !ruby/object:Gem::Dependency
28
+ name: mysql2
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
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: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: timecop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.7.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.7.0
97
+ description: Allows restoring your Active Records from the dead!
98
+ email:
99
+ - dasch@zendesk.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - CHANGELOG.md
108
+ - Gemfile
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - lib/zombie_record.rb
113
+ - lib/zombie_record/restorable.rb
114
+ - lib/zombie_record/version.rb
115
+ - spec/restorable_spec.rb
116
+ - spec/spec_helper.rb
117
+ - zombie_record.gemspec
118
+ homepage: https://github.com/dasch/zombie_record
119
+ licenses:
120
+ - MIT
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.2.2
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Allows restoring your Active Records from the dead!
142
+ test_files:
143
+ - spec/restorable_spec.rb
144
+ - spec/spec_helper.rb
145
+ has_rdoc: