deep_pluck_with_authorization 1.1.2
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 +9 -0
- data/.rubocop.yml +1228 -0
- data/.travis.yml +25 -0
- data/CHANGELOG.md +47 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/LICENSE.txt +21 -0
- data/README.md +171 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/deep_pluck_with_authorization.gemspec +40 -0
- data/gemfiles/3.2.gemfile +12 -0
- data/gemfiles/4.2.gemfile +12 -0
- data/gemfiles/5.0.gemfile +12 -0
- data/gemfiles/5.1.gemfile +12 -0
- data/gemfiles/5.2.gemfile +12 -0
- data/lib/deep_pluck.rb +28 -0
- data/lib/deep_pluck/data_combiner.rb +40 -0
- data/lib/deep_pluck/model.rb +174 -0
- data/lib/deep_pluck/preloaded_model.rb +14 -0
- data/lib/deep_pluck/version.rb +3 -0
- metadata +159 -0
data/lib/deep_pluck.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'deep_pluck/version'
|
2
|
+
require 'deep_pluck/model'
|
3
|
+
require 'active_record'
|
4
|
+
require 'pluck_all'
|
5
|
+
|
6
|
+
class ActiveRecord::Relation
|
7
|
+
def deep_pluck(*args)
|
8
|
+
DeepPluck::Model.new(self).add(args).load_all
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ActiveRecord::Base
|
13
|
+
def self.deep_pluck(*args)
|
14
|
+
where('').deep_pluck(*args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.visible_for(*)
|
18
|
+
all
|
19
|
+
end
|
20
|
+
|
21
|
+
def deep_pluck(*args)
|
22
|
+
hash_args, other_args = args.partition{|s| s.is_a?(Hash) }
|
23
|
+
preloaded_model = DeepPluck::PreloadedModel.new(self, other_args)
|
24
|
+
model = DeepPluck::Model.new(self.class.where(id: id), preloaded_model: preloaded_model)
|
25
|
+
model.add(*hash_args) if hash_args.any?
|
26
|
+
return model.load_all.first
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module DeepPluck
|
2
|
+
module DataCombiner
|
3
|
+
class << self
|
4
|
+
def combine_data(parent, children, primary_key, column_name, foreign_key, reverse, collection)
|
5
|
+
source = reverse ? parent : children
|
6
|
+
target = !reverse ? parent : children
|
7
|
+
data_hash = make_data_hash(collection, source, primary_key, column_name)
|
8
|
+
assign_values_to_parent(collection, target, data_hash, column_name, foreign_key, reverse: reverse)
|
9
|
+
return children
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def make_data_hash(collection, parent, primary_key, column_name)
|
15
|
+
return parent.map{|s| [s[primary_key], s] }.to_h if !collection
|
16
|
+
hash = {}
|
17
|
+
parent.each do |model_hash|
|
18
|
+
key = model_hash[primary_key]
|
19
|
+
array = (hash[key] ? hash[key][column_name] : []) # share the children if id is duplicated
|
20
|
+
model_hash[column_name] = array
|
21
|
+
hash[key] = model_hash
|
22
|
+
end
|
23
|
+
return hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def assign_values_to_parent(collection, parent, children_hash, column_name, foreign_key, reverse: false)
|
27
|
+
parent.each do |s|
|
28
|
+
next if (id = s[foreign_key]) == nil
|
29
|
+
left = reverse ? children_hash[id] : s
|
30
|
+
right = !reverse ? children_hash[id] : s
|
31
|
+
if collection
|
32
|
+
left[column_name] << right
|
33
|
+
else
|
34
|
+
left[column_name] = right
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'deep_pluck/preloaded_model'
|
2
|
+
require 'deep_pluck/data_combiner'
|
3
|
+
module DeepPluck
|
4
|
+
class Model
|
5
|
+
# ----------------------------------------------------------------
|
6
|
+
# ● Initialize
|
7
|
+
# ----------------------------------------------------------------
|
8
|
+
def initialize(relation, parent_association_key = nil, parent_model = nil, preloaded_model: nil, user: nil)
|
9
|
+
@relation = relation
|
10
|
+
@preloaded_model = preloaded_model
|
11
|
+
@parent_association_key = parent_association_key
|
12
|
+
@parent_model = parent_model
|
13
|
+
@need_columns = (preloaded_model ? preloaded_model.need_columns : [])
|
14
|
+
@associations = {}
|
15
|
+
@user = user
|
16
|
+
end
|
17
|
+
|
18
|
+
# ----------------------------------------------------------------
|
19
|
+
# ● Reader
|
20
|
+
# ----------------------------------------------------------------
|
21
|
+
def get_reflect(association_key)
|
22
|
+
@relation.klass.reflect_on_association(association_key.to_sym) || # add to_sym since rails 3 only support symbol
|
23
|
+
fail(ActiveRecord::ConfigurationError, "ActiveRecord::ConfigurationError: Association named \
|
24
|
+
'#{association_key}' was not found on #{@relation.klass.name}; perhaps you misspelled it?"
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def with_conditions(reflect, relation)
|
29
|
+
options = reflect.options
|
30
|
+
relation = relation.instance_exec(&reflect.scope) if reflect.respond_to?(:scope) and reflect.scope
|
31
|
+
relation = relation.where(options[:conditions]) if options[:conditions]
|
32
|
+
return relation
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_join_table(reflect)
|
36
|
+
options = reflect.options
|
37
|
+
return options[:through] if options[:through]
|
38
|
+
return (options[:join_table] || reflect.send(:derive_join_table)) if reflect.macro == :has_and_belongs_to_many
|
39
|
+
return nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_primary_key(reflect)
|
43
|
+
return (reflect.belongs_to? ? reflect.klass : reflect.active_record).primary_key
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_foreign_key(reflect, reverse: false, with_table_name: false)
|
47
|
+
if reverse and (table_name = get_join_table(reflect)) # reverse = parent
|
48
|
+
key = reflect.chain.last.foreign_key
|
49
|
+
else
|
50
|
+
key = (reflect.belongs_to? == reverse ? get_primary_key(reflect) : reflect.foreign_key)
|
51
|
+
table_name = (reverse ? reflect.klass : reflect.active_record).table_name
|
52
|
+
end
|
53
|
+
return "#{table_name}.#{key}" if with_table_name
|
54
|
+
return key.to_s # key may be symbol if specify foreign_key in association options
|
55
|
+
end
|
56
|
+
|
57
|
+
# ----------------------------------------------------------------
|
58
|
+
# ● Contruction OPs
|
59
|
+
# ----------------------------------------------------------------
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def add_need_column(column)
|
64
|
+
@need_columns << column.to_s
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_association(hash)
|
68
|
+
hash.each do |key, value|
|
69
|
+
model = (@associations[key] ||= Model.new(get_reflect(key).klass.where(''), key, self, user: @user))
|
70
|
+
model.add(value)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
public
|
75
|
+
|
76
|
+
def add(args)
|
77
|
+
return self if args == nil
|
78
|
+
args = [args] if not args.is_a?(Array)
|
79
|
+
args.each do |arg|
|
80
|
+
case arg
|
81
|
+
when Hash ; add_association(arg)
|
82
|
+
else ; add_need_column(arg)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
return self
|
86
|
+
end
|
87
|
+
|
88
|
+
# ----------------------------------------------------------------
|
89
|
+
# ● Load
|
90
|
+
# ----------------------------------------------------------------
|
91
|
+
private
|
92
|
+
|
93
|
+
def do_query(parent, reflect, relation)
|
94
|
+
parent_key = get_foreign_key(reflect)
|
95
|
+
relation_key = get_foreign_key(reflect, reverse: true, with_table_name: true)
|
96
|
+
ids = parent.map{|s| s[parent_key] }
|
97
|
+
ids.uniq!
|
98
|
+
ids.compact!
|
99
|
+
relation = with_conditions(reflect, relation)
|
100
|
+
query = { relation_key => ids }
|
101
|
+
query[reflect.type] = reflect.active_record.to_s if reflect.type
|
102
|
+
return relation.joins(get_join_table(reflect)).visible_for(user: @user).where(query)
|
103
|
+
end
|
104
|
+
|
105
|
+
def set_includes_data(parent, column_name, model)
|
106
|
+
reflect = get_reflect(column_name)
|
107
|
+
reverse = !reflect.belongs_to?
|
108
|
+
foreign_key = get_foreign_key(reflect, reverse: reverse)
|
109
|
+
primary_key = get_primary_key(reflect)
|
110
|
+
children = model.load_data{|relation| do_query(parent, reflect, relation) }
|
111
|
+
# reverse = false: Child.where(:id => parent.pluck(:child_id))
|
112
|
+
# reverse = true : Child.where(:parent_id => parent.pluck(:id))
|
113
|
+
return DataCombiner.combine_data(
|
114
|
+
parent,
|
115
|
+
children,
|
116
|
+
primary_key,
|
117
|
+
column_name,
|
118
|
+
foreign_key,
|
119
|
+
reverse,
|
120
|
+
reflect.collection?,
|
121
|
+
)
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_query_columns
|
125
|
+
if @parent_model
|
126
|
+
parent_reflect = @parent_model.get_reflect(@parent_association_key)
|
127
|
+
prev_need_columns = @parent_model.get_foreign_key(parent_reflect, reverse: true, with_table_name: true)
|
128
|
+
end
|
129
|
+
next_need_columns = @associations.map{|key, _| get_foreign_key(get_reflect(key), with_table_name: true) }.uniq
|
130
|
+
return [*prev_need_columns, *next_need_columns, *@need_columns].uniq(&Helper::TO_KEY_PROC)
|
131
|
+
end
|
132
|
+
|
133
|
+
public
|
134
|
+
|
135
|
+
def load_data
|
136
|
+
columns = get_query_columns
|
137
|
+
key_columns = columns.map(&Helper::TO_KEY_PROC)
|
138
|
+
@relation = yield(@relation) if block_given?
|
139
|
+
@data = @preloaded_model ? [@preloaded_model.get_hash_data(key_columns)] : @relation.pluck_all(*columns)
|
140
|
+
if @data.size != 0
|
141
|
+
# for delete_extra_column_data!
|
142
|
+
@extra_columns = key_columns - @need_columns.map(&Helper::TO_KEY_PROC)
|
143
|
+
@associations.each do |key, model|
|
144
|
+
set_includes_data(@data, key, model)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
return @data
|
148
|
+
end
|
149
|
+
|
150
|
+
def load_all
|
151
|
+
load_data
|
152
|
+
delete_extra_column_data!
|
153
|
+
return @data
|
154
|
+
end
|
155
|
+
|
156
|
+
def delete_extra_column_data!
|
157
|
+
return if @data.blank?
|
158
|
+
@data.each{|s| s.except!(*@extra_columns) }
|
159
|
+
@associations.each{|_, model| model.delete_extra_column_data! }
|
160
|
+
end
|
161
|
+
|
162
|
+
# ----------------------------------------------------------------
|
163
|
+
# ● Helper methods
|
164
|
+
# ----------------------------------------------------------------
|
165
|
+
module Helper
|
166
|
+
TO_KEY_PROC = proc{|s| Helper.column_to_key(s) }
|
167
|
+
def self.column_to_key(key) # user_achievements.user_id => user_id
|
168
|
+
key = key[/(\w+)[^\w]*\z/]
|
169
|
+
key.gsub!(/[^\w]+/, '')
|
170
|
+
return key
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module DeepPluck
|
2
|
+
class PreloadedModel
|
3
|
+
attr_reader :need_columns
|
4
|
+
|
5
|
+
def initialize(active_model, need_columns)
|
6
|
+
@active_model = active_model
|
7
|
+
@need_columns = need_columns
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_hash_data(extra_columns)
|
11
|
+
@active_model.as_json(root: false, only: @need_columns + extra_columns)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deep_pluck_with_authorization
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Poilon
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-02-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.17'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.x
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.17'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.x
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '12.0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '12.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: sqlite3
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.3'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.3'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: minitest
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '5.0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '5.0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: activerecord
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3'
|
82
|
+
type: :runtime
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '3'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: pluck_all
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: 1.2.3
|
96
|
+
type: :runtime
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.2.3
|
103
|
+
description: 'Use deep_pluck as a shortcut to select one or more attributes and include
|
104
|
+
associated models without loading a bunch of records. And DRY up your code when
|
105
|
+
using #as_json. Added a filter for graphql_rails_api gem'
|
106
|
+
email:
|
107
|
+
- poilon@gmail.com
|
108
|
+
executables: []
|
109
|
+
extensions: []
|
110
|
+
extra_rdoc_files: []
|
111
|
+
files:
|
112
|
+
- ".gitignore"
|
113
|
+
- ".rubocop.yml"
|
114
|
+
- ".travis.yml"
|
115
|
+
- CHANGELOG.md
|
116
|
+
- CODE_OF_CONDUCT.md
|
117
|
+
- LICENSE.txt
|
118
|
+
- README.md
|
119
|
+
- Rakefile
|
120
|
+
- bin/console
|
121
|
+
- bin/setup
|
122
|
+
- deep_pluck_with_authorization.gemspec
|
123
|
+
- gemfiles/3.2.gemfile
|
124
|
+
- gemfiles/4.2.gemfile
|
125
|
+
- gemfiles/5.0.gemfile
|
126
|
+
- gemfiles/5.1.gemfile
|
127
|
+
- gemfiles/5.2.gemfile
|
128
|
+
- lib/deep_pluck.rb
|
129
|
+
- lib/deep_pluck/data_combiner.rb
|
130
|
+
- lib/deep_pluck/model.rb
|
131
|
+
- lib/deep_pluck/preloaded_model.rb
|
132
|
+
- lib/deep_pluck/version.rb
|
133
|
+
homepage: https://github.com/poilon/deep_pluck
|
134
|
+
licenses:
|
135
|
+
- MIT
|
136
|
+
metadata: {}
|
137
|
+
post_install_message:
|
138
|
+
rdoc_options: []
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
requirements: []
|
152
|
+
rubyforge_project:
|
153
|
+
rubygems_version: 2.7.6
|
154
|
+
signing_key:
|
155
|
+
specification_version: 4
|
156
|
+
summary: Use deep_pluck as a shortcut to select one or more attributes and include
|
157
|
+
associated models without loading a bunch of records. Added a filter for graphql_rails_api
|
158
|
+
gem.
|
159
|
+
test_files: []
|