sunstone 5.1.0.4 → 6.1.0.rc1
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 +5 -5
- data/.travis.yml +28 -15
- data/ext/active_record/associations.rb +4 -9
- data/ext/active_record/associations/collection_association.rb +25 -18
- data/ext/active_record/attribute_methods.rb +14 -42
- data/ext/active_record/callbacks.rb +1 -1
- data/ext/active_record/finder_methods.rb +164 -106
- data/ext/active_record/persistence.rb +35 -13
- data/ext/active_record/relation.rb +7 -47
- data/ext/active_record/relation/calculations.rb +16 -8
- data/ext/active_record/relation/query_methods.rb +9 -0
- data/ext/active_record/statement_cache.rb +11 -9
- data/ext/active_record/transactions.rb +13 -23
- data/ext/arel/attributes/empty_relation.rb +31 -31
- data/ext/arel/nodes/select_statement.rb +27 -13
- data/lib/active_record/connection_adapters/sunstone/column.rb +2 -2
- data/lib/active_record/connection_adapters/sunstone/database_statements.rb +77 -26
- data/lib/active_record/connection_adapters/sunstone/schema_statements.rb +18 -8
- data/lib/active_record/connection_adapters/sunstone/type/array.rb +9 -8
- data/lib/active_record/connection_adapters/sunstone/type/binary.rb +34 -0
- data/lib/active_record/connection_adapters/sunstone/type/json.rb +1 -1
- data/lib/active_record/connection_adapters/sunstone_adapter.rb +42 -30
- data/lib/arel/collectors/sunstone.rb +21 -19
- data/lib/arel/visitors/sunstone.rb +56 -35
- data/lib/sunstone.rb +2 -4
- data/lib/sunstone/connection.rb +13 -11
- data/lib/sunstone/exception.rb +11 -1
- data/lib/sunstone/version.rb +1 -1
- data/sunstone.gemspec +5 -3
- data/test/active_record/associations/has_many_test.rb +30 -2
- data/test/active_record/eager_loading_test.rb +13 -1
- data/test/active_record/persistance_test.rb +38 -13
- data/test/active_record/query/count_test.rb +13 -0
- data/test/active_record/query_test.rb +7 -7
- data/test/active_record/rpc_test.rb +30 -0
- data/test/schema_mock.rb +31 -27
- data/test/sunstone/connection/column_definition_test.rb +30 -0
- data/test/sunstone/connection/configuration_test.rb +13 -13
- data/test/sunstone/connection/cookie_store_test.rb +2 -2
- data/test/sunstone/connection/request_helper_test.rb +12 -12
- data/test/sunstone/connection/send_request_test.rb +13 -13
- data/test/sunstone/connection_test.rb +2 -2
- data/test/test_helper.rb +1 -1
- metadata +38 -22
- data/ext/active_record/associations/association.rb +0 -16
- data/ext/active_record/batches.rb +0 -12
- data/ext/arel/attributes/relation.rb +0 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: afae21d218aa760e3a36c2db071a80f25f825fb618f56c9bc9cb25ddb628ee23
|
|
4
|
+
data.tar.gz: a1b4c27957ba0330a74bab059b5b352c56d74eeafd0d9e910845badae194a0e5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2bf28e210e209846f11c287b7153669721211b86e1e27de9388b52bbdeb37abe1ea53c8370abfc11ab50132275804796912eeeb67c0c4e83cd840f54be44634b
|
|
7
|
+
data.tar.gz: b0eb0399c0a7ff542d06b9b3b83a1dbc699d4de11b75e24813186fd6f6c6de375713903ecc4886efb6b78395bc44f77841238d365ec389ada64bfeca95449626
|
data/.travis.yml
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
dist: bionic
|
|
1
2
|
language: ruby
|
|
2
3
|
sudo: false
|
|
3
4
|
|
|
@@ -7,30 +8,42 @@ cache:
|
|
|
7
8
|
- /home/travis/.rvm/gems
|
|
8
9
|
|
|
9
10
|
rvm:
|
|
10
|
-
- 2.
|
|
11
|
+
- 2.7
|
|
11
12
|
|
|
12
13
|
env:
|
|
13
14
|
matrix:
|
|
14
|
-
- RAILS_VERSION=
|
|
15
|
-
- RAILS_VERSION=
|
|
16
|
-
- RAILS_VERSION=
|
|
17
|
-
- RAILS_VERSION=
|
|
18
|
-
- RAILS_VERSION=
|
|
19
|
-
- RAILS_VERSION=
|
|
20
|
-
- RAILS_VERSION=
|
|
21
|
-
- RAILS_VERSION=v5.1.2 GEM=ar:sqlite3
|
|
22
|
-
- RAILS_VERSION=v5.1.2 GEM=ar:postgresql
|
|
23
|
-
- RAILS_VERSION=v5.1.3 GEM=ar:mysql2
|
|
24
|
-
- RAILS_VERSION=v5.1.3 GEM=ar:sqlite3
|
|
25
|
-
- RAILS_VERSION=v5.1.3 GEM=ar:postgresql
|
|
15
|
+
- RAILS_VERSION=v6.1.0 TASK='db:mysql:rebuild mysql2:test'
|
|
16
|
+
- RAILS_VERSION=v6.1.0 TASK='db:mysql:rebuild mysql2:isolated_test'
|
|
17
|
+
- RAILS_VERSION=v6.1.0 TASK='db:postgresql:rebuild postgresql:test'
|
|
18
|
+
- RAILS_VERSION=v6.1.0 TASK='db:postgresql:rebuild postgresql:isolated_test'
|
|
19
|
+
- RAILS_VERSION=v6.1.0 TASK='sqlite3:test'
|
|
20
|
+
- RAILS_VERSION=v6.1.0 TASK='sqlite3:isolated_test'
|
|
21
|
+
- RAILS_VERSION=v6.1.0 TASK='sqlite3_mem:test'
|
|
26
22
|
|
|
23
|
+
services:
|
|
24
|
+
- mysql
|
|
27
25
|
addons:
|
|
28
|
-
postgresql: "
|
|
26
|
+
postgresql: "13"
|
|
27
|
+
apt:
|
|
28
|
+
packages:
|
|
29
|
+
- postgresql-13
|
|
30
|
+
- postgresql-client-13
|
|
31
|
+
|
|
29
32
|
|
|
30
33
|
before_install:
|
|
34
|
+
- sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/13/main/postgresql.conf
|
|
35
|
+
- sudo cp /etc/postgresql/{9.3,13}/main/pg_hba.conf
|
|
36
|
+
- sudo pg_ctlcluster 13 main restart
|
|
31
37
|
- unset BUNDLE_GEMFILE
|
|
32
38
|
- gem update --system
|
|
33
39
|
- gem update bundler
|
|
40
|
+
- gem install bundler --version 1.17.3
|
|
41
|
+
- mysql -e "create user rails@localhost;"
|
|
42
|
+
- mysql -e "grant all privileges on activerecord_unittest.* to rails@localhost;"
|
|
43
|
+
- mysql -e "grant all privileges on activerecord_unittest2.* to rails@localhost;"
|
|
44
|
+
- mysql -e "grant all privileges on inexistent_activerecord_unittest.* to rails@localhost;"
|
|
45
|
+
- mysql -e "create database activerecord_unittest default character set utf8mb4;"
|
|
46
|
+
- mysql -e "create database activerecord_unittest2 default character set utf8mb4;"
|
|
34
47
|
|
|
35
48
|
install:
|
|
36
49
|
- git clone --branch $RAILS_VERSION https://github.com/rails/rails.git ~/build/rails
|
|
@@ -52,4 +65,4 @@ before_script:
|
|
|
52
65
|
|
|
53
66
|
script:
|
|
54
67
|
- bundle exec rake test
|
|
55
|
-
- cd ~/build/rails &&
|
|
68
|
+
- cd ~/build/rails/activerecord && bundle exec rake $TASK
|
|
@@ -3,12 +3,7 @@ require 'active_record/associations'
|
|
|
3
3
|
module ActiveRecord
|
|
4
4
|
module Associations
|
|
5
5
|
module ClassMethods
|
|
6
|
-
def has_and_belongs_to_many(name, scope = nil, options
|
|
7
|
-
if scope.is_a?(Hash)
|
|
8
|
-
options = scope
|
|
9
|
-
scope = nil
|
|
10
|
-
end
|
|
11
|
-
|
|
6
|
+
def has_and_belongs_to_many(name, scope = nil, **options, &extension)
|
|
12
7
|
habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
|
|
13
8
|
|
|
14
9
|
builder = Builder::HasAndBelongsToMany.new name, self, options
|
|
@@ -40,12 +35,12 @@ module ActiveRecord
|
|
|
40
35
|
hm_options[:through] = middle_reflection.name
|
|
41
36
|
hm_options[:source] = join_model.right_reflection.name
|
|
42
37
|
|
|
43
|
-
[:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k|
|
|
38
|
+
[:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k|
|
|
44
39
|
hm_options[k] = options[k] if options.key? k
|
|
45
40
|
end
|
|
46
41
|
|
|
47
|
-
has_many name, scope, hm_options, &extension
|
|
48
|
-
|
|
42
|
+
has_many name, scope, **hm_options, &extension
|
|
43
|
+
_reflections[name.to_s].parent_reflection = habtm_reflection
|
|
49
44
|
end
|
|
50
45
|
end
|
|
51
46
|
end
|
|
@@ -9,17 +9,24 @@ module ActiveRecord
|
|
|
9
9
|
if owner.new_record?
|
|
10
10
|
replace_records(other_array, original_target)
|
|
11
11
|
elsif owner.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && owner.instance_variable_defined?(:@updating) && owner.instance_variable_get(:@updating)
|
|
12
|
-
self.instance_variable_set(:@sunstone_changed, true)
|
|
13
12
|
replace_common_records_in_memory(other_array, original_target)
|
|
14
13
|
|
|
15
14
|
# Remove from target
|
|
16
|
-
(original_target - other_array)
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
records_for_removal = (original_target - other_array)
|
|
16
|
+
if !records_for_removal.empty?
|
|
17
|
+
self.instance_variable_set(:@sunstone_changed, true)
|
|
18
|
+
records_for_removal.each { |record| callback(:before_remove, record) }
|
|
19
|
+
records_for_removal.each { |record| target.delete(record) }
|
|
20
|
+
records_for_removal.each { |record| callback(:after_remove, record) }
|
|
21
|
+
end
|
|
19
22
|
|
|
20
23
|
# Add to target
|
|
21
|
-
(other_array - original_target)
|
|
22
|
-
|
|
24
|
+
records_for_addition = (other_array - original_target)
|
|
25
|
+
if !records_for_addition.empty?
|
|
26
|
+
self.instance_variable_set(:@sunstone_changed, true)
|
|
27
|
+
(other_array - original_target).each do |record|
|
|
28
|
+
add_to_target(record)
|
|
29
|
+
end
|
|
23
30
|
end
|
|
24
31
|
|
|
25
32
|
other_array
|
|
@@ -33,28 +40,28 @@ module ActiveRecord
|
|
|
33
40
|
end
|
|
34
41
|
end
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
class HasManyAssociation
|
|
40
|
-
|
|
41
|
-
def insert_record(record, validate = true, raise = false)
|
|
42
|
-
set_owner_attributes(record)
|
|
43
|
-
set_inverse_instance(record)
|
|
44
|
-
|
|
43
|
+
def insert_record(record, validate = true, raise = false, &block)
|
|
45
44
|
if record.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && (!owner.instance_variable_defined?(:@updating) && owner.instance_variable_get(:@updating))
|
|
46
45
|
true
|
|
47
46
|
elsif raise
|
|
48
|
-
record.save!(:validate
|
|
47
|
+
record.save!(validate: validate, &block)
|
|
49
48
|
else
|
|
50
|
-
record.save(:validate
|
|
49
|
+
record.save(validate: validate, &block)
|
|
51
50
|
end
|
|
52
51
|
end
|
|
53
52
|
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class HasManyThroughAssociation
|
|
57
|
+
|
|
54
58
|
private
|
|
55
59
|
def save_through_record(record)
|
|
56
60
|
return if record.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
|
57
|
-
build_through_record(record)
|
|
61
|
+
association = build_through_record(record)
|
|
62
|
+
if association.changed?
|
|
63
|
+
association.save!
|
|
64
|
+
end
|
|
58
65
|
ensure
|
|
59
66
|
@through_records.delete(record.object_id)
|
|
60
67
|
end
|
|
@@ -5,26 +5,23 @@ module ActiveRecord
|
|
|
5
5
|
|
|
6
6
|
# Returns a Hash of the Arel::Attributes and attribute values that have been
|
|
7
7
|
# typecasted for use in an Arel insert/update method.
|
|
8
|
-
def
|
|
9
|
-
attrs =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
attribute_names.each do |name|
|
|
13
|
-
attrs[arel_table[name]] = typecasted_attribute_value(name)
|
|
8
|
+
def attributes_with_values(attribute_names)
|
|
9
|
+
attrs = attribute_names.index_with do |name|
|
|
10
|
+
_read_attribute(name)
|
|
14
11
|
end
|
|
15
12
|
|
|
16
13
|
if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
|
17
14
|
self.class.reflect_on_all_associations.each do |reflection|
|
|
18
15
|
if reflection.belongs_to?
|
|
19
16
|
if association(reflection.name).loaded? && association(reflection.name).target == Thread.current[:sunstone_updating_model]
|
|
20
|
-
attrs.delete(
|
|
17
|
+
attrs.delete(reflection.foreign_key)
|
|
21
18
|
else
|
|
22
19
|
add_attributes_for_belongs_to_association(reflection, attrs)
|
|
23
20
|
end
|
|
24
21
|
elsif reflection.has_one?
|
|
25
22
|
add_attributes_for_has_one_association(reflection, attrs)
|
|
26
23
|
elsif reflection.collection?
|
|
27
|
-
add_attributes_for_collection_association(reflection, attrs, arel_table)
|
|
24
|
+
add_attributes_for_collection_association(reflection, attrs, self.class.arel_table)
|
|
28
25
|
end
|
|
29
26
|
end
|
|
30
27
|
end
|
|
@@ -49,15 +46,7 @@ module ActiveRecord
|
|
|
49
46
|
record.destroy
|
|
50
47
|
elsif autosave != false
|
|
51
48
|
if record.new_record? || (autosave && record.changed_for_autosave?)
|
|
52
|
-
|
|
53
|
-
record.send(:arel_attributes_with_values_for_create, record.attribute_names).each do |k, v|
|
|
54
|
-
attrs[Arel::Attributes::Relation.new(k, reflection.name, false, true)] = v
|
|
55
|
-
end
|
|
56
|
-
else
|
|
57
|
-
record.send(:arel_attributes_with_values_for_update, record.attribute_names).each do |k, v|
|
|
58
|
-
attrs[Arel::Attributes::Relation.new(k, reflection.name, false, true)] = v
|
|
59
|
-
end
|
|
60
|
-
end
|
|
49
|
+
attrs["#{reflection.name}_attributes"] = record.send(:attributes_with_values, record.new_record? ? (record.attribute_names - ['id']) : record.attribute_names)
|
|
61
50
|
end
|
|
62
51
|
end
|
|
63
52
|
end
|
|
@@ -86,15 +75,7 @@ module ActiveRecord
|
|
|
86
75
|
record[reflection.foreign_key] = key
|
|
87
76
|
end
|
|
88
77
|
|
|
89
|
-
|
|
90
|
-
record.send(:arel_attributes_with_values_for_create, record.attribute_names).each do |k, v|
|
|
91
|
-
attrs[Arel::Attributes::Relation.new(k, reflection.name, false, true)] = v
|
|
92
|
-
end
|
|
93
|
-
else
|
|
94
|
-
record.send(:arel_attributes_with_values_for_update, record.attribute_names).each do |k, v|
|
|
95
|
-
attrs[Arel::Attributes::Relation.new(k, reflection.name, false, true)] = v
|
|
96
|
-
end
|
|
97
|
-
end
|
|
78
|
+
attrs["#{reflection.name}_attributes"] = record.send(:attributes_with_values, record.new_record? ? (record.attribute_names - ['id']): record.attribute_names)
|
|
98
79
|
end
|
|
99
80
|
end
|
|
100
81
|
end
|
|
@@ -111,22 +92,15 @@ module ActiveRecord
|
|
|
111
92
|
end
|
|
112
93
|
|
|
113
94
|
if association = association_instance_get(reflection.name)
|
|
114
|
-
if new_record? || (association.instance_variable_defined?(:@sunstone_changed) && association.instance_variable_get(:@sunstone_changed)) ||
|
|
115
|
-
attrs[
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if record.new_record?
|
|
121
|
-
record.send(:arel_attributes_with_values_for_create, record.send(:keys_for_partial_write) + [record.class.primary_key]).each do |k, v|
|
|
122
|
-
attrs[Arel::Attributes::Relation.new(k, reflection.name, idx, true)] = v
|
|
123
|
-
end
|
|
124
|
-
else
|
|
125
|
-
record.send(:arel_attributes_with_values_for_update, record.send(:keys_for_partial_write) + [record.class.primary_key]).each do |k, v|
|
|
126
|
-
attrs[Arel::Attributes::Relation.new(k, reflection.name, idx, true)] = v
|
|
127
|
-
end
|
|
95
|
+
if new_record? || (association.instance_variable_defined?(:@sunstone_changed) && association.instance_variable_get(:@sunstone_changed)) || association.target.any?(&:changed_for_autosave?) || association.target.any?(&:new_record?)
|
|
96
|
+
attrs["#{reflection.name}_attributes"] = if association.target.empty?
|
|
97
|
+
[]
|
|
98
|
+
else
|
|
99
|
+
association.target.select { |r| !r.destroyed? }.map do |record|
|
|
100
|
+
record.send(:attributes_with_values, record.send(:attribute_names_for_partial_writes) + (record.new_record? ? [] : [record.class.primary_key]))
|
|
128
101
|
end
|
|
129
102
|
end
|
|
103
|
+
|
|
130
104
|
association.instance_variable_set(:@sunstone_changed, false)
|
|
131
105
|
end
|
|
132
106
|
|
|
@@ -134,8 +108,6 @@ module ActiveRecord
|
|
|
134
108
|
association.reset_scope if association.respond_to?(:reset_scope)
|
|
135
109
|
end
|
|
136
110
|
end
|
|
137
|
-
|
|
138
|
-
|
|
139
111
|
|
|
140
112
|
end
|
|
141
113
|
end
|
|
@@ -2,7 +2,7 @@ module ActiveRecord
|
|
|
2
2
|
module Callbacks
|
|
3
3
|
private
|
|
4
4
|
|
|
5
|
-
def create_or_update(
|
|
5
|
+
def create_or_update(**) #:nodoc:
|
|
6
6
|
if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
|
7
7
|
@_already_called ||= {}
|
|
8
8
|
self.class.reflect_on_all_associations.each do |r|
|
|
@@ -1,25 +1,13 @@
|
|
|
1
|
-
module Arel
|
|
2
|
-
module Visitors
|
|
3
|
-
class ToSql < Arel::Visitors::Reduce
|
|
4
|
-
|
|
5
|
-
def visit_Arel_Attributes_Relation o, collector
|
|
6
|
-
visit(o.relation, collector)
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
1
|
module ActiveRecord
|
|
14
2
|
class PredicateBuilder # :nodoc:
|
|
15
3
|
|
|
16
|
-
def expand_from_hash(attributes)
|
|
4
|
+
def expand_from_hash(attributes, &block)
|
|
17
5
|
return ["1=0"] if attributes.empty?
|
|
18
6
|
|
|
19
7
|
attributes.flat_map do |key, value|
|
|
20
|
-
if value.is_a?(Hash)
|
|
21
|
-
ka =
|
|
22
|
-
if self.table.instance_variable_get(:@klass).connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
|
8
|
+
if value.is_a?(Hash) && !table.has_column?(key)
|
|
9
|
+
ka = table.associated_table(key, &block).predicate_builder.expand_from_hash(value.stringify_keys)
|
|
10
|
+
if self.send(:table).instance_variable_get(:@klass).connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
|
23
11
|
ka.each { |k|
|
|
24
12
|
if k.left.is_a?(Arel::Attributes::Attribute) || k.left.is_a?(Arel::Attributes::Relation)
|
|
25
13
|
k.left = Arel::Attributes::Relation.new(k.left, key)
|
|
@@ -27,8 +15,51 @@ module ActiveRecord
|
|
|
27
15
|
}
|
|
28
16
|
end
|
|
29
17
|
ka
|
|
18
|
+
elsif table.associated_with?(key)
|
|
19
|
+
# Find the foreign key when using queries such as:
|
|
20
|
+
# Post.where(author: author)
|
|
21
|
+
#
|
|
22
|
+
# For polymorphic relationships, find the foreign key and type:
|
|
23
|
+
# PriceEstimate.where(estimate_of: treasure)
|
|
24
|
+
associated_table = table.associated_table(key)
|
|
25
|
+
if associated_table.polymorphic_association?
|
|
26
|
+
case value.is_a?(Array) ? value.first : value
|
|
27
|
+
when Base, Relation
|
|
28
|
+
value = [value] unless value.is_a?(Array)
|
|
29
|
+
klass = PolymorphicArrayValue
|
|
30
|
+
end
|
|
31
|
+
elsif associated_table.through_association?
|
|
32
|
+
next associated_table.predicate_builder.expand_from_hash(
|
|
33
|
+
associated_table.primary_key => value
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
klass ||= AssociationQueryValue
|
|
38
|
+
queries = klass.new(associated_table, value).queries.map! do |query|
|
|
39
|
+
expand_from_hash(query)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
grouping_queries(queries)
|
|
43
|
+
elsif table.aggregated_with?(key)
|
|
44
|
+
mapping = table.reflect_on_aggregation(key).mapping
|
|
45
|
+
values = value.nil? ? [nil] : Array.wrap(value)
|
|
46
|
+
if mapping.length == 1 || values.empty?
|
|
47
|
+
column_name, aggr_attr = mapping.first
|
|
48
|
+
values = values.map do |object|
|
|
49
|
+
object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
|
|
50
|
+
end
|
|
51
|
+
self[column_name, values]
|
|
52
|
+
else
|
|
53
|
+
queries = values.map do |object|
|
|
54
|
+
mapping.map do |field_attr, aggregate_attr|
|
|
55
|
+
self[field_attr, object.try!(aggregate_attr)]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
grouping_queries(queries)
|
|
60
|
+
end
|
|
30
61
|
else
|
|
31
|
-
|
|
62
|
+
self[key, value]
|
|
32
63
|
end
|
|
33
64
|
end
|
|
34
65
|
end
|
|
@@ -39,98 +70,113 @@ end
|
|
|
39
70
|
module ActiveRecord
|
|
40
71
|
module FinderMethods
|
|
41
72
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
relation = if connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
|
46
|
-
arel.eager_load = Arel::Nodes::EagerLoad.new(eager_load_values)
|
|
47
|
-
self
|
|
48
|
-
else
|
|
49
|
-
join_dependency = construct_join_dependency(joins_values)
|
|
50
|
-
aliases = join_dependency.aliases
|
|
51
|
-
apply_join_dependency(select(aliases.columns), join_dependency)
|
|
73
|
+
class SunstoneJoinDependency
|
|
74
|
+
def initialize(klass)
|
|
75
|
+
@klass = klass
|
|
52
76
|
end
|
|
53
77
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
else
|
|
57
|
-
if ActiveRecord::NullRelation === relation
|
|
58
|
-
[]
|
|
59
|
-
else
|
|
60
|
-
arel = relation.arel
|
|
61
|
-
rows = if connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
|
62
|
-
connection.select_all(arel, 'SQL', arel.bind_values + relation.bound_attributes)
|
|
63
|
-
else
|
|
64
|
-
connection.select_all(arel, 'SQL', relation.bound_attributes)
|
|
65
|
-
end
|
|
66
|
-
if join_dependency
|
|
67
|
-
join_dependency.instantiate(rows, aliases)
|
|
68
|
-
else
|
|
69
|
-
instantiate_with_associations(rows, relation)
|
|
70
|
-
end
|
|
71
|
-
end
|
|
78
|
+
def reflections
|
|
79
|
+
[]
|
|
72
80
|
end
|
|
73
|
-
|
|
81
|
+
|
|
82
|
+
def apply_column_aliases(relation)
|
|
83
|
+
relation
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def instantiate(result_set, strict_loading_value, &block)
|
|
87
|
+
seen = Hash.new { |i, object_id|
|
|
88
|
+
i[object_id] = Hash.new { |j, child_class|
|
|
89
|
+
j[child_class] = {}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
model_cache = Hash.new { |h, klass| h[klass] = {} }
|
|
94
|
+
parents = model_cache[@klass]
|
|
74
95
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
96
|
+
message_bus = ActiveSupport::Notifications.instrumenter
|
|
97
|
+
|
|
98
|
+
payload = {
|
|
99
|
+
record_count: result_set.length,
|
|
100
|
+
class_name: @klass.name
|
|
79
101
|
}
|
|
80
|
-
}
|
|
81
102
|
|
|
82
|
-
|
|
83
|
-
|
|
103
|
+
message_bus.instrument("instantiation.active_record", payload) do
|
|
104
|
+
result_set.each { |row_hash|
|
|
105
|
+
parent_key = @klass.primary_key ? row_hash[@klass.primary_key] : row_hash
|
|
106
|
+
parent = parents[parent_key] ||= @klass.instantiate(row_hash.select{|k,v| @klass.column_names.include?(k.to_s) }, &block)
|
|
107
|
+
construct(parent, row_hash.select{|k,v| !@klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
|
|
108
|
+
}
|
|
109
|
+
end
|
|
84
110
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
construct(parent, row_hash.select{|k,v| !column_names.include?(k.to_s) }, seen, model_cache)
|
|
88
|
-
}
|
|
111
|
+
parents.values
|
|
112
|
+
end
|
|
89
113
|
|
|
90
|
-
|
|
91
|
-
|
|
114
|
+
def construct(parent, relations, seen, model_cache, strict_loading_value)
|
|
115
|
+
relations.each do |key, attributes|
|
|
116
|
+
reflection = parent.class.reflect_on_association(key)
|
|
117
|
+
next unless reflection
|
|
92
118
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
119
|
+
if reflection.collection?
|
|
120
|
+
other = parent.association(reflection.name)
|
|
121
|
+
other.loaded!
|
|
122
|
+
else
|
|
123
|
+
if parent.association_cached?(reflection.name)
|
|
124
|
+
model = parent.association(reflection.name).target
|
|
125
|
+
construct(model, attributes.select{|k,v| !model.class.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
97
128
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
construct(model, attributes.select{|k,v| !model.class.column_names.include?(k.to_s) }, seen, model_cache)
|
|
129
|
+
if !reflection.collection?
|
|
130
|
+
construct_association(parent, reflection, attributes, seen, model_cache, strict_loading_value)
|
|
131
|
+
else
|
|
132
|
+
attributes.each do |row|
|
|
133
|
+
construct_association(parent, reflection, row, seen, model_cache, strict_loading_value)
|
|
134
|
+
end
|
|
105
135
|
end
|
|
136
|
+
|
|
106
137
|
end
|
|
138
|
+
end
|
|
107
139
|
|
|
108
|
-
|
|
109
|
-
|
|
140
|
+
def construct_association(parent, reflection, attributes, seen, model_cache, strict_loading_value)
|
|
141
|
+
return if attributes.nil?
|
|
142
|
+
|
|
143
|
+
klass = if reflection.polymorphic?
|
|
144
|
+
parent.send(reflection.foreign_type).constantize.base_class
|
|
110
145
|
else
|
|
111
|
-
|
|
112
|
-
construct_association(parent, reflection, row, seen, model_cache)
|
|
113
|
-
end
|
|
146
|
+
reflection.klass
|
|
114
147
|
end
|
|
148
|
+
id = attributes[klass.primary_key]
|
|
149
|
+
model = seen[parent.object_id][klass][id]
|
|
115
150
|
|
|
116
|
-
|
|
117
|
-
|
|
151
|
+
if model
|
|
152
|
+
construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
|
|
118
153
|
|
|
119
|
-
|
|
120
|
-
return if attributes.nil?
|
|
154
|
+
other = parent.association(reflection.name)
|
|
121
155
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
156
|
+
if reflection.collection?
|
|
157
|
+
other.target.push(model)
|
|
158
|
+
else
|
|
159
|
+
other.target = model
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
other.set_inverse_instance(model)
|
|
163
|
+
else
|
|
164
|
+
model = construct_model(parent, reflection, id, attributes.select{|k,v| klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
|
|
165
|
+
seen[parent.object_id][model.class.base_class][id] = model
|
|
166
|
+
construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
|
|
167
|
+
end
|
|
126
168
|
end
|
|
127
|
-
id = attributes[klass.primary_key]
|
|
128
|
-
model = seen[parent.class.base_class][parent.id][klass][id]
|
|
129
169
|
|
|
130
|
-
if model
|
|
131
|
-
construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache)
|
|
132
170
|
|
|
133
|
-
|
|
171
|
+
def construct_model(record, reflection, id, attributes, seen, model_cache, strict_loading_value)
|
|
172
|
+
klass = if reflection.polymorphic?
|
|
173
|
+
record.send(reflection.foreign_type).constantize
|
|
174
|
+
else
|
|
175
|
+
reflection.klass
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
model = model_cache[klass][id] ||= klass.instantiate(attributes)
|
|
179
|
+
other = record.association(reflection.name)
|
|
134
180
|
|
|
135
181
|
if reflection.collection?
|
|
136
182
|
other.target.push(model)
|
|
@@ -139,34 +185,46 @@ module ActiveRecord
|
|
|
139
185
|
end
|
|
140
186
|
|
|
141
187
|
other.set_inverse_instance(model)
|
|
142
|
-
|
|
143
|
-
model = construct_model(parent, reflection, id, attributes.select{|k,v| klass.column_names.include?(k.to_s) }, seen, model_cache)
|
|
144
|
-
seen[parent.class.base_class][parent.id][model.class.base_class][id] = model
|
|
145
|
-
construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache)
|
|
188
|
+
model
|
|
146
189
|
end
|
|
190
|
+
|
|
147
191
|
end
|
|
148
192
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
193
|
+
def apply_join_dependency(eager_loading: group_values.empty?)
|
|
194
|
+
if connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
|
|
195
|
+
join_dependency = SunstoneJoinDependency.new(base_class)
|
|
196
|
+
relation = except(:includes, :eager_load, :preload)
|
|
197
|
+
relation.arel.eager_load = Arel::Nodes::EagerLoad.new(eager_load_values)
|
|
153
198
|
else
|
|
154
|
-
|
|
199
|
+
join_dependency = construct_join_dependency(
|
|
200
|
+
eager_load_values | includes_values, Arel::Nodes::OuterJoin
|
|
201
|
+
)
|
|
202
|
+
relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
|
|
155
203
|
end
|
|
156
204
|
|
|
157
|
-
|
|
158
|
-
|
|
205
|
+
if eager_loading && !(
|
|
206
|
+
using_limitable_reflections?(join_dependency.reflections) &&
|
|
207
|
+
using_limitable_reflections?(
|
|
208
|
+
construct_join_dependency(
|
|
209
|
+
select_association_list(joins_values).concat(
|
|
210
|
+
select_association_list(left_outer_joins_values)
|
|
211
|
+
), nil
|
|
212
|
+
).reflections
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
if has_limit_or_offset?
|
|
216
|
+
limited_ids = limited_ids_for(relation)
|
|
217
|
+
limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
|
|
218
|
+
end
|
|
219
|
+
relation.limit_value = relation.offset_value = nil
|
|
220
|
+
end
|
|
159
221
|
|
|
160
|
-
if
|
|
161
|
-
|
|
222
|
+
if block_given?
|
|
223
|
+
yield relation, join_dependency
|
|
162
224
|
else
|
|
163
|
-
|
|
225
|
+
relation
|
|
164
226
|
end
|
|
165
|
-
|
|
166
|
-
other.set_inverse_instance(model)
|
|
167
|
-
model
|
|
168
227
|
end
|
|
169
|
-
|
|
170
228
|
end
|
|
171
229
|
|
|
172
230
|
end
|