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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -8
  3. data/.ruby-version +1 -0
  4. data/.tool-versions +1 -0
  5. data/Gemfile +12 -4
  6. data/Gemfile.lock +92 -41
  7. data/README.md +20 -6
  8. data/Rakefile +15 -3
  9. data/activerecord-ejection_seat.gemspec +3 -2
  10. data/lib/activerecord-ejection_seat/attributes_builder.rb +42 -0
  11. data/lib/activerecord-ejection_seat/ejectable.rb +8 -10
  12. data/lib/activerecord-ejection_seat/props_builder.rb +56 -0
  13. data/lib/activerecord-ejection_seat/version.rb +2 -1
  14. data/lib/activerecord-ejection_seat.rb +8 -3
  15. data/sorbet/config +3 -0
  16. data/sorbet/rbi/annotations/activerecord.rbi +18 -0
  17. data/sorbet/rbi/annotations/activesupport.rbi +52 -0
  18. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  19. data/sorbet/rbi/gems/activemodel@7.0.4.1.rbi +6022 -0
  20. data/sorbet/rbi/gems/activerecord@7.0.4.1.rbi +37791 -0
  21. data/sorbet/rbi/gems/activesupport@7.0.4.1.rbi +18127 -0
  22. data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
  23. data/sorbet/rbi/gems/concurrent-ruby@1.1.10.rbi +11632 -0
  24. data/sorbet/rbi/gems/diff-lcs@1.5.0.rbi +1064 -0
  25. data/sorbet/rbi/gems/i18n@1.12.0.rbi +8 -0
  26. data/sorbet/rbi/gems/io-console@0.6.0.rbi +8 -0
  27. data/sorbet/rbi/gems/irb@1.6.2.rbi +342 -0
  28. data/sorbet/rbi/gems/json@2.6.3.rbi +1541 -0
  29. data/sorbet/rbi/gems/language_server-protocol@3.17.0.2.rbi +8 -0
  30. data/sorbet/rbi/gems/minitest@5.17.0.rbi +2319 -0
  31. data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
  32. data/sorbet/rbi/gems/parallel@1.22.1.rbi +277 -0
  33. data/sorbet/rbi/gems/parser@3.2.0.0.rbi +6964 -0
  34. data/sorbet/rbi/gems/prettier_print@1.2.0.rbi +8 -0
  35. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +397 -0
  36. data/sorbet/rbi/gems/rake@13.0.6.rbi +2946 -0
  37. data/sorbet/rbi/gems/rbi@0.0.16.rbi +3008 -0
  38. data/sorbet/rbi/gems/regexp_parser@2.6.1.rbi +3126 -0
  39. data/sorbet/rbi/gems/reline@0.3.2.rbi +8 -0
  40. data/sorbet/rbi/gems/rexml@3.2.5.rbi +4660 -0
  41. data/sorbet/rbi/gems/rubocop-ast@1.24.1.rbi +7034 -0
  42. data/sorbet/rbi/gems/rubocop-minitest@0.26.1.rbi +2320 -0
  43. data/sorbet/rbi/gems/rubocop-rake@0.6.0.rbi +354 -0
  44. data/sorbet/rbi/gems/rubocop-sorbet@0.6.11.rbi +1014 -0
  45. data/sorbet/rbi/gems/rubocop@1.43.0.rbi +54266 -0
  46. data/sorbet/rbi/gems/ruby-lsp@0.3.8.rbi +14 -0
  47. data/sorbet/rbi/gems/ruby-progressbar@1.11.0.rbi +1212 -0
  48. data/sorbet/rbi/gems/sorbet-struct-comparable@1.3.0.rbi +16 -0
  49. data/sorbet/rbi/gems/spoom@1.1.15.rbi +2383 -0
  50. data/sorbet/rbi/gems/sqlite3@1.6.0.rbi +1673 -0
  51. data/sorbet/rbi/gems/syntax_tree@5.2.0.rbi +8 -0
  52. data/sorbet/rbi/gems/tapioca@0.10.5.rbi +3215 -0
  53. data/sorbet/rbi/gems/thor@1.2.1.rbi +3950 -0
  54. data/sorbet/rbi/gems/tzinfo@2.0.5.rbi +8 -0
  55. data/sorbet/rbi/gems/unicode-display_width@2.4.2.rbi +65 -0
  56. data/sorbet/rbi/gems/unparser@0.6.7.rbi +4524 -0
  57. data/sorbet/rbi/gems/webrick@1.7.0.rbi +2498 -0
  58. data/sorbet/rbi/gems/yard-sorbet@0.8.0.rbi +441 -0
  59. data/sorbet/rbi/gems/yard@0.9.28.rbi +17033 -0
  60. data/sorbet/rbi/shims/location.rbi +13 -0
  61. data/sorbet/rbi/shims/post.rbi +16 -0
  62. data/sorbet/rbi/shims/user.rbi +19 -0
  63. data/sorbet/rbi/todo.rbi +5 -0
  64. data/sorbet/tapioca/config.yml +2 -0
  65. data/sorbet/tapioca/require.rb +18 -0
  66. metadata +75 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69a394852fa247c21ef30d77eb67ea89b0efed860b14ddabc2e08e82d4a2673f
