sunstone 8.0.1 → 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 (44) 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 +3 -1
  5. data/ext/active_record/associations/has_many_through_association.rb +6 -1
  6. data/ext/active_record/associations.rb +11 -9
  7. data/ext/active_record/attribute_methods.rb +3 -1
  8. data/ext/active_record/base.rb +5 -0
  9. data/ext/active_record/callbacks.rb +3 -1
  10. data/ext/active_record/locking/optimistic.rb +10 -0
  11. data/ext/active_record/persistence.rb +5 -0
  12. data/ext/active_record/relation/calculations.rb +8 -2
  13. data/ext/active_record/relation/finder_methods.rb +4 -1
  14. data/ext/active_record/relation/predicate_builder.rb +19 -6
  15. data/ext/active_record/relation/query_methods.rb +5 -0
  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 +7 -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 -6
  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 +2 -0
  38. data/lib/arel/visitors/sunstone.rb +2 -0
  39. data/lib/sunstone/connection.rb +2 -11
  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 +2 -0
  44. metadata +18 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9e9d652c2cfc067ca7a6921350122369134b44a200a262d93da2f9df98c90d5c
4
- data.tar.gz: b13d474440401eb82768422ece2047e9b0e1f1cf470264e4a2d786dcdf5c13eb
3
+ metadata.gz: e9d85052377699e6030067ce89f33b964b2193a1cd345a2ed013682fd457ecb3
4
+ data.tar.gz: 6f1f0054a2d338d2aaa6dd7ab9528a4e8be697a72ff670772afd89fe5d1928f7
5
5
  SHA512:
6
- metadata.gz: 5da172f2cfae9b86d5eaa2bdf37bce3b99d385171c505c4047116354c45a7231860ac7dc21dd4e0375ed72c5cdfa2e8bd93a1b289d23289ef002f02e6d547581
7
- data.tar.gz: 2a7b1fb0a8061847a605499ec8d7f31aa3364802a7bce7fc40bb269959769db4d4e7fe1cee40df47c3071301e16c8d20d40b898756a3f3037224b84f81a7a590
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,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
  class ActiveRecord::Associations::CollectionAssociation
4
6
 
5
7
  def replace(other_array)
@@ -1,3 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
1
6
  class ActiveRecord::Associations::CollectionAssociation::HasManyThroughAssociation
2
7
 
3
8
  private
@@ -10,7 +15,7 @@ class ActiveRecord::Associations::CollectionAssociation::HasManyThroughAssociati
10
15
  association.save!
11
16
  end
12
17
  ensure
13
- @through_records.delete(record.object_id)
18
+ @through_records.delete(record)
14
19
  end
15
20
 
16
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,17 +12,17 @@ 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 {
@@ -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
@@ -1,3 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
1
6
  class ActiveRecord::Base
2
7
 
3
8
  def self.sunstone?
@@ -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 Callbacks
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
3
6
  module ActiveRecord
4
7
  module Locking
5
8
  module Optimistic
@@ -15,6 +18,13 @@ module ActiveRecord
15
18
 
16
19
  update_constraints = _query_constraints_hash
17
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
+
18
28
  self[locking_column] += 1
19
29
 
20
30
  attribute_values = if attribute_values.is_a?(Hash)
@@ -1,3 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
1
6
  module ActiveRecord
2
7
  # = Active Record \Persistence
3
8
  module Persistence
@@ -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 Calculations
@@ -34,7 +36,11 @@ module ActiveRecord
34
36
  columns = relation.arel_columns(column_names)
35
37
  relation.select_values = columns
36
38
  result = skip_query_cache_if_necessary do
37
- if where_clause.contradiction?
39
+ # TODO: drop this Rails 8.0 compatibility guard when Rails 8.0 support is
40
+ # dropped, and call possible_aggregation?(column_names) directly.
41
+ # possible_aggregation? was added in Rails 8.1; on 8.0 a contradiction
42
+ # short-circuits to an empty result unconditionally.
43
+ if where_clause.contradiction? && !(respond_to?(:possible_aggregation?, true) && possible_aggregation?(column_names))
38
44
  ActiveRecord::Result.empty(async: @async)
39
45
  else
40
46
  model.with_connection do |c|
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # The last ref that this code was synced with Rails
2
- # ref: 9269f634d471ad6ca46752421eabd3e1c26220b5
4
+ # ref: 90a1eaa1b3
5
+
3
6
  module ActiveRecord::FinderMethods
4
7
  class SunstoneJoinDependency
5
8
  def initialize(klass)
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
1
6
  class ActiveRecord::PredicateBuilder # :nodoc:
2
7
 
3
8
  def expand_from_hash(attributes, &block)
4
- return ["1=0"] if attributes.empty?
9
+ return [Arel.sql("1=0", retryable: true)] if attributes.empty?
5
10
 
6
11
  attributes.flat_map do |key, value|
7
12
  if key.is_a?(Array) && key.size == 1
@@ -27,24 +32,32 @@ class ActiveRecord::PredicateBuilder # :nodoc:
27
32
  }
