rails_select_on_includes 0.4.13 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c852c3d1442a00364c772c952eb8f3d830c06a2e
4
- data.tar.gz: f82498a6fec69c8edd50051c3c4ada20dad2c2a6
3
+ metadata.gz: fb99c7d08c8db00bef1394aac2ac055df1707be5
4
+ data.tar.gz: 02591eb16f17bb61ba429b4a6d4eb6fad665bfa3
5
5
  SHA512:
6
- metadata.gz: c358688dc0abf1e5fedd3936fbd8cdb0d6531738ecc6a38e84a7449e8904d569794c93a2bb0ca3379033dc692bd6bb22b0da79f3257ed44a7b9979f4ddb1f383
7
- data.tar.gz: 81677479752a34e29db8fbb6beeb5c80b6e63f8d6f5996711c4f2820c5ec95c829ee0700a95e8f6bf6b07012849487e62776996b94d07fcf2cbd36218f6b8898
6
+ metadata.gz: 7639db4cbef9c67f0f5f04a5034709d2317dda111661f0519c5e5baefc60089edae19d32f844ec7d4293d0f826d878d9d2be3168625709aded1ddd7b8cc90405
7
+ data.tar.gz: c07bd198ed1cd3eb81d65bcdaa5ddcc25ebc541a505511941ab5ea0cfbdde981e3bcdcbcdde904eae7ac0e6a4696e86dab91b0550068db6793b441ea190446c2
data/README.md CHANGED
@@ -1,15 +1,9 @@
1
- # New Features
2
- Selected virtual attributes will be now typecasted as usual attributes
3
-
4
- #Rails version
5
- Supports rails 4.x and rails 5 now!
6
-
7
1
  # RailsSelectOnIncludes
8
2
 
9
- This gem solves issue in rails: https://github.com/rails/rails/issues/15185 for base_class.
3
+ This gem solves issue in rails: https://github.com/rails/rails/issues/15185 for base_class.
10
4
 
11
5
  It was impossible to select virtual attributes to object from its relations or any other way
