sunstone 8.0.0 → 8.1.0

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -0
  3. data/README.md +221 -0
  4. data/ext/active_record/associations/collection_association.rb +45 -63
  5. data/ext/active_record/associations/has_many_through_association.rb +21 -0
  6. data/ext/active_record/associations.rb +12 -10
  7. data/ext/active_record/attribute_methods.rb +4 -2
  8. data/ext/active_record/base.rb +16 -0
  9. data/ext/active_record/callbacks.rb +4 -2
  10. data/ext/active_record/locking/optimistic.rb +60 -0
  11. data/ext/active_record/persistence.rb +35 -12
  12. data/ext/active_record/relation/calculations.rb +26 -24
  13. data/ext/active_record/relation/finder_methods.rb +165 -0
  14. data/ext/active_record/relation/predicate_builder.rb +91 -0
  15. data/ext/active_record/relation/query_methods.rb +6 -1
  16. data/ext/active_record/relation.rb +5 -0
  17. data/ext/active_record/statement_cache.rb +11 -3
  18. data/ext/active_record/transactions.rb +29 -15
  19. data/ext/active_support/core_ext/object/to_query.rb +2 -0
  20. data/ext/arel/attributes/empty_relation.rb +2 -0
  21. data/ext/arel/nodes/eager_load.rb +2 -0
  22. data/ext/arel/nodes/select_statement.rb +2 -0
  23. data/ext/arel/select_manager.rb +2 -0
  24. data/lib/active_record/connection_adapters/sunstone/column.rb +4 -1
  25. data/lib/active_record/connection_adapters/sunstone/database_statements.rb +12 -1
  26. data/lib/active_record/connection_adapters/sunstone/quoting.rb +2 -0
  27. data/lib/active_record/connection_adapters/sunstone/schema_dumper.rb +2 -0
  28. data/lib/active_record/connection_adapters/sunstone/schema_statements.rb +6 -4
  29. data/lib/active_record/connection_adapters/sunstone/type/array.rb +2 -0
  30. data/lib/active_record/connection_adapters/sunstone/type/binary.rb +2 -0
  31. data/lib/active_record/connection_adapters/sunstone/type/date_time.rb +2 -0
  32. data/lib/active_record/connection_adapters/sunstone/type/ewkb.rb +2 -0
  33. data/lib/active_record/connection_adapters/sunstone/type/json.rb +2 -0
  34. data/lib/active_record/connection_adapters/sunstone/type/uuid.rb +2 -0
  35. data/lib/active_record/connection_adapters/sunstone/type_metadata.rb +2 -0
  36. data/lib/active_record/connection_adapters/sunstone_adapter.rb +2 -0
  37. data/lib/arel/collectors/sunstone.rb +15 -5
  38. data/lib/arel/visitors/sunstone.rb +2 -3
  39. data/lib/sunstone/connection.rb +2 -0
  40. data/lib/sunstone/exception.rb +2 -0
  41. data/lib/sunstone/gis.rb +2 -0
  42. data/lib/sunstone/version.rb +3 -1
  43. data/lib/sunstone.rb +9 -1
  44. metadata +23 -16
  45. data/ext/active_record/finder_methods.rb +0 -246
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd0eae0dbdee21a95ca29317302b67b50647f7d6654af15553425f55d1d11aed
4
- data.tar.gz: b0cdc960489411360b1ee4f2cb4cdcce93b7df2464dabdafa2bc26955be002b3
3
+ metadata.gz: e9d85052377699e6030067ce89f33b964b2193a1cd345a2ed013682fd457ecb3
4
+ data.tar.gz: 6f1f0054a2d338d2aaa6dd7ab9528a4e8be697a72ff670772afd89fe5d1928f7
5
5
  SHA512:
