preload_pluck 0.1.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 +7 -0
- data/LICENSE +9 -0
- data/README.md +49 -0
- data/lib/preload_pluck/version.rb +3 -0
- data/lib/preload_pluck.rb +118 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2a3f1d75e72b1ac020ff2482bcd6ca4f6e6567ef
|
4
|
+
data.tar.gz: 6b6aa4f06b7d7cb0acd732829053e182751d35ae
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a0716aafcb1a5bc1bd95dad6e07b7ec14004c1d38fd56126e6c6601da3b0609964525f0cb52be4a2714274b45ec9474d81ab718f5e74c7dedd3e3c2b99e49df2
|
7
|
+
data.tar.gz: 366240a3423b5a52bd4b6c60dd587a218a54e49f7b1999d5891a328268747026dc00abbfb8e629e14b469445bff834d82b07ef136f38618af7334dacbefe5102
|
data/LICENSE
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Copyright Assetricity, LLC
|
2
|
+
|
3
|
+
The MIT License (MIT)
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
|
+
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Preload Pluck
|
2
|
+
|
3
|
+
[](https://travis-ci.org/assetricity/preload_pluck)
|
4
|
+
[](https://gemnasium.com/assetricity/preload_pluck)
|
5
|
+
|
6
|
+
Adds a `preload_pluck` method to ActiveRecord that allows querying using Rails 4 eager loading-style for joined tables (`preload`), and returns a 2-dimensional array without ActiveRecord model creation overhead (`pluck`).
|
7
|
+
|
8
|
+
Note: Preload Pluck may not always increase query performance - always benchmark with your own queries and production data.
|
9
|
+
|
10
|
+
## Install
|
11
|
+
|
12
|
+
Add to the preload_pluck gem to your Gemfile:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
gem 'preload_pluck'
|
16
|
+
```
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
Call `preload_pluck` after any SQL conditions (e.g. where clauses, scopes, orders, limits) have been applied and pass immediate attributes or traverse nested `belongs_to` associations.
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
Comment.order(:created_at).preload_pluck(:text, 'user.name')
|
24
|
+
```
|
25
|
+
|
26
|
+
See `spec/preload_pluck_spec.rb` for more examples.
|
27
|
+
|
28
|
+
## Running Tests
|
29
|
+
|
30
|
+
SQLite must be installed before running tests.
|
31
|
+
|
32
|
+
To run tests:
|
33
|
+
|
34
|
+
```
|
35
|
+
bundle install
|
36
|
+
rspec spec
|
37
|
+
```
|
38
|
+
|
39
|
+
By default, performance tests are disabled as it takes several minutes to insert data. To run performance tests:
|
40
|
+
|
41
|
+
```
|
42
|
+
rspec spec --tag performance
|
43
|
+
```
|
44
|
+
|
45
|
+
## License
|
46
|
+
|
47
|
+
Copyright [Assetricity, LLC](http://assetricity.com)
|
48
|
+
|
49
|
+
Preload Pluck is released under the MIT License. See [LICENSE](https://github.com/assetricity/preload_pluck/blob/master/LICENSE) for details.
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'preload_pluck/version'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module PreloadPluck
|
5
|
+
class Field < Struct.new(:base_class, :path)
|
6
|
+
def nested?(level)
|
7
|
+
level + 2 <= path.length
|
8
|
+
end
|
9
|
+
|
10
|
+
def level?(level)
|
11
|
+
!!path[level]
|
12
|
+
end
|
13
|
+
|
14
|
+
def path_upto(level)
|
15
|
+
path[0..level].join('.')
|
16
|
+
end
|
17
|
+
|
18
|
+
def assoc(level)
|
19
|
+
return nil if level >= path.length
|
20
|
+
current_class = base_class
|
21
|
+
assoc = nil
|
22
|
+
(level + 1).times do |l|
|
23
|
+
assoc = current_class.reflect_on_association(path[l])
|
24
|
+
raise 'preload_pluck only supports belongs_to associations' unless assoc.macro == :belongs_to
|
25
|
+
current_class = assoc.class_name.constantize
|
26
|
+
end
|
27
|
+
assoc
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def preload_pluck(*args)
|
32
|
+
fields = args.map {|arg| Field.new(self, arg.to_s.split('.'))}
|
33
|
+
|
34
|
+
plucked_cols = fields.map do |field|
|
35
|
+
if field.nested?(0)
|
36
|
+
field.assoc(0).foreign_key
|
37
|
+
else
|
38
|
+
field.path.last
|
39
|
+
end
|
40
|
+
end.uniq
|
41
|
+
data = pluck(*plucked_cols)
|
42
|
+
if fields.length == 1
|
43
|
+
# Pluck returns a flat array if only one value, so use a consistent structure if there is one or multiple fields
|
44
|
+
data.map! {|val| [val]}
|
45
|
+
end
|
46
|
+
data = __preload_pluck_to_attrs(data, plucked_cols)
|
47
|
+
|
48
|
+
# A cache of records that we populate then join to later based on foreign keys
|
49
|
+
joined_data = {}
|
50
|
+
|
51
|
+
# Incrementally process nested fields by level
|
52
|
+
max_level = fields.map {|f| f.path.length - 1}.max
|
53
|
+
max_level.times do |level|
|
54
|
+
fields.select {|f| f.nested?(level)}
|
55
|
+
.group_by {|f| f.path_upto(level)}
|
56
|
+
.each do |current_path, group|
|
57
|
+
# Just use the first item - could use any item in the group
|
58
|
+
assoc = group.first.assoc(level)
|
59
|
+
klass = assoc.class_name.constantize
|
60
|
+
|
61
|
+
# List of ids that are related to the previous objects (the IN clause in SQL preload statement)
|
62
|
+
if level == 0 # Level 0 is different as data is stored in a different structure
|
63
|
+
collection = data
|
64
|
+
else
|
65
|
+
prev_path = group.first.path_upto(level - 1)
|
66
|
+
collection = joined_data[prev_path].values
|
67
|
+
end
|
68
|
+
ids = collection.map {|a| a[assoc.foreign_key]}.uniq
|
69
|
+
|
70
|
+
# Select id and other fields at the next level
|
71
|
+
cols = group.map do |f|
|
72
|
+
if f.nested?(level + 1)
|
73
|
+
f.assoc(level + 1).foreign_key
|
74
|
+
else
|
75
|
+
f.path[level + 1]
|
76
|
+
end
|
77
|
+
end.uniq
|
78
|
+
joined_plucked_cols = [klass.primary_key, *cols]
|
79
|
+
joined = klass.where(klass.primary_key => ids).pluck(*joined_plucked_cols)
|
80
|
+
attrs = __preload_pluck_to_attrs(joined, joined_plucked_cols)
|
81
|
+
|
82
|
+
# Index to quickly search on id
|
83
|
+
joined_data[current_path] = attrs.index_by {|a| a[klass.primary_key]}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
data.map do |attr|
|
88
|
+
fields.map do |field|
|
89
|
+
if field.nested?(0)
|
90
|
+
assoc = field.assoc(0)
|
91
|
+
val = attr[assoc.foreign_key]
|
92
|
+
(field.path.length - 1).times do |level|
|
93
|
+
current_path = field.path_upto(level)
|
94
|
+
if field.nested?(level + 1)
|
95
|
+
col = field.assoc(level + 1).foreign_key
|
96
|
+
else
|
97
|
+
col = field.path.last
|
98
|
+
end
|
99
|
+
val = joined_data[current_path][val]
|
100
|
+
val = val[col] if val
|
101
|
+
end
|
102
|
+
val
|
103
|
+
else
|
104
|
+
attr[field.path.last]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def __preload_pluck_to_attrs(array, column_names)
|
111
|
+
array.map do |item|
|
112
|
+
pairs = item.map.with_index {|element, index| [column_names[index], element]}.flatten
|
113
|
+
Hash[*pairs]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
ActiveRecord::Base.extend(PreloadPluck)
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: preload_pluck
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Assetricity
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-22 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.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.2.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activerecord-import
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.7.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.7.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: factory_girl
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 4.5.0
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 4.5.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 10.4.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 10.4.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.2.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.2.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sqlite3
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.3.10
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.3.10
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: database_cleaner
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.4.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.4.0
|
111
|
+
description: Adds a preload_pluck method to ActiveRecord that allows querying using
|
112
|
+
Rails 4 preload-style eager loading, and return a 2-dimensional array without ActiveRecord
|
113
|
+
model creation overhead.
|
114
|
+
email:
|
115
|
+
- info@assetricity.com
|
116
|
+
executables: []
|
117
|
+
extensions: []
|
118
|
+
extra_rdoc_files: []
|
119
|
+
files:
|
120
|
+
- lib/preload_pluck/version.rb
|
121
|
+
- lib/preload_pluck.rb
|
122
|
+
- LICENSE
|
123
|
+
- README.md
|
124
|
+
homepage: https://github.com/avinmathew/preload_pluck
|
125
|
+
licenses: []
|
126
|
+
metadata: {}
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - '>='
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - '>='
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 2.0.15
|
144
|
+
signing_key:
|
145
|
+
specification_version: 4
|
146
|
+
summary: Efficiently load data into a 2-dimensional array without ActiveRecord model
|
147
|
+
creation overhead.
|
148
|
+
test_files: []
|