12
- when using includes and where ( actually when includes becomes eager_load, i.e. when you add not SOME_CONDITION, but SOME_CONDITION_ON_INCLUDES, http://blog.bigbinary.com/2013/07/01/preload-vs-eager-load-vs-joins-vs-includes.html ).
6
+ when using includes and where.
13
7
 
14
8
  Example from upper rails issue:
15
9
 
@@ -36,7 +30,7 @@ post.comments.first.testval # Undefined method!
36
30
 
37
31
  Данный gem решает проблему в рельсах с виртуальными аттрибутами при использовании includes,
38
32
  когда рельсы собирают в запрос в joins с алиасами на все аттрибуты. В настоящий момент в модель не собираются
39
- никаким боком виртуальные аттрибуты ( имеется ввиду когда includes ведет себя как eager_load и создает сложный одинарный запрос, подробнее: http://blog.bigbinary.com/2013/07/01/preload-vs-eager-load-vs-joins-vs-includes.html ).
33
+ никаким боком виртуальные аттрибуты.
40
34
 
41
35
  В частности проблема описана здесь: https://github.com/rails/rails/issues/15185
42
36
 
@@ -62,16 +56,12 @@ post.comments.first.testval # Undefined method!
62
56
  ```
63
57
 
64
58
 
65
- ## Installation
59
+ ## Installation
66
60
 
67
61
  Add this line to your application's Gemfile:
68
62
 
69
63
  ```ruby
70
- #rails 4
71
- gem 'rails_select_on_includes', '~> 0.4.8'
72
-
73
- #rails 5
74
- gem 'rails_select_on_includes', '~> 0.5.2'
64
+ gem 'rails_select_on_includes'
75
65
  ```
76
66
 
77
67
  And then execute:
@@ -84,28 +74,27 @@ Or install it yourself as:
84
74
 
85
75
  ## Usage
86
76
 
87
- Works out of the box, monkey-patches base-class alias columns, for select attributes, and JoinBase with JoinDependency to proper typecasting.
88
-
89
- It not affecting query creation, since query already contains all columns, i.e. to_sql returns same string.
77
+ Works out of the box, gently monkey-patching base-class alias columns. It not affecting query creation,
78
+ since query already contains all columns, i.e. to_sql returns same string.
90
79
  Works with selection in all formats:
91
80
 
92
- 1. 'table_name.column' or 'table_name.column as column_1' will be parsed! distinct on can be used also
93
- 2. '(subquery with AS) AS column_1 '
94
- 3. Select with aliased arel function: .select(Comment.arel_table[:id].count.as('comments_count'))
95
- 4. Select with aliased arel attirubte: .select(Comment.arel_table[:column].as('column_alias'))
81
+ 1 'table_name.column' or 'table_name.column as column_1' or "distinct on(..) table_name.column as column_1"
82
+
83
+ 2 { table_name: column } or { table_name: [column1, column2] }
96
84
 
85
+ 3 { table_name: 2 } where 2 relates to upper syntax
97
86
 
98
87
  ## Usage (рус)
99
88
 
100
- Работает из коробки, нежно манки-патча алиасы прямо перед инстанцированием коллекции, а так же не менее нежно JoinBase и JoinDependency :), чтобы полученные аттрибуты были приличных типов, а не только строк, не влияет на создаваемый запрос в БД т.е to_sql не меняется.
89
+ Работает из коробки, нежно манки-патча алиасы прямо перед инстанцированием коллекции, не влияет на создаваемый запрос в БД т.е to_sql не меняется.
101
90
 
102
91
  Поддерживает select в следующих форматах :
103
92
 
104
- 1. 'table_name.column' or 'table_name.column as column_1' will be parsed! distinct on can be used also
105
- 2. '(subquery with AS) AS column_1 '
106
- 3. Select with aliased arel function: .select(Comment.arel_table[:id].count.as('comments_count'))
107
- 4. Select with aliased arel attirubte: .select(Comment.arel_table[:column].as('column_alias'))
93
+ 1 'table_name.column' or 'table_name.column as column_1' or "distinct on(..) table_name.column as column_1"
94
+
95
+ 2 { table_name: column } or { table_name: [column1, column2] }
108
96
 
97
+ 3 { table_name: 2 } where 2 relates to upper syntax
109
98
 
110
99
  ## Testing
111
100
 
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rails_select_on_includes"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -1,143 +1,94 @@
1
1
  require 'active_support/deprecation'
2
2
  require 'active_support/core_ext/string/filters'
3
3
 
4
-
5
- ::ActiveRecord::Associations::JoinDependency::Aliases.class_eval do # :nodoc:
6
- def initialize(tables)
7
- @tables = tables
8
- @alias_cache = tables.each_with_object({}) { |table,h|
9
- h[table.node] = table.columns.each_with_object({}) { |column,i|
10
- i[column.name] = column.alias
11
- }
12
- }
13
- @name_and_alias_cache = tables.each_with_object({}) { |table,h|
14
- h[table.node] = table.columns.map { |column|
15
- [column.name, column.alias]
16
- }
17
- @base_class_node_aliases ||= h[table.node] if table.node.is_a?(ActiveRecord::Associations::JoinDependency::JoinBase)
18
- }
19
-
20
- @virtual_attributes_names = []
21
- end
22
- # valid formats are:
23
- # 'table_name.column' or 'table_name.column as column_1' will be parsed! distinct on can be used also
24
- # '(subquery with AS) AS column_1 '
25
- # Select with aliased arel function: .select(Comment.arel_table[:id].count.as('comments_count'))
26
- # Select with aliased arel attirubte: .select(Comment.arel_table[:column].as('column_alias'))
27
- def update_aliases_to_select_values( select_values )
28
- return if select_values.blank?
29
- select_values.each do |sv|
30
- # if sv is symbol that we assume that its a base table column and it will be aliased and added as usual
31
- # all we need is some specials joins+select from related tables
32
- case sv
33
- when String
34
- sv.split(/,[\s$]*/).each do |sub_sv|
35
- if sub_sv[/.+ as .+/i]
36
- add_virtual_attribute(sub_sv.rpartition(/ as /i).last.strip)
37
- elsif sub_sv[/.+\.[^\*]+/]
38
- add_virtual_attribute(sub_sv[/\..+/][1..-1].strip)
4
+ module ActiveRecord
5
+ module Associations
6
+ class JoinDependency # :nodoc:
7
+
8
+ class Aliases # :nodoc:
9
+ def initialize(tables)
10
+ @tables = tables
11
+ @alias_cache = tables.each_with_object({}) { |table,h|
12
+ h[table.node] = table.columns.each_with_object({}) { |column,i|
13
+ i[column.name] = column.alias
14
+ }
15
+ }
16
+ @name_and_alias_cache = tables.each_with_object({}) { |table,h|
17
+ h[table.node] = table.columns.map { |column|
18
+ [column.name, column.alias]
19
+ }
20
+ @base_class_node_aliases ||= h[table.node] if table.node.is_a?(ActiveRecord::Associations::JoinDependency::JoinBase)
21
+ }
22
+ end
23
+ # valid formats are:
24
+ # 1 'table_name.column' or 'table_name.column as column_1' will be parsed! distinct on can be used also
25
+ # 2 {table_name: column} or { table_name: [column1, column2] }
26
+ # 3 table_name: 2
27
+ def update_aliases_to_select_values( select_values )
28
+ return if select_values.blank?
29
+ select_values.each do |sv|
30
+ # if sv is symbol that we assume that its a base table column and it will be aliased and added as usual
31
+ # all we need is some specials joins+select from related tables
32
+ if sv.is_a?(Hash)
33
+ flatten_hash_values(sv).each{|sub_sv| @base_class_node_aliases << [sub_sv, sub_sv] }
34
+ elsif sv.is_a?(String)
35
+ # this is the case of long raw select
36
+ sv.split( ", " ).each do |sub_sv|
37
+ if sub_sv[/.+ as .+/i]
38
+ selected_column = sub_sv[/ as .+/i][4..-1]
39
+ @base_class_node_aliases << [selected_column, selected_column]
40
+ elsif sub_sv[/.+\.[^\*]+/]
41
+ selected_column = sub_sv[/\..+/][1..-1]
42
+ @base_class_node_aliases << [selected_column, selected_column]
43
+ end
44
+ end
39
45
  end
40
46
  end
41
- when Arel::Nodes::As
42
- add_virtual_attribute(sv.right)
43
- when Arel::Nodes::TableAlias
44
- add_virtual_attribute(sv.right)
45
- when Arel::Nodes::Function
46
- add_virtual_attribute(sv.alias) if sv.alias.present?
47
- end
48
- end
49
- end
50
-
51
- def slice_selected_attr_types( column_types )
52
- column_types.slice( *@virtual_attributes_names )
53
- end
54
-
55
- private
56
- def flatten_hash_values( some_hash )
57
- some_hash.values.map{ |value| value.is_a?(Hash) ? flatten_hash_values( value ) : value }.flatten
58
- end
59
-
60
- def add_virtual_attribute(selected_column)
61
- @base_class_node_aliases << [selected_column, selected_column]
62
- @virtual_attributes_names << selected_column
63
- end
64
- end
65
-
66
- ::ActiveRecord::Associations::JoinDependency::JoinBase.class_eval do
67
- def instantiate(row, aliases, column_types = {}, &block)
68
- base_klass.instantiate(extract_record(row, aliases), column_types, &block)
69
- end
70
- end
71
-
72
- ::ActiveRecord::Associations::JoinDependency.class_eval do
73
- def instantiate(result_set, aliases)
74
- primary_key = aliases.column_alias(join_root, join_root.primary_key)
75
-
76
- seen = Hash.new { |h,parent_klass|
77
- h[parent_klass] = Hash.new { |i,parent_id|
78
- i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} }
79
- }
80
- }
81
-
82
- model_cache = Hash.new { |h,klass| h[klass] = {} }
83
- parents = model_cache[join_root]
84
- column_aliases = aliases.column_aliases join_root
85
-
86
- message_bus = ActiveSupport::Notifications.instrumenter
87
-
88
- payload = {
89
- record_count: result_set.respond_to?(:length) ? result_set.length : result_set.rows.length,
90
- class_name: join_root.base_klass.name
91
- }
47
+ end
92
48
 
93
- message_bus.instrument('instantiation.active_record', payload) do
94
- result_set.each { |row_hash|
95
- parent_key = primary_key ? row_hash[primary_key] : row_hash
96
- # DISTINCTION PART > join_root.instantiate(row_hash, column_aliases, aliases.slice_selected_attr_types( result_set.column_types ) )
97
- # PREVIOUS > join_root.instantiate(row_hash, column_aliases )
98
- parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases, aliases.slice_selected_attr_types( result_set.column_types ) )
99
- construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
100
- }
49
+ private
50
+ def flatten_hash_values( some_hash )
51
+ some_hash.values.map{ |value| value.is_a?(Hash) ? flatten_hash_values( value ) : value }.flatten
52
+ end
53
+ end
101
54
  end
102
-
103
- parents.values
104
55
  end
105
56
  end
106
-
107
-
108
- ::ActiveRecord::FinderMethods.class_eval do
109
- def find_with_associations
110
- # NOTE: the JoinDependency constructed here needs to know about
111
- # any joins already present in `self`, so pass them in
112
- #
113
- # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136
114
- # incorrect SQL is generated. In that case, the join dependency for
115
- # SpecialCategorizations is constructed without knowledge of the
116
- # preexisting join in joins_values to categorizations (by way of
117
- # the `has_many :through` for categories).
118
- #
119
- join_dependency = construct_join_dependency(joins_values)
120
-
121
- aliases = join_dependency.aliases
122
- relation = select aliases.columns
123
- relation = apply_join_dependency(relation, join_dependency)
124
-
125
- if block_given?
126
- yield relation
127
- else
128
- if ActiveRecord::NullRelation === relation
129
- []
57
+ module ActiveRecord
58
+ module FinderMethods
59
+
60
+ def find_with_associations
61
+ # NOTE: the JoinDependency constructed here needs to know about
62
+ # any joins already present in `self`, so pass them in
63
+ #
64
+ # failing to do so means that in cases like activerecord/test/cases/associations/inner_join_association_test.rb:136
65
+ # incorrect SQL is generated. In that case, the join dependency for
66
+ # SpecialCategorizations is constructed without knowledge of the
67
+ # preexisting join in joins_values to categorizations (by way of
68
+ # the `has_many :through` for categories).
69
+ #
70
+ join_dependency = construct_join_dependency(joins_values)
71
+
72
+ aliases = join_dependency.aliases
73
+ relation = select aliases.columns
74
+ relation = apply_join_dependency(relation, join_dependency)
75
+
76
+ if block_given?
77
+ yield relation
130
78
  else
131
- arel = relation.arel
132
- rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values)
133
- #DISTINCTION IS HERE:
134
- # now we gently mokey-patching existing column aliases with select values
135
- aliases.update_aliases_to_select_values(values[:select]) unless values[:select].blank?
136
-
137
- join_dependency.instantiate(rows, aliases)
79
+ if ActiveRecord::NullRelation === relation
80
+ []
81
+ else
82
+ arel = relation.arel
83
+ rows = connection.select_all(arel, 'SQL', relation.bound_attributes)
84
+ #DISTINCTION IS HERE:
85
+ # now we gently mokey-patching existing column aliases with select values
86
+ aliases.update_aliases_to_select_values(values[:select]) unless values[:select].blank?
87
+
88
+ join_dependency.instantiate(rows, aliases)
89
+ end
138
90
  end
139
- end
140
91
  end
92
+ end
141
93
  end
142
94
 
143
-
@@ -1,3 +1,3 @@
1
1
  module RailsSelectOnIncludes
2
- VERSION = "0.4.13"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -30,7 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- spec.add_dependency "activerecord", ">=4.1"
33
+ spec.add_dependency "activerecord", ">=5"
34
34
 
35
35
  spec.add_development_dependency "bundler", "~> 1.13"
36
36
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_select_on_includes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.13
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - alekseyl
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-05-24 00:00:00.000000000 Z
11
+ date: 2017-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '4.1'
19
+ version: '5'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '4.1'
26
+ version: '5'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -61,11 +61,18 @@ extensions: []
61
61
  extra_rdoc_files: []
62
62
  files:
63
63
  - ".gitignore"
64
- - CHANGELOG.md
64
+ - ".idea/.name"
65
+ - ".idea/misc.xml"
66
+ - ".idea/modules.xml"
67
+ - ".idea/rails_select_on_includes.iml"
68
+ - ".idea/vcs.xml"
69
+ - ".idea/workspace.xml"
65
70
  - Gemfile
66
71
  - LICENSE.txt
67
72
  - README.md
68
73
  - Rakefile
74
+ - bin/console
75
+ - bin/setup
69
76
  - lib/rails_select_on_includes.rb
70
77
  - lib/rails_select_on_includes/version.rb
71
78
  - rails_select_on_includes.gemspec
@@ -90,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
97
  version: '0'
91
98
  requirements: []
92
99
  rubyforge_project:
93
- rubygems_version: 2.6.11
100
+ rubygems_version: 2.5.1
94
101
  signing_key:
95
102
  specification_version: 4
96
103
  summary: Patching rails include/select/virtual attributes issue
data/CHANGELOG.md DELETED
@@ -1,6 +0,0 @@
1
- # 0.4.13
2
- * resolve issue #15
3
-
4
- # 0.4.12
5
-
6
- * Arel::Nodes::TableAlias added