activerecord-ejection_seat 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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