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.
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pluck_all.gemspec
4
+
5
+ gem 'activerecord', '~> 5.2.0'
6
+ gem 'pluck_all', '~> 1.2.2'
7
+
8
+ group :test do
9
+ gem 'simplecov'
10
+ end
11
+
12
+ gemspec path: '../'
@@ -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
@@ -0,0 +1,3 @@
1
+ module DeepPluck
2
+ VERSION = '1.1.2'
3
+ 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: []