constant_table_saver 4.2.0 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README +2 -3
- data/constant_table_saver.gemspec +2 -3
- data/lib/constant_table_saver/version.rb +1 -1
- data/lib/constant_table_saver.rb +72 -120
- data/test/constant_table_saver_test.rb +1 -53
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0478d2577af80dc5fc4c7bf860321b72c49e2c1
|
4
|
+
data.tar.gz: 27060f22739b2cdb8a0cbf36f56ebe186e9b6333
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3784c8649039aca241ae5e3b87b4b94d219c4c159d91b79d261dc1af3fa7795e4368e4ab97535290c0fec3c759d3729a5959988012bb7f10cd7d0b915fd854f2
|
7
|
+
data.tar.gz: 559071bc2fbadd429c6c2634a1f3eb71e27258c9dc260c38a60681527adf3b8436b4945a932cf83b7bc1e9e2c5898132879a87adc34a729a18486d8bd144ae6a
|
data/README
CHANGED
@@ -11,10 +11,9 @@ named after the name field you specify.
|
|
11
11
|
Compatibility
|
12
12
|
=============
|
13
13
|
|
14
|
-
Currently tested against Rails
|
14
|
+
Currently tested against Rails 5.1 (5.1.0beta2) and 5.0 (up to 5.0.2) and 4.2 (up to 4.2.7), on Ruby 2.3.4.
|
15
15
|
|
16
|
-
|
17
|
-
or 1.8.7 as appropriate, and may still work for them.
|
16
|
+
For earlier versions of Rails, use an older version of the gem.
|
18
17
|
|
19
18
|
|
20
19
|
Example
|
@@ -16,10 +16,9 @@ named after the name field you specify.
|
|
16
16
|
Compatibility
|
17
17
|
=============
|
18
18
|
|
19
|
-
Currently tested against Rails
|
19
|
+
Currently tested against Rails 5.1 (5.1.0beta2) and 5.0 (up to 5.0.2) and 4.2 (up to 4.2.7), on Ruby 2.3.4.
|
20
20
|
|
21
|
-
|
22
|
-
or 1.8.7 as appropriate, and may still work for them.
|
21
|
+
For earlier versions of Rails, use an older version of the gem.
|
23
22
|
EOF
|
24
23
|
gem.has_rdoc = false
|
25
24
|
gem.author = "Will Bryant"
|
data/lib/constant_table_saver.rb
CHANGED
@@ -7,11 +7,13 @@ module ConstantTableSaver
|
|
7
7
|
options.assert_valid_keys(:name, :name_prefix, :name_suffix)
|
8
8
|
class_attribute :constant_table_options, :instance_writer => false
|
9
9
|
self.constant_table_options = options
|
10
|
+
|
11
|
+
@constant_record_methods = nil
|
10
12
|
|
11
|
-
if ActiveRecord::VERSION::MAJOR ==
|
12
|
-
extend ActiveRecord3ClassMethods
|
13
|
-
else
|
13
|
+
if ActiveRecord::VERSION::MAJOR == 4
|
14
14
|
extend ActiveRecord4ClassMethods
|
15
|
+
else
|
16
|
+
extend ActiveRecord5ClassMethods
|
15
17
|
end
|
16
18
|
extend ClassMethods
|
17
19
|
extend NameClassMethods if constant_table_options[:name]
|
@@ -27,8 +29,10 @@ module ConstantTableSaver
|
|
27
29
|
def reset_cache_with_constant_tables(*args)
|
28
30
|
reset_cache_without_constant_tables(*args).tap { ConstantTableSaver.reset_all_caches }
|
29
31
|
end
|
30
|
-
|
31
|
-
|
32
|
+
alias :create_fixtures_without_constant_tables :create_fixtures
|
33
|
+
alias :create_fixtures :create_fixtures_with_constant_tables
|
34
|
+
alias :reset_cache_without_constant_tables :reset_cache
|
35
|
+
alias :reset_cache :reset_cache_with_constant_tables
|
32
36
|
end unless klass.respond_to?(:create_fixtures_with_constant_tables)
|
33
37
|
end
|
34
38
|
end
|
@@ -47,6 +51,69 @@ module ConstantTableSaver
|
|
47
51
|
@constant_record_methods.each {|method_id| (class << self; self; end;).send(:remove_method, method_id)} if @constant_record_methods
|
48
52
|
@cached_records = @cached_records_by_id = @constant_record_methods = @cached_blank_scope = @find_by_sql = nil
|
49
53
|
end
|
54
|
+
|
55
|
+
def _to_sql_with_binds(sql, binds)
|
56
|
+
if sql.respond_to?(:to_sql)
|
57
|
+
# an arel object
|
58
|
+
connection.to_sql(sql, binds)
|
59
|
+
else
|
60
|
+
# a plain string
|
61
|
+
sql
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def relation
|
66
|
+
super.tap do |s|
|
67
|
+
class << s
|
68
|
+
# we implement find_some here because we'd have to use partial string matching to catch
|
69
|
+
# this case in find_by_sql, which would be ugly. (we do the other cases in find_by_sql
|
70
|
+
# because it's simpler & the only place to catch things like association find queries.)
|
71
|
+
def find_some(ids)
|
72
|
+
return super if @values.present? # special cases such as offset and limit
|
73
|
+
ids.collect {|id| find_one(id)}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module ActiveRecord5ClassMethods
|
81
|
+
def find_by_sql(sql, binds = [], preparable: nil, &block)
|
82
|
+
@find_by_sql ||= {
|
83
|
+
:all => all.to_sql,
|
84
|
+
:id => relation.where(relation.table[primary_key].eq(Arel::Nodes::BindParam.new)).limit(1).arel,
|
85
|
+
:first => relation.order(relation.table[primary_key].asc).limit(1).arel,
|
86
|
+
:last => relation.order(relation.table[primary_key].desc).limit(1).arel,
|
87
|
+
}
|
88
|
+
|
89
|
+
@limit_one ||= ActiveRecord::Attribute.with_cast_value("LIMIT", 1, ActiveRecord::Type::Value.new)
|
90
|
+
|
91
|
+
_sql = _to_sql_with_binds(sql, binds)
|
92
|
+
|
93
|
+
if binds.empty?
|
94
|
+
if _sql == @find_by_sql[:all]
|
95
|
+
return @cached_records ||= super(relation.to_sql).each(&:freeze)
|
96
|
+
end
|
97
|
+
|
98
|
+
elsif binds.size == 1 &&
|
99
|
+
binds.last == @limit_one
|
100
|
+
if _sql == _to_sql_with_binds(@find_by_sql[:first], binds)
|
101
|
+
return [relation.to_a.first].compact
|
102
|
+
elsif _sql == _to_sql_with_binds(@find_by_sql[:last], binds)
|
103
|
+
return [relation.to_a.last].compact
|
104
|
+
end
|
105
|
+
|
106
|
+
elsif binds.size == 2 &&
|
107
|
+
binds.last == @limit_one &&
|
108
|
+
binds.first.is_a?(ActiveRecord::Relation::QueryAttribute) &&
|
109
|
+
binds.first.name == primary_key &&
|
110
|
+
_sql == _to_sql_with_binds(@find_by_sql[:id], binds) # we have to late-render the find(id) SQL because mysql2 on 4.1 and later requires the bind variables to render the SQL, and errors out with a nil dereference otherwise
|
111
|
+
@cached_records_by_id ||= relation.to_a.index_by {|record| record.id.to_param}
|
112
|
+
return [@cached_records_by_id[binds.first.value.to_param]].compact
|
113
|
+
end
|
114
|
+
|
115
|
+
super(sql, binds, preparable: preparable, &block)
|
116
|
+
end
|
50
117
|
end
|
51
118
|
|
52
119
|
module ActiveRecord4ClassMethods
|
@@ -81,121 +148,6 @@ module ConstantTableSaver
|
|
81
148
|
|
82
149
|
super
|
83
150
|
end
|
84
|
-
|
85
|
-
def _to_sql_with_binds(sql, binds)
|
86
|
-
if sql.respond_to?(:to_sql)
|
87
|
-
# an arel object
|
88
|
-
connection.to_sql(sql, binds)
|
89
|
-
else
|
90
|
-
# a plain string
|
91
|
-
sql
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def relation
|
96
|
-
super.tap do |s|
|
97
|
-
class << s
|
98
|
-
# we implement find_some here because we'd have to use partial string matching to catch
|
99
|
-
# this case in find_by_sql, which would be ugly. (we do the other cases in find_by_sql
|
100
|
-
# because it's simpler & the only place to catch things like association find queries.)
|
101
|
-
def find_some(ids)
|
102
|
-
return super if @values.present? # special cases such as offset and limit
|
103
|
-
ids.collect {|id| find_one(id)}
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
module ActiveRecord3ClassMethods
|
111
|
-
def scoped(options = nil)
|
112
|
-
return super if options
|
113
|
-
return super if respond_to?(:current_scoped_methods, true) && current_scoped_methods
|
114
|
-
return super if respond_to?(:current_scope, true) && current_scope
|
115
|
-
@cached_blank_scope ||= super.tap do |s|
|
116
|
-
class << s
|
117
|
-
def to_a
|
118
|
-
return @records if loaded?
|
119
|
-
super.each(&:freeze)
|
120
|
-
end
|
121
|
-
|
122
|
-
def find(*args)
|
123
|
-
# annoyingly, the call to find to load a belongs_to passes :conditions => nil, which causes
|
124
|
-
# the base find method to apply_finder_options and construct an entire new scope, which is
|
125
|
-
# unnecessary and also means that it bypasses our find_one implementation (we don't interfere
|
126
|
-
# with scopes or finds that actually do apply conditions etc.), so we check as a special case
|
127
|
-
return find_with_ids(args.first) if args.length == 2 && args.last == {:conditions => nil}
|
128
|
-
super
|
129
|
-
end
|
130
|
-
|
131
|
-
def find_first
|
132
|
-
# the normal scope implementation would cache this anyway, but we force a load of all records,
|
133
|
-
# since otherwise if the app used .first before using .all there'd be unnecessary queries
|
134
|
-
to_a.first
|
135
|
-
end
|
136
|
-
|
137
|
-
def find_last
|
138
|
-
# as for find_first
|
139
|
-
to_a.last
|
140
|
-
end
|
141
|
-
|
142
|
-
def find_one(id)
|
143
|
-
# see below re to_param
|
144
|
-
cached_records_by_id[id.to_param] || raise(::ActiveRecord::RecordNotFound, "Couldn't find #{name} with ID=#{id}")
|
145
|
-
end
|
146
|
-
|
147
|
-
def find_some(ids)
|
148
|
-
# see below re to_param
|
149
|
-
ids.collect {|id| cached_records_by_id[id.to_param]}.tap do |results| # obviously since find_one caches efficiently, this isn't inefficient as it would be for real finds
|
150
|
-
results.compact!
|
151
|
-
raise(::ActiveRecord::RecordNotFound, "Couldn't find all #{name.pluralize} with IDs #{ids.join ','} (found #{results.size} results, but was looking for #{ids.size}") unless results.size == ids.size
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
# in Rails 3.1 the associations code was rewritten to generalise its sql generation to support
|
156
|
-
# more complex relationships (eg. nested :through associations). however unfortunately, during
|
157
|
-
# this work the implementation of belongs_to associations was changed so that it no longer calls
|
158
|
-
# one of the basic find_ methods above; instead a vanilla target scope is constructed, a where()
|
159
|
-
# scope to add the constraint that the primary key = the FK value is constructed, the two are
|
160
|
-
# merged, and then #first is called on that scope. frustratingly, all this complexity means that
|
161
|
-
# our find_ hooks above are no longer called when dereferencing a belongs_to association; they
|
162
|
-
# work fine and are used elsewhere, but we have to explicitly handle belongs_to target scope
|
163
|
-
# merging to avoid those querying, which is a huge PITA. because we want to ensure that we don't
|
164
|
-
# end up accidentally caching other scope requests, we explicitly build a list of the possible
|
165
|
-
# ARel constrained scopes - indexing them by their expression in SQL so that we don't need to
|
166
|
-
# code in the list of all the possible ARel terms. we then go one step further and make this
|
167
|
-
# cached scope pre-loaded using the record we already have - there's sadly no external way to do
|
168
|
-
# this so we have to shove in the instance variables.
|
169
|
-
#
|
170
|
-
# it will be clear that this was a very problematic ActiveRecord refactoring.
|
171
|
-
def belongs_to_record_scopes
|
172
|
-
@belongs_to_record_scopes ||= to_a.each_with_object({}) do |record, results|
|
173
|
-
scope_that_belongs_to_will_want = where(table[primary_key].eq(record.id))
|
174
|
-
scope_that_belongs_to_will_want.instance_variable_set("@loaded", true)
|
175
|
-
scope_that_belongs_to_will_want.instance_variable_set("@records", [record])
|
176
|
-
results[scope_that_belongs_to_will_want.to_sql] = scope_that_belongs_to_will_want
|
177
|
-
end.freeze
|
178
|
-
end
|
179
|
-
|
180
|
-
def merge(other)
|
181
|
-
if belongs_to_record_scope = belongs_to_record_scopes[other.to_sql]
|
182
|
-
return belongs_to_record_scope
|
183
|
-
end
|
184
|
-
|
185
|
-
super other
|
186
|
-
end
|
187
|
-
|
188
|
-
private
|
189
|
-
def cached_records_by_id
|
190
|
-
# we'd like to use the same as ActiveRecord's finder_methods.rb, which uses:
|
191
|
-
# id = id.id if ActiveRecord::Base === id
|
192
|
-
# but referencing ActiveRecord::Base here segfaults my ruby 1.8.7
|
193
|
-
# (2009-06-12 patchlevel 174) [universal-darwin10.0]! instead we use to_param.
|
194
|
-
@cached_records_by_id ||= all.index_by {|record| record.id.to_param}
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
151
|
end
|
200
152
|
|
201
153
|
module NameClassMethods
|
@@ -54,8 +54,6 @@ class ConstantTableSaverTest < ActiveSupport::TestCase
|
|
54
54
|
assert_queries(0, &block)
|
55
55
|
end
|
56
56
|
|
57
|
-
DEPRECATED_FINDERS = ActiveRecord::VERSION::MAJOR == 3 || (ActiveRecord::VERSION::MAJOR == 4 && ActiveRecord::VERSION::MINOR == 0)
|
58
|
-
|
59
57
|
test "it caches all() results" do
|
60
58
|
@pies = StandardPie.all.to_a
|
61
59
|
assert_queries(1) do
|
@@ -66,16 +64,6 @@ class ConstantTableSaverTest < ActiveSupport::TestCase
|
|
66
64
|
end
|
67
65
|
end
|
68
66
|
|
69
|
-
test "it caches find(:all) results" do
|
70
|
-
@pies = StandardPie.find(:all).to_a
|
71
|
-
assert_queries(1) do
|
72
|
-
assert_equal @pies.collect(&:attributes), ConstantPie.find(:all).to_a.collect(&:attributes)
|
73
|
-
end
|
74
|
-
assert_no_queries do
|
75
|
-
assert_equal @pies.collect(&:attributes), ConstantPie.find(:all).to_a.collect(&:attributes)
|
76
|
-
end
|
77
|
-
end if DEPRECATED_FINDERS
|
78
|
-
|
79
67
|
test "it caches find(id) results" do
|
80
68
|
@pie = StandardPie.find(1)
|
81
69
|
@other_pie = StandardPie.find(2)
|
@@ -100,16 +88,6 @@ class ConstantTableSaverTest < ActiveSupport::TestCase
|
|
100
88
|
end
|
101
89
|
end
|
102
90
|
|
103
|
-
test "it caches find(:first) results" do
|
104
|
-
@pie = StandardPie.find(:first)
|
105
|
-
assert_queries(1) do
|
106
|
-
assert_equal @pie.attributes, ConstantPie.find(:first).attributes
|
107
|
-
end
|
108
|
-
assert_no_queries do
|
109
|
-
assert_equal @pie.attributes, ConstantPie.find(:first).attributes
|
110
|
-
end
|
111
|
-
end if DEPRECATED_FINDERS
|
112
|
-
|
113
91
|
test "it caches first() results" do
|
114
92
|
@pie = StandardPie.first
|
115
93
|
assert_queries(1) do
|
@@ -119,16 +97,6 @@ class ConstantTableSaverTest < ActiveSupport::TestCase
|
|
119
97
|
assert_equal @pie.attributes, ConstantPie.first.attributes
|
120
98
|
end
|
121
99
|
end
|
122
|
-
|
123
|
-
test "it caches find(:last) results" do
|
124
|
-
@pie = StandardPie.find(:last)
|
125
|
-
assert_queries(1) do
|
126
|
-
assert_equal @pie.attributes, ConstantPie.find(:last).attributes
|
127
|
-
end
|
128
|
-
assert_no_queries do
|
129
|
-
assert_equal @pie.attributes, ConstantPie.find(:last).attributes
|
130
|
-
end
|
131
|
-
end if DEPRECATED_FINDERS
|
132
100
|
|
133
101
|
test "it caches last() results" do
|
134
102
|
@pie = StandardPie.last
|
@@ -154,7 +122,6 @@ class ConstantTableSaverTest < ActiveSupport::TestCase
|
|
154
122
|
|
155
123
|
test "it isn't affected by scopes active at the time of first load" do
|
156
124
|
assert_equal 0, ConstantPie.filled_with_unicorn.all.to_a.size
|
157
|
-
assert_equal 0, ConstantPie.with_unicorn_filling_scope { ConstantPie.all.to_a.length } if DEPRECATED_FINDERS
|
158
125
|
assert_equal StandardPie.all.to_a.size, ConstantPie.all.to_a.size
|
159
126
|
end
|
160
127
|
|
@@ -193,28 +160,9 @@ class ConstantTableSaverTest < ActiveSupport::TestCase
|
|
193
160
|
end
|
194
161
|
end
|
195
162
|
|
196
|
-
test "it doesn't cache find queries with options" do
|
197
|
-
@pies = StandardPie.all(:select => "id").to_a
|
198
|
-
@pie = StandardPie.find(1, :select => "id")
|
199
|
-
assert_queries(3) do
|
200
|
-
assert_equal @pies.collect(&:attributes), ConstantPie.all(:select => "id").collect(&:attributes)
|
201
|
-
assert_equal @pies.collect(&:attributes), ConstantPie.find(:all, :select => "id").collect(&:attributes)
|
202
|
-
assert_equal @pie.attributes, ConstantPie.find(1, :select => "id").attributes
|
203
|
-
end
|
204
|
-
assert_queries(3) do
|
205
|
-
assert_equal @pies.collect(&:attributes), ConstantPie.all(:select => "id").collect(&:attributes)
|
206
|
-
assert_equal @pies.collect(&:attributes), ConstantPie.find(:all, :select => "id").collect(&:attributes)
|
207
|
-
assert_equal @pie.attributes, ConstantPie.find(1, :select => "id").attributes
|
208
|
-
end
|
209
|
-
end if DEPRECATED_FINDERS
|
210
|
-
|
211
163
|
test "it passes the options preventing caching to the underlying query methods" do
|
212
|
-
|
213
|
-
assert_equal nil, ConstantPie.first(:conditions => {:filling => 'unicorn'}) if DEPRECATED_FINDERS
|
214
|
-
assert_equal nil, ConstantPie.find(:first, :conditions => {:filling => 'unicorn'}) if DEPRECATED_FINDERS
|
164
|
+
assert_nil nil, ConstantPie.where(:filling => 'unicorn').first
|
215
165
|
assert_equal [], ConstantPie.where(:filling => 'unicorn').all
|
216
|
-
assert_equal [], ConstantPie.all(:conditions => {:filling => 'unicorn'}) if DEPRECATED_FINDERS
|
217
|
-
assert_equal [], ConstantPie.find(:all, :conditions => {:filling => 'unicorn'}) if DEPRECATED_FINDERS
|
218
166
|
end
|
219
167
|
|
220
168
|
test "it creates named class methods if a :name option is given" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: constant_table_saver
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Will Bryant
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -63,10 +63,9 @@ description: |
|
|
63
63
|
Compatibility
|
64
64
|
=============
|
65
65
|
|
66
|
-
Currently tested against Rails
|
66
|
+
Currently tested against Rails 5.1 (5.1.0beta2) and 5.0 (up to 5.0.2) and 4.2 (up to 4.2.7), on Ruby 2.3.4.
|
67
67
|
|
68
|
-
|
69
|
-
or 1.8.7 as appropriate, and may still work for them.
|
68
|
+
For earlier versions of Rails, use an older version of the gem.
|
70
69
|
email: will.bryant@gmail.com
|
71
70
|
executables: []
|
72
71
|
extensions: []
|
@@ -109,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
108
|
version: '0'
|
110
109
|
requirements: []
|
111
110
|
rubyforge_project:
|
112
|
-
rubygems_version: 2.
|
111
|
+
rubygems_version: 2.5.2
|
113
112
|
signing_key:
|
114
113
|
specification_version: 4
|
115
114
|
summary: Caches the records from fixed tables, and provides convenience methods to
|