includes_many 0.1.0

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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +14 -0
  5. data/Appraisals +18 -0
  6. data/Gemfile +14 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +66 -0
  9. data/Rakefile +18 -0
  10. data/certs/razum2um.pem +21 -0
  11. data/gemfiles/.gitignore +1 -0
  12. data/gemfiles/rails_3_2.gemfile +14 -0
  13. data/gemfiles/rails_4_0.gemfile +14 -0
  14. data/gemfiles/rails_4_1.gemfile +14 -0
  15. data/gemfiles/rails_4_2.gemfile +17 -0
  16. data/includes_many.gemspec +30 -0
  17. data/lib/includes_many.rb +19 -0
  18. data/lib/includes_many/active_record32.rb +4 -0
  19. data/lib/includes_many/active_record32/association_scope.rb +93 -0
  20. data/lib/includes_many/active_record32/has_many.rb +43 -0
  21. data/lib/includes_many/active_record32/join_association.rb +37 -0
  22. data/lib/includes_many/active_record40.rb +4 -0
  23. data/lib/includes_many/active_record40/association_scope.rb +97 -0
  24. data/lib/includes_many/active_record40/has_many.rb +32 -0
  25. data/lib/includes_many/active_record40/join_association.rb +35 -0
  26. data/lib/includes_many/active_record41.rb +4 -0
  27. data/lib/includes_many/active_record41/association_scope.rb +88 -0
  28. data/lib/includes_many/active_record41/has_many.rb +34 -0
  29. data/lib/includes_many/active_record41/join_association.rb +30 -0
  30. data/lib/includes_many/active_record42.rb +4 -0
  31. data/lib/includes_many/active_record42/association_scope.rb +81 -0
  32. data/lib/includes_many/active_record42/has_many.rb +34 -0
  33. data/lib/includes_many/active_record42/join_association.rb +30 -0
  34. data/lib/includes_many/version.rb +3 -0
  35. data/spec/includes_many_spec.rb +83 -0
  36. data/spec/internal/app/models/comment.rb +12 -0
  37. data/spec/internal/config/database.yml +9 -0
  38. data/spec/internal/db/schema.rb +6 -0
  39. data/spec/internal/log/.gitkeep +0 -0
  40. data/spec/spec_helper.rb +26 -0
  41. metadata +207 -0
