Module: PreloadPluck
- Defined in:
- lib/preload_pluck.rb,
lib/preload_pluck/version.rb
Defined Under Namespace
Classes: Field
Constant Summary collapse
- VERSION =
'0.3.0'
Instance Method Summary collapse
- #__preload_pluck_to_header_lookup(array) ⇒ Object
-
#preload_pluck(*args) ⇒ Array<Array>
Return a 2-dimensional array of values where columns correspond to supplied arguments.
Instance Method Details
#__preload_pluck_to_header_lookup(array) ⇒ Object
143 144 145 |
# File 'lib/preload_pluck.rb', line 143 def __preload_pluck_to_header_lookup(array) Hash[array.map.with_index {|x, i| [x, i]}] end |
#preload_pluck(*args) ⇒ Array<Array>
Return a 2-dimensional array of values where columns correspond to supplied arguments. Data from associations is eager loaded.
Attributes on the current model can be supplied by name:
Comment.preload_pluck(:title, :text)
Nested attributes should be separated by a period:
Comment.preload_pluck('post.title', 'post.text')
Both immediate and nested attributes can be mixed:
Comment.preload_pluck(:title, :text, 'post.title', 'post.text')
Any SQL conditions should be set before ‘preload_pluck` is called:
Comment.order(:created_at)
.joins(:user)
.where(user: {name: 'Alice'))
.preload_pluck(:title, :text, 'post.title', 'post.text')
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/preload_pluck.rb', line 55 def preload_pluck(*args) fields = args.map {|arg| Field.new(self, arg.to_s.split('.'))} plucked_cols = fields.map do |field| if field.nested?(0) field.assoc(0).foreign_key else field.path.last end end.uniq data = pluck(*plucked_cols) if fields.length == 1 # Pluck returns a flat array if only one value, so use a consistent structure if there is one or multiple fields data.map! {|val| [val]} end data_headers = __preload_pluck_to_header_lookup(plucked_cols) # A cache of records that we populate then join to later based on foreign keys nested_data = {} # Incrementally process nested fields by level max_level = fields.map {|f| f.path.length - 1}.max max_level.times do |level| fields.select {|f| f.nested?(level)} .group_by {|f| f.path_upto(level)} .each do |current_path, group| # Just use the first item - could use any item in the group assoc = group.first.assoc(level) klass = assoc.class_name.constantize # List of ids that are related to the previous objects (the IN clause in SQL preload statement) if level == 0 # Level 0 is different as data is stored in a different structure index = data_headers[assoc.foreign_key] collection = data else prev_path = group.first.path_upto(level - 1) index = nested_data[prev_path][:header][assoc.foreign_key] collection = nested_data[prev_path][:data].values end ids = collection.map {|d| d[index]}.uniq # Select id and other fields at the next level cols = group.map do |f| if f.nested?(level + 1) f.assoc(level + 1).foreign_key else f.path[level + 1] end end.uniq # If id is specified by user, we need to make this list unique plucked_cols = [klass.primary_key, *cols].uniq indexed_data = klass.where(klass.primary_key => ids) .pluck(*plucked_cols) .index_by {|d| d[0]} # Index to quickly search on id nested_data[current_path] = { header: __preload_pluck_to_header_lookup(plucked_cols), data: indexed_data } end end data.map do |attr| fields.map do |field| if field.nested?(0) assoc = field.assoc(0) val = attr[data_headers[assoc.foreign_key]] (field.path.length - 1).times do |level| current_path = field.path_upto(level) if field.nested?(level + 1) col = field.assoc(level + 1).foreign_key else col = field.path.last end current_data = nested_data[current_path] current_row = current_data[:data][val] if current_row index = current_data[:header][col] val = current_row[index] end end val else attr[data_headers[field.path.last]] end end end end |