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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -0
- data/README.md +221 -0
- data/ext/active_record/associations/collection_association.rb +3 -1
- data/ext/active_record/associations/has_many_through_association.rb +6 -1
- data/ext/active_record/associations.rb +11 -9
- data/ext/active_record/attribute_methods.rb +3 -1
- data/ext/active_record/base.rb +5 -0
- data/ext/active_record/callbacks.rb +3 -1
- data/ext/active_record/locking/optimistic.rb +10 -0
- data/ext/active_record/persistence.rb +5 -0
- data/ext/active_record/relation/calculations.rb +8 -2
- data/ext/active_record/relation/finder_methods.rb +4 -1
- data/ext/active_record/relation/predicate_builder.rb +19 -6
- data/ext/active_record/relation/query_methods.rb +5 -0
- data/ext/active_record/relation.rb +5 -0
- data/ext/active_record/statement_cache.rb +11 -3
- data/ext/active_record/transactions.rb +29 -15
- data/ext/active_support/core_ext/object/to_query.rb +2 -0
- data/ext/arel/attributes/empty_relation.rb +2 -0
- data/ext/arel/nodes/eager_load.rb +2 -0
- data/ext/arel/nodes/select_statement.rb +2 -0
- data/ext/arel/select_manager.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/column.rb +4 -1
- data/lib/active_record/connection_adapters/sunstone/database_statements.rb +7 -1
- data/lib/active_record/connection_adapters/sunstone/quoting.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/schema_dumper.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/schema_statements.rb +6 -6
- data/lib/active_record/connection_adapters/sunstone/type/array.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type/binary.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type/date_time.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type/ewkb.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type/json.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type/uuid.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone/type_metadata.rb +2 -0
- data/lib/active_record/connection_adapters/sunstone_adapter.rb +2 -0
- data/lib/arel/collectors/sunstone.rb +2 -0
- data/lib/arel/visitors/sunstone.rb +2 -0
- data/lib/sunstone/connection.rb +2 -11
- data/lib/sunstone/exception.rb +2 -0
- data/lib/sunstone/gis.rb +2 -0
- data/lib/sunstone/version.rb +3 -1
- data/lib/sunstone.rb +2 -0
- metadata +18 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e9d85052377699e6030067ce89f33b964b2193a1cd345a2ed013682fd457ecb3
|
|
4
|
+
data.tar.gz: 6f1f0054a2d338d2aaa6dd7ab9528a4e8be697a72ff670772afd89fe5d1928f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,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
|
|
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:
|
|
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
|
|
15
|
+
builder = Builder::HasAndBelongsToMany.new(name, self, options)
|
|
14
16
|
|
|
15
17
|
join_model = builder.through_model
|
|
16
18
|
|
|
17
|
-
const_set
|
|
18
|
-
private_constant
|
|
19
|
+
const_set(join_model.name, join_model)
|
|
20
|
+
private_constant(join_model.name)
|
|
19
21
|
|
|
20
|
-
middle_reflection = builder.middle_reflection
|
|
22
|
+
middle_reflection = builder.middle_reflection(join_model)
|
|
21
23
|
|
|
22
|
-
Builder::HasMany.define_callbacks
|
|
23
|
-
Reflection.add_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?
|
|
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
|
data/ext/active_record/base.rb
CHANGED
|
@@ -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,5 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# The last ref that this code was synced with Rails
|
|
2
|
-
# ref:
|
|
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
|
-
|
|
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,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
|
-
|
|
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
|
-
|
|
37
|
-
if
|
|
43
|
+
|
|
44
|
+
if associated_reflection.polymorphic?
|
|
38
45
|
value = [value] unless value.is_a?(Array)
|
|
39
46
|
klass = PolymorphicArrayValue
|
|
40
|
-
elsif
|
|
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
|
-
|
|
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,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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/ext/arel/select_manager.rb
CHANGED
|
@@ -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
|
|
@@ -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
|
-
|
|
65
|
-
|
|
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,
|
data/lib/sunstone/connection.rb
CHANGED
|
@@ -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
|
data/lib/sunstone/exception.rb
CHANGED
data/lib/sunstone/gis.rb
CHANGED
data/lib/sunstone/version.rb
CHANGED
data/lib/sunstone.rb
CHANGED
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
316
|
-
|
|
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: '
|
|
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:
|
|
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: []
|