Cthulhu 0.1.1

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: c4b7288760b69b6b550d647ea7f0c2b769a2c716
4
+ data.tar.gz: 43acd21b68e3c87204e3074b568faf7896358c61
5
+ SHA512:
6
+ metadata.gz: a4bde1eb17bc1df9a4aa656147475ee4400fac963f0afad59ca6b2a0cb670db8382bc807d74e01b9ecde54ef0ce1869fe95b354a32d0e2c0e4852bcf676ee947
7
+ data.tar.gz: b2613453c30e869b5cbec7f006fec13c7c1a422301ad0eda98cac6a22c4484ac2048f81bbdf5d4ac1eacec98cddbf68e72168c0fbf1308e9fb78d4e86e3d8dc1
@@ -0,0 +1,19 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ # vim swap files:
12
+ *.swp
13
+ *.swo
14
+
15
+ # OS specific files
16
+ .DS_Store
17
+
18
+ # gem file
19
+ *.gem
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ services:
5
+ - postgresql
6
+ env: POSTGRESQL_DB_USER=postgres
7
+ before_script: bundle exec rake create_databases
8
+ script: bundle exec rake test
9
+ after_script: bundle exec rake drop_databases
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cthulhu/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "Cthulhu"
8
+ spec.version = Cthulhu::VERSION
9
+ spec.authors = ["behrooz shabani (everplays)\n"]
10
+ spec.email = ["everplays@gmail.com"]
11
+
12
+ spec.summary = %q{replacement for ActiveRecord dependent and Database's cascade}
13
+ spec.description = %q{If you do not want to setup foreign keys with cascade and ActiveRecord's dependent is too slow for your big database, this is what you are looking for.}
14
+ spec.homepage = "https://github.com/bookingexperts/cthulhu"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|bin)/}) }
18
+ spec.bindir = "bin"
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.11"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "minitest", "~> 5.0"
25
+ spec.add_development_dependency "pg", '~> 0'
26
+ spec.add_development_dependency "schema_dev", '~> 0'
27
+ spec.add_development_dependency "pry", '~> 0'
28
+ spec.add_development_dependency "factory_girl", '~> 0'
29
+ spec.add_development_dependency "database_cleaner", '~> 0'
30
+
31
+ spec.add_dependency "activerecord", "~> 4.2"
32
+ end
Binary file
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cthulhu.gemspec
4
+ gemspec
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 BookingExperts
4
+
5
+ Author: behrooz shabani (everplays)
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
@@ -0,0 +1,166 @@
1
+ # Cthulhu
2
+
3
+ ![Cthulhu, the destroyer of words](Cthulhu.jpg?raw=true "Cthulhu")
4
+
5
+ By using this gem, you will be able to destroy objects and all of their associated children without fetching them from database or using cascade.
6
+
7
+ This is useful for big applications that:
8
+
9
+ * have foreign keys but you do not want to setup cascades to protect users from removing stuff that they do not mean.
10
+ * Database is too big and you do not want to use ActiveRecord's `dependant: :destroy` for performance reasons.
11
+
12
+ So as foreign keys are setup, you can not accidentally destroy a record and all of its children by doing something like this in console:
13
+
14
+ ```ruby
15
+ User.destroy 1
16
+ ```
17
+
18
+ However, if needed, you can remove it by:
19
+
20
+ ```ruby
21
+ Cthulhu.destroy! User.find(1)
22
+ ```
23
+
24
+ ## Transaction
25
+
26
+ Keep in mind that Cthulhu is destroyer, therefore, it is not wrapped in any transaction. To be safe, you need to call it within a transaction like so:
27
+
28
+ ```ruby
29
+ user = User.find 1
30
+ User.connection.transaction do
31
+ Cthulhu.destroy! user
32
+ end
33
+ ```
34
+
35
+ ## Installation
36
+
37
+ Add this line to your application's Gemfile:
38
+
39
+ ```ruby
40
+ gem 'Cthulhu'
41
+ ```
42
+
43
+ And then execute:
44
+
45
+ $ bundle
46
+
47
+ Or install it yourself as:
48
+
49
+ $ gem install Cthulhu
50
+
51
+ ## How it works?
52
+
53
+ Cthulhu crawls over all child associations (AKA `has_many`, `has_one`, `has_and_belongs_to_many`) of given record until it finds a model that does not have any child association and starts deleting records until it can destroy the actual record.
54
+
55
+ So if you have an application that its database looks like this:
56
+
57
+ ```
58
+
59
+ users
60
+ / | \
61
+ posts | \
62
+ | \ | \
63
+ \ comments |
64
+ \ | /
65
+ \ | /
66
+ \ | /
67
+ images
68
+
69
+ ```
70
+ (see test/models.rb)
71
+
72
+ You can expect the following queries to be executed if you do `Cthulhu.destroy! User.find(5)`
73
+
74
+ ```sql
75
+ -- images that belong to #5's posts
76
+ DELETE FROM "images"
77
+ WHERE "images"."id" IN (SELECT "images"."id"
78
+ FROM "images"
79
+ INNER JOIN "posts" AS "t0"
80
+ ON "t0"."id" = "images"."imagable_id"
81
+ AND "images"."imagable_type" IN (
82
+ 'Post' )
83
+ INNER JOIN "users" AS "t1"
84
+ ON "t1"."id" = "t0"."user_id"
85
+ WHERE "t1"."id" = 5);
86
+
87
+ -- images that belong to #5's comments
88
+ DELETE FROM "images"
89
+ WHERE "images"."id" IN (SELECT "images"."id"
90
+ FROM "images"
91
+ INNER JOIN "comments" AS "t0"
92
+ ON "t0"."id" = "images"."imagable_id"
93
+ AND "images"."imagable_type" IN
94
+ ( 'Comment' )
95
+ INNER JOIN "posts" AS "t1"
96
+ ON "t1"."id" = "t0"."post_id"
97
+ INNER JOIN "users" AS "t2"
98
+ ON "t2"."id" = "t1"."user_id"
99
+ WHERE "t2"."id" = 5);
100
+
101
+ -- Comments of #5's posts
102
+ DELETE FROM "comments"
103
+ WHERE "comments"."id" IN (SELECT "comments"."id"
104
+ FROM "comments"
105
+ INNER JOIN "posts" AS "t0"
106
+ ON "t0"."id" = "comments"."post_id"
107
+ INNER JOIN "users" AS "t1"
108
+ ON "t1"."id" = "t0"."user_id"
109
+ WHERE "t1"."id" = 5);
110
+
111
+ -- #5's posts
112
+ DELETE FROM "posts"
113
+ WHERE "posts"."id" IN (SELECT "posts"."id"
114
+ FROM "posts"
115
+ INNER JOIN "users" AS "t0"
116
+ ON "t0"."id" = "posts"."user_id"
117
+ WHERE "t0"."id" = 5);
118
+
119
+ -- #5's comments
120
+ DELETE FROM "comments"
121
+ WHERE "comments"."id" IN (SELECT "comments"."id"
122
+ FROM "comments"
123
+ INNER JOIN "users" AS "t0"
124
+ ON "t0"."id" = "comments"."user_id"
125
+ WHERE "t0"."id" = 5);
126
+
127
+ -- #5's images
128
+ DELETE FROM "images"
129
+ WHERE "images"."id" IN (SELECT "images"."id"
130
+ FROM "images"
131
+ INNER JOIN "users" AS "t0"
132
+ ON "t0"."id" = "images"."user_id"
133
+ WHERE "t0"."id" = 5);
134
+
135
+ -- #5
136
+ DELETE FROM "users"
137
+ WHERE "users"."id" = 5;
138
+ ```
139
+
140
+ In above scenario, it is possibly to nullify the comments and images of user instead of removing them completely without changing anything in actual association:
141
+
142
+ ```ruby
143
+ Cthulhu.destroy! User.find(5),
144
+ blacklisted: [],
145
+ not_to_be_crawled: [],
146
+ overrides: {
147
+ User => {
148
+ comments: {
149
+ dependent: :nullify
150
+ },
151
+ uploaded_images: {
152
+ dependent: :nullify
153
+ }
154
+ }
155
+ }
156
+ ```
157
+
158
+ Please note that as Cthulhu works based on crawling associations, you need to provide `inverse_of` option for associations that ActiveRecord can not determine by itself.
159
+
160
+ ## Contributing
161
+
162
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bookingexperts/cthulhu.
163
+
164
+ ## License
165
+
166
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+ require 'schema_dev/tasks'
4
+
5
+ # without this, we'll get a warning because DATABASES is already defined by
6
+ # schema_dev.
7
+ Object.send :remove_const, :DATABASES
8
+ DATABASES = %w[cthulhu_test]
9
+
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << "test"
12
+ t.libs << "lib"
13
+ t.test_files = FileList['test/**/*_test.rb']
14
+ end
15
+
16
+ task :default => :spec
@@ -0,0 +1,27 @@
1
+ require "cthulhu/version"
2
+ require "cthulhu/uncrawlable_hierarchy"
3
+ require "cthulhu/scopper"
4
+ require "cthulhu/destroyer"
5
+ require "active_record"
6
+
7
+ module Cthulhu
8
+
9
+ # Removes the object and all of its children by crawling through defined
10
+ # associations.
11
+ #
12
+ # * +active_record_object+ - the record to be destroyed.
13
+ # * +:blacklisted+ - models not to be destroyed. i.e. views that you are
14
+ # using through active record.
15
+ # * +:not_to_be_crawled+ - Association that should not be traveled. e.g.
16
+ # 'Post-Comment' means crawling from Post to Comment model is forbidden.
17
+ # * +:overrides+ - here you can override options of active record
18
+ # associations. For example, you can set inverse_of or dependent: :nullify
19
+ # without actually changing the association.
20
+ def self.destroy! active_record_object,
21
+ blacklisted: [],
22
+ not_to_be_crawled: [],
23
+ overrides: {}
24
+ Destroyer.new(active_record_object, blacklisted, not_to_be_crawled, overrides).destroy!
25
+ end
26
+
27
+ end
@@ -0,0 +1,113 @@
1
+ module Cthulhu
2
+ class Destroyer
3
+
4
+ attr_reader :root, :root_klass, :option_overrides, :blacklisted
5
+ attr_accessor :skip
6
+
7
+ def initialize root, blacklisted = [], not_to_be_crawled = [], option_overrides = {}
8
+ @root = root
9
+ @root_klass = root.class
10
+ @skip = not_to_be_crawled.to_set
11
+ @option_overrides = option_overrides
12
+ @blacklisted = blacklisted
13
+ end
14
+
15
+ def destroy!
16
+ crawl root.class, [root.class]
17
+ end
18
+
19
+ private
20
+
21
+ def crawl crawling, chained_classes, chain = [], inverse_chain = []
22
+ has_and_belongs_to_many_usages = []
23
+
24
+ return if blacklisted.include? crawling
25
+ added = !skip.include?(crawling)
26
+ key = [chained_classes.second, crawling].compact.join('-')
27
+ return if skip.include? key
28
+ skip.add key
29
+
30
+ nullify = {}
31
+
32
+ crawling.reflections.each do |name, association|
33
+ if association.is_a?(ActiveRecord::Reflection::HasAndBelongsToManyReflection)
34
+ has_and_belongs_to_many_usages << name
35
+ next
36
+ end
37
+ if !association.is_a?(ActiveRecord::Reflection::HasManyReflection) &&
38
+ !association.is_a?(ActiveRecord::Reflection::HasOneReflection) &&
39
+ next
40
+ end
41
+ next unless association.through_reflection.nil?
42
+ klass = association.klass rescue Object
43
+ next unless klass < ActiveRecord::Base
44
+
45
+ tmp_chain = chain.map do |link|
46
+ link.to_s.singularize
47
+ end
48
+ tmp_inverse_chain = inverse_chain.dup
49
+ tmp_chained_classes = chained_classes.dup
50
+
51
+ inverse_of = inverse_name_of association
52
+ if inverse_of.blank?
53
+ raise UncrawlableHierarchy,
54
+ "You need to set inverse_of for #{crawling}.#{name} association"
55
+ end
56
+ inverse_association = klass.reflections[inverse_of]
57
+ name_according_to_inverse = inverse_name_of inverse_association, name
58
+ if name_according_to_inverse.blank?
59
+ raise UncrawlableHierarchy,
60
+ "You need to set inverse_of for #{klass}.#{inverse_of} association"
61
+ end
62
+
63
+ singularized_inverse_of = inverse_of.singularize
64
+ if tmp_chain.first != singularized_inverse_of
65
+ tmp_chain.unshift singularized_inverse_of
66
+ tmp_inverse_chain << name
67
+ tmp_chained_classes.unshift klass
68
+ end
69
+
70
+ if option(association, :dependent) == :nullify
71
+ nullify[name] = [klass, tmp_chained_classes, tmp_chain, tmp_inverse_chain]
72
+ next
73
+ end
74
+
75
+ crawl klass, tmp_chained_classes, tmp_chain, tmp_inverse_chain
76
+ end
77
+
78
+ Scopper.new(
79
+ root,
80
+ crawling,
81
+ chained_classes,
82
+ chain,
83
+ inverse_chain,
84
+ has_and_belongs_to_many_usages,
85
+ nullify
86
+ ).delete!
87
+ end
88
+
89
+ def inverse_name_of association, default = nil
90
+ (
91
+ direct_inverse_of(association) ||
92
+ option_inverse_of(association) ||
93
+ default
94
+ ).to_s
95
+ end
96
+
97
+ def direct_inverse_of association
98
+ association.inverse_of.try(:name) rescue nil
99
+ end
100
+
101
+ def option_inverse_of association
102
+ option association, :inverse_of rescue nil
103
+ end
104
+
105
+ def option association, name
106
+ override = option_overrides[association.active_record].
107
+ try(:[], association.name).
108
+ try(:[], name)
109
+ override || association.options[name]
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,87 @@
1
+ module Cthulhu
2
+ class Scopper < Struct.new(:root, :klass, :chained_classes, :chain, :inverse_chain, :has_and_belongs_to_many_usages, :nullify)
3
+
4
+ def scope
5
+ @scope ||= begin
6
+ result = klass.unscoped
7
+ left = []
8
+ right = []
9
+ @last_klass = klass
10
+ @last_inverse_klass = root.class
11
+ chain.each_with_index do |link, index|
12
+ reflection = @last_klass.reflections[link]
13
+ inverse_reflection = @last_inverse_klass.reflections[inverse_chain[index]]
14
+ left << reflection
15
+ right.unshift inverse_reflection
16
+ @last_klass = chained_classes[index + 1]
17
+ @last_inverse_klass = inverse_reflection.klass
18
+ end
19
+ left.each_with_index do |_, index|
20
+ result = result.joins join_expression(left[index], right[index], index)
21
+ end
22
+ last_table = left.length > 0 ? table_alias(left.length - 1) : root.class.table_name
23
+ result.reorder('').where last_table => { id: root.id }
24
+ end
25
+ end
26
+
27
+ def delete!
28
+ has_and_belongs_to_many_usages.each do |reflection_name|
29
+ reflection = scope.reflections[reflection_name]
30
+ table = t reflection.join_table
31
+ root.class.connection.
32
+ execute "delete from #{table} where #{table}.#{reflection.foreign_key} in (#{scope.select(:id).to_sql})"
33
+ end
34
+ nullify.each do |name, args|
35
+ Scopper.new(root, *args, [], {}).nullify! klass.reflections[name].foreign_key
36
+ end
37
+ scope.delete_all
38
+ end
39
+
40
+ def nullify! column
41
+ scope.update_all column => nil
42
+ end
43
+
44
+ private
45
+
46
+ def join_expression reflection, inverse_reflection, index
47
+ previous_table = t(index.zero? ? inverse_reflection.table_name : "t#{index - 1}")
48
+ current_table = t table_alias(index)
49
+ current_class = chained_classes[index + 1]
50
+ association_primary_key = reflection.association_primary_key rescue current_class.primary_key
51
+ [
52
+ "inner join #{current_class.quoted_table_name} as #{current_table} on ",
53
+ [
54
+ [
55
+ "#{current_table}.#{c(association_primary_key)}",
56
+ "#{previous_table}.#{c(reflection.foreign_key)}"
57
+ ].join('='),
58
+ (
59
+ if reflection.polymorphic?
60
+ types = ([current_class] + current_class.subclasses).map { |type| q type }
61
+ [
62
+ "#{previous_table}.#{c(reflection.foreign_type)} in (#{types.join(',')})"
63
+ ]
64
+ end
65
+ )
66
+ ].compact.join(' AND ')
67
+ ].join
68
+ end
69
+
70
+ def table_alias index
71
+ "t#{index}"
72
+ end
73
+
74
+ def c column
75
+ klass.connection.quote_column_name column
76
+ end
77
+
78
+ def q text
79
+ klass.connection.quote text
80
+ end
81
+
82
+ def t table
83
+ klass.connection.quote_table_name table
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,4 @@
1
+ module Cthulhu
2
+ class UncrawlableHierarchy < RuntimeError
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Cthulhu
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,6 @@
1
+ ruby:
2
+ - 2.2.3
3
+ activerecord:
4
+ - 4.2.6
5
+ db:
6
+ - postgresql
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Cthulhu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - |
8
+ behrooz shabani (everplays)
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-06-18 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.11'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.11'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '10.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '10.0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: minitest
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '5.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '5.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: pg
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: schema_dev
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: pry
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: factory_girl
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: database_cleaner
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: activerecord
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - "~>"
131
+ - !ruby/object:Gem::Version
132
+ version: '4.2'
133
+ type: :runtime
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '4.2'
140
+ description: If you do not want to setup foreign keys with cascade and ActiveRecord's
141
+ dependent is too slow for your big database, this is what you are looking for.
142
+ email:
143
+ - everplays@gmail.com
144
+ executables: []
145
+ extensions: []
146
+ extra_rdoc_files: []
147
+ files:
148
+ - ".gitignore"
149
+ - ".travis.yml"
150
+ - Cthulhu.gemspec
151
+ - Cthulhu.jpg
152
+ - Gemfile
153
+ - LICENSE.txt
154
+ - README.md
155
+ - Rakefile
156
+ - lib/cthulhu.rb
157
+ - lib/cthulhu/destroyer.rb
158
+ - lib/cthulhu/scopper.rb
159
+ - lib/cthulhu/uncrawlable_hierarchy.rb
160
+ - lib/cthulhu/version.rb
161
+ - schema_dev.yml
162
+ homepage: https://github.com/bookingexperts/cthulhu
163
+ licenses:
164
+ - MIT
165
+ metadata: {}
166
+ post_install_message:
167
+ rdoc_options: []
168
+ require_paths:
169
+ - lib
170
+ required_ruby_version: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ required_rubygems_version: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ requirements: []
181
+ rubyforge_project:
182
+ rubygems_version: 2.4.5.1
183
+ signing_key:
184
+ specification_version: 4
185
+ summary: replacement for ActiveRecord dependent and Database's cascade
186
+ test_files: []