sunstone 6.0.0.4 → 6.1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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