activerecord-count_loader 0.2.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b18fe399d79f7718389245f46d6c8344489c7797
4
- data.tar.gz: 41ce60a38bb937a114c0edcff1b79e183e1d94d5
3
+ metadata.gz: 8489adecb44640ee21f220153382a63fded5759d
4
+ data.tar.gz: 49b3d38e965877df8269225787e5d234356aad8d
5
5
  SHA512:
6
- metadata.gz: fab66eaef7777f10c7ec1778ee58f1cc2588187d8194bcb4698b6c458452e7822fd9e5fd4c47dd054f2410dde1f234788d77f292d44e32777f69516ab4b63cf7
7
- data.tar.gz: 7ae932c1f0ef58173a8f679863a6292e3a8f9195445476671857b29656d51b2e85aa20edaaceb6f2e45551b775ad1f208d3c683b7352fc42d8741e75dbd12244
6
+ metadata.gz: 455eafc3a74e192add146c9459bbec579ab4bc54d5021678ae2f9116a8c97af82df618dbaf5d58fbf6fb20d44c37cf283b4bff0daf90ab3cea6528da7aa95082
7
+ data.tar.gz: 2579d3db7c17e70aab0008fcbdac5978510e1a087974ab53b5144ef25acab4b83b18bc7b21e675727c950900b4ef1cf7fcbdaae23400bc2a952091e1851c780c
data/.gitignore CHANGED
@@ -16,3 +16,4 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  *.sqlite3
19
+ test/config.yml
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
4
+ - 2.1.5
5
+ - ruby-head
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # ActiveRecord::CountLoader
1
+ # ActiveRecord::CountLoader [![Build Status](https://travis-ci.org/k0kubun/activerecord-count_loader.svg?branch=master)](https://travis-ci.org/k0kubun/activerecord-count_loader)
2
2
 
3
3
  N+1 count query killer for ActiveRecord.
4
4
  ActiveRecord::CountLoader allows you to cache count of associated records by eager loading.
@@ -63,6 +63,12 @@ Since it is association, you can preload nested `count_loader` association.
63
63
  end
