sunstone 5.1.0.4 → 6.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +28 -15
  3. data/ext/active_record/associations.rb +4 -9
  4. data/ext/active_record/associations/collection_association.rb +25 -18
  5. data/ext/active_record/attribute_methods.rb +14 -42
  6. data/ext/active_record/callbacks.rb +1 -1
  7. data/ext/active_record/finder_methods.rb +164 -106
  8. data/ext/active_record/persistence.rb +35 -13
  9. data/ext/active_record/relation.rb +7 -47
  10. data/ext/active_record/relation/calculations.rb +16 -8
  11. data/ext/active_record/relation/query_methods.rb +9 -0
  12. data/ext/active_record/statement_cache.rb +11 -9
  13. data/ext/active_record/transactions.rb +13 -23
  14. data/ext/arel/attributes/empty_relation.rb +31 -31
  15. data/ext/arel/nodes/select_statement.rb +27 -13
  16. data/lib/active_record/connection_adapters/sunstone/column.rb +2 -2
  17. data/lib/active_record/connection_adapters/sunstone/database_statements.rb +77 -26
  18. data/lib/active_record/connection_adapters/sunstone/schema_statements.rb +18 -8
  19. data/lib/active_record/connection_adapters/sunstone/type/array.rb +9 -8
  20. data/lib/active_record/connection_adapters/sunstone/type/binary.rb +34 -0
  21. data/lib/active_record/connection_adapters/sunstone/type/json.rb +1 -1
  22. data/lib/active_record/connection_adapters/sunstone_adapter.rb +42 -30
  23. data/lib/arel/collectors/sunstone.rb +21 -19
  24. data/lib/arel/visitors/sunstone.rb +56 -35
  25. data/lib/sunstone.rb +2 -4
  26. data/lib/sunstone/connection.rb +13 -11
  27. data/lib/sunstone/exception.rb +11 -1
  28. data/lib/sunstone/version.rb +1 -1
  29. data/sunstone.gemspec +5 -3
  30. data/test/active_record/associations/has_many_test.rb +30 -2
  31. data/test/active_record/eager_loading_test.rb +13 -1
  32. data/test/active_record/persistance_test.rb +38 -13
  33. data/test/active_record/query/count_test.rb +13 -0
  34. data/test/active_record/query_test.rb +7 -7
  35. data/test/active_record/rpc_test.rb +30 -0
  36. data/test/schema_mock.rb +31 -27
  37. data/test/sunstone/connection/column_definition_test.rb +30 -0
  38. data/test/sunstone/connection/configuration_test.rb +13 -13
  39. data/test/sunstone/connection/cookie_store_test.rb +2 -2
  40. data/test/sunstone/connection/request_helper_test.rb +12 -12
  41. data/test/sunstone/connection/send_request_test.rb +13 -13
  42. data/test/sunstone/connection_test.rb +2 -2
  43. data/test/test_helper.rb +1 -1
  44. metadata +38 -22
  45. data/ext/active_record/associations/association.rb +0 -16
  46. data/ext/active_record/batches.rb +0 -12
  47. data/ext/arel/attributes/relation.rb +0 -31
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d53af8c7411662e7621971b1c2a3f39f130ea8c3
4
- data.tar.gz: 8a61637315c7faf35265b47b8334901340b41798
2
+ SHA256:
3
+ metadata.gz: afae21d218aa760e3a36c2db071a80f25f825fb618f56c9bc9cb25ddb628ee23
4
+ data.tar.gz: a1b4c27957ba0330a74bab059b5b352c56d74eeafd0d9e910845badae194a0e5
5
5
  SHA512:
6
- metadata.gz: 690936bd2ba33aab551c98da50ea1871e29fdfa9207dd71c7bcd78050bfa56f219ad859c068ac33346833ffb44feb058bc2f41a46ab7c2e4f70b11c0f1449a62
7
- data.tar.gz: 6e9f804cc2b2e75584e184b9791dc3f5b2d0d682c71cd5ef1c60cb4ffdf1af23b6607bf3f650be218acb87f0173dad53b2621fffa3aa9dac40f72fc51efc4663
6
+ metadata.gz: 2bf28e210e209846f11c287b7153669721211b86e1e27de9388b52bbdeb37abe1ea53c8370abfc11ab50132275804796912eeeb67c0c4e83cd840f54be44634b
7
+ data.tar.gz: b0eb0399c0a7ff542d06b9b3b83a1dbc699d4de11b75e24813186fd6f6c6de375713903ecc4886efb6b78395bc44f77841238d365ec389ada64bfeca95449626
@@ -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.4.1
11
+ - 2.7
11
12
 
