sunstone 6.0.0.4 → 6.1.0.2

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
  SHA256:
3
- metadata.gz: 5ecbeb6967817d2f58c9602526839eeee2b9aa161dc00adf1f962549874c49af
4
- data.tar.gz: 7759fa8e537ae1dd04009dca78ae7ffad19cbb6166629e99778f2b22aa982837
3
+ metadata.gz: 52f0e0a557ddc1ec6d7dcc9728d3cfbf602d8c467d1647571cb887b56755e3cc
4
+ data.tar.gz: 8db1bd8849700b80328ac2cf1248ff5260659d0afc49648e694fae33790c79cc
5
5
  SHA512:
6
- metadata.gz: c72e778804a92ea85dadd3f7adbae2f5171ca63c47da09b065dcb9c14202a8d78e9ecd95ad15f7b2f0bfe7384a858497a1758b056112f703cc87fb8dd91607dd
7
- data.tar.gz: 26d94406a0f9b50ad8429c0dd5d49d3da1fd182e1b8b12d87d6e0f0604f343f0920a10b6f0597b4830e3d6856cf62e36b65382e081381f6d5e9b196e94f28c72
6
+ metadata.gz: a656ce5545ac0ad4a3f660f61857e8068a9c1f59060d4e9f9f06b1d80d2f6daca73e3317e02b35669a100ea1f8c578695f8ca0cc3a9eb2f00fc908708c1d18ff
7
+ data.tar.gz: 3395da82c6d52adc2029570a459f98cb7311c428a3f92c9f598d61ae8f0b7c5c7a354bb4d88933a6096e2cd5271997f929535f9ad2a35cda2c066d59472dae72
@@ -0,0 +1,141 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ sunstone:
11
+ name: Sunstone Test
12
+ runs-on: ubuntu-20.04
13
+
14
+ steps:
15
+ - uses: ruby/setup-ruby@v1
16
+ with:
17
+ ruby-version: 3.0
18
+
19
+ - uses: actions/checkout@v2
20
+
21
+ - run: bundle
22
+
23
+ - run: bundle exec rake test
24
+
25
+ ar-postgresql:
26
+ name: ActiveRecord PostgresQL Test
27
+ strategy:
28
+ matrix:
29
+ rails: [v6.1.3]
30
+
31
+ runs-on: ubuntu-20.04
32
+
33
+ steps:
34
+ - name: Install Postgresql
35
+ run: |
36
+ sudo apt-get install curl ca-certificates gnupg
37
+ curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -
38
+ sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
39
+ sudo apt-get update
40
+ sudo apt-get install postgresql-13
41
+ sudo systemctl start postgresql@13-main.service
42
+ sudo -u postgres createuser runner --superuser
43
+
44
+ - uses: ruby/setup-ruby@v1
45
+ with:
46
+ ruby-version: 3.0
47
+
48
+ - uses: actions/checkout@v2
49
+
50
+ - name: Download Rails
51
+ run: |
52
+ git clone --branch ${{ matrix.rails }} https://github.com/rails/rails.git ~/rails
53
+ pushd ~/rails
54
+ cat /home/runner/work/_temp/*.sh
55
+ sed -i "s/Gem.ruby, '-w'/Gem.ruby, '-w0'/" ~/rails/activerecord/Rakefile
56
+ sed -i "s/t.warning = true/t.warning = false/g" ~/rails/activerecord/Rakefile
57
+ sed -i "/require 'support\/connection'/a \$LOAD_PATH.unshift\(File.expand_path\(ENV['GITHUB_WORKSPACE']\)\)\nrequire 'sunstone'" ~/rails/activerecord/test/cases/helper.rb
58
+ rm ~/rails/Gemfile.lock
59
+ sed -i "/# Active Record./a gem 'sunstone', path: File.expand_path\(ENV['GITHUB_WORKSPACE']\)" ~/rails/Gemfile
60
+ cat ~/rails/Gemfile
61
+ bundle update --jobs=3 --retry=3
62
+
63
+ - run: |
64
+ pushd ~/rails/activerecord
65
+ bundle exec rake db:postgresql:rebuild postgresql:test
66
+ bundle exec rake db:postgresql:rebuild postgresql:isolated_test
67
+
68
+ ar-sqlite:
69
+ name: ActiveRecord SQLite Test
70
+ strategy:
71
+ matrix:
72
+ rails: [v6.1.3]
73
+
74
+ runs-on: ubuntu-20.04
75
+
76
+ steps:
77
+ - uses: ruby/setup-ruby@v1
78
+ with:
79
+ ruby-version: 3.0
80
+
81
+ - uses: actions/checkout@v2
82
+
83
+ - name: Download Rails
84
+ run: |
85
+ git clone --branch ${{ matrix.rails }} https://github.com/rails/rails.git ~/rails
86
+ pushd ~/rails
87
+ cat /home/runner/work/_temp/*.sh
88
+ sed -i "s/Gem.ruby, '-w'/Gem.ruby, '-w0'/" ~/rails/activerecord/Rakefile
89
+ sed -i "s/t.warning = true/t.warning = false/g" ~/rails/activerecord/Rakefile
90
+ sed -i "/require 'support\/connection'/a \$LOAD_PATH.unshift\(File.expand_path\(ENV['GITHUB_WORKSPACE']\)\)\nrequire 'sunstone'" ~/rails/activerecord/test/cases/helper.rb
91
+ rm ~/rails/Gemfile.lock
92
+ sed -i "/# Active Record./a gem 'sunstone', path: File.expand_path\(ENV['GITHUB_WORKSPACE']\)" ~/rails/Gemfile
93
+ cat ~/rails/Gemfile
94
+ bundle update --jobs=3 --retry=3
95
+
96
+ - run: |
97
+ pushd ~/rails/activerecord
98
+ bundle exec rake sqlite3:test
99
+ rm test/db/*.sqlite3 test/fixtures/*.sqlite3
100
+ bundle exec rake sqlite3:isolated_test
101
+ rm test/db/*.sqlite3 test/fixtures/*.sqlite3
102
+ bundle exec rake sqlite3_mem:test
103
+
104
+ ar-mysql:
105
+ name: ActiveRecord MySQL Test
106
+ strategy:
107
+ matrix:
108
+ rails: [v6.1.3]
109
+
110
+ runs-on: ubuntu-20.04
111
+
112
+ steps:
113
+ - name: Install MySQL
114
+ run: |
115
+ sudo /etc/init.d/mysql start
116
+ mysql -uroot -proot -e "CREATE USER 'rails'@'%';"
117
+ mysql -uroot -proot -e "GRANT ALL PRIVILEGES ON *.* TO 'rails'@'%' WITH GRANT OPTION;"
118
+
119
+ - uses: ruby/setup-ruby@v1
120
+ with:
121
+ ruby-version: 3.0
122
+
123
+ - uses: actions/checkout@v2
124
+
125
+ - name: Download Rails
126
+ run: |
127
+ git clone --branch ${{ matrix.rails }} https://github.com/rails/rails.git ~/rails
128
+ pushd ~/rails
129
+ cat /home/runner/work/_temp/*.sh
130
+ sed -i "s/Gem.ruby, '-w'/Gem.ruby, '-w0'/" ~/rails/activerecord/Rakefile
131
+ sed -i "s/t.warning = true/t.warning = false/g" ~/rails/activerecord/Rakefile
132
+ sed -i "/require 'support\/connection'/a \$LOAD_PATH.unshift\(File.expand_path\(ENV['GITHUB_WORKSPACE']\)\)\nrequire 'sunstone'" ~/rails/activerecord/test/cases/helper.rb
133
+ rm ~/rails/Gemfile.lock
134
+ sed -i "/# Active Record./a gem 'sunstone', path: File.expand_path\(ENV['GITHUB_WORKSPACE']\)" ~/rails/Gemfile
135
+ cat ~/rails/Gemfile
136
+ bundle update --jobs=3 --retry=3
137
+
138
+ - run: |
139
+ pushd ~/rails/activerecord
140
+ bundle exec rake db:mysql:rebuild mysql2:test
141
+ bundle exec rake db:mysql:rebuild mysql2:isolated_test
data/README.md CHANGED
@@ -1,8 +1,44 @@
1
- # Sunstone [![Travis CI](https://travis-ci.org/malomalo/sunstone.svg)](https://travis-ci.org/malomalo/sunstone)
1
+ # Sunstone
2
2
 
3
- An [ActiveRecord](https://rubygems.org/gems/activerecord) adapter for quering
4
- APIs over Standard API (https://github.com/waratuman/standardapi).
3
+ Sunstone is an [ActiveRecord](https://rubygems.org/gems/activerecord) adapter for quering
4
+ APIs conforming to [Standard API](https://github.com/waratuman/standardapi).
5
5
 
6
+ Configuration
7
+ -------------
8
+
9
+ ### Rails
10
+
11
+ Add `sunstone` to your Gemfile:
12
+
13
+ ```ruby
14
+ gem 'sunstone'
15
+ ```
16
+
17
+ Update `config/database.yml`"
18
+
19
+ ```yaml
20
+ development:
21
+ adapter: sunstone
22
+ url: https://mystanda.rd/api
23
+ api_key: ..optional..
24
+ user_agent: ..optional..
25
+ ```
26
+
27
+ ### Standalone ActiveRecord
28
+
29
+ Initialize the connection on `ActiveRecord::Base` or your abstract model (`ApplicationRecord` for example)
30
+
31
+ ```ruby
32
+ ActiveRecord::Base.establish_connection(
33
+ adapter: 'sunstone',
34
+ url: 'https://mystanda.rd/api'
35
+ )
36
+ ```
37
+
38
+ Usage
39
+ -----
40
+
41
+ Mention fitler / etc...
6
42
 
7
43
  TODO:
8
44
  =====
@@ -35,11 +35,11 @@ module ActiveRecord
35
35
  hm_options[:through] = middle_reflection.name
36
36
  hm_options[:source] = join_model.right_reflection.name
37
37
 
38
- [: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|
39
39
  hm_options[k] = options[k] if options.key? k
40
40
  end
41
41
 
42
- has_many name, scope, hm_options, &extension
42
+ has_many name, scope, **hm_options, &extension
43
43
  _reflections[name.to_s].parent_reflection = habtm_reflection
44
44
  end
45
45
  end
@@ -6,8 +6,8 @@ module ActiveRecord
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
8
  def attributes_with_values(attribute_names)
9
- attrs = attribute_names.each_with_object({}) do |name, attrs|
10
- attrs[name] = _read_attribute(name)
9
+ attrs = attribute_names.index_with do |name|
10
+ _read_attribute(name)
11
11
  end
12
12
 
13
13
  if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
@@ -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,24 +1,12 @@
1
- module Arel
2
- module Visitors
3
- class ToSql < Arel::Visitors::Visitor
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
8
  if value.is_a?(Hash) && !table.has_column?(key)
21
- ka = associated_predicate_builder(key).expand_from_hash(value)
9
+ ka = table.associated_table(key, &block).predicate_builder.expand_from_hash(value.stringify_keys)
22
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)
@@ -40,13 +28,18 @@ module ActiveRecord
40
28
  value = [value] unless value.is_a?(Array)
41
29
  klass = PolymorphicArrayValue
42
30
  end
31
+ elsif associated_table.through_association?
32
+ next associated_table.predicate_builder.expand_from_hash(
33
+ associated_table.primary_key => value
34
+ )
43
35
  end
44
36
 
45
37
  klass ||= AssociationQueryValue
46
- queries = klass.new(associated_table, value).queries.map do |query|
47
- expand_from_hash(query).reduce(&:and)
38
+ queries = klass.new(associated_table, value).queries.map! do |query|
39
+ expand_from_hash(query)
48
40
  end
49
- queries.reduce(&:or)
41
+
42
+ grouping_queries(queries)
50
43
  elsif table.aggregated_with?(key)
51
44
  mapping = table.reflect_on_aggregation(key).mapping
52
45
  values = value.nil? ? [nil] : Array.wrap(value)
@@ -55,17 +48,18 @@ module ActiveRecord
55
48
  values = values.map do |object|
56
49
  object.respond_to?(aggr_attr) ? object.public_send(aggr_attr) : object
57
50
  end
58
- build(table.arel_attribute(column_name), values)
51
+ self[column_name, values]
59
52
  else
60
53
  queries = values.map do |object|
61
54
  mapping.map do |field_attr, aggregate_attr|
62
- build(table.arel_attribute(field_attr), object.try!(aggregate_attr))
63
- end.reduce(&:and)
55
+ self[field_attr, object.try!(aggregate_attr)]
56
+ end
64
57
  end
65
- queries.reduce(&:or)
58
+
59
+ grouping_queries(queries)
66
60
  end
67
61
  else
68
- build(table.arel_attribute(key), value)
62
+ self[key, value]
69
63
  end
70
64
  end
71
65
  end
@@ -89,7 +83,7 @@ module ActiveRecord
89
83
  relation
90
84
  end
91
85
 
92
- def instantiate(result_set, &block)
86
+ def instantiate(result_set, strict_loading_value, &block)
93
87
  seen = Hash.new { |i, object_id|
94
88
  i[object_id] = Hash.new { |j, child_class|
95
89
  j[child_class] = {}
@@ -110,14 +104,14 @@ module ActiveRecord
110
104
  result_set.each { |row_hash|
111
105
  parent_key = @klass.primary_key ? row_hash[@klass.primary_key] : row_hash
112
106
  parent = parents[parent_key] ||= @klass.instantiate(row_hash.select{|k,v| @klass.column_names.include?(k.to_s) }, &block)
113
- construct(parent, row_hash.select{|k,v| !@klass.column_names.include?(k.to_s) }, seen, model_cache)
107
+ construct(parent, row_hash.select{|k,v| !@klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
114
108
  }
115
109
  end
116
110
 
117
111
  parents.values
118
112
  end
119
113
 
120
- def construct(parent, relations, seen, model_cache)
114
+ def construct(parent, relations, seen, model_cache, strict_loading_value)
121
115
  relations.each do |key, attributes|
122
116
  reflection = parent.class.reflect_on_association(key)
123
117
  next unless reflection
@@ -128,22 +122,22 @@ module ActiveRecord
128
122
  else
129
123
  if parent.association_cached?(reflection.name)
130
124
  model = parent.association(reflection.name).target
131
- construct(model, attributes.select{|k,v| !model.class.column_names.include?(k.to_s) }, seen, model_cache)
125
+ construct(model, attributes.select{|k,v| !model.class.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
132
126
  end
133
127
  end
134
128
 
135
129
  if !reflection.collection?
136
- construct_association(parent, reflection, attributes, seen, model_cache)
130
+ construct_association(parent, reflection, attributes, seen, model_cache, strict_loading_value)
137
131
  else
138
132
  attributes.each do |row|
139
- construct_association(parent, reflection, row, seen, model_cache)
133
+ construct_association(parent, reflection, row, seen, model_cache, strict_loading_value)
140
134
  end
141
135
  end
142
136
 
143
137
  end
144
138
  end
145
139
 
146
- def construct_association(parent, reflection, attributes, seen, model_cache)
140
+ def construct_association(parent, reflection, attributes, seen, model_cache, strict_loading_value)
147
141
  return if attributes.nil?
148
142
 
149
143
  klass = if reflection.polymorphic?
@@ -155,7 +149,7 @@ module ActiveRecord
155
149
  model = seen[parent.object_id][klass][id]
156
150
 
157
151
  if model
158
- construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache)
152
+ construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
159
153
 
160
154
  other = parent.association(reflection.name)
161
155
 
@@ -167,14 +161,14 @@ module ActiveRecord
167
161
 
168
162
  other.set_inverse_instance(model)
169
163
  else
170
- model = construct_model(parent, reflection, id, attributes.select{|k,v| klass.column_names.include?(k.to_s) }, seen, model_cache)
164
+ model = construct_model(parent, reflection, id, attributes.select{|k,v| klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
171
165
  seen[parent.object_id][model.class.base_class][id] = model
172
- construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache)
166
+ construct(model, attributes.select{|k,v| !klass.column_names.include?(k.to_s) }, seen, model_cache, strict_loading_value)
173
167
  end
174
168
  end
175
169
 
176
170
 
177
- def construct_model(record, reflection, id, attributes, seen, model_cache)
171
+ def construct_model(record, reflection, id, attributes, seen, model_cache, strict_loading_value)
178
172
  klass = if reflection.polymorphic?
179
173
  record.send(reflection.foreign_type).constantize
180
174
  else
@@ -202,11 +196,22 @@ module ActiveRecord
202
196
  relation = except(:includes, :eager_load, :preload)
203
197
  relation.arel.eager_load = Arel::Nodes::EagerLoad.new(eager_load_values)
204
198
  else
205
- join_dependency = construct_join_dependency(eager_load_values + includes_values)
199
+ join_dependency = construct_join_dependency(
200
+ eager_load_values | includes_values, Arel::Nodes::OuterJoin
201
+ )
206
202
  relation = except(:includes, :eager_load, :preload).joins!(join_dependency)
207
203
  end
208
204
 
209
- if eager_loading && !using_limitable_reflections?(join_dependency.reflections)
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
+ )
210
215
  if has_limit_or_offset?
211
216
  limited_ids = limited_ids_for(relation)
212
217
  limited_ids.empty? ? relation.none! : relation.where!(primary_key => limited_ids)
@@ -105,6 +105,8 @@ module ActiveRecord
105
105
  @_trigger_update_callback = affected_rows == 1
106
106
  end
107
107
 
108
+ @previously_new_record = false
109
+
108
110
  yield(self) if block_given?
109
111
 
110
112
  affected_rows
@@ -5,13 +5,13 @@ module ActiveRecord
5
5
  if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
6
6
  return records.pluck(*column_names)
7
7
  end
8
-
8
+
9
9
  if has_include?(column_names.first)
10
10
  relation = apply_join_dependency
11
11
  relation.pluck(*column_names)
12
12
  elsif klass.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
13
13
  load
14
- return records.pluck(*column_names.map{|n| n.sub(/^#{klass.table_name}\./, "")})
14
+ return records.pluck(*column_names.map{|n| n.to_s.sub(/^#{klass.table_name}\./, "")})
15
15
  else
16
16
  klass.disallow_raw_sql!(column_names)
17
17
  relation = spawn
@@ -5,10 +5,9 @@ module ActiveRecord
5
5
  def initialize(values, sunstone=false)
6
6
  @values = values
7
7
  @indexes = if sunstone
8
-
9
8
  else
10
- values.each_with_index.find_all { |thing,i|
11
- Arel::Nodes::BindParam === thing
9
+ values.each_with_index.find_all { |thing, i|
10
+ Substitute === thing
12
11
  }.map(&:last)
13
12
  end
14
13
  end
@@ -19,8 +18,13 @@ module ActiveRecord
19
18
  @values
20
19
  else
21
20
  val = @values.dup
22
- casted_binds = binds.map(&:value_for_database)
23
- @indexes.each { |i| val[i] = connection.quote(casted_binds.shift) }
21
+ @indexes.each do |i|
22
+ value = binds.shift
23
+ if ActiveModel::Attribute === value
24
+ value = value.value_for_database
25
+ end
26
+ val[i] = connection.quote(value)
27
+ end
24
28
  val.join
25
29
  end
26
30
  end