64
64
  ```
65
65
 
66
+ ## Testing
67
+
68
+ ```bash
69
+ $ bundle exec rake
70
+ ```
71
+
66
72
  ## Contributing
67
73
 
68
74
  1. Fork it ( https://github.com/k0kubun/activerecord-count_loader/fork )
data/Rakefile CHANGED
@@ -1 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "lib" << "test"
6
+ t.test_files = Dir.glob("test/**/*_test.rb")
7
+ end
8
+
9
+ task default: :test
@@ -16,12 +16,12 @@ Gem::Specification.new do |spec|
16
16
  spec.test_files = spec.files.grep(%r{^spec/})
17
17
  spec.require_paths = ["lib"]
18
18
 
19
- spec.required_ruby_version = ">= 1.9.2"
19
+ spec.required_ruby_version = ">= 2.1"
20
20
  spec.add_runtime_dependency "activerecord", ">= 3.2.0"
21
- spec.add_development_dependency "rspec", "~> 3.0.0"
22
- spec.add_development_dependency "factory_girl", "~> 4.2.0"
23
- spec.add_development_dependency "sqlite3"
21
+ spec.add_development_dependency "minitest", ">= 5.4"
24
22
  spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "erubis"
25
24
  spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "sqlite3"
26
26
  spec.add_development_dependency "pry"
27
27
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module CountLoader
3
- VERSION = "0.2.0"
3
+ VERSION = "0.2.1"
4
4
  end
5
5
  end
@@ -0,0 +1,20 @@
1
+ require 'cases/helper'
2
+ require 'models/favorite'
3
+ require 'models/tweet'
4
+
5
+ class EagerLoadTest < ActiveRecord::CountLoader::TestCase
6
+ def setup
7
+ tweet = Tweet.create
8
+ Favorite.create(tweet: tweet)
9
+ end
10
+
11
+ def teardown
12
+ [Tweet, Favorite].each(&:delete_all)
13
+ end
14
+
15
+ def test_eager_loaded_count_loader_raises_an_error
16
+ assert_raises ActiveRecord::EagerLoadCountLoaderError do
17
+ Tweet.eager_load(:favorites_count).to_a
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ require 'cases/helper'
2
+
3
+ class IncludesTest < ActiveRecord::CountLoader::TestCase
4
+ def setup
5
+ tweets_count.times.map do |index|
6
+ tweet = Tweet.create
7
+ index.times { Favorite.create(tweet: tweet) }
8
+ end
9
+ end
10
+
11
+ def teardown
12
+ [Tweet, Favorite].each(&:delete_all)
13
+ end
14
+
15
+ def tweets_count
16
+ 3
17
+ end
18
+
19
+ def test_includes_does_not_execute_n_1_queries
20
+ assert_queries(1 + tweets_count) { Tweet.all.map { |t| t.favorites.count } }
21
+ assert_queries(1 + tweets_count) { Tweet.all.map(&:favorites_count) }
22
+ assert_queries(2) { Tweet.includes(:favorites_count).map(&:favorites_count) }
23
+ end
24
+
25
+ def test_included_count_loader_counts_properly
26
+ expected = Tweet.all.map { |t| t.favorites.count }
27
+ assert_equal(Tweet.all.map(&:favorites_count), expected)
28
+ assert_equal(Tweet.includes(:favorites_count).map(&:favorites_count), expected)
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ require 'cases/helper'
2
+
3
+ class PreloadTest < ActiveRecord::CountLoader::TestCase
4
+ def setup
5
+ tweets_count.times.map do |index|
6
+ tweet = Tweet.create
7
+ index.times { Favorite.create(tweet: tweet) }
8
+ end
9
+ end
10
+
11
+ def teardown
12
+ [Tweet, Favorite].each(&:delete_all)
13
+ end
14
+
15
+ def tweets_count
16
+ 3
17
+ end
18
+
19
+ def test_preload_does_not_execute_n_1_queries
20
+ assert_queries(1 + tweets_count) { Tweet.all.map { |t| t.favorites.count } }
21
+ assert_queries(1 + tweets_count) { Tweet.all.map(&:favorites_count) }
22
+ assert_queries(2) { Tweet.preload(:favorites_count).map(&:favorites_count) }
23
+ end
24
+
25
+ def test_preloaded_count_loader_counts_properly
26
+ expected = Tweet.all.map { |t| t.favorites.count }
27
+ assert_equal(Tweet.all.map(&:favorites_count), expected)
28
+ assert_equal(Tweet.preload(:favorites_count).map(&:favorites_count), expected)
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ require 'config'
2
+
3
+ require 'support/autorun'
4
+
5
+ require 'active_record'
6
+ require 'activerecord-count_loader'
7
+ require 'cases/test_case'
8
+
9
+ require 'support/config'
10
+ require 'support/connection'
11
+
12
+ # Connect to the database
13
+ ARTest.connect
14
+
15
+ def load_schema
16
+ # silence verbose schema loading
17
+ original_stdout = $stdout
18
+ $stdout = StringIO.new
19
+
20
+ adapter_name = ActiveRecord::Base.connection.adapter_name.downcase
21
+
22
+ load SCHEMA_ROOT + "/schema.rb"
23
+ ensure
24
+ $stdout = original_stdout
25
+ end
26
+
27
+ load_schema
@@ -0,0 +1,86 @@
1
+ module ActiveRecord::CountLoader
2
+ class TestCase < Minitest::Test
3
+ def teardown
4
+ SQLCounter.clear_log
5
+ end
6
+
7
+ def capture_sql
8
+ SQLCounter.clear_log
9
+ yield
10
+ SQLCounter.log_all.dup
11
+ end
12
+
13
+ def assert_sql(*patterns_to_match)
14
+ SQLCounter.clear_log
15
+ yield
16
+ SQLCounter.log_all
17
+ ensure
18
+ failed_patterns = []
19
+ patterns_to_match.each do |pattern|
20
+ failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql }
21
+ end
22
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
23
+ end
24
+
25
+ def assert_queries(num = 1, options = {})
26
+ ignore_none = options.fetch(:ignore_none) { num == :any }
27
+ SQLCounter.clear_log
28
+ x = yield
29
+ the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log
30
+ if num == :any
31
+ assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed."
32
+ else
33
+ mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}"
34
+ assert_equal num, the_log.size, mesg
35
+ end
36
+ x
37
+ end
38
+
39
+ def assert_no_queries(options = {}, &block)
40
+ options.reverse_merge! ignore_none: true
41
+ assert_queries(0, options, &block)
42
+ end
43
+ end
44
+
45
+ class SQLCounter
46
+ class << self
47
+ attr_accessor :ignored_sql, :log, :log_all
48
+ def clear_log; self.log = []; self.log_all = []; end
49
+ end
50
+
51
+ self.clear_log
52
+
53
+ self.ignored_sql = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
54
+
55
+ # FIXME: this needs to be refactored so specific database can add their own
56
+ # ignored SQL, or better yet, use a different notification for the queries
57
+ # instead examining the SQL content.
58
+ oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im]
59
+ mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i]
60
+ postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]
61
+ sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im]
62
+
63
+ [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
64
+ ignored_sql.concat db_ignored_sql
65
+ end
66
+
67
+ attr_reader :ignore
68
+
69
+ def initialize(ignore = Regexp.union(self.class.ignored_sql))
70
+ @ignore = ignore
71
+ end
72
+
73
+ def call(name, start, finish, message_id, values)
74
+ sql = values[:sql]
75
+
76
+ # FIXME: this seems bad. we should probably have a better way to indicate
77
+ # the query was cached
78
+ return if 'CACHE' == values[:name]
79
+
80
+ self.class.log_all << sql
81
+ self.class.log << sql unless ignore =~ sql
82
+ end
83
+ end
84
+
85
+ ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
86
+ end
@@ -0,0 +1,140 @@
1
+ default_connection: <%= defined?(JRUBY_VERSION) ? 'jdbcsqlite3' : 'sqlite3' %>
2
+
3
+ with_manual_interventions: false
4
+
5
+ connections:
6
+ jdbcderby:
7
+ arunit: activerecord_unittest
8
+ arunit2: activerecord_unittest2
9
+
10
+ jdbch2:
11
+ arunit: activerecord_unittest
12
+ arunit2: activerecord_unittest2
13
+
14
+ jdbchsqldb:
15
+ arunit: activerecord_unittest
16
+ arunit2: activerecord_unittest2
17
+
18
+ jdbcmysql:
19
+ arunit:
20
+ username: rails
21
+ encoding: utf8
22
+ arunit2:
23
+ username: rails
24
+ encoding: utf8
25
+
26
+ jdbcpostgresql:
27
+ arunit:
28
+ username: <%= ENV['user'] || 'rails' %>
29
+ arunit2:
30
+ username: <%= ENV['user'] || 'rails' %>
31
+
32
+ jdbcsqlite3:
33
+ arunit:
34
+ database: <%= FIXTURES_ROOT %>/fixture_database.sqlite3
35
+ timeout: 5000
36
+ arunit2:
37
+ database: <%= FIXTURES_ROOT %>/fixture_database_2.sqlite3
38
+ timeout: 5000
39
+
40
+ db2:
41
+ arunit:
42
+ adapter: ibm_db
43
+ host: localhost
44
+ username: arunit
45
+ password: arunit
46
+ database: arunit
47
+ arunit2:
48
+ adapter: ibm_db
49
+ host: localhost
50
+ username: arunit
51
+ password: arunit
52
+ database: arunit2
53
+
54
+ firebird:
55
+ arunit:
56
+ host: localhost
57
+ username: rails
58
+ password: rails
59
+ charset: UTF8
60
+ arunit2:
61
+ host: localhost
62
+ username: rails
63
+ password: rails
64
+ charset: UTF8
65
+
66
+ frontbase:
67
+ arunit:
68
+ host: localhost
69
+ username: rails
70
+ session_name: unittest-<%= $$ %>
71
+ arunit2:
72
+ host: localhost
73
+ username: rails
74
+ session_name: unittest-<%= $$ %>
75
+
76
+ mysql:
77
+ arunit:
78
+ username: rails
79
+ encoding: utf8
80
+ arunit2:
81
+ username: rails
82
+ encoding: utf8
83
+
84
+ mysql2:
85
+ arunit:
86
+ username: rails
87
+ encoding: utf8
88
+ arunit2:
89
+ username: rails
90
+ encoding: utf8
91
+
92
+ openbase:
93
+ arunit:
94
+ username: admin
95
+ arunit2:
96
+ username: admin
97
+
98
+ oracle:
99
+ arunit:
100
+ adapter: oracle_enhanced
101
+ database: <%= ENV['ARUNIT_DB_NAME'] || 'orcl' %>
102
+ username: <%= ENV['ARUNIT_USER_NAME'] || 'arunit' %>
103
+ password: <%= ENV['ARUNIT_PASSWORD'] || 'arunit' %>
104
+ emulate_oracle_adapter: true
105
+ arunit2:
106
+ adapter: oracle_enhanced
107
+ database: <%= ENV['ARUNIT_DB_NAME'] || 'orcl' %>
108
+ username: <%= ENV['ARUNIT2_USER_NAME'] || 'arunit2' %>
109
+ password: <%= ENV['ARUNIT2_PASSWORD'] || 'arunit2' %>
110
+ emulate_oracle_adapter: true
111
+
112
+ postgresql:
113
+ arunit:
114
+ min_messages: warning
115
+ arunit2:
116
+ min_messages: warning
117
+
118
+ sqlite3:
119
+ arunit:
120
+ database: <%= FIXTURES_ROOT %>/fixture_database.sqlite3
121
+ timeout: 5000
122
+ arunit2:
123
+ database: <%= FIXTURES_ROOT %>/fixture_database_2.sqlite3
124
+ timeout: 5000
125
+
126
+ sqlite3_mem:
127
+ arunit:
128
+ adapter: sqlite3
129
+ database: ':memory:'
130
+ arunit2:
131
+ adapter: sqlite3
132
+ database: ':memory:'
133
+
134
+ sybase:
135
+ arunit:
136
+ host: database_ASE
137
+ username: sa
138
+ arunit2:
139
+ host: database_ASE
140
+ username: sa
data/test/config.rb ADDED
@@ -0,0 +1,5 @@
1
+ TEST_ROOT = File.expand_path(File.dirname(__FILE__))
2
+ ASSETS_ROOT = TEST_ROOT + "/assets"
3
+ FIXTURES_ROOT = TEST_ROOT + "/fixtures"
4
+ MIGRATIONS_ROOT = TEST_ROOT + "/migrations"
5
+ SCHEMA_ROOT = TEST_ROOT + "/schema"
@@ -0,0 +1,3 @@
1
+ class Favorite < ActiveRecord::Base
2
+ belongs_to :tweet
3
+ end
@@ -0,0 +1,3 @@
1
+ class Tweet < ActiveRecord::Base
2
+ has_many :favorites, count_loader: true
3
+ end
@@ -0,0 +1,14 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table :favorites, force: true do |t|
3
+ t.integer :tweet_id
4
+ t.integer :user_id
5
+ t.datetime :created_at
6
+ t.timestamps
7
+ end
8
+
9
+ create_table :tweets, force: true do |t|
10
+ t.integer :in_reply_to_tweet_id
11
+ t.integer :user_id
12
+ t.timestamps
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ gem 'minitest'
2
+
3
+ require 'minitest'
4
+
5
+ Minitest.autorun
@@ -0,0 +1,43 @@
1
+ require 'yaml'
2
+ require 'erubis'
3
+ require 'fileutils'
4
+ require 'pathname'
5
+
6
+ module ARTest
7
+ class << self
8
+ def config
9
+ @config ||= read_config
10
+ end
11
+
12
+ private
13
+
14
+ def config_file
15
+ Pathname.new(ENV['ARCONFIG'] || TEST_ROOT + '/config.yml')
16
+ end
17
+
18
+ def read_config
19
+ unless config_file.exist?
20
+ FileUtils.cp TEST_ROOT + '/config.example.yml', config_file
21
+ end
22
+
23
+ erb = Erubis::Eruby.new(config_file.read)
24
+ expand_config(YAML.parse(erb.result(binding)).transform)
25
+ end
26
+
27
+ def expand_config(config)
28
+ config['connections'].each do |adapter, connection|
29
+ dbs = [['arunit', 'activerecord_unittest'], ['arunit2', 'activerecord_unittest2']]
30
+ dbs.each do |name, dbname|
31
+ unless connection[name].is_a?(Hash)
32
+ connection[name] = { 'database' => connection[name] }
33
+ end
34
+
35
+ connection[name]['database'] ||= dbname
36
+ connection[name]['adapter'] ||= adapter
37
+ end
38
+ end
39
+
40
+ config
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,15 @@
1
+ module ARTest
2
+ def self.connection_name
3
+ ENV['ARCONN'] || config['default_connection']
4
+ end
5
+
6
+ def self.connection_config
7
+ config['connections'][connection_name]
8
+ end
9
+
10
+ def self.connect
11
+ puts "Using #{connection_name}"
12
+ ActiveRecord::Base.configurations = connection_config
13
+ ActiveRecord::Base.establish_connection :arunit
14
+ end
15
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-count_loader
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Takashi Kokubun
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-22 00:00:00.000000000 Z
11
+ date: 2014-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -25,35 +25,35 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 3.2.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: rspec
28
+ name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 3.0.0
33
+ version: '5.4'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 3.0.0
40
+ version: '5.4'
41
41
  - !ruby/object:Gem::Dependency
42
- name: factory_girl
42
+ name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 4.2.0
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: 4.2.0
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: sqlite3
56
+ name: erubis
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: rake
70
+ name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,7 +81,7 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: bundler
84
+ name: sqlite3
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
@@ -116,6 +116,7 @@ extensions: []
116
116
  extra_rdoc_files: []
117
117
  files:
118
118
  - ".gitignore"
119
+ - ".travis.yml"
119
120
  - Gemfile
120
121
  - LICENSE
121
122
  - README.md
@@ -186,6 +187,19 @@ files:
186
187
  - sample/public/robots.txt
187
188
  - sample/vendor/assets/javascripts/.keep
188
189
  - sample/vendor/assets/stylesheets/.keep
190
+ - test/cases/associations/eager_load_test.rb
191
+ - test/cases/associations/includes_test.rb
192
+ - test/cases/associations/preload_test.rb
193
+ - test/cases/helper.rb
194
+ - test/cases/test_case.rb
195
+ - test/config.example.yml
196
+ - test/config.rb
197
+ - test/models/favorite.rb
198
+ - test/models/tweet.rb
199
+ - test/schema/schema.rb
200
+ - test/support/autorun.rb
201
+ - test/support/config.rb
202
+ - test/support/connection.rb
189
203
  homepage: https://github.com/k0kubun/activerecord-count_loader
190
204
  licenses:
191
205
  - MIT
@@ -198,7 +212,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
198
212
  requirements:
199
213
  - - ">="
200
214
  - !ruby/object:Gem::Version
201
- version: 1.9.2
215
+ version: '2.1'
202
216
  required_rubygems_version: !ruby/object:Gem::Requirement
203
217
  requirements:
204
218
  - - ">="