12
13
  env:
13
14
  matrix:
14
- - RAILS_VERSION=v5.1.0 GEM=ar:mysql2
15
- - RAILS_VERSION=v5.1.0 GEM=ar:sqlite3
16
- - RAILS_VERSION=v5.1.0 GEM=ar:postgresql
17
- - RAILS_VERSION=v5.1.1 GEM=ar:mysql2
18
- - RAILS_VERSION=v5.1.1 GEM=ar:sqlite3
19
- - RAILS_VERSION=v5.1.1 GEM=ar:postgresql
20
- - RAILS_VERSION=v5.1.2 GEM=ar:mysql2
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: "9.4"
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 && ci/travis.rb
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 = {}, &extension)
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
- self._reflections[name.to_s].parent_reflection = habtm_reflection
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).each { |record| callback(:before_remove, record) }
17
- (original_target - other_array).each { |record| target.delete(record) }
18
- (original_target - other_array).each { |record| callback(:after_remove, record) }
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).each do |record|
22
- add_to_target(record)
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 => validate)
47
+ record.save!(validate: validate, &block)
49
48
  else
50
- record.save(:validate => 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).save!
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 arel_attributes_with_values(attribute_names)
9
- attrs = {}
10
- arel_table = self.class.arel_table
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(arel_table[reflection.foreign_key])
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
- if record.new_record?
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
- if record.new_record?
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)) || reflection.options[:autosave] || association.target.any?(&:changed_for_autosave?) || association.target.any?(&:new_record?)
115
- attrs[Arel::Attributes::EmptyRelation.new(arel_table, reflection.name, true, true)] = [] if association.target.empty?
116
-
117
- association.target.each_with_index do |record, idx|
118
- next if record.destroyed?
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(*) #:nodoc:
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 = associated_predicate_builder(key).expand_from_hash(value)
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
- build(table.arel_attribute(key), value)
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
- def find_with_associations
43
- join_dependency = nil
44
- aliases = nil
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
- if block_given?
55
- yield relation
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
- end
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
- def instantiate_with_associations(result_set, klass)
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] = {} }
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
- model_cache = Hash.new { |h,kklass| h[kklass] = {} }
83
- parents = model_cache[self.base_class]
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
- result_set.each { |row_hash|
86
- parent = parents[row_hash[primary_key]] ||= instantiate(row_hash.select{|k,v| column_names.include?(k.to_s) })
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
- parents.values
91
- end
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
- def construct(parent, relations, seen, model_cache)
94
- relations.each do |key, attributes|
95
- reflection = parent.class.reflect_on_association(key)
96
- next unless reflection
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
- if reflection.collection?
99
- other = parent.association(reflection.name)
100
- other.loaded!
101
- else
102
- if parent.association_cached?(reflection.name)
103
- model = parent.association(reflection.name).target
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
- if !reflection.collection?
109
- construct_association(parent, reflection, attributes, seen, model_cache)
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
- attributes.each do |row|
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
- end
117
- end
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
- def construct_association(parent, reflection, attributes, seen, model_cache)
120
- return if attributes.nil?
154
+ other = parent.association(reflection.name)
121
155
 
122
- klass = if reflection.polymorphic?
123
- parent.send(reflection.foreign_type).constantize.base_class
124
- else
125
- reflection.klass
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
- other = parent.association(reflection.name)
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
- else
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
- def construct_model(record, reflection, id, attributes, seen, model_cache)
151
- klass = if reflection.polymorphic?
152
- record.send(reflection.foreign_type).constantize
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
- reflection.klass
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
- model = model_cache[klass][id] ||= klass.instantiate(attributes)
158
- other = record.association(reflection.name)
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 reflection.collection?
161
- other.target.push(model)
222
+ if block_given?
223
+ yield relation, join_dependency
162
224
  else
163
- other.target = model
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