Cthulhu 0.1.1

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.
@@ -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: []