rails_select_on_includes 0.4.13 → 0.5.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 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