4
- data.tar.gz: 40a97e34b9db820ff71daafd3161d6ee35249fde15d366dd4fc7957cf41fe2c6
3
+ metadata.gz: f8cb74f8d55bb740891ac604fbb974cbbc78ce7e01bd67eb739a01cd4cdae756
4
+ data.tar.gz: c14a4d5a2382acda29d63c3a77bbf0e4080eff154e3b6cccfb4b91aa13b120e1
5
5
  SHA512:
6
- metadata.gz: 03e11c26d5afa8e43ff424d03a5eb5a47772fc13fa938068cf390c06c7b65869ffb1c3c84273a2f3d90ce1af06edd79ab8e5b48891a0016fe0b3527de11efc90
7
- data.tar.gz: da16079ff6cdcec598cf0205eb0ad448b08704658f403be35975573045b1855414540a83a3876da793790bac1c83b16ef3362a1f99bfca3a8596b13dced2f93a
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-rspec
8
+ - rubocop-sorbet
4
9
 
5
10
  AllCops:
6
11
  NewCops: enable
7
- TargetRubyVersion: 2.7
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", "~> 13.0"
10
- gem "rubocop", "~> 1.21"
9
+ gem "rake"
10
+ gem "rubocop"
11
+ gem "rubocop-minitest"
11
12
  gem "rubocop-rake"
12
- gem "rubocop-rspec"
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 "rspec", "~> 3.0"
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.1.0)
5
- activerecord (>= 5.2)
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.3.1)
11
- activesupport (= 7.0.3.1)
12
- activerecord (7.0.3.1)
13
- activemodel (= 7.0.3.1)
14
- activesupport (= 7.0.3.1)
15
- activesupport (7.0.3.1)
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
- json (2.6.2)
26
- minitest (5.16.2)
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.1.2.0)
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
- regexp_parser (2.5.0)
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
- rspec (3.11.0)
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.1.0.0)
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.18.0, < 2.0)
58
+ rubocop-ast (>= 1.24.1, < 2.0)
55
59
  ruby-progressbar (~> 1.7)
56
- unicode-display_width (>= 1.4.0, < 3.0)
57
- rubocop-ast (1.19.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-rspec (2.12.1)
62
- rubocop (~> 1.31)
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-runtime (0.5.10187)
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
- sqlite3 (1.4.4)
68
- tzinfo (2.0.4)
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.0)
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
- rake (~> 13.0)
79
- rspec (~> 3.0)
80
- rubocop (~> 1.21)
123
+ debug
124
+ minitest
125
+ rake
126
+ rubocop
127
+ rubocop-minitest
81
128
  rubocop-rake
82
- rubocop-rspec
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.3.16
139
+ 2.4.4
data/README.md CHANGED
@@ -1,9 +1,13 @@
1
1
  # ActiveRecord::EjectionSeat
2
2
 
3
+ [![IMAGE ALT TEXT](https://img.youtube.com/vi/HfCY-bkVJKM/0.jpg)](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 and an integer `age` column. Another way to express this would be a `User` type that is a simple, typed struct.
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 "rspec/core/rake_task"
4
+ require "minitest/test_task"
5
5
 
6
- RSpec::Core::RakeTask.new(:spec)
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
- task default: %i[rubocop spec]
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 = ">= 2.7.0"
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", ">= 5.2"
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
- attrs = attributes.deep_symbolize_keys
19
- attribute_props = klass.props.keys & attrs.keys
20
-
21
- klass.new(attrs.slice(*attribute_props))
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
- attrs = struct.serialize
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,7 +1,8 @@
1
+ # typed: strict
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module ActiveRecord
4
5
  module EjectionSeat
5
- VERSION = "0.1.0"
6
+ VERSION = "0.3.0"
6
7
  end
7
8
  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
- require_relative "activerecord-ejection_seat/version"
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,3 @@
1
+ --dir
2
+ .
3
+ --ignore=vendor/
@@ -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