low_card_tables 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +59 -0
- data/Gemfile +17 -0
- data/LICENSE +21 -0
- data/README.md +75 -0
- data/Rakefile +6 -0
- data/lib/low_card_tables.rb +72 -0
- data/lib/low_card_tables/active_record/base.rb +55 -0
- data/lib/low_card_tables/active_record/migrations.rb +223 -0
- data/lib/low_card_tables/active_record/relation.rb +35 -0
- data/lib/low_card_tables/active_record/scoping.rb +87 -0
- data/lib/low_card_tables/errors.rb +74 -0
- data/lib/low_card_tables/has_low_card_table/base.rb +114 -0
- data/lib/low_card_tables/has_low_card_table/low_card_association.rb +273 -0
- data/lib/low_card_tables/has_low_card_table/low_card_associations_manager.rb +143 -0
- data/lib/low_card_tables/has_low_card_table/low_card_dynamic_method_manager.rb +224 -0
- data/lib/low_card_tables/has_low_card_table/low_card_objects_manager.rb +80 -0
- data/lib/low_card_tables/low_card_table/base.rb +184 -0
- data/lib/low_card_tables/low_card_table/cache.rb +214 -0
- data/lib/low_card_tables/low_card_table/cache_expiration/exponential_cache_expiration_policy.rb +151 -0
- data/lib/low_card_tables/low_card_table/cache_expiration/fixed_cache_expiration_policy.rb +23 -0
- data/lib/low_card_tables/low_card_table/cache_expiration/has_cache_expiration.rb +100 -0
- data/lib/low_card_tables/low_card_table/cache_expiration/no_caching_expiration_policy.rb +13 -0
- data/lib/low_card_tables/low_card_table/cache_expiration/unlimited_cache_expiration_policy.rb +13 -0
- data/lib/low_card_tables/low_card_table/row_collapser.rb +175 -0
- data/lib/low_card_tables/low_card_table/row_manager.rb +681 -0
- data/lib/low_card_tables/low_card_table/table_unique_index.rb +134 -0
- data/lib/low_card_tables/version.rb +4 -0
- data/lib/low_card_tables/version_support.rb +52 -0
- data/low_card_tables.gemspec +69 -0
- data/spec/low_card_tables/helpers/database_helper.rb +148 -0
- data/spec/low_card_tables/helpers/query_spy_helper.rb +47 -0
- data/spec/low_card_tables/helpers/system_helpers.rb +63 -0
- data/spec/low_card_tables/system/basic_system_spec.rb +254 -0
- data/spec/low_card_tables/system/bulk_system_spec.rb +334 -0
- data/spec/low_card_tables/system/caching_system_spec.rb +531 -0
- data/spec/low_card_tables/system/migrations_system_spec.rb +747 -0
- data/spec/low_card_tables/system/options_system_spec.rb +581 -0
- data/spec/low_card_tables/system/queries_system_spec.rb +142 -0
- data/spec/low_card_tables/system/validations_system_spec.rb +88 -0
- data/spec/low_card_tables/unit/active_record/base_spec.rb +53 -0
- data/spec/low_card_tables/unit/active_record/migrations_spec.rb +207 -0
- data/spec/low_card_tables/unit/active_record/relation_spec.rb +47 -0
- data/spec/low_card_tables/unit/active_record/scoping_spec.rb +101 -0
- data/spec/low_card_tables/unit/has_low_card_table/base_spec.rb +79 -0
- data/spec/low_card_tables/unit/has_low_card_table/low_card_association_spec.rb +287 -0
- data/spec/low_card_tables/unit/has_low_card_table/low_card_associations_manager_spec.rb +190 -0
- data/spec/low_card_tables/unit/has_low_card_table/low_card_dynamic_method_manager_spec.rb +234 -0
- data/spec/low_card_tables/unit/has_low_card_table/low_card_objects_manager_spec.rb +70 -0
- data/spec/low_card_tables/unit/low_card_table/base_spec.rb +207 -0
- data/spec/low_card_tables/unit/low_card_table/cache_expiration/exponential_cache_expiration_policy_spec.rb +128 -0
- data/spec/low_card_tables/unit/low_card_table/cache_expiration/fixed_cache_expiration_policy_spec.rb +25 -0
- data/spec/low_card_tables/unit/low_card_table/cache_expiration/has_cache_expiration_policy_spec.rb +100 -0
- data/spec/low_card_tables/unit/low_card_table/cache_expiration/no_caching_expiration_policy_spec.rb +14 -0
- data/spec/low_card_tables/unit/low_card_table/cache_expiration/unlimited_cache_expiration_policy_spec.rb +14 -0
- data/spec/low_card_tables/unit/low_card_table/cache_spec.rb +282 -0
- data/spec/low_card_tables/unit/low_card_table/row_collapser_spec.rb +109 -0
- data/spec/low_card_tables/unit/low_card_table/row_manager_spec.rb +918 -0
- data/spec/low_card_tables/unit/low_card_table/table_unique_index_spec.rb +117 -0
- metadata +206 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
module LowCardTables
|
2
|
+
module LowCardTable
|
3
|
+
# A TableUniqueIndex represents the concept of a unique index for a given low-card model class. I say "the concept",
|
4
|
+
# because there should only be one instance of this class for any given low-card model class -- there isn't one
|
5
|
+
# instance of this class for each actual unique index for the class in question.
|
6
|
+
#
|
7
|
+
# This class started as code that was directly part of the RowManager, and was factored out to create this class
|
8
|
+
# instead -- simply so that the RowManager wouldn't have any more code in it than necessary.
|
9
|
+
class TableUniqueIndex
|
10
|
+
# Creates a new instance for the low-card model class in question.
|
11
|
+
def initialize(low_card_model)
|
12
|
+
unless low_card_model.respond_to?(:is_low_card_table?) && low_card_model.is_low_card_table?
|
13
|
+
raise ArgumentError, "You must supply a low-card AR model class, not: #{low_card_model.inspect}"
|
14
|
+
end
|
15
|
+
|
16
|
+
@low_card_model = low_card_model
|
17
|
+
end
|
18
|
+
|
19
|
+
# Ensures that the unique index is present. If the index is present, does nothing else.
|
20
|
+
#
|
21
|
+
# If the index is not present, then looks at +create_if_needed+. If this evaluates to true, then it will create
|
22
|
+
# the index. If this evaluates to false, then it will raise an exception.
|
23
|
+
def ensure_present!(create_if_needed)
|
24
|
+
return unless @low_card_model.table_exists?
|
25
|
+
|
26
|
+
current_name = current_unique_all_columns_index_name
|
27
|
+
return true if current_name
|
28
|
+
|
29
|
+
if create_if_needed
|
30
|
+
create_unique_index!
|
31
|
+
true
|
32
|
+
else
|
33
|
+
message = %{You said that the table '#{low_card_model.table_name}' is a low-card table.
|
34
|
+
However, it currently does not seem to have a unique index on all its columns. For the
|
35
|
+
low-card system to work properly, this is *required* -- although the low-card system
|
36
|
+
tries very hard to lock tables and otherwise ensure that it never will create duplicate
|
37
|
+
rows, this is important enough that we really want the database to enforce it.
|
38
|
+
|
39
|
+
We're looking for an index on the following columns:
|
40
|
+
|
41
|
+
#{value_column_names.sort.join(", ")}
|
42
|
+
|
43
|
+
...and we have the following unique indexes:
|
44
|
+
|
45
|
+
}
|
46
|
+
current_unique_indexes.each do |unique_index|
|
47
|
+
message << " '#{unique_index.name}': #{unique_index.columns.sort.join(", ")}\n"
|
48
|
+
end
|
49
|
+
message << "\n"
|
50
|
+
|
51
|
+
raise LowCardTables::Errors::LowCardNoUniqueIndexError, message
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Removes the unique index, if one is present. If one is not present, does nothing.
|
56
|
+
def remove!
|
57
|
+
table_name = low_card_model.table_name
|
58
|
+
current_name = current_unique_all_columns_index_name
|
59
|
+
|
60
|
+
if current_name
|
61
|
+
migrate do
|
62
|
+
remove_index table_name, :name => current_name
|
63
|
+
end
|
64
|
+
|
65
|
+
now_current_name = current_unique_all_columns_index_name
|
66
|
+
if now_current_name
|
67
|
+
raise "Whoa -- we tried to remove the unique index on #{table_name}, which was named '#{current_name}', but, after we removed it, we still have a unique all-columns index called '#{now_current_name}'!"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
attr_reader :low_card_model
|
74
|
+
|
75
|
+
def value_column_names
|
76
|
+
low_card_model.low_card_value_column_names
|
77
|
+
end
|
78
|
+
|
79
|
+
def migrate(&block)
|
80
|
+
migration_class = Class.new(::ActiveRecord::Migration)
|
81
|
+
metaclass = migration_class.class_eval { class << self; self; end }
|
82
|
+
metaclass.instance_eval { define_method(:up, &block) }
|
83
|
+
|
84
|
+
::ActiveRecord::Migration.suppress_messages do
|
85
|
+
migration_class.migrate(:up)
|
86
|
+
end
|
87
|
+
|
88
|
+
low_card_model.reset_column_information
|
89
|
+
LowCardTables::VersionSupport.clear_schema_cache!(low_card_model)
|
90
|
+
end
|
91
|
+
|
92
|
+
def create_unique_index!
|
93
|
+
raise "Whoa -- there should never already be a unique index for #{low_card_model}!" if current_unique_all_columns_index_name
|
94
|
+
|
95
|
+
table_name = low_card_model.table_name
|
96
|
+
column_names = value_column_names.sort
|
97
|
+
ideal_name = ideal_unique_all_columns_index_name
|
98
|
+
|
99
|
+
migrate do
|
100
|
+
remove_index table_name, :name => ideal_name rescue nil
|
101
|
+
add_index table_name, column_names, :unique => true, :name => ideal_name
|
102
|
+
end
|
103
|
+
|
104
|
+
unless current_unique_all_columns_index_name
|
105
|
+
raise "Whoa -- there should always be a unique index by now for #{low_card_model}! We think we created one, but now it still doesn't exist?!?"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def current_unique_indexes
|
110
|
+
return [ ] if (! low_card_model.table_exists?)
|
111
|
+
low_card_model.connection.indexes(low_card_model.table_name).select { |i| i.unique }
|
112
|
+
end
|
113
|
+
|
114
|
+
def current_unique_all_columns_index_name
|
115
|
+
index = current_unique_indexes.detect { |index| index.columns.sort == value_column_names.sort }
|
116
|
+
index.name if index
|
117
|
+
end
|
118
|
+
|
119
|
+
# We just limit all index names to this length -- this should be the smallest maximum index-name length that
|
120
|
+
# any database supports.
|
121
|
+
MINIMUM_MAX_INDEX_NAME_LENGTH = 63
|
122
|
+
|
123
|
+
def ideal_unique_all_columns_index_name
|
124
|
+
index_part_1 = "index_"
|
125
|
+
index_part_2 = "_lc_on_all"
|
126
|
+
|
127
|
+
remaining_characters = MINIMUM_MAX_INDEX_NAME_LENGTH - (index_part_1.length + index_part_2.length)
|
128
|
+
index_name = index_part_1 + (@low_card_model.table_name[0..(remaining_characters - 1)]) + index_part_2
|
129
|
+
|
130
|
+
index_name
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module LowCardTables
|
2
|
+
# Contains methods used by the codebase to support differing ActiveRecord versions. This is just a clean way of
|
3
|
+
# factoring out differing ActiveRecord API into a single class.
|
4
|
+
class VersionSupport
|
5
|
+
class << self
|
6
|
+
# Clear the schema cache for a given model.
|
7
|
+
def clear_schema_cache!(model)
|
8
|
+
if model.connection.respond_to?(:schema_cache)
|
9
|
+
model.connection.schema_cache.clear!
|
10
|
+
elsif model.connection.respond_to?(:clear_cache!)
|
11
|
+
model.connection.clear_cache!
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Can you specify a block on default_scope? This was added in ActiveRecord 3.1.
|
16
|
+
def default_scopes_accept_a_block?
|
17
|
+
! (::ActiveRecord::VERSION::MAJOR <= 3 && ::ActiveRecord::VERSION::MINOR == 0)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Is #migrate a class method, or an instance method, on ActiveRecord::Migration? It changed to an instance method
|
21
|
+
# as of ActiveRecord 3.1.
|
22
|
+
def migrate_is_a_class_method?
|
23
|
+
(::ActiveRecord::VERSION::MAJOR <= 3 && ::ActiveRecord::VERSION::MINOR == 0)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Define a default scope on the class in question. This is only actually used from our specs.
|
27
|
+
def define_default_scope(klass, conditions)
|
28
|
+
if default_scopes_accept_a_block?
|
29
|
+
if conditions
|
30
|
+
klass.instance_eval %{
|
31
|
+
default_scope { where(#{conditions.inspect}) }
|
32
|
+
}
|
33
|
+
else
|
34
|
+
klass.instance_eval %{
|
35
|
+
default_scope { }
|
36
|
+
}
|
37
|
+
end
|
38
|
+
else
|
39
|
+
if conditions
|
40
|
+
klass.instance_eval %{
|
41
|
+
default_scope where(#{conditions.inspect})
|
42
|
+
}
|
43
|
+
else
|
44
|
+
klass.instance_eval %{
|
45
|
+
default_scope nil
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "low_card_tables/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "low_card_tables"
|
7
|
+
s.version = LowCardTables::VERSION
|
8
|
+
s.authors = ["Andrew Geweke"]
|
9
|
+
s.email = ["andrew@geweke.org"]
|
10
|
+
s.homepage = "https://github.com/ageweke/low_card_tables"
|
11
|
+
s.summary = %q{"Bitfields for ActiveRecord": instead of storing multiple columns with low cardinality (few distinct values) directly in a table, which results in performance and maintainability problems, break them out into a separate table with almost zero overhead. Trivially add new columns without migrating a main, enormous table. Query on combinations of values very efficiently.}
|
12
|
+
s.description = %q{"Bitfields for ActiveRecord": store low-cardinality columns in a separate table for vastly more flexibility and better performance.}
|
13
|
+
s.license = "MIT"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
|
20
|
+
s.add_development_dependency "bundler"
|
21
|
+
s.add_development_dependency "rake"
|
22
|
+
s.add_development_dependency "rspec", "~> 2.14"
|
23
|
+
|
24
|
+
if (RUBY_VERSION =~ /^1\.9\./ || RUBY_VERSION =~ /^2\.0\./) && ((! defined?(RUBY_ENGINE)) || (RUBY_ENGINE != 'jruby'))
|
25
|
+
s.add_development_dependency "pry"
|
26
|
+
s.add_development_dependency "pry-debugger"
|
27
|
+
s.add_development_dependency "pry-stack_explorer"
|
28
|
+
end
|
29
|
+
|
30
|
+
ar_version = ENV['LOW_CARD_TABLES_AR_TEST_VERSION']
|
31
|
+
ar_version = ar_version.strip if ar_version
|
32
|
+
|
33
|
+
version_spec = case ar_version
|
34
|
+
when nil then [ ">= 3.0", "<= 4.99.99" ]
|
35
|
+
when 'master' then nil
|
36
|
+
else [ "=#{ar_version}" ]
|
37
|
+
end
|
38
|
+
|
39
|
+
if version_spec
|
40
|
+
s.add_dependency("activerecord", *version_spec)
|
41
|
+
end
|
42
|
+
|
43
|
+
s.add_dependency "activesupport", ">= 3.0", "<= 4.99.99"
|
44
|
+
|
45
|
+
ar_import_version = case ar_version
|
46
|
+
when nil then nil
|
47
|
+
when 'master', /^4\.0\./ then '~> 0.4.1'
|
48
|
+
when /^3\.0\./ then '~> 0.2.11'
|
49
|
+
when /^3\.1\./, /^3\.2\./ then '~> 0.3.1'
|
50
|
+
else raise "Don't know what activerecord-import version to require for activerecord version #{ar_version.inspect}!"
|
51
|
+
end
|
52
|
+
|
53
|
+
if ar_import_version
|
54
|
+
s.add_dependency("activerecord-import", ar_import_version)
|
55
|
+
else
|
56
|
+
s.add_dependency("activerecord-import")
|
57
|
+
end
|
58
|
+
|
59
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec', 'low_card_tables', 'helpers', 'database_helper'))
|
60
|
+
database_gem_name = LowCardTables::Helpers::DatabaseHelper.maybe_database_gem_name
|
61
|
+
|
62
|
+
# Ugh. Later versions of the 'mysql2' gem are incompatible with AR 3.0.x; so, here, we explicitly trap that case
|
63
|
+
# and use an earlier version of that Gem.
|
64
|
+
if database_gem_name && database_gem_name == 'mysql2' && ar_version && ar_version =~ /^3\.0\./
|
65
|
+
s.add_development_dependency('mysql2', '~> 0.2.0')
|
66
|
+
else
|
67
|
+
s.add_development_dependency(database_gem_name)
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'low_card_tables/version_support'
|
2
|
+
|
3
|
+
module LowCardTables
|
4
|
+
module Helpers
|
5
|
+
class DatabaseHelper
|
6
|
+
class InvalidDatabaseConfigurationError < StandardError; end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def maybe_database_gem_name
|
10
|
+
begin
|
11
|
+
dh = new
|
12
|
+
dh.database_gem_name
|
13
|
+
rescue InvalidDatabaseConfigurationError => idce
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
config # make sure we raise on instantiation if configuration is invalid
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup_activerecord!
|
24
|
+
require 'active_record'
|
25
|
+
require config[:require]
|
26
|
+
::ActiveRecord::Base.establish_connection(config[:config])
|
27
|
+
|
28
|
+
require 'logger'
|
29
|
+
require 'stringio'
|
30
|
+
@logs = StringIO.new
|
31
|
+
::ActiveRecord::Base.logger = Logger.new(@logs)
|
32
|
+
|
33
|
+
if config[:config][:adapter] == 'sqlite3'
|
34
|
+
sqlite_version = ::ActiveRecord::Base.connection.send(:sqlite_version).instance_variable_get("@version").inspect rescue "unknown"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def table_name(name)
|
39
|
+
"lctables_spec_#{name}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def database_gem_name
|
43
|
+
config[:database_gem_name]
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def config
|
48
|
+
config_from_config_file || travis_ci_config_from_environment || invalid_config_file!
|
49
|
+
end
|
50
|
+
|
51
|
+
def config_from_config_file
|
52
|
+
return nil unless File.exist?(config_file_path)
|
53
|
+
require config_file_path
|
54
|
+
|
55
|
+
return nil unless defined?(LOW_CARD_TABLES_SPEC_DATABASE_CONFIG)
|
56
|
+
return nil unless LOW_CARD_TABLES_SPEC_DATABASE_CONFIG.kind_of?(Hash)
|
57
|
+
|
58
|
+
return nil unless LOW_CARD_TABLES_SPEC_DATABASE_CONFIG[:require]
|
59
|
+
return nil unless LOW_CARD_TABLES_SPEC_DATABASE_CONFIG[:database_gem_name]
|
60
|
+
|
61
|
+
return nil unless LOW_CARD_TABLES_SPEC_DATABASE_CONFIG
|
62
|
+
LOW_CARD_TABLES_SPEC_DATABASE_CONFIG
|
63
|
+
end
|
64
|
+
|
65
|
+
def travis_ci_config_from_environment
|
66
|
+
dbtype = (ENV['LOW_CARD_TABLES_TRAVIS_CI_DATABASE_TYPE'] || '').strip.downcase
|
67
|
+
is_jruby = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
68
|
+
|
69
|
+
if is_jruby
|
70
|
+
case dbtype
|
71
|
+
when 'mysql'
|
72
|
+
{
|
73
|
+
:require => 'activerecord-jdbcmysql-adapter',
|
74
|
+
:database_gem_name => 'activerecord-jdbcmysql-adapter',
|
75
|
+
:config => {
|
76
|
+
:adapter => 'jdbcmysql',
|
77
|
+
:database => 'myapp_test',
|
78
|
+
:username => 'travis',
|
79
|
+
:encoding => 'utf8'
|
80
|
+
}
|
81
|
+
}
|
82
|
+
when '', nil then nil
|
83
|
+
else
|
84
|
+
raise "Unknown Travis CI database type: #{dbtype.inspect}"
|
85
|
+
end
|
86
|
+
else
|
87
|
+
case dbtype
|
88
|
+
when 'postgres', 'postgresql'
|
89
|
+
{
|
90
|
+
:require => 'pg',
|
91
|
+
:database_gem_name => 'pg',
|
92
|
+
:config => {
|
93
|
+
:adapter => 'postgresql',
|
94
|
+
:database => 'myapp_test',
|
95
|
+
:username => 'postgres',
|
96
|
+
:min_messages => 'WARNING'
|
97
|
+
}
|
98
|
+
}
|
99
|
+
when 'mysql'
|
100
|
+
{
|
101
|
+
:require => 'mysql2',
|
102
|
+
:database_gem_name => 'mysql2',
|
103
|
+
:config => {
|
104
|
+
:adapter => 'mysql2',
|
105
|
+
:database => 'myapp_test',
|
106
|
+
:username => 'travis',
|
107
|
+
:encoding => 'utf8'
|
108
|
+
}
|
109
|
+
}
|
110
|
+
when 'sqlite'
|
111
|
+
{
|
112
|
+
:require => 'sqlite3',
|
113
|
+
:database_gem_name => 'sqlite3',
|
114
|
+
:config => {
|
115
|
+
:adapter => 'sqlite3',
|
116
|
+
:database => ':memory:',
|
117
|
+
:timeout => 500
|
118
|
+
}
|
119
|
+
}
|
120
|
+
when '', nil then nil
|
121
|
+
else
|
122
|
+
raise "Unknown Travis CI database type: #{dbtype.inspect}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def config_file_path
|
128
|
+
@config_file_path ||= File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'spec_database_config.rb'))
|
129
|
+
end
|
130
|
+
|
131
|
+
def invalid_config_file!
|
132
|
+
raise Errno::ENOENT, %{In order to run specs for LowCardTables, you need to create a file at:
|
133
|
+
|
134
|
+
#{config_file_path}
|
135
|
+
|
136
|
+
...that defines a top-level LOW_CARD_TABLES_SPEC_DATABASE_CONFIG hash, with members:
|
137
|
+
|
138
|
+
:require => 'name_of_adapter_to_require',
|
139
|
+
:database_gem_name => 'name_of_gem_for_adapter',
|
140
|
+
:config => { ...whatever ActiveRecord::Base.establish_connection should be passed... }
|
141
|
+
|
142
|
+
Alternatively, if you're running under Travis CI, you can set the environment variable
|
143
|
+
LOW_CARD_TABLES_TRAVIS_CI_DATABASE_TYPE to 'postgres', 'mysql', or 'sqlite', and it will
|
144
|
+
use the correct configuration for testing on Travis CI.}
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module LowCardTables
|
2
|
+
module Helpers
|
3
|
+
class QuerySpyHelper
|
4
|
+
class << self
|
5
|
+
def with_query_spy(*args, &block)
|
6
|
+
new(*args).spy(&block)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(table_name)
|
11
|
+
@table_name = table_name
|
12
|
+
@calls = [ ]
|
13
|
+
end
|
14
|
+
|
15
|
+
def spy(&block)
|
16
|
+
begin
|
17
|
+
register!
|
18
|
+
block.call(self)
|
19
|
+
ensure
|
20
|
+
deregister!
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def call_count
|
25
|
+
@calls.length
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(notification_name, when1, when2, id, data)
|
29
|
+
sql = data[:sql]
|
30
|
+
if sql && sql.strip.length > 0
|
31
|
+
if sql =~ /^\s*SELECT.*FROM\s+['"\`]*\s*#{@table_name}\s*['"\`]*\s+/mi
|
32
|
+
@calls << data.merge(:backtrace => caller)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def register!
|
39
|
+
ActiveSupport::Notifications.subscribe("sql.active_record", self)
|
40
|
+
end
|
41
|
+
|
42
|
+
def deregister!
|
43
|
+
ActiveSupport::Notifications.unsubscribe(self)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|