@@ -0,0 +1,34 @@
1
+ module ActiveRecord
2
+ module Associations
3
+ class Preloader
4
+ class HasMany
5
+ safe_monkeypatch :owners_by_key, md5: '70b0628dd3c79928ee0bcff75052894a'
6
+
7
+ def owners_by_key
8
+ @owners_by_key ||= begin
9
+ res = Hash.new { |h,k| h[k] = Set.new }
10
+ owners.each do |owner|
11
+ key = if owner_key_name.respond_to?(:call)
12
+ owner_key_name.call(owner)
13
+ else
14
+ owner[owner_key_name]
15
+ end
16
+
17
+ if key.respond_to?(:each)
18
+ key.each do |k|
19
+ k = k.to_s if key_conversion_required?
20
+ res[k] << owner
21
+ end
22
+ else
23
+ key = key.to_s if key_conversion_required?
24
+ res[key] << owner
25
+ end
26
+ end
27
+ res
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,30 @@
1
+ module ActiveRecord
2
+ class NonScalarPrimaryKeyError < ActiveRecordError
3
+ def initialize(reflection)
4
+ super("Can not join association #{reflection.name.inspect}, because :primary_key is a callable. Use Relation#includes or specify a join column name")
5
+ end
6
+ end
7
+
8
+ module Associations
9
+ class JoinDependency
10
+ class JoinAssociation
11
+ safe_monkeypatch :initialize, md5: '2332c88140aa9e9efa96975fd48e6958'
12
+
13
+ def initialize(reflection, children)
14
+
15
+ # PATCH here
16
+ if reflection.options[:primary_key].respond_to?(:call)
17
+ raise NonScalarPrimaryKeyError.new(reflection)
18
+ end
19
+ # end PATCH
20
+
21
+ super(reflection.klass, children)
22
+
23
+ @reflection = reflection
24
+ @tables = nil
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,3 @@
1
+ module IncludesMany
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ class SQLCounter
4
+ attr_accessor :log, :ignore
5
+
6
+ def initialize
7
+ @log = []
8
+ @ignore = [/^PRAGMA (?!(table_info))/,
9
+ /^SELECT currval/, /^SELECT CAST/,
10
+ /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/,
11
+ /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/,
12
+ /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/,
13
+ /^BEGIN/, /^COMMIT/]
14
+ end
15
+
16
+ def call(name, start, finish, message_id, values)
17
+ sql = values[:sql]
18
+ return if 'CACHE' == values[:name] || ignore.any? { |x| x =~ sql }
19
+ @log << sql
20
+ end
21
+ end
22
+
23
+ RSpec.describe IncludesMany do
24
+ describe 'self_siblings_and_children' do
25
+ let(:parent) { Comment.create! :body => 'some comment' }
26
+ let(:child1) { Comment.create! :body => 'child1', :parent => parent }
27
+ let(:child2) { Comment.create! :body => 'child2', :parent => parent }
28
+ let(:child3) { Comment.create! :body => 'child3', :parent => parent }
29
+ let(:subchild11) { Comment.create! :body => 'subchild11', :parent => child1 }
30
+ let(:subchild12) { Comment.create! :body => 'subchild12', :parent => child1 }
31
+ let(:subchild21) { Comment.create! :body => 'subchild21', :parent => child2 }
32
+ let(:subchild31) { Comment.create! :body => 'subchild31', :parent => child3 }
33
+
34
+ let(:subscriber) { SQLCounter.new }
35
+
36
+ before do
37
+ Comment.find_each(&:destroy)
38
+ subchild11
39
+ subchild12
40
+ subchild21
41
+ subchild31
42
+ ActiveSupport::Notifications.subscribe('sql.active_record', subscriber)
43
+ end
44
+
45
+ it 'works', :focus do
46
+ x = child1.self_siblings_and_children.to_a
47
+ expect(x.map(&:body).sort).to eq ["child1", "child2", "child3", "subchild11", "subchild12"]
48
+ # expect(child2.self_siblings_and_children.map(&:body).sort).to eq ["child1", "child2", "child3", "subchild21"]
49
+ # expect(child3.self_siblings_and_children.map(&:body).sort).to eq ["child1", "child2", "child3", "subchild31"]
50
+ end
51
+
52
+ it 'executes N+1 if not used includes' do
53
+ expect {
54
+ childs = Comment.where("body like 'child%'").order('body ASC')
55
+ expect(childs.map(&:self_siblings_and_children).map(&:size).flatten).to eq [5, 4, 4]
56
+ }.to change { subscriber.log.size } .from(0).to(4)
57
+ end
58
+
59
+ it 'includes relation' do
60
+ expect {
61
+ childs = Comment.where("body like 'child%'").order('body ASC').includes(:self_siblings_and_children)
62
+ expect(childs.map(&:self_siblings_and_children).map(&:size).flatten).to eq [5, 4, 4]
63
+ }.to change { subscriber.log.size } .from(0).to(2)
64
+
65
+ expect(subscriber.log.first).to match Regexp.new( #Regexp.escape(
66
+ %q{SELECT "comments".* FROM "comments"\s+WHERE \(body like 'child%'\)\s+ORDER BY body ASC}
67
+ )
68
+
69
+ expect(subscriber.log.last).to match Regexp.new( #Regexp.escape(
70
+ %q{SELECT "comments".* FROM "comments"\s+WHERE "comments"."parent_id" IN}
71
+ )
72
+ end
73
+
74
+ it 'denies join on includes_many' do
75
+ expect {
76
+ Comment.where("body like 'child%'").order('body ASC').joins(:self_siblings_and_children).to_a
77
+ }.to raise_error(
78
+ ActiveRecord::NonScalarPrimaryKeyError,
79
+ /Can not join association :self_siblings_and_children, because :primary_key is a callable/
80
+ )
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,12 @@
1
+ class Comment < ::ActiveRecord::Base
2
+ belongs_to :parent, :class_name => 'Comment', :foreign_key => :parent_id
3
+ has_many :children, :class_name => 'Comment', :foreign_key => :parent_id
4
+ has_many :self_siblings, :class_name => 'Comment',
5
+ :foreign_key => :parent_id, :primary_key => :parent_id
6
+
7
+ includes_many :self_or_children, :class_name => 'Comment',
8
+ :foreign_key => proc { parent_id && :parent_id or :id }, :primary_key => :id
9
+
10
+ includes_many :self_siblings_and_children, :class_name => 'Comment',
11
+ :foreign_key => :parent_id, :primary_key => proc { |c| [c.parent_id, c.id] }
12
+ end
@@ -0,0 +1,9 @@
1
+ development: &development
2
+ adapter: sqlite3
3
+ database: db/includes_many.sqlite3
4
+
5
+ production:
6
+ <<: *development
7
+
8
+ test:
9
+ <<: *development
@@ -0,0 +1,6 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table(:comments, :force => true) do |t|
3
+ t.string :body
4
+ t.integer :parent_id
5
+ end
6
+ end
File without changes
@@ -0,0 +1,26 @@
1
+ require 'bundler/setup'
2
+ require 'combustion'
3
+ require 'pry-debugger'
4
+
5
+ Combustion.initialize! :active_record
6
+
7
+ RSpec.configure do |config|
8
+ config.filter_run :focus
9
+ config.run_all_when_everything_filtered = true
10
+
11
+ if config.files_to_run.one?
12
+ config.default_formatter = 'doc'
13
+ end
14
+
15
+ config.order = :random
16
+ Kernel.srand config.seed
17
+
18
+ config.expect_with :rspec do |expectations|
19
+ expectations.syntax = :expect
20
+ end
21
+
22
+ config.mock_with :rspec do |mocks|
23
+ mocks.syntax = :expect
24
+ mocks.verify_partial_doubles = true
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,207 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: includes_many
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Vlad Bokov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-18 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'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '4.2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '4.2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: safe_monkeypatch
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.1'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.1'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.6'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.6'
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
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: rake
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: combustion
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: appraisal
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ description: ActiveRecord has_many + includes(:relation) on steroids
132
+ email:
133
+ - razum2um@mail.ru
134
+ executables: []
135
+ extensions: []
136
+ extra_rdoc_files: []
137
+ files:
138
+ - ".gitignore"
139
+ - ".rspec"
140
+ - ".travis.yml"
141
+ - Appraisals
142
+ - Gemfile
143
+ - LICENSE.txt
144
+ - README.md
145
+ - Rakefile
146
+ - certs/razum2um.pem
147
+ - gemfiles/.gitignore
148
+ - gemfiles/rails_3_2.gemfile
149
+ - gemfiles/rails_4_0.gemfile
150
+ - gemfiles/rails_4_1.gemfile
151
+ - gemfiles/rails_4_2.gemfile
152
+ - includes_many.gemspec
153
+ - lib/includes_many.rb
154
+ - lib/includes_many/active_record32.rb
155
+ - lib/includes_many/active_record32/association_scope.rb
156
+ - lib/includes_many/active_record32/has_many.rb
157
+ - lib/includes_many/active_record32/join_association.rb
158
+ - lib/includes_many/active_record40.rb
159
+ - lib/includes_many/active_record40/association_scope.rb
160
+ - lib/includes_many/active_record40/has_many.rb
161
+ - lib/includes_many/active_record40/join_association.rb
162
+ - lib/includes_many/active_record41.rb
163
+ - lib/includes_many/active_record41/association_scope.rb
164
+ - lib/includes_many/active_record41/has_many.rb
165
+ - lib/includes_many/active_record41/join_association.rb
166
+ - lib/includes_many/active_record42.rb
167
+ - lib/includes_many/active_record42/association_scope.rb
168
+ - lib/includes_many/active_record42/has_many.rb
169
+ - lib/includes_many/active_record42/join_association.rb
170
+ - lib/includes_many/version.rb
171
+ - spec/includes_many_spec.rb
172
+ - spec/internal/app/models/comment.rb
173
+ - spec/internal/config/database.yml
174
+ - spec/internal/db/schema.rb
175
+ - spec/internal/log/.gitkeep
176
+ - spec/spec_helper.rb
177
+ homepage: https://github.com/razum2um/include_many
178
+ licenses:
179
+ - MIT
180
+ metadata: {}
181
+ post_install_message:
182
+ rdoc_options: []
183
+ require_paths:
184
+ - lib
185
+ required_ruby_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ required_rubygems_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ requirements: []
196
+ rubyforge_project:
197
+ rubygems_version: 2.2.2
198
+ signing_key:
199
+ specification_version: 4
200
+ summary: ActiveRecord has_many + includes(:relation) on steroids
201
+ test_files:
202
+ - spec/includes_many_spec.rb
203
+ - spec/internal/app/models/comment.rb
204
+ - spec/internal/config/database.yml
205
+ - spec/internal/db/schema.rb
206
+ - spec/internal/log/.gitkeep
207
+ - spec/spec_helper.rb