28
33
  end
29
34
  ka
30
- elsif table.associated_with?(key)
35
+ # TODO: drop this Rails 8.0 compatibility branch when Rails 8.0 support is
36
+ # dropped, and always use table.associated_with(key). (8.0 named it associated_with?)
37
+ elsif (associated_reflection = (table.respond_to?(:associated_with) ? table.associated_with(key) : table.associated_with?(key)))
31
38
  # Find the foreign key when using queries such as:
32
39
  # Post.where(author: author)
33
40
  #
34
41
  # For polymorphic relationships, find the foreign key and type:
35
42
  # PriceEstimate.where(estimate_of: treasure)
36
- associated_table = table.associated_table(key)
37
- if associated_table.polymorphic_association?
43
+
44
+ if associated_reflection.polymorphic?
38
45
  value = [value] unless value.is_a?(Array)
39
46
  klass = PolymorphicArrayValue
40
- elsif associated_table.through_association?
47
+ elsif associated_reflection.through_reflection?
48
+ associated_table = table.associated_table(key)
49
+
41
50
  next associated_table.predicate_builder.expand_from_hash(
42
51
  associated_table.primary_key => value
43
52
  )
44
53
  end
45
54
 
46
55
  klass ||= AssociationQueryValue
47
- queries = klass.new(associated_table, value).queries.map! do |query|
56
+ # TODO: drop this Rails 8.0 compatibility branch when Rails 8.0 support is
57
+ # dropped, and always pass associated_reflection.
58
+ # Rails 8.1 threads the reflection through the value objects; 8.0 threads the table metadata.
59
+ query_source = table.respond_to?(:associated_with) ? associated_reflection : table.associated_table(key)
60
+ queries = klass.new(query_source, value).queries.map! do |query|
48
61
  # If the query produced is identical to attributes don't go any deeper.
49
62
  # Prevents stack level too deep errors when association and foreign_key are identical.
50
63
  query == attributes ? self[key, value] : expand_from_hash(query)
@@ -1,3 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
1
6
  module ActiveRecord
2
7
  module QueryMethods
3
8
  private
@@ -1,3 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
1
6
  module ActiveRecord
2
7
  class Relation
3
8
 
@@ -1,8 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
1
6
  module ActiveRecord
2
7
  class StatementCache
3
8
  class PartialQuery
4
9
 
5
- def initialize(values, sunstone=false)
10
+ # TODO: drop this Rails 8.0 compatibility default when Rails 8.0 support is
11
+ # dropped, and make retryable: a required keyword again.
12
+ # retryable: is required on Rails 8.1 but absent from 8.0's caller.
13
+ def initialize(values, retryable: false, sunstone: false)
6
14
  @values = values
7
15
  @indexes = if sunstone
8
16
  else
@@ -10,8 +18,9 @@ module ActiveRecord
10
18
  Substitute === thing
11
19
  }.map(&:last)
