activerecord-ejection_seat 0.1.0 → 0.3.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/.rubocop.yml +12 -8
- data/.ruby-version +1 -0
- data/.tool-versions +1 -0
- data/Gemfile +12 -4
- data/Gemfile.lock +92 -41
- data/README.md +20 -6
- data/Rakefile +15 -3
- data/activerecord-ejection_seat.gemspec +3 -2
- data/lib/activerecord-ejection_seat/attributes_builder.rb +42 -0
- data/lib/activerecord-ejection_seat/ejectable.rb +8 -10
- data/lib/activerecord-ejection_seat/props_builder.rb +56 -0
- data/lib/activerecord-ejection_seat/version.rb +2 -1
- data/lib/activerecord-ejection_seat.rb +8 -3
- data/sorbet/config +3 -0
- data/sorbet/rbi/annotations/activerecord.rbi +18 -0
- data/sorbet/rbi/annotations/activesupport.rbi +52 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/gems/activemodel@7.0.4.1.rbi +6022 -0
- data/sorbet/rbi/gems/activerecord@7.0.4.1.rbi +37791 -0
- data/sorbet/rbi/gems/activesupport@7.0.4.1.rbi +18127 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +11632 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1064 -0
- data/sorbet/rbi/gems/i18n@1.12.0.rbi +8 -0
- data/sorbet/rbi/gems/io-console@0.6.0.rbi +8 -0
- data/sorbet/rbi/gems/irb@1.6.2.rbi +342 -0
- data/sorbet/rbi/gems/json@2.6.3.rbi +1541 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.2.rbi +8 -0
- data/sorbet/rbi/gems/minitest@5.17.0.rbi +2319 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
- data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
- data/sorbet/rbi/gems/parser@3.2.0.0.rbi +6964 -0
- data/sorbet/rbi/gems/prettier_print@1.2.0.rbi +8 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +397 -0
- data/sorbet/rbi/gems/rake@13.0.6.rbi +2946 -0
- data/sorbet/rbi/gems/rbi@0.0.16.rbi +3008 -0
- data/sorbet/rbi/gems/regexp_parser@2.6.1.rbi +3126 -0
- data/sorbet/rbi/gems/reline@0.3.2.rbi +8 -0
- data/sorbet/rbi/gems/rexml@3.2.5.rbi +4660 -0
- data/sorbet/rbi/gems/rubocop-ast@1.24.1.rbi +7034 -0
- data/sorbet/rbi/gems/rubocop-minitest@0.26.1.rbi +2320 -0
- data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +354 -0
- data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +1014 -0
- data/sorbet/rbi/gems/rubocop@1.43.0.rbi +54266 -0
- data/sorbet/rbi/gems/ruby-lsp@0.3.8.rbi +14 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +1212 -0
- data/sorbet/rbi/gems/sorbet-struct-comparable@1.3.0.rbi +16 -0
- data/sorbet/rbi/gems/spoom@1.1.15.rbi +2383 -0
- data/sorbet/rbi/gems/sqlite3@1.6.0.rbi +1673 -0
- data/sorbet/rbi/gems/syntax_tree@5.2.0.rbi +8 -0
- data/sorbet/rbi/gems/tapioca@0.10.5.rbi +3215 -0
- data/sorbet/rbi/gems/thor@1.2.1.rbi +3950 -0
- data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +8 -0
- data/sorbet/rbi/gems/unicode-display_width@2.4.2.rbi +65 -0
- data/sorbet/rbi/gems/unparser@0.6.7.rbi +4524 -0
- data/sorbet/rbi/gems/webrick@1.7.0.rbi +2498 -0
- data/sorbet/rbi/gems/yard-sorbet@0.8.0.rbi +441 -0
- data/sorbet/rbi/gems/yard@0.9.28.rbi +17033 -0
- data/sorbet/rbi/shims/location.rbi +13 -0
- data/sorbet/rbi/shims/post.rbi +16 -0
- data/sorbet/rbi/shims/user.rbi +19 -0
- data/sorbet/rbi/todo.rbi +5 -0
- data/sorbet/tapioca/config.yml +2 -0
- data/sorbet/tapioca/require.rb +18 -0
- metadata +75 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f8cb74f8d55bb740891ac604fbb974cbbc78ce7e01bd67eb739a01cd4cdae756
|
|
4
|
+
data.tar.gz: c14a4d5a2382acda29d63c3a77bbf0e4080eff154e3b6cccfb4b91aa13b120e1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e4f736acf5cf15bab547d35f2cf4c21963a6a188029df0c23f59dbbbc900ef7474f0de67013ccac7afa853d832f5fb839e0503019b37a90a2508cab7ce0def55
|
|
7
|
+
data.tar.gz: 787f28eaa9428fbe03e6d48be126d591af896da66e4a360c250e9d9b70ae9d3a03505f66134b53e6ba8be927cbced95b52fd12d2e8a288e582a9189618d307e6
|
data/.rubocop.yml
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
|
+
inherit_mode:
|
|
2
|
+
merge:
|
|
3
|
+
- Exclude
|
|
4
|
+
|
|
1
5
|
require:
|
|
6
|
+
- rubocop-minitest
|
|
2
7
|
- rubocop-rake
|
|
3
|
-
- rubocop-
|
|
8
|
+
- rubocop-sorbet
|
|
4
9
|
|
|
5
10
|
AllCops:
|
|
6
11
|
NewCops: enable
|
|
7
|
-
TargetRubyVersion:
|
|
12
|
+
TargetRubyVersion: 3.0
|
|
13
|
+
Exclude:
|
|
14
|
+
- sorbet/**/*.rbi
|
|
15
|
+
|
|
16
|
+
Style/AccessorGrouping:
|
|
17
|
+
Enabled: false
|
|
8
18
|
|
|
9
19
|
Style/StringLiterals:
|
|
10
20
|
Enabled: true
|
|
@@ -16,9 +26,3 @@ Style/StringLiteralsInInterpolation:
|
|
|
16
26
|
|
|
17
27
|
Layout/LineLength:
|
|
18
28
|
Max: 120
|
|
19
|
-
|
|
20
|
-
RSpec/FilePath:
|
|
21
|
-
Enabled: false
|
|
22
|
-
|
|
23
|
-
RSpec/MultipleExpectations:
|
|
24
|
-
Max: 3
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.2.0
|
data/.tool-versions
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby 3.2.0
|
data/Gemfile
CHANGED
|
@@ -6,15 +6,23 @@ source "https://rubygems.org"
|
|
|
6
6
|
gemspec
|
|
7
7
|
|
|
8
8
|
group :development do
|
|
9
|
-
gem "rake"
|
|
10
|
-
gem "rubocop"
|
|
9
|
+
gem "rake"
|
|
10
|
+
gem "rubocop"
|
|
11
|
+
gem "rubocop-minitest"
|
|
11
12
|
gem "rubocop-rake"
|
|
12
|
-
gem "rubocop-
|
|
13
|
+
gem "rubocop-sorbet"
|
|
14
|
+
gem "sorbet"
|
|
15
|
+
gem "spoom", require: false
|
|
16
|
+
gem "tapioca", require: false
|
|
17
|
+
|
|
18
|
+
gem "ruby-lsp"
|
|
13
19
|
end
|
|
14
20
|
|
|
15
21
|
group :development, :test do
|
|
16
|
-
gem "
|
|
22
|
+
gem "minitest"
|
|
17
23
|
gem "sorbet-runtime"
|
|
18
24
|
gem "sorbet-struct-comparable"
|
|
19
25
|
gem "sqlite3"
|
|
26
|
+
|
|
27
|
+
gem "debug"
|
|
20
28
|
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,88 +1,139 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
activerecord-ejection_seat (0.
|
|
5
|
-
activerecord (>=
|
|
4
|
+
activerecord-ejection_seat (0.3.0)
|
|
5
|
+
activerecord (>= 6.0)
|
|
6
|
+
sorbet-runtime
|
|
6
7
|
|
|
7
8
|
GEM
|
|
8
9
|
remote: https://rubygems.org/
|
|
9
10
|
specs:
|
|
10
|
-
activemodel (7.0.
|
|
11
|
-
activesupport (= 7.0.
|
|
12
|
-
activerecord (7.0.
|
|
13
|
-
activemodel (= 7.0.
|
|
14
|
-
activesupport (= 7.0.
|
|
15
|
-
activesupport (7.0.
|
|
11
|
+
activemodel (7.0.4.1)
|
|
12
|
+
activesupport (= 7.0.4.1)
|
|
13
|
+
activerecord (7.0.4.1)
|
|
14
|
+
activemodel (= 7.0.4.1)
|
|
15
|
+
activesupport (= 7.0.4.1)
|
|
16
|
+
activesupport (7.0.4.1)
|
|
16
17
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
17
18
|
i18n (>= 1.6, < 2)
|
|
18
19
|
minitest (>= 5.1)
|
|
19
20
|
tzinfo (~> 2.0)
|
|
20
21
|
ast (2.4.2)
|
|
21
22
|
concurrent-ruby (1.1.10)
|
|
23
|
+
debug (1.7.1)
|
|
24
|
+
irb (>= 1.5.0)
|
|
25
|
+
reline (>= 0.3.1)
|
|
22
26
|
diff-lcs (1.5.0)
|
|
23
27
|
i18n (1.12.0)
|
|
24
28
|
concurrent-ruby (~> 1.0)
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
io-console (0.6.0)
|
|
30
|
+
irb (1.6.2)
|
|
31
|
+
reline (>= 0.3.0)
|
|
32
|
+
json (2.6.3)
|
|
33
|
+
language_server-protocol (3.17.0.2)
|
|
34
|
+
minitest (5.17.0)
|
|
35
|
+
netrc (0.11.0)
|
|
27
36
|
parallel (1.22.1)
|
|
28
|
-
parser (3.
|
|
37
|
+
parser (3.2.0.0)
|
|
29
38
|
ast (~> 2.4.1)
|
|
39
|
+
prettier_print (1.2.0)
|
|
30
40
|
rainbow (3.1.1)
|
|
31
41
|
rake (13.0.6)
|
|
32
|
-
|
|
42
|
+
rbi (0.0.16)
|
|
43
|
+
ast
|
|
44
|
+
parser (>= 2.6.4.0)
|
|
45
|
+
sorbet-runtime (>= 0.5.9204)
|
|
46
|
+
unparser
|
|
47
|
+
regexp_parser (2.6.1)
|
|
48
|
+
reline (0.3.2)
|
|
49
|
+
io-console (~> 0.5)
|
|
33
50
|
rexml (3.2.5)
|
|
34
|
-
|
|
35
|
-
rspec-core (~> 3.11.0)
|
|
36
|
-
rspec-expectations (~> 3.11.0)
|
|
37
|
-
rspec-mocks (~> 3.11.0)
|
|
38
|
-
rspec-core (3.11.0)
|
|
39
|
-
rspec-support (~> 3.11.0)
|
|
40
|
-
rspec-expectations (3.11.0)
|
|
41
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
42
|
-
rspec-support (~> 3.11.0)
|
|
43
|
-
rspec-mocks (3.11.1)
|
|
44
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
|
45
|
-
rspec-support (~> 3.11.0)
|
|
46
|
-
rspec-support (3.11.0)
|
|
47
|
-
rubocop (1.31.2)
|
|
51
|
+
rubocop (1.43.0)
|
|
48
52
|
json (~> 2.3)
|
|
49
53
|
parallel (~> 1.10)
|
|
50
|
-
parser (>= 3.
|
|
54
|
+
parser (>= 3.2.0.0)
|
|
51
55
|
rainbow (>= 2.2.2, < 4.0)
|
|
52
56
|
regexp_parser (>= 1.8, < 3.0)
|
|
53
57
|
rexml (>= 3.2.5, < 4.0)
|
|
54
|
-
rubocop-ast (>= 1.
|
|
58
|
+
rubocop-ast (>= 1.24.1, < 2.0)
|
|
55
59
|
ruby-progressbar (~> 1.7)
|
|
56
|
-
unicode-display_width (>=
|
|
57
|
-
rubocop-ast (1.
|
|
60
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
|
61
|
+
rubocop-ast (1.24.1)
|
|
58
62
|
parser (>= 3.1.1.0)
|
|
63
|
+
rubocop-minitest (0.26.1)
|
|
64
|
+
rubocop (>= 0.90, < 2.0)
|
|
59
65
|
rubocop-rake (0.6.0)
|
|
60
66
|
rubocop (~> 1.0)
|
|
61
|
-
rubocop-
|
|
62
|
-
rubocop (
|
|
67
|
+
rubocop-sorbet (0.6.11)
|
|
68
|
+
rubocop (>= 0.90.0)
|
|
69
|
+
ruby-lsp (0.3.8)
|
|
70
|
+
language_server-protocol (~> 3.17.0)
|
|
71
|
+
sorbet-runtime
|
|
72
|
+
syntax_tree (>= 5.0.0, < 6)
|
|
63
73
|
ruby-progressbar (1.11.0)
|
|
64
|
-
sorbet
|
|
74
|
+
sorbet (0.5.10626)
|
|
75
|
+
sorbet-static (= 0.5.10626)
|
|
76
|
+
sorbet-runtime (0.5.10626)
|
|
77
|
+
sorbet-static (0.5.10626-universal-darwin-21)
|
|
78
|
+
sorbet-static (0.5.10626-universal-darwin-22)
|
|
79
|
+
sorbet-static (0.5.10626-x86_64-linux)
|
|
80
|
+
sorbet-static-and-runtime (0.5.10626)
|
|
81
|
+
sorbet (= 0.5.10626)
|
|
82
|
+
sorbet-runtime (= 0.5.10626)
|
|
65
83
|
sorbet-struct-comparable (1.3.0)
|
|
66
84
|
sorbet-runtime (>= 0.5)
|
|
67
|
-
|
|
68
|
-
|
|
85
|
+
spoom (1.1.15)
|
|
86
|
+
sorbet (>= 0.5.10187)
|
|
87
|
+
sorbet-runtime (>= 0.5.9204)
|
|
88
|
+
thor (>= 0.19.2)
|
|
89
|
+
sqlite3 (1.6.0-arm64-darwin)
|
|
90
|
+
sqlite3 (1.6.0-x86_64-linux)
|
|
91
|
+
syntax_tree (5.2.0)
|
|
92
|
+
prettier_print (>= 1.2.0)
|
|
93
|
+
tapioca (0.10.5)
|
|
94
|
+
bundler (>= 1.17.3)
|
|
95
|
+
netrc (>= 0.11.0)
|
|
96
|
+
parallel (>= 1.21.0)
|
|
97
|
+
rbi (~> 0.0.0, >= 0.0.16)
|
|
98
|
+
sorbet-static-and-runtime (>= 0.5.10187)
|
|
99
|
+
spoom (~> 1.1.0, >= 1.1.11)
|
|
100
|
+
thor (>= 1.2.0)
|
|
101
|
+
yard-sorbet
|
|
102
|
+
thor (1.2.1)
|
|
103
|
+
tzinfo (2.0.5)
|
|
69
104
|
concurrent-ruby (~> 1.0)
|
|
70
|
-
unicode-display_width (2.2
|
|
105
|
+
unicode-display_width (2.4.2)
|
|
106
|
+
unparser (0.6.7)
|
|
107
|
+
diff-lcs (~> 1.3)
|
|
108
|
+
parser (>= 3.2.0)
|
|
109
|
+
webrick (1.7.0)
|
|
110
|
+
yard (0.9.28)
|
|
111
|
+
webrick (~> 1.7.0)
|
|
112
|
+
yard-sorbet (0.8.0)
|
|
113
|
+
sorbet-runtime (>= 0.5)
|
|
114
|
+
yard (>= 0.9)
|
|
71
115
|
|
|
72
116
|
PLATFORMS
|
|
73
117
|
arm64-darwin-21
|
|
118
|
+
arm64-darwin-22
|
|
74
119
|
x86_64-linux
|
|
75
120
|
|
|
76
121
|
DEPENDENCIES
|
|
77
122
|
activerecord-ejection_seat!
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
123
|
+
debug
|
|
124
|
+
minitest
|
|
125
|
+
rake
|
|
126
|
+
rubocop
|
|
127
|
+
rubocop-minitest
|
|
81
128
|
rubocop-rake
|
|
82
|
-
rubocop-
|
|
129
|
+
rubocop-sorbet
|
|
130
|
+
ruby-lsp
|
|
131
|
+
sorbet
|
|
83
132
|
sorbet-runtime
|
|
84
133
|
sorbet-struct-comparable
|
|
134
|
+
spoom
|
|
85
135
|
sqlite3
|
|
136
|
+
tapioca
|
|
86
137
|
|
|
87
138
|
BUNDLED WITH
|
|
88
|
-
2.
|
|
139
|
+
2.4.4
|
data/README.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# ActiveRecord::EjectionSeat
|
|
2
2
|
|
|
3
|
+
[](http://www.youtube.com/watch?v=HfCY-bkVJKM "Introduction to activerecord-ejection_seat")
|
|
4
|
+
|
|
3
5
|
## ActiveRecord <=> T::Struct
|
|
4
6
|
|
|
5
7
|
Sometimes, you want to convert from an `ActiveRecord` model to something more akin to a simple data class. Enter [Sorbet's `T::Struct`](https://sorbet.org/docs/tstruct). This gem helps to eject out of bulky `ActiveRecord` models (or buckle into them) and work with simple `T::Struct`'s in the rest of your app.
|
|
6
8
|
|
|
9
|
+
For more information about the "why" behind this gem, check out [this post](https://maxveld.ink/ejecting-out-of-an-orm/).
|
|
10
|
+
|
|
7
11
|
## Installation
|
|
8
12
|
|
|
9
13
|
Install the gem and add to the application's Gemfile by executing:
|
|
@@ -16,13 +20,21 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
|
16
20
|
|
|
17
21
|
## Usage
|
|
18
22
|
|
|
19
|
-
Let's say you have an `ActiveRecord` model for a `User` with a string `name` column
|
|
23
|
+
Let's say you have an `ActiveRecord` model for a `User` with a string `name` column, an integer `age` column and a `role` enum column. Another way to express this would be a `User` type that is a simple, typed struct.
|
|
20
24
|
|
|
21
25
|
```ruby
|
|
22
26
|
module Types
|
|
27
|
+
class UserRoles < T::Enum
|
|
28
|
+
enums do
|
|
29
|
+
Admin = new("admin")
|
|
30
|
+
Member = new("member")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
23
34
|
class User < T::Struct
|
|
24
35
|
const :name, String
|
|
25
36
|
const :age, Integer
|
|
37
|
+
const :role, UserRoles
|
|
26
38
|
end
|
|
27
39
|
end
|
|
28
40
|
```
|
|
@@ -31,6 +43,8 @@ In our model, we can specify an ejection to this type.
|
|
|
31
43
|
|
|
32
44
|
```ruby
|
|
33
45
|
class User
|
|
46
|
+
enum :role, { admin: "admin", member: "member" }
|
|
47
|
+
|
|
34
48
|
ejects_to Types::User
|
|
35
49
|
end
|
|
36
50
|
```
|
|
@@ -38,17 +52,17 @@ end
|
|
|
38
52
|
Now, we have two new methods available on `User`. First, we can eject from a `User` instance to a `Types::User`.
|
|
39
53
|
|
|
40
54
|
```ruby
|
|
41
|
-
User.new(name: "Max", age: 28).eject
|
|
42
|
-
# => Types::User(name: "Max", age: 28)
|
|
43
|
-
User.new(name: "Max", age: 28).to_struct # alias
|
|
55
|
+
User.new(name: "Max", age: 28, role: "admin").eject
|
|
56
|
+
# => Types::User(name: "Max", age: 28, role: Types::UserRoles::Admin)
|
|
57
|
+
User.new(name: "Max", age: 28, role: "admin").to_struct # alias
|
|
44
58
|
```
|
|
45
59
|
|
|
46
60
|
Second, we can buckle into the `User` model with a `Types::User`.
|
|
47
61
|
|
|
48
62
|
```ruby
|
|
49
|
-
user_struct = Types::User.new(name: "Max", age: 28)
|
|
63
|
+
user_struct = Types::User.new(name: "Max", age: 28, role: Types::UserRoles::Admin)
|
|
50
64
|
User.buckle(user_struct)
|
|
51
|
-
# => User(name: "Max", age: 28)
|
|
65
|
+
# => User(name: "Max", age: 28, role: "admin")
|
|
52
66
|
User.from_struct(user_struct) # alias
|
|
53
67
|
```
|
|
54
68
|
|
data/Rakefile
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
|
-
require "
|
|
4
|
+
require "minitest/test_task"
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Minitest::TestTask.create do |t|
|
|
7
|
+
t.test_globs = ["test/**/*_test.rb"]
|
|
8
|
+
end
|
|
7
9
|
|
|
8
10
|
require "rubocop/rake_task"
|
|
9
11
|
|
|
10
12
|
RuboCop::RakeTask.new
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
desc "Run tapioca compilers"
|
|
15
|
+
task :tapioca do
|
|
16
|
+
sh "bin/tapioca gem"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
desc "Run Sorbet typechecker"
|
|
20
|
+
task :sorbet do
|
|
21
|
+
sh "bundle exec srb tc"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
task default: %i[rubocop:autocorrect_all sorbet test]
|
|
@@ -15,7 +15,7 @@ Gem::Specification.new do |spec|
|
|
|
15
15
|
It also allows you to buckle in from a simple struct to a new ActiveRecord model instance."
|
|
16
16
|
spec.homepage = "https://github.com/AudaciousAardvark/activerecord-ejection_seat"
|
|
17
17
|
spec.license = "MIT"
|
|
18
|
-
spec.required_ruby_version = ">=
|
|
18
|
+
spec.required_ruby_version = ">= 3.0.0"
|
|
19
19
|
|
|
20
20
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
21
21
|
spec.metadata["rubygems_mfa_required"] = "true"
|
|
@@ -35,5 +35,6 @@ Gem::Specification.new do |spec|
|
|
|
35
35
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
36
36
|
spec.require_paths = ["lib"]
|
|
37
37
|
|
|
38
|
-
spec.add_runtime_dependency "activerecord", ">=
|
|
38
|
+
spec.add_runtime_dependency "activerecord", ">= 6.0"
|
|
39
|
+
spec.add_runtime_dependency "sorbet-runtime"
|
|
39
40
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Creates initialization payload for targeted ActiveRecord model
|
|
5
|
+
class AttributesBuilder
|
|
6
|
+
extend T::Sig
|
|
7
|
+
|
|
8
|
+
sig { params(struct: T::Struct, target_model: T.class_of(ActiveRecord::Base)).void }
|
|
9
|
+
def initialize(struct:, target_model:)
|
|
10
|
+
@struct = struct
|
|
11
|
+
@target_model = target_model
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
sig { returns(T::Hash[String, T.untyped]) }
|
|
15
|
+
def build
|
|
16
|
+
attrs = struct.serialize
|
|
17
|
+
props = attrs.keys & target_model.column_names.map { |name| name.delete_suffix("_id") }
|
|
18
|
+
|
|
19
|
+
attrs.slice(*props).to_h { |key, value| build_attribute(name: key, value: value) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
sig { params(name: String, value: T.untyped).returns(T::Array[T.untyped]) }
|
|
25
|
+
def build_attribute(name:, value:)
|
|
26
|
+
if value.is_a?(Hash)
|
|
27
|
+
association_class = target_model.reflect_on_all_associations.find do |association|
|
|
28
|
+
association.name.to_s == name
|
|
29
|
+
end&.klass
|
|
30
|
+
|
|
31
|
+
value = association_class&.new(value)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
[name, value]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
sig { returns(T::Struct) }
|
|
38
|
+
attr_reader :struct
|
|
39
|
+
|
|
40
|
+
sig { returns(T.class_of(ActiveRecord::Base)) }
|
|
41
|
+
attr_reader :target_model
|
|
42
|
+
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
+
# typed: false
|
|
1
2
|
# frozen_string_literal: true
|
|
2
3
|
|
|
3
|
-
require "active_record"
|
|
4
|
-
|
|
5
4
|
module ActiveRecord
|
|
6
5
|
module EjectionSeat
|
|
7
6
|
# Defines `#eject` `.buckle` methods for going between ActiveRecord models and Sorbet T::Structs.
|
|
@@ -15,10 +14,12 @@ module ActiveRecord
|
|
|
15
14
|
|
|
16
15
|
def define_eject_method(klass)
|
|
17
16
|
define_method(:eject) do
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
klass.new(
|
|
18
|
+
PropsBuilder.new(
|
|
19
|
+
model: self,
|
|
20
|
+
target_struct: klass
|
|
21
|
+
).build
|
|
22
|
+
)
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
alias_method :to_struct, :eject
|
|
@@ -28,10 +29,7 @@ module ActiveRecord
|
|
|
28
29
|
define_singleton_method(:buckle) do |struct|
|
|
29
30
|
raise ArgumentError if struct.class != klass
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
props = attrs.keys & column_names
|
|
33
|
-
|
|
34
|
-
new(attrs.slice(*props))
|
|
32
|
+
new(AttributesBuilder.new(struct: struct, target_model: self).build)
|
|
35
33
|
end
|
|
36
34
|
|
|
37
35
|
singleton_class.send :alias_method, :from_struct, :buckle
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Creates initialization payload for targeted T::Struct
|
|
5
|
+
class PropsBuilder
|
|
6
|
+
extend T::Sig
|
|
7
|
+
|
|
8
|
+
sig do
|
|
9
|
+
params(
|
|
10
|
+
model: ActiveRecord::Base,
|
|
11
|
+
target_struct: T.class_of(T::Struct)
|
|
12
|
+
).void
|
|
13
|
+
end
|
|
14
|
+
def initialize(model:, target_struct:)
|
|
15
|
+
@model = model
|
|
16
|
+
@target_struct = target_struct
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
|
20
|
+
def build
|
|
21
|
+
target_struct.props.keys.each_with_object({}) do |prop_name, returned_props|
|
|
22
|
+
attribute = model.respond_to?(prop_name) ? build_attribute(prop_name) : nil
|
|
23
|
+
|
|
24
|
+
returned_props[prop_name] = attribute
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
sig { params(prop_name: Symbol).returns(T.untyped) }
|
|
31
|
+
def build_attribute(prop_name)
|
|
32
|
+
attribute = model.send(prop_name)
|
|
33
|
+
prop_type = target_struct.props.dig(prop_name, :type)
|
|
34
|
+
|
|
35
|
+
cast_attribute(attribute, prop_type)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
sig { params(attribute: T.untyped, prop_type: Class).returns(T.untyped) }
|
|
39
|
+
def cast_attribute(attribute, prop_type)
|
|
40
|
+
if prop_type < T::Enum
|
|
41
|
+
prop_type.deserialize(attribute)
|
|
42
|
+
elsif prop_type < T::Struct
|
|
43
|
+
prop_type.new(PropsBuilder.new(model: attribute, target_struct: prop_type).build)
|
|
44
|
+
else
|
|
45
|
+
attribute
|
|
46
|
+
end
|
|
47
|
+
rescue KeyError
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
sig { returns(ActiveRecord::Base) }
|
|
52
|
+
attr_reader :model
|
|
53
|
+
|
|
54
|
+
sig { returns(T.class_of(T::Struct)) }
|
|
55
|
+
attr_reader :target_struct
|
|
56
|
+
end
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
# rubocop:disable Naming/FileName
|
|
2
|
+
# rubocop:enable Naming/FileName
|
|
3
|
+
# typed: strict
|
|
2
4
|
# frozen_string_literal: true
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
require "active_record"
|
|
7
|
+
require "sorbet-runtime"
|
|
6
8
|
require "active_support/lazy_load_hooks"
|
|
7
9
|
|
|
10
|
+
require_relative "activerecord-ejection_seat/version"
|
|
11
|
+
require_relative "activerecord-ejection_seat/props_builder"
|
|
12
|
+
require_relative "activerecord-ejection_seat/attributes_builder"
|
|
13
|
+
|
|
8
14
|
ActiveSupport.on_load(:active_record) do
|
|
9
15
|
require "activerecord-ejection_seat/ejectable"
|
|
10
16
|
end
|
|
11
|
-
# rubocop:enable Naming/FileName
|
data/sorbet/config
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
|
|
3
|
+
# DO NOT EDIT MANUALLY
|
|
4
|
+
# This file was pulled from a central RBI files repository.
|
|
5
|
+
# Please run `bin/tapioca annotations` to update it.
|
|
6
|
+
|
|
7
|
+
class ActiveRecord::Schema
|
|
8
|
+
sig { params(info: T::Hash[T.untyped, T.untyped], blk: T.proc.bind(ActiveRecord::Schema).void).void }
|
|
9
|
+
def self.define(info = nil, &blk); end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class ActiveRecord::Migration
|
|
13
|
+
# @shim: Methods on migration are delegated to `SchemaStatements` using `method_missing`
|
|
14
|
+
include ActiveRecord::ConnectionAdapters::SchemaStatements
|
|
15
|
+
|
|
16
|
+
# @shim: Methods on migration are delegated to `DatabaseaStatements` using `method_missing`
|
|
17
|
+
include ActiveRecord::ConnectionAdapters::DatabaseStatements
|
|
18
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
|
|
3
|
+
# DO NOT EDIT MANUALLY
|
|
4
|
+
# This file was pulled from a central RBI files repository.
|
|
5
|
+
# Please run `bin/tapioca annotations` to update it.
|
|
6
|
+
|
|
7
|
+
module ActiveSupport::Testing::Declarative
|
|
8
|
+
sig { params(name: String, block: T.proc.bind(T.untyped).void).void }
|
|
9
|
+
def test(name, &block); end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class ActiveSupport::EnvironmentInquirer
|
|
13
|
+
sig { returns(T::Boolean) }
|
|
14
|
+
def development?; end
|
|
15
|
+
|
|
16
|
+
sig { returns(T::Boolean) }
|
|
17
|
+
def production?; end
|
|
18
|
+
|
|
19
|
+
sig { returns(T::Boolean) }
|
|
20
|
+
def test?; end
|
|
21
|
+
|
|
22
|
+
# @method_missing: delegated to String through ActiveSupport::StringInquirer
|
|
23
|
+
sig { returns(T::Boolean) }
|
|
24
|
+
def staging?; end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module ActiveSupport::Testing::SetupAndTeardown::ClassMethods
|
|
28
|
+
sig { params(args: T.untyped, block: T.nilable(T.proc.bind(T.untyped).void)).void }
|
|
29
|
+
def setup(*args, &block); end
|
|
30
|
+
|
|
31
|
+
sig { params(args: T.untyped, block: T.nilable(T.proc.bind(T.untyped).void)).void }
|
|
32
|
+
def teardown(*args, &block); end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
class ActiveSupport::TestCase
|
|
36
|
+
sig { params(args: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).void)).void }
|
|
37
|
+
def self.setup(*args, &block); end
|
|
38
|
+
|
|
39
|
+
sig { params(args: T.untyped, block: T.nilable(T.proc.bind(T.attached_class).void)).void }
|
|
40
|
+
def self.teardown(*args, &block); end
|
|
41
|
+
|
|
42
|
+
sig { params(name: String, block: T.proc.bind(T.attached_class).void).void }
|
|
43
|
+
def self.test(name, &block); end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class Object
|
|
47
|
+
sig { returns(T::Boolean) }
|
|
48
|
+
def blank?; end
|
|
49
|
+
|
|
50
|
+
sig { returns(T::Boolean) }
|
|
51
|
+
def present?; end
|
|
52
|
+
end
|