columns_on_demand 4.3.0 → 5.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.
- checksums.yaml +4 -4
- data/README.md +2 -3
- data/columns_on_demand.gemspec +1 -0
- data/lib/columns_on_demand.rb +28 -101
- data/lib/columns_on_demand/version.rb +1 -1
- data/test/columns_on_demand_test.rb +49 -44
- data/test/schema.rb +6 -0
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c49cd47105f630d84e2f6ad368006311ee60c3c3
|
4
|
+
data.tar.gz: c239c30034961fd54de59f856c101c027d21bb36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3408bb8a756cd559c996ca3afdb7b7994a9137bc94ae19e97b365f12c845da1d154353e6b1900b28e631cee76d8cb73289bffe09f46bd71300d560a5be94ff17
|
7
|
+
data.tar.gz: 3ffd5d76e1fc8f8ce5a260927faa6dd022c6d151bea1c72e0fea4e7c8804f81443f265e6b89960747d38d4d7d4690e5d101a635fc5deb16e399c91b312adafa3
|
data/README.md
CHANGED
@@ -21,10 +21,9 @@ Compatibility
|
|
21
21
|
|
22
22
|
Supports mysql, mysql2, postgresql, and sqlite3.
|
23
23
|
|
24
|
-
Currently tested against Rails
|
24
|
+
Currently tested against Rails 5.1 (up to 5.1.0beta1), 5.0 (up to 5.0.2), and 4.2 (up to 4.2.7), on Ruby 2.3.4.
|
25
25
|
|
26
|
-
|
27
|
-
or 1.8.7 as appropriate, and may still work for them.
|
26
|
+
For earlier versions of Rails, use an older version of the gem.
|
28
27
|
|
29
28
|
|
30
29
|
Example
|
data/columns_on_demand.gemspec
CHANGED
data/lib/columns_on_demand.rb
CHANGED
@@ -5,30 +5,11 @@ module ColumnsOnDemand
|
|
5
5
|
self.columns_to_load_on_demand = columns_to_load_on_demand.empty? ? blob_and_text_columns : columns_to_load_on_demand.collect(&:to_s)
|
6
6
|
|
7
7
|
extend ClassMethods
|
8
|
-
|
8
|
+
prepend InstanceMethods
|
9
9
|
|
10
10
|
class <<self
|
11
|
-
|
12
|
-
|
13
|
-
ActiveRecord::AttributeMethods::const_defined?(:Serialization) &&
|
14
|
-
ActiveRecord::AttributeMethods::Serialization::const_defined?(:Attribute))
|
15
|
-
alias_method_chain :define_read_method_for_serialized_attribute, :columns_on_demand
|
16
|
-
end
|
17
|
-
alias_method_chain :reset_column_information, :columns_on_demand
|
18
|
-
end
|
19
|
-
alias_method_chain :attributes, :columns_on_demand
|
20
|
-
alias_method_chain :attribute_names, :columns_on_demand
|
21
|
-
alias_method_chain :read_attribute, :columns_on_demand
|
22
|
-
alias_method_chain :read_attribute_before_type_cast, :columns_on_demand
|
23
|
-
if ActiveRecord::AttributeMethods::Read.instance_methods.include?(:_read_attribute)
|
24
|
-
alias_method_chain :_read_attribute, :columns_on_demand
|
25
|
-
end
|
26
|
-
alias_method_chain :missing_attribute, :columns_on_demand
|
27
|
-
alias_method_chain :reload, :columns_on_demand
|
28
|
-
if ActiveRecord::AttributeMethods::Dirty.instance_methods.include?(:attribute_changed_in_place?)
|
29
|
-
alias_method_chain :attribute_changed_in_place?, :columns_on_demand
|
30
|
-
elsif ActiveRecord::AttributeMethods::Dirty.instance_methods.include?(:changed_attributes)
|
31
|
-
alias_method_chain :changed_in_place?, :columns_on_demand
|
11
|
+
alias reset_column_information_without_columns_on_demand reset_column_information
|
12
|
+
alias reset_column_information reset_column_information_with_columns_on_demand
|
32
13
|
end
|
33
14
|
end
|
34
15
|
|
@@ -36,12 +17,6 @@ module ColumnsOnDemand
|
|
36
17
|
@columns_to_select = nil
|
37
18
|
reset_column_information_without_columns_on_demand
|
38
19
|
end
|
39
|
-
|
40
|
-
def define_read_method_for_serialized_attribute_with_columns_on_demand(attr_name)
|
41
|
-
define_read_method_for_serialized_attribute_without_columns_on_demand(attr_name)
|
42
|
-
scope = method_defined?(:generated_attribute_methods) ? generated_attribute_methods : self
|
43
|
-
scope.module_eval("def #{attr_name}_with_columns_on_demand; ensure_loaded('#{attr_name}'); #{attr_name}_without_columns_on_demand; end; alias_method_chain :#{attr_name}, :columns_on_demand", __FILE__, __LINE__)
|
44
|
-
end
|
45
20
|
|
46
21
|
def blob_and_text_columns
|
47
22
|
columns.inject([]) do |blob_and_text_columns, column|
|
@@ -72,13 +47,13 @@ module ColumnsOnDemand
|
|
72
47
|
!columns_to_load_on_demand.include?(attr_name) || @attributes.key?(attr_name) || new_record? || columns_loaded.include?(attr_name)
|
73
48
|
end
|
74
49
|
|
75
|
-
def
|
50
|
+
def attributes
|
76
51
|
load_attributes(*columns_to_load_on_demand.reject {|attr_name| column_loaded?(attr_name)})
|
77
|
-
|
52
|
+
super
|
78
53
|
end
|
79
54
|
|
80
|
-
def
|
81
|
-
(
|
55
|
+
def attribute_names
|
56
|
+
(super + columns_to_load_on_demand).uniq.sort
|
82
57
|
end
|
83
58
|
|
84
59
|
def load_attributes(*attr_names)
|
@@ -87,42 +62,15 @@ module ColumnsOnDemand
|
|
87
62
|
values = self.class.connection.select_rows(
|
88
63
|
"SELECT #{attr_names.collect {|attr_name| self.class.connection.quote_column_name(attr_name)}.join(", ")}" +
|
89
64
|
" FROM #{self.class.quoted_table_name}" +
|
90
|
-
" WHERE #{self.class.connection.quote_column_name(self.class.primary_key)} = #{self.class.
|
65
|
+
" WHERE #{self.class.connection.quote_column_name(self.class.primary_key)} = #{self.class.connection.quote(id)}")
|
91
66
|
row = values.first || raise(ActiveRecord::RecordNotFound, "Couldn't find #{self.class.name} with ID=#{id}")
|
92
67
|
|
93
68
|
attr_names.each_with_index do |attr_name, i|
|
94
69
|
columns_loaded << attr_name
|
95
70
|
value = row[i]
|
96
71
|
|
97
|
-
|
98
|
-
|
99
|
-
@attributes.write_from_database(attr_name, value)
|
100
|
-
|
101
|
-
elsif coder = self.class.serialized_attributes[attr_name]
|
102
|
-
# activerecord 4.1 or earlier, with a serialized column
|
103
|
-
# for some database adapters, @column_types_override gets populated with type data from query used to load the record originally.
|
104
|
-
# this is fine, but unfortunately some special-case "decorate_columns" code in ActiveRecord will wrap those types in serialization
|
105
|
-
# objects, and it does this for each column listed in @serialized_column_names *even if they are not present in the query results*.
|
106
|
-
# as a result it unfortunately overrides the normal @column_type with a @column_type_override with a nil @column, which explodes
|
107
|
-
# when it tries to run the typecast. make it use the normal @column_type value, since we know that we've loading the regular column.
|
108
|
-
@column_types_override.delete(attr_name) if @column_types_override
|
109
|
-
|
110
|
-
if ActiveRecord.const_defined?(:AttributeMethods) &&
|
111
|
-
ActiveRecord::AttributeMethods::const_defined?(:Serialization) &&
|
112
|
-
ActiveRecord::AttributeMethods::Serialization::const_defined?(:Attribute)
|
113
|
-
# in 3.2 @attributes has a special Attribute struct to help cache both serialized and unserialized forms
|
114
|
-
@attributes[attr_name] = ActiveRecord::AttributeMethods::Serialization::Attribute.new(coder, value, :serialized)
|
115
|
-
elsif ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 1
|
116
|
-
# from 3.1 it has the deserialized form
|
117
|
-
@attributes[attr_name] = coder.load value
|
118
|
-
else
|
119
|
-
# in 2.3 an 3.0, @attributes has the serialized form
|
120
|
-
@attributes[attr_name] = value
|
121
|
-
end
|
122
|
-
else
|
123
|
-
# activerecord 4.1 or earlier, with a regular unserialized column
|
124
|
-
@attributes[attr_name] = value
|
125
|
-
end
|
72
|
+
# activerecord 4.2 or later, which make it easy to replicate the normal typecasting and deserialization logic
|
73
|
+
@attributes.write_from_database(attr_name, value)
|
126
74
|
end
|
127
75
|
end
|
128
76
|
|
@@ -130,39 +78,39 @@ module ColumnsOnDemand
|
|
130
78
|
load_attributes(attr_name.to_s) unless column_loaded?(attr_name.to_s)
|
131
79
|
end
|
132
80
|
|
133
|
-
def
|
134
|
-
column_loaded?(attr_name) &&
|
81
|
+
def changed_in_place?(attr_name)
|
82
|
+
column_loaded?(attr_name) && super(attr_name)
|
135
83
|
end
|
136
84
|
|
137
|
-
def
|
138
|
-
column_loaded?(attr_name) &&
|
85
|
+
def attribute_changed_in_place?(attr_name)
|
86
|
+
column_loaded?(attr_name) && super(attr_name)
|
139
87
|
end
|
140
88
|
|
141
|
-
def
|
89
|
+
def read_attribute(attr_name, &block)
|
142
90
|
ensure_loaded(attr_name)
|
143
|
-
|
91
|
+
super(attr_name, &block)
|
144
92
|
end
|
145
93
|
|
146
|
-
def
|
94
|
+
def read_attribute_before_type_cast(attr_name)
|
147
95
|
ensure_loaded(attr_name)
|
148
|
-
|
96
|
+
super(attr_name)
|
149
97
|
end
|
150
98
|
|
151
|
-
def
|
99
|
+
def _read_attribute(attr_name, &block)
|
152
100
|
ensure_loaded(attr_name)
|
153
|
-
|
101
|
+
super(attr_name, &block)
|
154
102
|
end
|
155
103
|
|
156
|
-
def
|
104
|
+
def missing_attribute(attr_name, *args)
|
157
105
|
if columns_to_load_on_demand.include?(attr_name)
|
158
106
|
load_attributes(attr_name)
|
159
107
|
else
|
160
|
-
|
108
|
+
super(attr_name, *args)
|
161
109
|
end
|
162
110
|
end
|
163
111
|
|
164
|
-
def
|
165
|
-
|
112
|
+
def reload(*args)
|
113
|
+
super(*args).tap do
|
166
114
|
columns_loaded.clear
|
167
115
|
columns_to_load_on_demand.each do |attr_name|
|
168
116
|
if @attributes.respond_to?(:reset)
|
@@ -177,37 +125,16 @@ module ColumnsOnDemand
|
|
177
125
|
end
|
178
126
|
end
|
179
127
|
|
180
|
-
module
|
181
|
-
def
|
182
|
-
if (selects.empty? || selects == [table[Arel.star]] || selects == ['*']) && klass < ColumnsOnDemand::InstanceMethods
|
183
|
-
build_select_without_columns_on_demand(arel, [default_select(true)])
|
184
|
-
else
|
185
|
-
build_select_without_columns_on_demand(arel, selects)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
module RelationMethodsArity1
|
191
|
-
def build_select_with_columns_on_demand(arel)
|
128
|
+
module RelationMethods
|
129
|
+
def build_select(arel)
|
192
130
|
if (select_values.empty? || select_values == [table[Arel.star]] || select_values == ['*']) && klass < ColumnsOnDemand::InstanceMethods
|
193
131
|
arel.project(*arel_columns([default_select(true)]))
|
194
132
|
else
|
195
|
-
|
133
|
+
super(arel)
|
196
134
|
end
|
197
135
|
end
|
198
136
|
end
|
199
137
|
end
|
200
138
|
|
201
139
|
ActiveRecord::Base.send(:extend, ColumnsOnDemand::BaseMethods)
|
202
|
-
|
203
|
-
if ActiveRecord.const_defined?(:Relation)
|
204
|
-
if ActiveRecord::Relation.instance_method(:build_select).arity == 1
|
205
|
-
# 4.2.1 and above
|
206
|
-
ActiveRecord::Relation.send(:include, ColumnsOnDemand::RelationMethodsArity1)
|
207
|
-
else
|
208
|
-
# 4.2.0 and below
|
209
|
-
ActiveRecord::Relation.send(:include, ColumnsOnDemand::RelationMethodsArity2)
|
210
|
-
end
|
211
|
-
|
212
|
-
ActiveRecord::Relation.alias_method_chain :build_select, :columns_on_demand
|
213
|
-
end
|
140
|
+
ActiveRecord::Relation.send(:prepend, ColumnsOnDemand::RelationMethods)
|
@@ -43,7 +43,11 @@ class ColumnsOnDemandTest < ActiveSupport::TestCase
|
|
43
43
|
end
|
44
44
|
|
45
45
|
fixtures :all
|
46
|
-
|
46
|
+
if respond_to?(:use_transactional_tests=)
|
47
|
+
self.use_transactional_tests = true
|
48
|
+
else
|
49
|
+
self.use_transactional_fixtures = true
|
50
|
+
end
|
47
51
|
|
48
52
|
test "it lists explicitly given columns for loading on demand" do
|
49
53
|
assert_equal ["file_data", "processing_log", "original_filename"], Explicit.columns_to_load_on_demand
|
@@ -54,11 +58,11 @@ class ColumnsOnDemandTest < ActiveSupport::TestCase
|
|
54
58
|
end
|
55
59
|
|
56
60
|
test "it selects all the other columns for loading eagerly" do
|
57
|
-
assert_match
|
58
|
-
assert_match
|
61
|
+
assert_match(/\W*id\W*, \W*results\W*, \W*processed_at\W*/, Explicit.default_select(false))
|
62
|
+
assert_match(/\W*explicits\W*.results/, Explicit.default_select(true))
|
59
63
|
|
60
|
-
assert_match
|
61
|
-
assert_match
|
64
|
+
assert_match(/\W*id\W*, \W*original_filename\W*, \W*processed_at\W*/, Implicit.default_select(false))
|
65
|
+
assert_match(/\W*implicits\W*.original_filename/, Implicit.default_select(true))
|
62
66
|
end
|
63
67
|
|
64
68
|
test "it doesn't load the columns_to_load_on_demand straight away when finding the records" do
|
@@ -92,10 +96,10 @@ class ColumnsOnDemandTest < ActiveSupport::TestCase
|
|
92
96
|
|
93
97
|
record = Implicit.first
|
94
98
|
assert_not_loaded record, "file_data"
|
95
|
-
|
99
|
+
assert_nil record.file_data
|
96
100
|
assert_loaded record, "file_data"
|
97
101
|
assert_no_queries do
|
98
|
-
|
102
|
+
assert_nil record.file_data
|
99
103
|
end
|
100
104
|
end
|
101
105
|
|
@@ -156,6 +160,7 @@ class ColumnsOnDemandTest < ActiveSupport::TestCase
|
|
156
160
|
|
157
161
|
assert_not_loaded record, "file_data"
|
158
162
|
assert_equal "New file data", record.file_data
|
163
|
+
assert_not_equal old_object_id, record.file_data.object_id
|
159
164
|
end
|
160
165
|
|
161
166
|
test "it doesn't override custom select() finds" do
|
@@ -194,42 +199,7 @@ class ColumnsOnDemandTest < ActiveSupport::TestCase
|
|
194
199
|
assert_equal "This is the file data!", record.file_data # check it doesn't raise
|
195
200
|
end
|
196
201
|
|
197
|
-
test "it updates the select strings when columns are changed and the column information is reset" do
|
198
|
-
ActiveRecord::Schema.define(:version => 1) do
|
199
|
-
create_table :dummies, :force => true do |t|
|
200
|
-
t.string :some_field
|
201
|
-
t.binary :big_field
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
class Dummy < ActiveRecord::Base
|
206
|
-
columns_on_demand
|
207
|
-
end
|
208
|
-
|
209
|
-
assert_match /\W*id\W*, \W*some_field\W*/, Dummy.default_select(false)
|
210
|
-
|
211
|
-
ActiveRecord::Schema.define(:version => 2) do
|
212
|
-
create_table :dummies, :force => true do |t|
|
213
|
-
t.string :some_field
|
214
|
-
t.binary :big_field
|
215
|
-
t.string :another_field
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
assert_match /\W*id\W*, \W*some_field\W*/, Dummy.default_select(false)
|
220
|
-
Dummy.reset_column_information
|
221
|
-
assert_match /\W*id\W*, \W*some_field\W*, \W*another_field\W*/, Dummy.default_select(false)
|
222
|
-
end
|
223
|
-
|
224
202
|
test "it handles STI models" do
|
225
|
-
ActiveRecord::Schema.define(:version => 1) do
|
226
|
-
create_table :stis, :force => true do |t|
|
227
|
-
t.string :type
|
228
|
-
t.string :some_field
|
229
|
-
t.binary :big_field
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
203
|
class Sti < ActiveRecord::Base
|
234
204
|
columns_on_demand
|
235
205
|
end
|
@@ -238,8 +208,8 @@ class ColumnsOnDemandTest < ActiveSupport::TestCase
|
|
238
208
|
columns_on_demand :some_field
|
239
209
|
end
|
240
210
|
|
241
|
-
assert_match
|
242
|
-
assert_match
|
211
|
+
assert_match(/\W*id\W*, \W*type\W*, \W*some_field\W*/, Sti.default_select(false))
|
212
|
+
assert_match(/\W*id\W*, \W*type\W*, \W*big_field\W*/, StiChild.default_select(false))
|
243
213
|
end
|
244
214
|
|
245
215
|
test "it works on child records loaded from associations" do
|
@@ -322,3 +292,38 @@ class ColumnsOnDemandTest < ActiveSupport::TestCase
|
|
322
292
|
assert_equal select_sql, reference_sql
|
323
293
|
end
|
324
294
|
end
|
295
|
+
|
296
|
+
class ColumnsOnDemandSchemaTest < ActiveSupport::TestCase
|
297
|
+
if respond_to?(:use_transactional_tests=)
|
298
|
+
self.use_transactional_tests = false
|
299
|
+
else
|
300
|
+
self.use_transactional_fixtures = false
|
301
|
+
end
|
302
|
+
|
303
|
+
test "it updates the select strings when columns are changed and the column information is reset" do
|
304
|
+
ActiveRecord::Schema.define(:version => 1) do
|
305
|
+
create_table :dummies, :force => true do |t|
|
306
|
+
t.string :some_field
|
307
|
+
t.binary :big_field
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
class Dummy < ActiveRecord::Base
|
312
|
+
columns_on_demand
|
313
|
+
end
|
314
|
+
|
315
|
+
assert_match(/\W*id\W*, \W*some_field\W*/, Dummy.default_select(false))
|
316
|
+
|
317
|
+
ActiveRecord::Schema.define(:version => 2) do
|
318
|
+
create_table :dummies, :force => true do |t|
|
319
|
+
t.string :some_field
|
320
|
+
t.binary :big_field
|
321
|
+
t.string :another_field
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
assert_match(/\W*id\W*, \W*some_field\W*/, Dummy.default_select(false))
|
326
|
+
Dummy.reset_column_information
|
327
|
+
assert_match(/\W*id\W*, \W*some_field\W*, \W*another_field\W*/, Dummy.default_select(false))
|
328
|
+
end
|
329
|
+
end
|
data/test/schema.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: columns_on_demand
|
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-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: byebug
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
97
111
|
description: |
|
98
112
|
Lazily loads large columns on demand.
|
99
113
|
|
@@ -158,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
172
|
version: '0'
|
159
173
|
requirements: []
|
160
174
|
rubyforge_project:
|
161
|
-
rubygems_version: 2.2
|
175
|
+
rubygems_version: 2.5.2
|
162
176
|
signing_key:
|
163
177
|
specification_version: 4
|
164
178
|
summary: Lazily loads large columns on demand.
|