12
20
  end
21
+ @retryable = retryable
13
22
  end
14
-
23
+
15
24
  def sql_for(binds, connection)
16
25
  if connection.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
17
26
  @values
@@ -29,6 +38,5 @@ module ActiveRecord
29
38
  end
30
39
 
31
40
  end
32
-
33
41
  end
34
42
  end
@@ -1,3 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The last ref that this code was synced with Rails
4
+ # ref: 90a1eaa1b3
5
+
1
6
  require 'active_record'
2
7
  require 'active_record/transactions'
3
8
 
@@ -35,26 +40,35 @@ module ActiveRecord
35
40
 
36
41
  def with_transaction_returning_status
37
42
  self.class.with_connection do |connection|
38
- status = nil
39
- # connection = self.class.connection
40
-
41
- if sunstone? && instance_variable_defined?(:@sunstone_updating) && @sunstone_updating
42
- status = yield
43
- else
44
- ensure_finalize = !connection.transaction_open?
45
- connection.transaction do
46
- add_to_transaction(ensure_finalize || has_transactional_callbacks?)
47
- remember_transaction_record_state
43
+ transaction_body = proc do
44
+ status = nil
48
45
 
46
+ if sunstone? && instance_variable_defined?(:@sunstone_updating) && @sunstone_updating
49
47
  status = yield
50
- raise ActiveRecord::Rollback unless status
48
+ else
49
+ ensure_finalize = !connection.transaction_open?
50
+ connection.transaction do
51
+ add_to_transaction(ensure_finalize || has_transactional_callbacks?)
52
+ remember_transaction_record_state
53
+
54
+ status = yield
55
+ raise ActiveRecord::Rollback unless status
56
+ end
57
+ @_last_transaction_return_status = status
58
+ status
51
59
  end
52
60
  end
53
-
54
- status
61
+
62
+ # TODO: drop this Rails 8.0 compatibility branch when Rails 8.0
63
+ # support is dropped, and always use with_pool_transaction_isolation_level.
64
+ # Rails 8.1 wraps the transaction in a pool-level isolation context;
65
+ # this API doesn't exist on 8.0, so run the body directly there.
66
+ if ActiveRecord.respond_to?(:default_transaction_isolation_level)
67
+ connection.pool.with_pool_transaction_isolation_level(ActiveRecord.default_transaction_isolation_level, connection.transaction_open?, &transaction_body)
68
+ else
69
+ transaction_body.call
70
+ end
55
71
  end
56
72
  end
57
-
58
-
59
73
  end
60
74
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class NilClass
2
4
 
3
5
  def to_query(namespace)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # module Arel
2
4
  # module Attributes
3
5
  # class EmptyRelation < Attribute
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Arel
2
4
  module Nodes
3
5
  class EagerLoad < Arel::Nodes::Unary
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Arel
2
4
  module Nodes
3
5
  class SelectStatement < Arel::Nodes::NodeExpression
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Arel
2
4
  class SelectManager < Arel::TreeManager
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  # Sunstone-specific extensions to column definitions in a table.
@@ -6,8 +8,9 @@ module ActiveRecord
6
8
 
7
9
  attr_reader :array
8
10
 
9
- def initialize(name, sql_type_metadata, options={})
11
+ def initialize(name, cast_type, sql_type_metadata, options={})
10
12
  @name = name.freeze
13
+ @cast_type = cast_type
11
14
  @sql_type_metadata = sql_type_metadata
12
15
  @null = options['null']
13
16
  @default = options['default'] ? JSON.generate(options['default']) : options['default']
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "arel/collectors/sql_string"
2
4
 
3
5
  module ActiveRecord
@@ -66,7 +68,7 @@ module ActiveRecord
66
68
  elsif self.is_a?(ActiveRecord::ConnectionAdapters::SunstoneAPIAdapter)
67
69
  collector = SunstonePartialQueryCollector.new(self.collector)
68
70
  parts, binds = visitor.compile(arel.ast, collector)
