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.
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