better_pluck 0.8.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 +7 -0
- data/.gitignore +10 -0
- data/Gemfile +3 -0
- data/README.md +74 -0
- data/better_pluck.gemspec +29 -0
- data/lib/better_pluck/pluck_with_methods.rb +186 -0
- data/lib/better_pluck/version.rb +3 -0
- data/lib/better_pluck.rb +9 -0
- metadata +143 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ab797655791d6f81727b4da00bd628a89da68b10343f9c50303b39f1651b6592
|
|
4
|
+
data.tar.gz: 274e7f8dca7d9b9ae5c5e1b0590d1399b9e6144a81e2b8ceaac5d0ec45d91603
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: a1a031b47a3322543f3534e6afffcb2a9c1e2f8e05b80dab5cbbd7572e9e9edfcb8fec66a081ca9436029fd3111674ab142e0fe3bcb4f2940d84abf310f2f311
|
|
7
|
+
data.tar.gz: 9b66b44a7c09a438a57d33e7afc7f1170d5c453d1b0031943fcc7641d388d8a8602ccd9ef35130369eb52089491d7bc0e22bfab2625324d0619e527d5396f7af
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# BetterPluck
|
|
2
|
+
|
|
3
|
+
`BetterPluck` adds `pluck_with_methods` and `pluck_with_display_name` to ActiveRecord. These methods allow you to pluck not only database columns but also virtual methods and association data, returning lightweight `Struct` objects instead of heavy ActiveRecord instances.
|
|
4
|
+
|
|
5
|
+
This approach can reduce memory usage by up to 3-20x compared to loading full ActiveRecord objects.
|
|
6
|
+
|
|
7
|
+
Primary purpose: to be used in huge form dropdowns for select inputs.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Add this line to your application's Gemfile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
gem 'better_pluck', github: "NazarK/better_pluck"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
And then execute:
|
|
18
|
+
|
|
19
|
+
$ bundle install
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Basic usage
|
|
24
|
+
|
|
25
|
+
You can pluck database columns and instance methods:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
# Assuming Article has a method :name_on_site
|
|
29
|
+
articles = Article.pluck_with_methods(:id, :name, :email, :name_on_site)
|
|
30
|
+
|
|
31
|
+
articles.first.id # => 1
|
|
32
|
+
articles.first.name_on_site # => "My Article" (virtual method)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Associations
|
|
36
|
+
|
|
37
|
+
You can also pluck data from nested associations:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
articles = Article.pluck_with_methods(
|
|
41
|
+
:id,
|
|
42
|
+
:name,
|
|
43
|
+
author: [:name, :email, :display_name]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
articles.first.author.email # => "author@example.com"
|
|
47
|
+
articles.first.author.display_name # => "John Doe"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Pluck with Display Name
|
|
51
|
+
|
|
52
|
+
A convenience method specifically for dropdown lists and collections:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
# Automatically includes :display_name for the model and associations
|
|
56
|
+
articles = Article.pluck_with_display_name(:id, :name, author: [:name, :email])
|
|
57
|
+
|
|
58
|
+
articles.first.display_name # => "Article Name"
|
|
59
|
+
articles.first.author.display_name # => "Author Name <author@example.com>"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## How it works
|
|
63
|
+
|
|
64
|
+
1. **Parsing**: It parses the requested fields to distinguish between database columns, methods, and associations.
|
|
65
|
+
2. **Joining**: It automatically applies `left_joins` for requested associations.
|
|
66
|
+
3. **Plucking**: It uses the standard ActiveRecord `pluck` to fetch only the required database columns.
|
|
67
|
+
4. **Struct Generation**: It creates a `Struct` class (cached for performance) and injects the source code of requested instance methods into it.
|
|
68
|
+
5. **Instantiation**: It maps the raw data into these `Struct` objects.
|
|
69
|
+
|
|
70
|
+
## Requirements
|
|
71
|
+
|
|
72
|
+
* ActiveRecord >= 6.0
|
|
73
|
+
* method_source
|
|
74
|
+
* concurrent-ruby
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
|
+
require "better_pluck/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "better_pluck"
|
|
7
|
+
spec.version = BetterPluck::VERSION
|
|
8
|
+
spec.authors = ["NazarK"]
|
|
9
|
+
spec.email = ["nazar.kuliev@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Pluck with methods for ActiveRecord"
|
|
12
|
+
spec.description = "Convert arrays of AR objects to Struct objects including virtual methods."
|
|
13
|
+
spec.homepage = "https://github.com/NazarK/better_pluck"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
17
|
+
spec.bindir = "exe"
|
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
|
+
spec.require_paths = ["lib"]
|
|
20
|
+
spec.required_ruby_version = ">= 2.7.0"
|
|
21
|
+
|
|
22
|
+
spec.add_dependency "activerecord", ">= 6.0", "< 9.0"
|
|
23
|
+
spec.add_dependency "activesupport", ">= 6.0", "< 9.0"
|
|
24
|
+
spec.add_dependency "concurrent-ruby", "~> 1.0"
|
|
25
|
+
spec.add_dependency "method_source", "~> 1.0"
|
|
26
|
+
|
|
27
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
|
28
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
29
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# this module is supposed to be included in ActiveRecord descendants
|
|
2
|
+
# to reduce ram usage by converting arrays of AR objects to Struct objects
|
|
3
|
+
# in particular for dropdown lists (in f.input collection: Article.pluck_with_display_name(:id,:name,:email), as: :select)
|
|
4
|
+
# also takes associations display_name from their ActiveRecord class
|
|
5
|
+
# usage: zones = Zone.pluck_with_display_name(:id,:name, partner: [:имя,:email], location: [:name])
|
|
6
|
+
# zones.first.display_name, zones.first.partner.display_name
|
|
7
|
+
|
|
8
|
+
#pluck_with_methods method also available where you can list any fields and methods
|
|
9
|
+
#example: articles = Article.pluck_with_methods(:id, :name, :email, :name_on_site, author: [:name, :email,:display_name])
|
|
10
|
+
#
|
|
11
|
+
#in returned result you can use:
|
|
12
|
+
#article.first.name_on_site, article.first.author.email, articles.first.author.display_name etc.
|
|
13
|
+
#
|
|
14
|
+
#returned structure is much cheaper (in terms of memory usage) than ActiveRecord objects, about 3 times less memory usage
|
|
15
|
+
|
|
16
|
+
#NK 2026-10-13, done with Gemini AI
|
|
17
|
+
|
|
18
|
+
module BetterPluck::PluckWithMethods
|
|
19
|
+
extend ActiveSupport::Concern
|
|
20
|
+
|
|
21
|
+
# Use Concurrent::Map for thread-safe caching across multiple Puma threads
|
|
22
|
+
STRUCT_CLASSES = Concurrent::Map.new
|
|
23
|
+
|
|
24
|
+
class_methods do
|
|
25
|
+
|
|
26
|
+
# usage: zones = Zone.pluck_with_methods(:id, :name, :display_name, partner: [:имя, :email, :display_name], location: [:name, :display_name])
|
|
27
|
+
def pluck_with_methods(*fields_and_methods)
|
|
28
|
+
require "method_source" unless defined?(MethodSource)
|
|
29
|
+
|
|
30
|
+
metadata = _pluck_methods_parse_fields(self, fields_and_methods)
|
|
31
|
+
pluck_columns = []
|
|
32
|
+
_pluck_methods_build_columns(self, metadata, pluck_columns, self.table_name)
|
|
33
|
+
|
|
34
|
+
joins_arg = _pluck_methods_build_joins(metadata)
|
|
35
|
+
|
|
36
|
+
query = joins_arg.present? ? left_joins(joins_arg) : all
|
|
37
|
+
raw_rows = query.pluck(*pluck_columns)
|
|
38
|
+
|
|
39
|
+
# Root struct class
|
|
40
|
+
root_struct_klass = _pluck_methods_get_struct(metadata)
|
|
41
|
+
|
|
42
|
+
raw_rows.map do |row|
|
|
43
|
+
row_data = row.is_a?(Array) ? row.dup : [row]
|
|
44
|
+
_pluck_methods_instantiate(root_struct_klass, metadata, row_data)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# usage: articles = Article.pluck_with_display_name(:id,:name, author: [:name,:email])
|
|
49
|
+
# articles.first.display_name, articles.first.author.display_name
|
|
50
|
+
def pluck_with_display_name(*fields_and_methods)
|
|
51
|
+
fields_and_methods << :display_name unless fields_and_methods.include?(:display_name)
|
|
52
|
+
|
|
53
|
+
processed_params = fields_and_methods.map do |arg|
|
|
54
|
+
if arg.is_a?(Hash)
|
|
55
|
+
arg.each_with_object({}) do |(assoc, fields), hash|
|
|
56
|
+
fields = Array(fields).dup
|
|
57
|
+
fields << :display_name unless fields.include?(:display_name)
|
|
58
|
+
hash[assoc] = fields
|
|
59
|
+
end
|
|
60
|
+
else
|
|
61
|
+
arg
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
pluck_with_methods(*processed_params)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def _pluck_methods_parse_fields(klass, fields)
|
|
71
|
+
db_columns = []
|
|
72
|
+
methods = {}
|
|
73
|
+
associations = {}
|
|
74
|
+
all_fields = []
|
|
75
|
+
|
|
76
|
+
available_columns = klass.column_names.map(&:to_sym) rescue []
|
|
77
|
+
|
|
78
|
+
# Sort fields to ensure deterministic cache key and struct structure
|
|
79
|
+
# Symbols first, then hashes (associations) sorted by their first key
|
|
80
|
+
sorted_fields = Array(fields).sort_by { |f| f.is_a?(Hash) ? [1, f.keys.first.to_s] : [0, f.to_s] }
|
|
81
|
+
|
|
82
|
+
sorted_fields.each do |item|
|
|
83
|
+
if item.is_a?(Hash)
|
|
84
|
+
# Sort association keys within the hash
|
|
85
|
+
item.keys.sort_by(&:to_s).each do |assoc_name|
|
|
86
|
+
assoc_fields = item[assoc_name]
|
|
87
|
+
assoc_reflection = klass.reflect_on_association(assoc_name)
|
|
88
|
+
if assoc_reflection
|
|
89
|
+
associations[assoc_name.to_sym] = _pluck_methods_parse_fields(assoc_reflection.klass, Array(assoc_fields))
|
|
90
|
+
all_fields << assoc_name.to_sym
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
else
|
|
94
|
+
name = item.to_sym
|
|
95
|
+
all_fields << name
|
|
96
|
+
if available_columns.include?(name)
|
|
97
|
+
db_columns << name
|
|
98
|
+
else
|
|
99
|
+
begin
|
|
100
|
+
source = klass.instance_method(name).source
|
|
101
|
+
methods[name] = source
|
|
102
|
+
rescue
|
|
103
|
+
# Treat as db column if not a method or source not found
|
|
104
|
+
db_columns << name
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
klass_name: klass.name,
|
|
112
|
+
db_columns: db_columns,
|
|
113
|
+
methods: methods,
|
|
114
|
+
associations: associations,
|
|
115
|
+
all_fields: all_fields,
|
|
116
|
+
total_db_columns: db_columns.size + associations.values.sum { |a| a[:total_db_columns] }
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def _pluck_methods_build_columns(klass, metadata, list, table_alias)
|
|
121
|
+
metadata[:db_columns].each do |col|
|
|
122
|
+
list << Arel.sql("#{klass.connection.quote_table_name(table_alias)}.\#{col}")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
metadata[:associations].each do |assoc_name, sub_metadata|
|
|
126
|
+
assoc_reflection = klass.reflect_on_association(assoc_name)
|
|
127
|
+
# ARs left_joins usually uses the table name for the join alias unless there is a collision
|
|
128
|
+
_pluck_methods_build_columns(assoc_reflection.klass, sub_metadata, list, assoc_reflection.table_name)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def _pluck_methods_build_joins(metadata)
|
|
133
|
+
return nil if metadata[:associations].empty?
|
|
134
|
+
|
|
135
|
+
joins = metadata[:associations].map do |assoc_name, sub_metadata|
|
|
136
|
+
sub_joins = _pluck_methods_build_joins(sub_metadata)
|
|
137
|
+
if sub_joins
|
|
138
|
+
{ assoc_name => sub_joins }
|
|
139
|
+
else
|
|
140
|
+
assoc_name
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
joins.size == 1 ? joins.first : joins
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def _pluck_methods_get_struct(metadata)
|
|
148
|
+
metadata[:struct_klass] ||= begin
|
|
149
|
+
# 1st list: database fields and associations
|
|
150
|
+
# 2nd list: virtual methods (copied from ActiveRecord)
|
|
151
|
+
db_and_assoc = metadata[:all_fields] - metadata[:methods].keys
|
|
152
|
+
cache_key = [metadata[:klass_name], db_and_assoc, metadata[:methods].keys]
|
|
153
|
+
|
|
154
|
+
STRUCT_CLASSES.compute_if_absent(cache_key) do
|
|
155
|
+
struct_klass = Struct.new(*metadata[:all_fields])
|
|
156
|
+
metadata[:methods].each do |name, source|
|
|
157
|
+
struct_klass.class_eval(source)
|
|
158
|
+
end
|
|
159
|
+
struct_klass
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def _pluck_methods_instantiate(struct_klass, metadata, row_data)
|
|
165
|
+
args = metadata[:all_fields].map do |field|
|
|
166
|
+
if metadata[:db_columns].include?(field)
|
|
167
|
+
row_data.shift
|
|
168
|
+
elsif (sub_metadata = metadata[:associations][field])
|
|
169
|
+
sub_struct_klass = _pluck_methods_get_struct(sub_metadata)
|
|
170
|
+
sub_col_count = sub_metadata[:total_db_columns]
|
|
171
|
+
|
|
172
|
+
if row_data[0...sub_col_count].all?(&:nil?)
|
|
173
|
+
row_data.shift(sub_col_count)
|
|
174
|
+
nil
|
|
175
|
+
else
|
|
176
|
+
_pluck_methods_instantiate(sub_struct_klass, sub_metadata, row_data)
|
|
177
|
+
end
|
|
178
|
+
else
|
|
179
|
+
nil # Method field
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
struct_klass.new(*args)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
end
|
|
186
|
+
end
|
data/lib/better_pluck.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: better_pluck
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.8.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- NazarK
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '6.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: activesupport
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '6.0'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '9.0'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '6.0'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '9.0'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: concurrent-ruby
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - "~>"
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '1.0'
|
|
59
|
+
type: :runtime
|
|
60
|
+
prerelease: false
|
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - "~>"
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '1.0'
|
|
66
|
+
- !ruby/object:Gem::Dependency
|
|
67
|
+
name: method_source
|
|
68
|
+
requirement: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - "~>"
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '1.0'
|
|
73
|
+
type: :runtime
|
|
74
|
+
prerelease: false
|
|
75
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - "~>"
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '1.0'
|
|
80
|
+
- !ruby/object:Gem::Dependency
|
|
81
|
+
name: bundler
|
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - "~>"
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '2.0'
|
|
87
|
+
type: :development
|
|
88
|
+
prerelease: false
|
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - "~>"
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '2.0'
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: rake
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - "~>"
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '13.0'
|
|
101
|
+
type: :development
|
|
102
|
+
prerelease: false
|
|
103
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - "~>"
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '13.0'
|
|
108
|
+
description: Convert arrays of AR objects to Struct objects including virtual methods.
|
|
109
|
+
email:
|
|
110
|
+
- nazar.kuliev@gmail.com
|
|
111
|
+
executables: []
|
|
112
|
+
extensions: []
|
|
113
|
+
extra_rdoc_files: []
|
|
114
|
+
files:
|
|
115
|
+
- ".gitignore"
|
|
116
|
+
- Gemfile
|
|
117
|
+
- README.md
|
|
118
|
+
- better_pluck.gemspec
|
|
119
|
+
- lib/better_pluck.rb
|
|
120
|
+
- lib/better_pluck/pluck_with_methods.rb
|
|
121
|
+
- lib/better_pluck/version.rb
|
|
122
|
+
homepage: https://github.com/NazarK/better_pluck
|
|
123
|
+
licenses:
|
|
124
|
+
- MIT
|
|
125
|
+
metadata: {}
|
|
126
|
+
rdoc_options: []
|
|
127
|
+
require_paths:
|
|
128
|
+
- lib
|
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
130
|
+
requirements:
|
|
131
|
+
- - ">="
|
|
132
|
+
- !ruby/object:Gem::Version
|
|
133
|
+
version: 2.7.0
|
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
|
+
requirements:
|
|
136
|
+
- - ">="
|
|
137
|
+
- !ruby/object:Gem::Version
|
|
138
|
+
version: '0'
|
|
139
|
+
requirements: []
|
|
140
|
+
rubygems_version: 3.6.9
|
|
141
|
+
specification_version: 4
|
|
142
|
+
summary: Pluck with methods for ActiveRecord
|
|
143
|
+
test_files: []
|