6
- metadata.gz: ce768f425153db1d5a2242f845bc3de221a4d0d0d8a30a1428c128d2bb1e4353ef986020283fdd1d17e7652937c38d03eb1f42478acbc694da32135f335cab22
7
- data.tar.gz: ece020a27e0554bad07f0e831919e8969cebdd6970adc344fb4f018b3a45032588dcd7c4eb512d9fcb8ff4acfc09ef27d9fe6124fa2b9db0d5eeff659885d800
6
+ metadata.gz: fcb5ec1577d0671923144212ab4048276db625873b9e1cdf4a6f5e8f655de64ee0c60593167efb5e04c04fa37214763184ac5f6f8e881ec1b2b952244d1b0b58
7
+ data.tar.gz: 1441be879d54bbaa74f3f980813809f08b4c5c4497e35da5e40fe4f64d2a4157482b5c06413840f75ee4e8af087e4b0525b2fa8fd1adf2d1ca6608441adb38ae
data/CHANGELOG.md ADDED
@@ -0,0 +1,43 @@
1
+ ## [8.1.0] - 2026-08-27
2
+
3
+ ### Changed
4
+ - Support both Rails 8.0 and 8.1. The ActiveRecord overrides in `ext/` had been
5
+ synced to Rails 8.1 and used 8.1-only APIs; they now work against 8.0 as well.
6
+ - Require Ruby >= 3.3. The gemspec previously declared `>= 2.6`, which the
7
+ `activerecord >= 8.0.1` dependency never actually permitted.
8
+ - Require `arel-extensions >= 9.0.0` and `activerecord-filter >= 9.0.0`; the
9
+ previous `>= 7.x` floors predated Rails 8 support.
10
+
11
+ ### Added
12
+ - Declare the MIT license in the gemspec.
13
+ - Add gemspec `metadata` (source code and changelog URIs, `rubygems_mfa_required`).
14
+
15
+ ### Fixed
16
+ - Package `README.md` in the published gem. The gemspec referenced a
17
+ non-existent `README.rdoc`, so the README was omitted from the built gem.
18
+
19
+ ### Internal
20
+ - Modernized CI: test Rails 8.0 and 8.1 across Ruby 3.3–4.0, run the upstream
21
+ ActiveRecord suites on Postgres 18, SQLite, and a MySQL service container,
22
+ auto-resolve the latest Rails patch per series, and guard every upstream
23
+ source patch so the build fails loudly if an anchor moves.
24
+
25
+ ## Earlier releases
26
+
27
+ Prior versions tracked the matching Rails release. See the Git tags for the
28
+ full history; notable releases:
29
+
30
+ - [8.0.0] - 2025-01-24
31
+ - [7.1.0.1] - 2023-12-20
32
+ - [7.1.0] - 2023-12-15
33
+ - [6.1.0.2] - 2021-03-23
34
+ - [6.1.0] - 2021-01-14
35
+ - [6.0.0] - 2019-06-10
36
+
37
+ [8.1.0]: https://github.com/malomalo/sunstone/compare/v8.0.0...v8.1.0
38
+ [8.0.0]: https://github.com/malomalo/sunstone/releases/tag/v8.0.0
39
+ [7.1.0.1]: https://github.com/malomalo/sunstone/releases/tag/v7.1.0.1
40
+ [7.1.0]: https://github.com/malomalo/sunstone/releases/tag/v7.1.0
41
+ [6.1.0.2]: https://github.com/malomalo/sunstone/releases/tag/v6.1.0.2
42
+ [6.1.0]: https://github.com/malomalo/sunstone/releases/tag/v6.1.0
43
+ [6.0.0]: https://github.com/malomalo/sunstone/releases/tag/v6.0.0
data/README.md ADDED
@@ -0,0 +1,221 @@
1
+ # Sunstone
2
+
3
+ Sunstone is an [ActiveRecord](https://rubygems.org/gems/activerecord) adapter for
4
+ querying APIs that conform to [StandardAPI](https://github.com/waratuman/standardapi).
5
+
6
+ It lets you talk to a remote HTTP API using the ActiveRecord query interface you
7
+ already know — `where`, `order`, `limit`, `find`, associations, and persistence
8
+ (`create`/`update`/`destroy`) — while Sunstone translates those calls into
9
+ StandardAPI HTTP requests instead of SQL.
10
+
11
+ ## Requirements
12
+
13
+ - Ruby >= 3.3
14
+ - ActiveRecord >= 8.0.1 (Rails 8.0 and 8.1)
15
+
16
+ ## Installation
17
+
18
+ Add `sunstone` to your Gemfile:
19
+
20
+ ```ruby
21
+ gem 'sunstone'
22
+ ```
23
+
24
+ ## Configuration
25
+
26
+ ### Rails
27
+
28
+ Configure the connection in `config/database.yml`:
29
+
30
+ ```yaml
31
+ development:
32
+ adapter: sunstone
33
+ url: https://my-api-key@mystanda.rd/api
34
+ ```
35
+
36
+ The connection accepts the following options:
37
+
38
+ | Option | Description |
39
+ | ------------- | ------------------------------------------------------------------ |
40
+ | `url` | Sets `host`, `port`, `use_ssl`, and (from userinfo) `api_key` |
41
+ | `host` | API host (defaults to `127.0.0.1`) |
42
+ | `port` | API port (defaults to `80`, or `443` when `use_ssl`) |
43
+ | `use_ssl` | Connect over HTTPS (inferred from an `https://` url) |
44
+ | `api_key` | Token sent in the `Api-Key` request header |
45
+ | `user_agent` | String prepended to Sunstone's own `User-Agent` |
46
+ | `ca_cert` | Path to a CA certificate file (used when `use_ssl` is set) |
47
+
48
+ `url` is a convenience that fills in the others, so this:
49
+
50
+ ```yaml
51
+ url: https://my-api-key@mystanda.rd:443/api
52
+ ```
53
+
54
+ is equivalent to:
55
+
56
+ ```yaml
57
+ host: mystanda.rd
58
+ port: 443
59
+ use_ssl: true
60
+ api_key: my-api-key
61
+ ```
62
+
63
+ ### Standalone ActiveRecord
64
+
65
+ Establish the connection on `ActiveRecord::Base` (or your abstract model, e.g.
66
+ `ApplicationRecord`):
67
+
68
+ ```ruby
69
+ ActiveRecord::Base.establish_connection(
70
+ adapter: 'sunstone',
71
+ url: 'https://my-api-key@mystanda.rd/api'
72
+ )
73
+ ```
74
+
75
+ ## Usage
76
+
77
+ The models below are used throughout the examples:
78
+
79
+ ```ruby
80
+ class Fleet < ApplicationRecord
81
+ has_many :ships
82
+ end
83
+
84
+ class Ship < ApplicationRecord
85
+ belongs_to :fleet
86
+ has_and_belongs_to_many :sailors
87
+ end
88
+ ```
89
+
90
+ ### Querying
91
+
92
+ ```ruby
93
+ Ship.find(42) # where: {id: 42}, limit: 1
94
+ Ship.all # GET /ships
95
+ Ship.where(id: 10) # where: {id: 10}
96
+ Ship.where(fleet_id: nil) # where: {fleet_id: nil}
97
+ Ship.where(id: [10, 12]) # id IN (10, 12)
98
+ Ship.order(:id) # order: [{id: :asc}]
99
+ Ship.limit(5000) # limit: 5000
100
+ ```
101
+
102
+ `find_each` pages through the collection automatically:
103
+
104
+ ```ruby
105
+ Ship.find_each { |ship| ... } # GET /ships in batches with limit/offset
106
+ ```
107
+
108
+ Arel predicates are supported for comparisons and boolean logic:
109
+
110
+ ```ruby
111
+ Ship.where(Ship.arel_table[:id].gt(10))
112
+ Ship.where(Ship.arel_table[:id].eq(10).or(Ship.arel_table[:name].eq('name')))
113
+ ```
114
+
115
+ You can inspect the query a relation will send:
116
+
117
+ ```ruby
118
+ Ship.where(id: 10).to_sql
119
+ # => "SELECT ships.* FROM ships WHERE ships.id = 10"
120
+ ```
121
+
122
+ ### Querying across associations
123
+
124
+ Pass a nested hash to filter on an associated resource:
125
+
126
+ ```ruby
127
+ Ship.where(fleet: { id: 1 }) # belongs_to
128
+ Fleet.where(ships: { id: 1 }) # has_many
129
+ Ship.where(sailors: { id: 1 }) # has_and_belongs_to_many
130
+ Fleet.where(ships: { sailors: { id: 1 } }) # nested
131
+ ```
132
+
133
+ ### Aggregations
134
+
135
+ ```ruby
136
+ Ship.count # select: [{count: "*"}] (GET /ships/calculate)
137
+ Ship.count(:id) # select: [{count: "id"}]
138
+ Ship.sum(:weight) # select: [{sum: "weight"}]
139
+ Ship.distinct # distinct: true
140
+ ```
141
+
142
+ ### Eager loading
143
+
144
+ `eager_load` loads associations in a single request; `preload` issues a
145
+ separate request per association:
146
+
147
+ ```ruby
148
+ Fleet.eager_load(ships: :sailors) # one request, nested payload
149
+ Fleet.preload(ships: :sailors) # a request per association
150
+ ```
151
+
152
+ ### Persistence
153
+
154
+ ```ruby
155
+ fleet = Fleet.create(name: 'Armada Uno') # POST /fleets
156
+ ship = Ship.find(1)
157
+ ship.update(name: 'Definant') # PATCH /ships/1
158
+ ship.destroy # DELETE /ships/1
159
+ ```
160
+
161
+ Validation errors returned by the API (e.g. a `400` response) are written back
162
+ onto the record:
163
+
164
+ ```ruby
165
+ fleet = Fleet.create(name: nil)
166
+ fleet.errors[:name] # => [...] populated from the API response
167
+ ```
168
+
169
+ Associations can be written through the parent, matching ActiveRecord's nested
170
+ attributes and `*_ids` conventions:
171
+
172
+ ```ruby
173
+ # belongs_to — creates the fleet alongside the ship
174
+ Ship.create(name: 'Definant', fleet: Fleet.new(name: 'Armada Duo'))
175
+
176
+ # has_many — assign by id, by records, or clear
177
+ Fleet.create(name: 'Spanish Armada', ship_ids: [2])
178
+ fleet.update(ships: fleet.ships + [Ship.find(3)])
179
+ fleet.update(ships: []) # clears the relation
180
+
181
+ # has_and_belongs_to_many
182
+ ship.update(sailors: [Sailor.find(1)])
183
+ ```
184
+
185
+ ### RPC
186
+
187
+ `rpc` declares a custom, non-CRUD action that maps to a `POST` on a member
188
+ endpoint. Any attributes the API returns are written back onto the record:
189
+
190
+ ```ruby
191
+ class Ship < ApplicationRecord
192
+ rpc :self_destruct
193
+ end
194
+
195
+ ship.self_destruct! # POST /ships/:id/self_destruct
196
+ ```
197
+
198
+ ## Column types
199
+
200
+ Beyond the standard ActiveRecord types, the adapter registers:
201
+
202
+ - `array` — JSON-serialized arrays of any subtype
203
+ - `json`
204
+ - `uuid` — validated and cast as a UUID
205
+ - `binary` — Base64-encoded over the wire
206
+ - `datetime` — serialized as ISO 8601
207
+
208
+ Geospatial (GIS) support is opt-in. Require it to register the `geometry` /
209
+ `geography` (EWKB) types, which use [RGeo](https://rubygems.org/gems/rgeo):
210
+
211
+ ```ruby
212
+ require 'sunstone/gis'
213
+ ```
214
+
215
+ ## License
216
+
217
+ Sunstone is released under the [MIT License](LICENSE).
218
+
219
+ ## Changelog
220
+
221
+ See [CHANGELOG.md](CHANGELOG.md).
@@ -1,73 +1,55 @@
1
- # The last ref that this code was synced with Rails
2
- # ref: 9269f634d471ad6ca46752421eabd3e1c26220b5
3
- module ActiveRecord
4
- module Associations
5
- class CollectionAssociation
6
-
7
- def replace(other_array)
8
- other_array.each { |val| raise_on_type_mismatch!(val) }
9
- original_target = skip_strict_loading { load_target }.dup
10
-
11
- if owner.new_record?
12
- replace_records(other_array, original_target)
13
- elsif owner.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && owner.instance_variable_defined?(:@sunstone_updating) && owner.instance_variable_get(:@sunstone_updating)
14
- replace_common_records_in_memory(other_array, original_target)
15
-
16
- # Remove from target
17
- records_for_removal = (original_target - other_array)
18
- if !records_for_removal.empty?
19
- self.instance_variable_set(:@sunstone_changed, true)
20
- records_for_removal.each { |record| callback(:before_remove, record) }
21
- records_for_removal.each { |record| target.delete(record) }
22
- records_for_removal.each { |record| callback(:after_remove, record) }
23
- end
1
+ # frozen_string_literal: true
24
2
 
25
- # Add to target
26
- records_for_addition = (other_array - original_target)
27
- if !records_for_addition.empty?
28
- self.instance_variable_set(:@sunstone_changed, true)
29
- (other_array - original_target).each do |record|
30
- add_to_target(record)
31
- end
32
- end
33
-
34
- other_array
35
- else
36
- replace_common_records_in_memory(other_array, original_target)
37
- if other_array != original_target
38
- transaction { replace_records(other_array, original_target) }
39
- else
40
- other_array
41
- end
42
- end
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+ class ActiveRecord::Associations::CollectionAssociation
6
+
7
+ def replace(other_array)
8
+ other_array.each { |val| raise_on_type_mismatch!(val) }
9
+ original_target = skip_strict_loading { load_target }.dup
10
+
11
+ if owner.new_record?
12
+ replace_records(other_array, original_target)
13
+ elsif owner.sunstone? && owner.instance_variable_defined?(:@sunstone_updating) && owner.instance_variable_get(:@sunstone_updating)
14
+ replace_common_records_in_memory(other_array, original_target)
15
+
16
+ # Remove from target
17
+ records_for_removal = (original_target - other_array)
18
+ if !records_for_removal.empty?
19
+ self.instance_variable_set(:@sunstone_changed, true)
20
+ records_for_removal.each { |record| callback(:before_remove, record) }
21
+ records_for_removal.each { |record| target.delete(record) }
22
+ records_for_removal.each { |record| callback(:after_remove, record) }
43
23
  end
44
24
 
45
- def insert_record(record, validate = true, raise = false, &block)
46
- if record.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter) && owner.instance_variable_defined?(:@sunstone_updating) && owner.instance_variable_get(:@sunstone_updating)
47
- true
48
- elsif raise
49
- record.save!(validate: validate, &block)
50
- else
51
- record.save(validate: validate, &block)
25
+ # Add to target
26
+ records_for_addition = (other_array - original_target)
27
+ if !records_for_addition.empty?
28
+ self.instance_variable_set(:@sunstone_changed, true)
29
+ (other_array - original_target).each do |record|
30
+ add_to_target(record)
52
31
  end
53
32
  end
54
33
 
55
- end
56
-
57
- class HasManyThroughAssociation
58
-
59
- private
60
- def save_through_record(record)
61
- return if record.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
62
- association = build_through_record(record)
63
- if association.changed?
64
- association.save!
65
- end
66
- ensure
67
- @through_records.delete(record.object_id)
34
+ other_array
35
+ else
36
+ replace_common_records_in_memory(other_array, original_target)
37
+ if other_array != original_target
38
+ transaction { replace_records(other_array, original_target) }
39
+ else
40
+ other_array
68
41
  end
69
-
70
42
  end
43
+ end
71
44
 
45
+ def insert_record(record, validate = true, raise = false, &block)
46
+ if record.sunstone? && owner.instance_variable_defined?(:@sunstone_updating) && owner.instance_variable_get(:@sunstone_updating)
47
+ true
48
+ elsif raise
49
+ record.save!(validate: validate, &block)
50
+ else
51
+ record.save(validate: validate, &block)
52
+ end
72
53
  end
73
- end
54
+
55
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
6
+ class ActiveRecord::Associations::CollectionAssociation::HasManyThroughAssociation
7
+
8
+ private
9
+
10
+ def save_through_record(record)
11
+ return if record.sunstone?
12
+
13
+ association = build_through_record(record)
14
+ if association.changed?
15
+ association.save!
16
+ end
17
+ ensure
18
+ @through_records.delete(record)
19
+ end
20
+
21
+ end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # The last ref that this code was synced with Rails
2
- # ref: 9269f634d471ad6ca46752421eabd3e1c26220b5
4
+ # ref: 90a1eaa1b3
3
5
 
4
6
  require 'active_record'
5
7
  require 'active_record/associations'
@@ -10,23 +12,23 @@ module ActiveRecord
10
12
  def has_and_belongs_to_many(name, scope = nil, **options, &extension)
11
13
  habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
12
14
 
13
- builder = Builder::HasAndBelongsToMany.new name, self, options
15
+ builder = Builder::HasAndBelongsToMany.new(name, self, options)
14
16
 
15
17
  join_model = builder.through_model
16
18
 
17
- const_set join_model.name, join_model
18
- private_constant join_model.name
19
+ const_set(join_model.name, join_model)
20
+ private_constant(join_model.name)
19
21
 
20
- middle_reflection = builder.middle_reflection join_model
22
+ middle_reflection = builder.middle_reflection(join_model)
21
23
 
22
- Builder::HasMany.define_callbacks self, middle_reflection
23
- Reflection.add_reflection self, middle_reflection.name, middle_reflection
24
+ Builder::HasMany.define_callbacks(self, middle_reflection)
25
+ Reflection.add_reflection(self, middle_reflection.name, middle_reflection)
24
26
  middle_reflection.parent_reflection = habtm_reflection
25
27
 
26
28
  include Module.new {
27
29
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
28
30
  def destroy_associations
29
- if !self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
31
+ if !self.sunstone?
30
32
  association(:#{middle_reflection.name}).delete_all(:delete_all)
31
33
  association(:#{name}).reset
32
34
  end
@@ -39,8 +41,8 @@ module ActiveRecord
39
41
  hm_options[:through] = middle_reflection.name
40
42
  hm_options[:source] = join_model.right_reflection.name
41
43
 
42
- [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k|
43
- hm_options[k] = options[k] if options.key? k
44
+ [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading, :deprecated].each do |k|
45
+ hm_options[k] = options[k] if options.key?(k)
44
46
  end
45
47
 
46
48
  has_many name, scope, **hm_options, &extension
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # The last ref that this code was synced with Rails
2
- # ref: 9269f634d471ad6ca46752421eabd3e1c26220b5
4
+ # ref: 90a1eaa1b3
3
5
 
4
6
  module ActiveRecord
5
7
  module AttributeMethods
@@ -11,7 +13,7 @@ module ActiveRecord
11
13
  def attributes_with_values(attribute_names)
12
14
  attrs = attribute_names.index_with { |name| @attributes[name] }
13
15
 
14
- if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
16
+ if self.sunstone?
15
17
  self.class.reflect_on_all_associations.each do |reflection|
16
18
  if reflection.belongs_to?
17
19
  if association(reflection.name).loaded? && association(reflection.name).target == Thread.current[:sunstone_updating_model]
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
6
+ class ActiveRecord::Base
7
+
8
+ def self.sunstone?
9
+ connection_pool.db_config.adapter_class == ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter
10
+ end
11
+
12
+ def sunstone?
13
+ self.class.sunstone?
14
+ end
15
+
16
+ end
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # The last ref that this code was synced with Rails
2
- # ref: 9269f634d471ad6ca46752421eabd3e1c26220b5
4
+ # ref: 90a1eaa1b3
3
5
 
4
6
  module ActiveRecord
5
7
  module Callbacks
6
8
  private
7
9
 
8
10
  def create_or_update(**) #:nodoc:
9
- if self.class.connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
11
+ if self.sunstone?
10
12
  @_already_called ||= {}
11
13
  self.class.reflect_on_all_associations.each do |r|
12
14
  @_already_called[:"autosave_associated_records_for_#{r.name}"] = true
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
6
+ module ActiveRecord
7
+ module Locking
8
+ module Optimistic
9
+
10
+ private
11
+
12
+ def _update_row(attribute_values, attempted_action = "update")
13
+ return super unless locking_enabled?
14
+
15
+ begin
16
+ locking_column = self.class.locking_column
17
+ lock_attribute_was = @attributes[locking_column]
18
+
19
+ update_constraints = _query_constraints_hash
20
+
21
+ if self[locking_column].nil?
22
+ raise(<<-MSG.squish)
23
+ For optimistic locking, locking_column ('#{locking_column}') can't be nil.
24
+ Are you missing a default value or validation on '#{locking_column}'?
25
+ MSG
26
+ end
27
+
28
+ self[locking_column] += 1
29
+
30
+ attribute_values = if attribute_values.is_a?(Hash)
31
+ attribute_values.merge(attributes_with_values([locking_column]))
32
+ else
33
+ attribute_values = attribute_values.dup if attribute_values.frozen?
34
+ attribute_values << locking_column
35
+ attributes_with_values(attribute_values)
36
+ end
37
+
38
+ # Suntone returns the row(s) not a int of afftecd_rows
39
+ result = self.class._update_record(
40
+ attribute_values,
41
+ update_constraints
42
+ )
43
+ affected_rows = sunstone? ? result.rows.size : result
44
+
45
+ if affected_rows != 1
46
+ raise ActiveRecord::StaleObjectError.new(self, attempted_action)
47
+ end
48
+
49
+ affected_rows
50
+
51
+ # If something went wrong, revert the locking_column value.
52
+ rescue Exception
53
+ @attributes[locking_column] = lock_attribute_was
54
+ raise
55
+ end
56
+ end
57
+
58
+ end
59
+ end
60
+ end