69
- query = StatementCache::PartialQuery.new(parts, true)
71
+ query = StatementCache::PartialQuery.new(parts, retryable: collector.retryable, sunstone: true)
70
72
  else
71
73
  collector = klass.partial_query_collector
72
74
  parts, binds = visitor.compile(arel.ast, collector)
@@ -245,6 +247,10 @@ module ActiveRecord
245
247
  result.rows.first
246
248
  end
247
249
 
250
+ def write_query?(arel)
251
+ arel.request_type != Net::HTTP::Get
252
+ end
253
+
248
254
  end
249
255
  end
250
256
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module Sunstone
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module Sunstone
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module Sunstone
@@ -39,8 +41,6 @@ module ActiveRecord
39
41
  # - format_type includes the column size constraint, e.g. varchar(50)
40
42
  # - ::regclass is a function that gives the id for a table name
41
43
  def column_definitions(table_name) # :nodoc:
42
- puts table_name.inspect
43
- puts definition(table_name).inspect
44
44
  # TODO: settle on schema, I think we've switched to attributes, so
45
45
  # columns can be removed soon?
46
46
  definition(table_name)['attributes'] || definition(table_name)['columns']
@@ -61,16 +61,16 @@ module ActiveRecord
61
61
  end
62
62
 
63
63
  def new_column(name, options)
64
- sql_type_metadata = fetch_type_metadata(options)
65
- SunstoneColumn.new(name, sql_type_metadata, options)
64
+ cast_type = lookup_cast_type(options)
65
+ sql_type_metadata = fetch_type_metadata(cast_type, options)
66
+ SunstoneColumn.new(name, cast_type, sql_type_metadata, options)
66
67
  end
67
68
 
68
69
  def lookup_cast_type(options)
69
70
  @type_map.lookup(options['type'], options.symbolize_keys)
70
71
  end
71
72
 
72
- def fetch_type_metadata(options)
73
- cast_type = lookup_cast_type(options)
73
+ def fetch_type_metadata(cast_type, options)
74
74
  simple_type = SqlTypeMetadata.new(
75
75
  sql_type: options['type'],
76
76
  type: cast_type.type,
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module Sunstone
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module ActiveRecord
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module Sunstone
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rgeo'
2
4
 
3
5
  module ActiveRecord
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module Sunstone
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  module Sunstone
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveRecord
2
4
  module ConnectionAdapters
3
5
  class SunstoneSQLTypeMetadata < DelegateClass(SqlTypeMetadata)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/connection_adapters/abstract_adapter'
2
4
 
3
5
  require 'arel/nodes/relation'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Arel
2
4
  module Collectors
3
5
  class Sunstone < Arel::Collectors::Bind
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'arel/visitors/visitor'
2
4
 
3
5
  module Arel
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # _Sunstone_ is a low-level API. It provides basic HTTP #get, #post, #put, and
2
4
  # #delete calls to the Sunstone Server. It can also provides basic error
3
5
  # checking of responses.
@@ -128,17 +130,6 @@ module Sunstone
128
130
  # end
129
131
  # end
130
132
  def send_request(request, body=nil, &block)
131
- path_and_query = request.path.split('?', 2)
132
- message = "SENDING: #{request.method} #{path_and_query[0]}"
133
- if path_and_query[1]
134
- if request['Query-Encoding'] == 'application/msgpack'
135
- message << " " << MessagePack.unpack(CGI.unescape(path_and_query[1])).inspect
136
- else
137
- message << " " << CGI.unescape(path_and_query[1])
138
- end
139
- end
140
- puts message
141
-
142
133
  if request.method != 'GET' && Thread.current[:sunstone_transaction_count]
143
134
  if Thread.current[:sunstone_transaction_count] == 1 && !Thread.current[:sunstone_request_sent]
144
135
  Thread.current[:sunstone_request_sent] = request
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sunstone
2
4
 
3
5
  class ServerError < ::RuntimeError
data/lib/sunstone/gis.rb CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_record/connection_adapters/sunstone/type/ewkb'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Sunstone
2
- VERSION = '8.0.1'
4
+ VERSION = '8.1.0'
3
5
  end
data/lib/sunstone.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'uri'
2
4
  require 'net/http'
3
5
  require 'net/https'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sunstone
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.1
4
+ version: 8.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Bracy
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-01-31 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rake
@@ -184,14 +183,14 @@ dependencies:
184
183
  requirements:
185
184
  - - ">="
186
185
  - !ruby/object:Gem::Version
187
- version: 7.2.0
186
+ version: '8.0'
188
187
  type: :development
189
188
  prerelease: false
190
189
  version_requirements: !ruby/object:Gem::Requirement
191
190
  requirements:
192
191
  - - ">="
193
192
  - !ruby/object:Gem::Version
194
- version: 7.2.0
193
+ version: '8.0'
195
194
  - !ruby/object:Gem::Dependency
196
195
  name: msgpack
197
196
  requirement: !ruby/object:Gem::Requirement
@@ -240,28 +239,28 @@ dependencies:
240
239
  requirements:
241
240
  - - ">="
242
241
  - !ruby/object:Gem::Version
243
- version: 7.0.1
242
+ version: 9.0.0
244
243
  type: :runtime
245
244
  prerelease: false
246
245
  version_requirements: !ruby/object:Gem::Requirement
247
246
  requirements:
248
247
  - - ">="
249
248
  - !ruby/object:Gem::Version
250
- version: 7.0.1
249
+ version: 9.0.0
251
250
  - !ruby/object:Gem::Dependency
252
251
  name: activerecord-filter
253
252
  requirement: !ruby/object:Gem::Requirement
254
253
  requirements:
255
254
  - - ">="
256
255
  - !ruby/object:Gem::Version
257
- version: 7.0.0
256
+ version: 9.0.0
258
257
  type: :runtime
259
258
  prerelease: false
260
259
  version_requirements: !ruby/object:Gem::Requirement
261
260
  requirements:
262
261
  - - ">="
263
262
  - !ruby/object:Gem::Version
264
- version: 7.0.0
263
+ version: 9.0.0
265
264
  description: A library for interacting with REST APIs. Similar to ActiveResource
266
265
  email:
267
266
  - jonbracy@gmail.com
@@ -269,7 +268,9 @@ executables: []
269
268
  extensions: []
270
269
  extra_rdoc_files: []
271
270
  files:
271
+ - CHANGELOG.md
272
272
  - LICENSE
273
+ - README.md
273
274
  - ext/active_record/associations.rb
274
275
  - ext/active_record/associations/collection_association.rb
275
276
  - ext/active_record/associations/has_many_through_association.rb
@@ -311,9 +312,12 @@ files:
311
312
  - lib/sunstone/gis.rb
312
313
  - lib/sunstone/version.rb
313
314
  homepage: http://sunstonerb.com
314
- licenses: []
315
- metadata: {}
316
- post_install_message:
315
+ licenses:
316
+ - MIT
317
+ metadata:
318
+ source_code_uri: https://github.com/malomalo/sunstone
319
+ changelog_uri: https://github.com/malomalo/sunstone/blob/master/CHANGELOG.md
320
+ rubygems_mfa_required: 'true'
317
321
  rdoc_options: []
318
322
  require_paths:
319
323
  - lib
@@ -321,15 +325,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
321
325
  requirements:
322
326
  - - ">="
323
327
  - !ruby/object:Gem::Version
324
- version: '2.6'
328
+ version: '3.3'
325
329
  required_rubygems_version: !ruby/object:Gem::Requirement
326
330
  requirements:
327
331
  - - ">="
328
332
  - !ruby/object:Gem::Version
329
333
  version: '0'
330
334
  requirements: []
331
- rubygems_version: 3.5.21
332
- signing_key:
335
+ rubygems_version: 4.0.11
333
336
  specification_version: 4
334
337
  summary: A library for interacting with REST APIs
335
338
  test_files: []